Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

Commit

Permalink
Refactor terminal detection
Browse files Browse the repository at this point in the history
Introduce a separate TerminalApp struct to abstract terminal detection
separately from terminal capabilities.

Check $TERM before all other environment variables; terminal emulators
always set this variable and if it's something other than
xterm-256colors it's definitely accurate.

Closes GH-230
  • Loading branch information
swsnr committed Jan 7, 2023
1 parent 29ec771 commit 800701d
Show file tree
Hide file tree
Showing 16 changed files with 402 additions and 210 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,15 @@ To publish a new release run `scripts/release` from the project directory.
- 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
Original file line number Diff line number Diff line change
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
38 changes: 32 additions & 6 deletions mdcat.1.adoc
Original file line number Diff line number Diff line change
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 @@ -109,15 +121,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 +209,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
22 changes: 11 additions & 11 deletions src/bin/mdcat/main.rs
Original file line number Diff line number Diff line change
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);
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 800701d

Please sign in to comment.