diff --git a/CHANGELOG.md b/CHANGELOG.md index 37c2513a..10834e95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Add paging to `--list-themes`, see PR #3239 (@einfachIrgendwer0815) - Support negative relative line ranges, e.g. `bat -r :-10` / `bat -r='-10:'`, see #3068 (@ajesipow) - Support context in line ranges, e.g. `bat -r 30::5` / `bat -r 30:40:5`, see #3345 (@cavanaug) +- Add built-in 'minus' pager, e.g. `bat --pager=builtin` see PR #3402 (@academician) ## Bugfixes diff --git a/Cargo.lock b/Cargo.lock index 5c1364ff..4389a082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,7 @@ dependencies = [ "indexmap", "itertools 0.13.0", "itertools 0.14.0", + "minus", "nix", "nu-ansi-term", "once_cell", @@ -339,6 +340,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -364,6 +374,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.6.0", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "darling" version = "0.20.10" @@ -983,6 +1018,33 @@ dependencies = [ "adler2", ] +[[package]] +name = "minus" +version = "5.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093bd0520d2a37943566a73750e6d44094dac75d66a978d1f0d97ffc78686832" +dependencies = [ + "crossbeam-channel", + "crossterm", + "once_cell", + "parking_lot", + "regex", + "textwrap", + "thiserror 1.0.69", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -1050,6 +1112,9 @@ name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +dependencies = [ + "parking_lot_core", +] [[package]] name = "onig" @@ -1428,6 +1493,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1543,7 +1638,7 @@ dependencies = [ "cfg-if", "libc", "memchr", - "mio", + "mio 1.0.3", "terminal-trx", "windows-sys 0.59.0", "xterm-color", @@ -1576,6 +1671,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1932,6 +2036,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1959,6 +2072,21 @@ dependencies = [ "windows-targets 0.53.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1991,6 +2119,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2003,6 +2137,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2015,6 +2155,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2039,6 +2185,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2051,6 +2203,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2063,6 +2221,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2075,6 +2239,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 75c6c311..089822b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ minimal-application = [ "wild", ] git = ["git2"] # Support indicating git modifications -paging = ["shell-words", "grep-cli"] # Support applying a pager on the output +paging = [ "shell-words", "grep-cli", "minus"] # Support applying a pager on the output lessopen = ["execute"] # Support $LESSOPEN preprocessor build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"] @@ -52,6 +52,10 @@ thiserror = "2.0" wild = { version = "2.2", optional = true } content_inspector = "0.2.4" shell-words = { version = "1.1.0", optional = true } +minus = { version = "5.6", optional = true, features = [ + "dynamic_output", + "search", +] } unicode-width = "0.2.1" globset = "0.4" serde = "1.0" diff --git a/README.md b/README.md index a9cc97b6..254d070d 100644 --- a/README.md +++ b/README.md @@ -635,9 +635,11 @@ syntax, use (this mapping is already built in): ### Using a different pager `bat` uses the pager that is specified in the `PAGER` environment variable. If this variable is not -set, `less` is used by default. If you want to use a different pager, you can either modify the -`PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in -`PAGER`. +set, `less` is used by default. You can also use bat's built-in pager with `--pager=builtin` or +by setting the `BAT_PAGER` environment variable to "builtin". + +If you want to use a different pager, you can either modify the `PAGER` variable or set the +`BAT_PAGER` environment variable to override what is specified in `PAGER`. >[!NOTE] > If `PAGER` is `more` or `most`, `bat` will silently use `less` instead to ensure support for colors. diff --git a/assets/completions/bat.fish.in b/assets/completions/bat.fish.in index e2712706..29979e8b 100644 --- a/assets/completions/bat.fish.in +++ b/assets/completions/bat.fish.in @@ -106,6 +106,7 @@ set -l pager_opts ' less\ -FR\t more\t vimpager\t + builtin\t ' set -l italic_text_opts ' diff --git a/assets/manual/bat.1.in b/assets/manual/bat.1.in index 86923db7..faa69d53 100644 --- a/assets/manual/bat.1.in +++ b/assets/manual/bat.1.in @@ -149,8 +149,9 @@ which pager is used, see the '\-\-pager' option. Possible values: *auto*, never, \fB\-\-pager\fR .IP Determine which pager is used. This option will override the PAGER and BAT_PAGER -environment variables. The default pager is 'less'. To control when the pager is used, see -the '\-\-paging' option. Example: '\-\-pager "less \fB\-RF\fR"'. +environment variables. The default pager is 'less'. If you provide '\-\-pager=builtin', use +the built-in 'minus' pager. To control when the pager is used, see the '\-\-paging' option. +Example: '\-\-pager "less \fB\-RF\fR"'. Note: By default, if the pager is set to 'less' (and no command-line options are specified), 'bat' will pass the following command line options to the pager: '-R'/'--RAW-CONTROL-CHARS', '-F'/'--quit-if-one-screen' and '-X'/'--no-init'. The last option ('-X') is only used for 'less' versions older than 530. The '-R' option is needed to interpret ANSI colors correctly. The second option ('-F') instructs less to exit immediately if the output size is smaller than the vertical size of the terminal. This is convenient for small files because you do not have to press 'q' to quit the pager. The third option ('-X') is needed to fix a bug with the '--quit-if-one-screen' feature in old versions of 'less'. Unfortunately, it also breaks mouse-wheel support in 'less'. If you want to enable mouse-wheel scrolling on older versions of 'less', you can pass just '-R' (as in the example above, this will disable the quit-if-one-screen feature). For less 530 or newer, it should work out of the box. .HP diff --git a/doc/long-help.txt b/doc/long-help.txt index 231b711e..ac5ded9e 100644 --- a/doc/long-help.txt +++ b/doc/long-help.txt @@ -95,14 +95,15 @@ Options: piped to another program, but you want to keep the colorization/decorations. --paging - Specify when to use the pager. To disable the pager, use --paging=never' or its + Specify when to use the pager. To disable the pager, use '--paging=never' or its alias,'-P'. To disable the pager permanently, set BAT_PAGING to 'never'. To control which pager is used, see the '--pager' option. Possible values: *auto*, never, always. --pager Determine which pager is used. This option will override the PAGER and BAT_PAGER - environment variables. The default pager is 'less'. To control when the pager is used, see - the '--paging' option. Example: '--pager "less -RF"'. + environment variables. The default pager is 'less'. If you provide '--pager=builtin', use + the built-in 'minus' pager. To control when the pager is used, see the '--paging' option. + Example: '--pager "less -RF"'. -m, --map-syntax Map a glob pattern to an existing syntax name. The glob pattern is matched on the full diff --git a/examples/buffer.rs b/examples/buffer.rs index eefdb249..a09af0d5 100644 --- a/examples/buffer.rs +++ b/examples/buffer.rs @@ -14,7 +14,7 @@ fn main() { controller .run( vec![input.into()], - Some(OutputHandle::FmtWrite(&mut buffer)), + Some(&mut OutputHandle::FmtWrite(&mut buffer)), ) .unwrap(); diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index 7aa1ef20..f7e86dbc 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -328,7 +328,7 @@ pub fn build_app(interactive_output: bool) -> Command { .help("Specify when to use the pager, or use `-P` to disable (*auto*, never, always).") .long_help( "Specify when to use the pager. To disable the pager, use \ - --paging=never' or its alias,'-P'. To disable the pager permanently, \ + '--paging=never' or its alias,'-P'. To disable the pager permanently, \ set BAT_PAGING to 'never'. To control which pager is used, see the \ '--pager' option. Possible values: *auto*, never, always." ), @@ -354,6 +354,7 @@ pub fn build_app(interactive_output: bool) -> Command { .long_help( "Determine which pager is used. This option will override the \ PAGER and BAT_PAGER environment variables. The default pager is 'less'. \ + If you provide '--pager=builtin', use the built-in 'minus' pager. \ To control when the pager is used, see the '--paging' option. \ Example: '--pager \"less -RF\"'." ), diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index 1a7b9056..d489992c 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -16,7 +16,7 @@ use std::io::{BufReader, Write}; use std::path::Path; use std::process; -use bat::output::{OutputHandle, OutputType}; +use bat::output::OutputType; use bat::theme::DetectColorScheme; use nu_ansi_term::Color::Green; use nu_ansi_term::Style; @@ -229,10 +229,7 @@ pub fn list_themes( )?; config.theme = theme.to_string(); Controller::new(&config, &assets) - .run( - vec![theme_preview_file()], - Some(OutputHandle::IoWrite(&mut writer)), - ) + .run(vec![theme_preview_file()], Some(&mut writer)) .ok(); writeln!(writer)?; } else if config.loop_through { diff --git a/src/controller.rs b/src/controller.rs index 57720ab0..44e7ab5b 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -36,14 +36,18 @@ impl Controller<'_> { } } - pub fn run(&self, inputs: Vec, output_handle: Option>) -> Result { + pub fn run( + &self, + inputs: Vec, + output_handle: Option<&mut OutputHandle<'_>>, + ) -> Result { self.run_with_error_handler(inputs, output_handle, default_error_handler) } pub fn run_with_error_handler( &self, inputs: Vec, - output_handle: Option>, + output_handle: Option<&mut OutputHandle<'_>>, mut handle_error: impl FnMut(&Error, &mut dyn Write), ) -> Result { let mut output_type; @@ -88,7 +92,7 @@ impl Controller<'_> { let mut writer = match output_handle { Some(OutputHandle::FmtWrite(w)) => OutputHandle::FmtWrite(w), Some(OutputHandle::IoWrite(w)) => OutputHandle::IoWrite(w), - None => OutputHandle::IoWrite(output_type.handle()?), + None => output_type.handle()?, }; let mut no_errors: bool = true; let stderr = io::stderr(); diff --git a/src/error.rs b/src/error.rs index 0b1d3f95..93a00930 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { InvalidPagerValueBat, #[error("{0}")] Msg(String), + #[cfg(feature = "paging")] + #[error(transparent)] + MinusError(#[from] ::minus::MinusError), #[cfg(feature = "lessopen")] #[error(transparent)] VarError(#[from] ::std::env::VarError), diff --git a/src/output.rs b/src/output.rs index e15c478e..aea37333 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,7 +1,9 @@ use std::fmt; -use std::io::{self, Write}; +use std::io; #[cfg(feature = "paging")] use std::process::Child; +#[cfg(feature = "paging")] +use std::thread::{spawn, JoinHandle}; use crate::error::*; #[cfg(feature = "paging")] @@ -11,6 +13,36 @@ use crate::paging::PagingMode; #[cfg(feature = "paging")] use crate::wrapping::WrappingMode; +#[cfg(feature = "paging")] +pub struct BuiltinPager { + pager: minus::Pager, + handle: Option>>, +} + +#[cfg(feature = "paging")] +impl BuiltinPager { + fn new() -> Self { + let pager = minus::Pager::new(); + let handle = { + let pager = pager.clone(); + Some(spawn(move || { + minus::dynamic_paging(pager).map_err(Error::from) + })) + }; + Self { pager, handle } + } +} + +#[cfg(feature = "paging")] +impl std::fmt::Debug for BuiltinPager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BuiltinPager") + //.field("pager", &self.pager) /// minus::Pager doesn't implement fmt::Debug + .field("handle", &self.handle) + .finish() + } +} + #[cfg(feature = "paging")] #[derive(Debug, PartialEq)] enum SingleScreenAction { @@ -22,6 +54,8 @@ enum SingleScreenAction { pub enum OutputType { #[cfg(feature = "paging")] Pager(Child), + #[cfg(feature = "paging")] + BuiltinPager(BuiltinPager), Stdout(io::Stdout), } @@ -64,6 +98,10 @@ impl OutputType { return Err(Error::InvalidPagerValueBat); } + if pager.kind == PagerKind::Builtin { + return Ok(OutputType::BuiltinPager(BuiltinPager::new())); + } + let resolved_path = match grep_cli::resolve_binary(&pager.bin) { Ok(path) => path, Err(_) => { @@ -138,7 +176,7 @@ impl OutputType { #[cfg(feature = "paging")] pub(crate) fn is_pager(&self) -> bool { - matches!(self, OutputType::Pager(_)) + matches!(self, OutputType::Pager(_) | OutputType::BuiltinPager(_)) } #[cfg(not(feature = "paging"))] @@ -146,14 +184,18 @@ impl OutputType { false } - pub fn handle(&mut self) -> Result<&mut dyn Write> { + pub fn handle<'a>(&'a mut self) -> Result> { Ok(match *self { #[cfg(feature = "paging")] - OutputType::Pager(ref mut command) => command - .stdin - .as_mut() - .ok_or("Could not open stdin for pager")?, - OutputType::Stdout(ref mut handle) => handle, + OutputType::Pager(ref mut command) => OutputHandle::IoWrite( + command + .stdin + .as_mut() + .ok_or("Could not open stdin for pager")?, + ), + #[cfg(feature = "paging")] + OutputType::BuiltinPager(ref mut pager) => OutputHandle::FmtWrite(&mut pager.pager), + OutputType::Stdout(ref mut handle) => OutputHandle::IoWrite(handle), }) } } @@ -161,8 +203,16 @@ impl OutputType { #[cfg(feature = "paging")] impl Drop for OutputType { fn drop(&mut self) { - if let OutputType::Pager(ref mut command) = *self { - let _ = command.wait(); + match *self { + OutputType::Pager(ref mut command) => { + let _ = command.wait(); + } + OutputType::BuiltinPager(ref mut pager) => { + if pager.handle.is_some() { + let _ = pager.handle.take().unwrap().join().unwrap(); + } + } + OutputType::Stdout(_) => (), } } } diff --git a/src/pager.rs b/src/pager.rs index 5b707779..3017066c 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -32,6 +32,9 @@ pub(crate) enum PagerKind { /// most Most, + /// builtin + Builtin, + /// A pager we don't know about Unknown, } @@ -40,6 +43,10 @@ impl PagerKind { fn from_bin(bin: &str) -> PagerKind { use std::path::Path; + if bin == "builtin" { + return PagerKind::Builtin; + } + // Set to `less` by default on most Linux distros. let pager_bin = Path::new(bin).file_stem(); diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index 4979bab5..89a9854e 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -328,7 +328,7 @@ impl<'a> PrettyPrinter<'a> { if let Some(mut w) = writer { controller.run( inputs.into_iter().map(|i| i.into()).collect(), - Some(OutputHandle::FmtWrite(&mut w)), + Some(&mut OutputHandle::FmtWrite(&mut w)), ) } else { controller.run(inputs.into_iter().map(|i| i.into()).collect(), None)