use std::io::Write as _;
use std::process::{Command, Stdio};
use crate::cx::Cx;
use crate::error::Result;
const DEFAULT_PAGER: &str = "less -FRX";
const DEFAULT_ROWS: usize = 24;
pub fn page(cx: &mut Cx, content: &str) -> Result<()> {
if should_page(cx.out.is_tty(), cx.no_pager, content, terminal_rows())
&& run_pager(cx, content).is_ok()
{
return Ok(());
}
cx.out.text(content)
}
pub fn should_page(is_tty: bool, no_pager: bool, content: &str, rows: usize) -> bool {
is_tty && !no_pager && content.lines().count() > rows
}
fn terminal_rows() -> usize {
crossterm::terminal::size()
.map(|(_, h)| usize::from(h))
.unwrap_or(DEFAULT_ROWS)
}
fn run_pager(cx: &Cx, content: &str) -> Result<()> {
let pager = cx
.env
.get("PAGER")
.filter(|p| !p.is_empty())
.unwrap_or(DEFAULT_PAGER);
let argv = shell_words::split(pager).unwrap_or_else(|_| vec![pager.to_string()]);
let Some((program, rest)) = argv.split_first() else {
return cx_text_fallback();
};
let mut child = Command::new(program)
.args(rest)
.stdin(Stdio::piped())
.spawn()?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(content.as_bytes())?;
}
child.wait()?;
Ok(())
}
fn cx_text_fallback() -> Result<()> {
Err(crate::error::Error::operation("empty pager command"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testutil::test_cx;
#[test]
fn should_page_only_when_tty_and_overflowing() {
let long = "x\n".repeat(100);
assert!(!should_page(false, false, &long, 24));
assert!(!should_page(true, true, &long, 24));
assert!(should_page(true, false, &long, 24));
assert!(!should_page(true, false, "one\ntwo\n", 24));
}
#[test]
fn page_writes_directly_when_not_a_tty() {
let mut t = test_cx(&[], "/tmp");
let content = "line1\nline2\n".repeat(50);
page(&mut t.cx, &content).unwrap();
assert_eq!(t.out.contents(), content);
}
}