diff --git a/src/examples.rs b/src/examples.rs index 17a0170..a4ecb83 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -2,7 +2,7 @@ use crate::colors::Colorizer; use crate::colors::GREEN; use crate::colors::RESET; use crate::colors::YELLOW; -use crate::pager; +use crate::pager::Pager; use anstream::adapter::strip_str; use anstream::stdout; use anyhow::Result; @@ -13,8 +13,6 @@ use clap::ArgMatches; use clap::Command; use std::io; use std::io::Write; -use std::panic::resume_unwind; -use std::thread; use terminal_size::terminal_size; use terminal_size::Width; use unicode_width::UnicodeWidthStr; @@ -75,22 +73,8 @@ pub fn is_arg_set(matches: &ArgMatches) -> bool { } pub fn print(command: &'static str, examples: &'static [Example]) -> Result<()> { - if let Some(mut pager) = pager::open()? { - let mut stdin = pager.take_stdin().expect("could not get pager stdin"); - - let thread = thread::spawn(move || { - write(&mut stdin.inner, command, examples).map_err(|err| { - stdin - .context - .apply(err) - .context("failed to write to child process stdin") - }) - }); - - pager.wait()?; - thread.join().map_err(resume_unwind)??; - - Ok(()) + if let Some(mut pager) = Pager::detect() { + pager.open(|stdin| write(stdin, command, examples)) } else { write(&mut stdout().lock(), command, examples) } diff --git a/src/pager.rs b/src/pager.rs index 5043196..bfb0b7f 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -1,41 +1,62 @@ use crate::process::CommandEx; -use crate::process::Spawned; use anstream::stream::IsTerminal; use anyhow::Result; use std::io::stdout; -use std::path::Path; -use std::process::Child; +use std::panic::resume_unwind; +use std::process::ChildStdin; use std::process::Command; use std::process::Stdio; +use std::thread; use which::which; -pub fn open() -> Result>> { - if !stdout().is_terminal() { - return Ok(None); - } +pub struct Pager(Command); - // We could eventually do something more complex, such as parsing PAGER - // env variable like `bat` does https://github.com/sharkdp/bat/issues/158, - // but that would be an overkill for our use case. - if let Ok(path) = which("less") { - // F = Exit immediately if the text fits the entire screen. - // I = Ignore case when searching. - // r = Causes "raw" control characters to be displayed. - // X = Disables sending the termcap (in)itialization. - return spawn(&path, &["-FIrX"]); - } +impl Pager { + pub fn detect() -> Option { + if !stdout().is_terminal() { + return None; + } + + // We could eventually do something more complex, such as parsing PAGER + // env variable like `bat` does https://github.com/sharkdp/bat/issues/158, + // but that would be an overkill for our use case. + + if let Ok(path) = which("less") { + let mut command = Command::new(path); + // F = Exit immediately if the text fits the entire screen. + // I = Ignore case when searching. + // r = Causes "raw" control characters to be displayed. + // X = Disables sending the termcap (de)itialization. + command.arg("-FIrX"); + return Some(Pager(command)); + } + + if let Ok(path) = which("more") { + return Some(Pager(Command::new(path))); + } - if let Ok(path) = which("more") { - return spawn(&path, &[]); + None } - Ok(None) -} + pub fn open( + &mut self, + callback: impl Fn(&mut ChildStdin) -> Result<()> + Send + 'static, + ) -> Result<()> { + let mut pager = self.0.stdin(Stdio::piped()).spawn_with_context()?; + let mut stdin = pager.take_stdin().expect("could not get pager stdin"); + + let thread = thread::spawn(move || { + callback(&mut stdin.inner).map_err(|err| { + stdin + .context + .apply(err) + .context("failed to write to child process stdin") + }) + }); -fn spawn(path: &Path, args: &[&str]) -> Result>> { - Command::new(path) - .args(args) - .stdin(Stdio::piped()) - .spawn_with_context() - .map(Some) + pager.wait()?; + thread.join().map_err(resume_unwind)??; + + Ok(()) + } }