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

Refactor terminal detection #232

Merged
merged 3 commits into from Jan 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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