Skip to main content

mesh_llm_cli/
pager.rs

1use anyhow::Result;
2use std::ffi::OsStr;
3use std::io::{self, IsTerminal, Write};
4use std::process::{Command, Stdio};
5
6const DEFAULT_PAGER: &str = "less";
7const DEFAULT_PAGER_ARGS: &[&str] = &["-F", "-R", "-X"];
8
9pub fn print_or_page(output: &str) -> Result<()> {
10    if !should_use_pager(
11        std::io::stdin().is_terminal(),
12        std::io::stdout().is_terminal(),
13        std::env::var_os("TERM").as_deref(),
14    ) {
15        return print_direct(output);
16    }
17
18    match page_with_less(output) {
19        Ok(()) => Ok(()),
20        Err(err) if pager_missing(&err) => print_direct(output),
21        Err(err) => Err(err),
22    }
23}
24
25fn should_use_pager(
26    stdin_is_terminal: bool,
27    stdout_is_terminal: bool,
28    term: Option<&OsStr>,
29) -> bool {
30    stdin_is_terminal
31        && stdout_is_terminal
32        && term.is_none_or(|value| !value.eq_ignore_ascii_case(OsStr::new("dumb")))
33}
34
35fn print_direct(output: &str) -> Result<()> {
36    let mut stdout = io::stdout().lock();
37    stdout.write_all(output.as_bytes())?;
38    stdout.flush()?;
39    Ok(())
40}
41
42fn page_with_less(output: &str) -> Result<()> {
43    let mut child = Command::new(DEFAULT_PAGER)
44        .args(DEFAULT_PAGER_ARGS)
45        .stdin(Stdio::piped())
46        .spawn()?;
47
48    if let Some(mut stdin) = child.stdin.take() {
49        match stdin.write_all(output.as_bytes()) {
50            Ok(()) => {}
51            Err(err) if err.kind() == io::ErrorKind::BrokenPipe => {}
52            Err(err) => return Err(err.into()),
53        }
54    }
55
56    let _ = child.wait()?;
57    Ok(())
58}
59
60fn pager_missing(err: &anyhow::Error) -> bool {
61    err.downcast_ref::<io::Error>()
62        .is_some_and(|io_err| io_err.kind() == io::ErrorKind::NotFound)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::should_use_pager;
68    use std::ffi::OsStr;
69
70    #[test]
71    fn pager_requires_tty_input_and_output() {
72        assert!(!should_use_pager(
73            false,
74            true,
75            Some(OsStr::new("xterm-256color"))
76        ));
77        assert!(!should_use_pager(
78            true,
79            false,
80            Some(OsStr::new("xterm-256color"))
81        ));
82        assert!(should_use_pager(
83            true,
84            true,
85            Some(OsStr::new("xterm-256color"))
86        ));
87    }
88
89    #[test]
90    fn pager_skips_dumb_terminals() {
91        assert!(!should_use_pager(true, true, Some(OsStr::new("dumb"))));
92        assert!(should_use_pager(true, true, None));
93    }
94}