use std::env;
use std::process::{Command, Stdio};
use crate::core::render::{Renderable, write_rendered};
use crate::core::terminal::{ColorSupport, is_output_tty, term_width};
use crate::error::{Result, SparcliError};
#[cfg(not(windows))]
const DEFAULT_PAGER: &str = "less -R";
#[cfg(windows)]
const DEFAULT_PAGER: &str = "more";
pub struct Pager {
command: Option<String>,
always: bool,
}
impl Default for Pager {
fn default() -> Self {
Self::new()
}
}
impl Pager {
pub fn new() -> Self {
Self {
command: None,
always: false,
}
}
#[must_use]
pub fn command(mut self, command: impl Into<String>) -> Self {
self.command = Some(command.into());
self
}
#[must_use]
pub fn always(mut self) -> Self {
self.always = true;
self
}
pub fn page(&self, content: &impl Renderable) -> Result<()> {
if !self.always && !is_output_tty() {
return content.print();
}
let rendered = content.render(term_width());
let argv = self.resolve_command();
let mut parts = argv.split_whitespace();
let program = parts
.next()
.ok_or_else(|| SparcliError::Config("empty pager".into()))?;
let mut child = Command::new(program)
.args(parts)
.stdin(Stdio::piped())
.spawn()?;
let mut stdin = child
.stdin
.take()
.ok_or_else(|| SparcliError::Config("pager stdin".into()))?;
write_rendered(&mut stdin, &rendered, ColorSupport::TrueColor)?;
drop(stdin);
child.wait()?;
Ok(())
}
fn resolve_command(&self) -> String {
if let Some(command) = &self.command {
return command.clone();
}
env::var("PAGER")
.ok()
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| DEFAULT_PAGER.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_explicit_command() {
let pager = Pager::new().command("bat --paging always");
assert_eq!(pager.resolve_command(), "bat --paging always");
}
}