use std::io::{IsTerminal, Write};
use std::process::Stdio;
use worktrunk::shell_exec::ShellConfig;
use worktrunk::styling::print;
use crate::pager::{git_config_pager, parse_pager_value};
fn detect_help_pager() -> Option<String> {
let shell = ShellConfig::get().ok()?;
let pager = std::env::var("GIT_PAGER")
.ok()
.and_then(|s| parse_pager_value(&s))
.or_else(git_config_pager)
.or_else(|| {
std::env::var("PAGER")
.ok()
.and_then(|s| parse_pager_value(&s))
});
if pager.is_some() {
return pager;
}
if shell.is_posix() {
Some("less".to_string())
} else {
log::debug!("No POSIX shell available, skipping pager (less not available)");
None
}
}
pub(crate) fn show_help_in_pager(help_text: &str, use_pager: bool) -> std::io::Result<()> {
if !use_pager {
log::debug!("Short help (-h) requested, printing directly to stdout");
print!("{}", help_text);
return Ok(());
}
let Some(pager_cmd) = detect_help_pager() else {
log::debug!("No pager configured, printing help directly to stdout");
print!("{}", help_text);
return Ok(());
};
if !std::io::stdout().is_terminal() {
log::debug!("stdout is not a TTY, skipping pager");
print!("{}", help_text);
return Ok(());
}
log::debug!("Invoking pager: {}", pager_cmd);
let less_flags = compute_less_flags(std::env::var("LESS").ok().as_deref());
let shell = match ShellConfig::get() {
Ok(shell) => shell,
Err(e) => {
log::debug!("Shell unavailable for pager: {}", e);
print!("{}", help_text);
return Ok(());
}
};
log::debug!("$ {} (pager)", pager_cmd);
let mut cmd = shell.command(&pager_cmd);
worktrunk::shell_exec::scrub_directive_env_vars(&mut cmd);
let mut child = match cmd.stdin(Stdio::piped()).env("LESS", &less_flags).spawn() {
Ok(child) => child,
Err(e) => {
log::debug!(
"Failed to spawn pager '{}' with {}: {}",
pager_cmd,
shell.name,
e
);
print!("{}", help_text);
return Ok(());
}
};
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(help_text.as_bytes())?;
}
child.wait()?;
Ok(())
}
fn compute_less_flags(user_less: Option<&str>) -> String {
format!("{} -FRX", user_less.unwrap_or_default())
}
#[cfg(test)]
mod tests {
use super::{compute_less_flags, parse_pager_value};
#[test]
fn test_validate_excludes_cat() {
assert_eq!(parse_pager_value("cat"), None);
assert_eq!(parse_pager_value(" cat "), None);
assert_eq!(parse_pager_value(""), None);
assert_eq!(parse_pager_value(" "), None);
}
#[test]
fn test_validate_accepts_valid_pagers() {
assert_eq!(parse_pager_value("less"), Some("less".to_string()));
assert_eq!(parse_pager_value(" less "), Some("less".to_string()));
assert_eq!(parse_pager_value("delta"), Some("delta".to_string()));
assert_eq!(parse_pager_value("less -R"), Some("less -R".to_string()));
}
#[test]
fn test_compute_less_flags_empty() {
assert_eq!(compute_less_flags(None), " -FRX");
assert_eq!(compute_less_flags(Some("")), " -FRX");
}
#[test]
fn test_compute_less_flags_short_options() {
assert_eq!(compute_less_flags(Some("-R")), "-R -FRX");
assert_eq!(compute_less_flags(Some("-iMRS")), "-iMRS -FRX");
}
#[test]
fn test_compute_less_flags_long_options() {
assert_eq!(compute_less_flags(Some("--mouse")), "--mouse -FRX");
assert_eq!(
compute_less_flags(Some("--mouse --shift=4")),
"--mouse --shift=4 -FRX"
);
}
#[test]
fn test_compute_less_flags_mixed() {
assert_eq!(compute_less_flags(Some("-R --mouse")), "-R --mouse -FRX");
}
}