From a470cebf3231772dc5ec99755fad24c33a5ad775 Mon Sep 17 00:00:00 2001 From: Daniel Waechter Date: Thu, 5 Sep 2024 18:27:29 -0400 Subject: [PATCH] Add a "builtin" pager using the Minus crate --- Cargo.lock | 172 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 6 +- examples/buffer.rs | 2 +- src/bin/bat/main.rs | 7 +- src/controller.rs | 10 ++- src/error.rs | 3 + src/output.rs | 70 ++++++++++++++--- src/pager.rs | 7 ++ src/pretty_printer.rs | 2 +- 9 files changed, 257 insertions(+), 22 deletions(-) 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/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/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)