Skip to content
This repository has been archived by the owner on Mar 31, 2024. It is now read-only.

Commit

Permalink
Merge pull request #232 from swsnr/refactor-terminal-detection
Browse files Browse the repository at this point in the history
Refactor terminal detection
  • Loading branch information
swsnr committed Jan 7, 2023
2 parents 29ec771 + eedb1f7 commit a1c3b02
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 217 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,13 +8,24 @@ To publish a new release run `scripts/release` from the project directory.

## [Unreleased]

### Added
- Add `--detect-terminal` to print the name of the detected terminal program (see [GH-232]).
- Add `--ansi` to skip terminal detection and use ANSI-formatting only (see [GH-232]).

### Changed

- Replace `ureq` with `reqwest` (see [GH-229]).
This implies that the default build now creates a binary linked against the system standard SSL library, i.e. openssl under Linux.
A fully static build now requires `--no-default-features --features static` for `cargo build`.
- Terminal detection always checks `$TERM` first and trusts its value if it denotes a specific terminal emulator (see [GH-232]).

### Fixed

- Correctly detect kitty started from iTerm (see [GH-230] and [GH-232]).

[GH-229]: https://github.com/swsnr/mdcat/pull/229
[GH-230]: https://github.com/swsnr/mdcat/pull/230
[GH-232]: https://github.com/swsnr/mdcat/pull/232

## [0.30.3] – 2022-12-01

Expand Down
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -50,6 +50,7 @@ terminal_size = "0.2.3"
pretty_assertions = "1.3.0"
lazy_static = "1.4.0"
glob = "0.3.0"
temp-env = "0.3.1"

[build-dependencies]
# To generate completions during build
Expand Down
46 changes: 39 additions & 7 deletions mdcat.1.adoc
Expand Up @@ -25,9 +25,21 @@ If invoked as `mdless` automatically use a pager to display the output, see belo

=== CommonMark and terminal support

mdcat supports all basic CommonMark syntax plus a few extensions, highlights syntax in code blocks, and shows inline links and even inline images in some terminal emulators.
mdcat supports all basic CommonMark syntax plus a few extensions, highlights syntax in code blocks, and shows inline links and even inline images in some terminal programs.
In iTerm2 it also adds jump marks for section headings.

See <<Terminal support>> below for a list of supported terminal programs and their features.

=== Terminal detection

To enable formatting extensions such sa inline images, `mdcat` needs to detect the terminal program, by checking the following environment variables in the gven order:

1. `$TERM`
2. `$TERM_PROGRAM`
3. `$TERMINOLOGY`

See <<Environment>> below for a detailed description of each environment variable.

=== Pagination

mdcat can render output in a pager; this is the default when run as `mdless`.
Expand Down Expand Up @@ -78,6 +90,9 @@ This is the default when run as `mdcat`.
--no-colour::
Disable all colours and other styles.

--ansi::
Skip terminal detection and only use ANSI formatting.

--columns::
Maximum number of columns to use for text output.
Defaults to the size of the underlying terminal.
Expand All @@ -88,7 +103,10 @@ This is the default when run as `mdcat`.

--fail::
Fail immediately at the first FILE which fails to read.
By default mdcat continues with the next file.
By default, mdcat continues with the next file.

--detect-terminal::
Detect the terminal program, print its name, and exit.

-h::
--help::
Expand All @@ -109,15 +127,29 @@ If run as `mdless` or if `--paginate` is given and the pager fails to start mdca
== Environment

TERM::
If this variable is `wezterm`, mdcat assumes that the terminal is WezTerm. By default WezTerm does not set TERM to wezterm but mdcat can still detect it trough TERM_PROGRAM.
If this variable is `xterm-kitty`, mdcat assumes that the terminal is Kitty.

`mdcat` first checks this variable to identify the terminal program (see <<Terminal detection>>).
It understands the following values.
+
* `wezterm`: WezTerm. Note that WezTerm sets `$TERM` to `xterm-256color` by default, and only uses `wezterm` for `$TERM` if explicitly configured to do so.
* `xterm-kitty`: Kitty
+
For all other values `mdcat` proceeds to check `$TERM_PROGRAM`.

TERM_PROGRAM::
If this variable is `iTerm.app`, mdcat assumes that the terminal is iTerm2.
If this variable is `WezTerm`, mdcat assumes that the terminal is WezTerm.

If `$TERM` does not conclusively identify the terminal program `mdcat` checks this variable next. It understands the following values:
+
* `iTerm.app`: iTerm2
* `WezTerm`: WezTerm
+
For all other values `mdcat` proceeds to check `$TERMINOLOGY`.

TERMINOLOGY::

If this variable is `1`, mdcat assumes that the terminal is Terminology.
+
Otherwise `mdcat` ends terminal detection and assumes that the terminal is only capable of standard ANSI formatting.

COLUMNS::
The number of character columns on screen.
Expand Down Expand Up @@ -183,7 +215,7 @@ Unless `--no-colour` is given, mdcat translates CommonMark text into ANSI format
It uses bold (SGR 1), italic (SGR 3) and strikethrough (SGR 9) formatting, and the standard 4-bit color sequences, as well as https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda[OSC 8] for hyperlinks.
It does not use 8-bit or 24-bit color sequences, though this may change in future releases.

Additionally, it uses proprietary escape code if it detects specific terminal emulators:
Additionally, it uses proprietary escape code if it detects one of the following terminal emulators (see <<Terminal detection>> and <<Environment>> for details):

* https://iterm2.com/[iTerm2]: Inline images (https://iterm2.com/documentation-images.html[iTerm2 protocol]) and
https://iterm2.com/documentation-escape-codes.html[Marks].
Expand Down
10 changes: 5 additions & 5 deletions src/bin/mdcat/args.rs
Expand Up @@ -98,10 +98,10 @@ pub struct CommonArgs {
/// Exit immediately if any error occurs processing an input file.
#[arg(long = "fail")]
pub fail_fast: bool,
/// Only detect the terminal type and exit.
#[arg(long, hide = true)]
pub detect_only: bool,
/// Limit to standard ANSI formatting.
#[arg(long, conflicts_with = "no_colour", hide = true)]
/// Print detected terminal name and exit.
#[arg(long = "detect-terminal")]
pub detect_and_exit: bool,
/// Skip terminal detection and only use ANSI formatting.
#[arg(long = "ansi", conflicts_with = "no_colour")]
pub ansi_only: bool,
}
24 changes: 12 additions & 12 deletions src/bin/mdcat/main.rs
Expand Up @@ -21,8 +21,9 @@ use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::EnvFilter;

use crate::output::Output;
use mdcat::terminal::{TerminalProgram, TerminalSize};
use mdcat::ResourceAccess;
use mdcat::{Environment, Settings};
use mdcat::{ResourceAccess, TerminalCapabilities, TerminalSize};

mod args;
mod output;
Expand Down Expand Up @@ -102,21 +103,17 @@ fn main() {
let args = Args::parse().command;
event!(target: "mdcat::main", Level::TRACE, ?args, "mdcat arguments");

let terminal_capabilities = if args.no_colour {
// If the user disabled colours assume a dumb terminal
TerminalCapabilities::none()
let terminal = if args.no_colour {
TerminalProgram::Dumb
} else if args.paginate() || args.ansi_only {
// A pager won't support any terminal-specific features
TerminalCapabilities::ansi()
TerminalProgram::Ansi
} else {
TerminalCapabilities::detect()
TerminalProgram::detect()
};

let size = TerminalSize::detect().unwrap_or_default();
let columns = args.columns.unwrap_or(size.columns);

if args.detect_only {
println!("Terminal: {}", terminal_capabilities.name);
if args.detect_and_exit {
println!("Terminal: {terminal}");
} else {
// On Windows 10 we need to enable ANSI term explicitly.
#[cfg(windows)]
Expand All @@ -125,10 +122,13 @@ fn main() {
ansi_term::enable_ansi_support().ok();
}

let size = TerminalSize::detect().unwrap_or_default();
let columns = args.columns.unwrap_or(size.columns);

let exit_code = match Output::new(args.paginate()) {
Ok(mut output) => {
let settings = Settings {
terminal_capabilities,
terminal_capabilities: terminal.capabilities(),
terminal_size: TerminalSize { columns, ..size },
resource_access: if args.local_only {
ResourceAccess::LocalOnly
Expand Down
11 changes: 7 additions & 4 deletions src/lib.rs
Expand Up @@ -18,14 +18,15 @@ use tracing::instrument;

// Expose some select things for use in main
pub use crate::resources::ResourceAccess;
pub use crate::terminal::*;
use crate::terminal::capabilities::TerminalCapabilities;
use crate::terminal::TerminalSize;
use url::Url;

mod magic;
mod references;
mod resources;
mod svg;
mod terminal;
pub mod terminal;

mod render;

Expand Down Expand Up @@ -143,6 +144,7 @@ mod tests {
use pretty_assertions::assert_eq;
use syntect::parsing::SyntaxSet;

use crate::terminal::TerminalProgram;
use crate::*;

use super::render_string;
Expand All @@ -153,7 +155,7 @@ mod tests {
&Settings {
resource_access: ResourceAccess::LocalOnly,
syntax_set: SyntaxSet::default(),
terminal_capabilities: TerminalCapabilities::none(),
terminal_capabilities: TerminalProgram::Dumb.capabilities(),
terminal_size: TerminalSize::default(),
},
)
Expand Down Expand Up @@ -286,6 +288,7 @@ Hello Donald[2]
use pretty_assertions::assert_eq;
use syntect::parsing::SyntaxSet;

use crate::terminal::TerminalProgram;
use crate::*;

use super::render_string;
Expand All @@ -296,7 +299,7 @@ Hello Donald[2]
&Settings {
resource_access: ResourceAccess::LocalOnly,
syntax_set: SyntaxSet::default(),
terminal_capabilities: TerminalCapabilities::none(),
terminal_capabilities: TerminalProgram::Dumb.capabilities(),
terminal_size: TerminalSize::default(),
},
)
Expand Down
3 changes: 2 additions & 1 deletion src/render.rs
Expand Up @@ -31,6 +31,7 @@ use state::*;
use write::*;

use crate::render::state::MarginControl::{Margin, NoMargin};
use crate::terminal::capabilities::LinkCapability;
pub use data::StateData;
pub use state::State;
pub use state::StateAndData;
Expand Down Expand Up @@ -607,7 +608,7 @@ pub fn write_event<'a, W: Write>(
// Images
(Stacked(stack, Inline(state, attrs)), Start(Image(_, link, _))) => {
let InlineAttrs { style, indent } = attrs;
use ImageCapability::*;
use crate::terminal::capabilities::ImageCapability::*;
let resolved_link = environment.resolve_reference(&link);
let image_state = match (settings.terminal_capabilities.image, resolved_link) {
(Some(Terminology(terminology)), Some(ref url)) => {
Expand Down
3 changes: 2 additions & 1 deletion src/render/state.rs
Expand Up @@ -4,7 +4,8 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use crate::{AnsiStyle, LinkCapability};
use crate::terminal::capabilities::LinkCapability;
use crate::terminal::AnsiStyle;
use ansi_term::Style;
use std::borrow::Borrow;
use syntect::highlighting::HighlightState;
Expand Down
8 changes: 4 additions & 4 deletions src/render/write.rs
Expand Up @@ -14,9 +14,9 @@ use syntect::parsing::{ParseState, ScopeStack};
use crate::references::*;
use crate::render::data::LinkReferenceDefinition;
use crate::render::state::*;
use crate::{
Environment, MarkCapability, Settings, StyleCapability, TerminalCapabilities, TerminalSize,
};
use crate::terminal::capabilities::{MarkCapability, StyleCapability, TerminalCapabilities};
use crate::terminal::TerminalSize;
use crate::{Environment, Settings};

#[inline]
pub fn write_indent<W: Write>(writer: &mut W, level: u16) -> Result<()> {
Expand Down Expand Up @@ -86,7 +86,7 @@ pub fn write_link_refs<W: Write>(
// clickable. This mostly helps images inside inline links which we had to write as
// reference links because we can't nest inline links.
if let Some(url) = environment.resolve_reference(&link.target) {
use crate::LinkCapability::*;
use crate::terminal::capabilities::LinkCapability::*;
match &capabilities.links {
Some(Osc8(links)) => {
links.set_link_url(writer, url, &environment.hostname)?;
Expand Down

0 comments on commit a1c3b02

Please sign in to comment.