Skip to main content

ito_core/viewer/
bat.rs

1use std::process::{Command, Stdio};
2
3use crate::errors::{CoreError, CoreResult};
4
5use super::ViewerBackend;
6
7/// Render markdown via `bat` with paging.
8pub struct BatViewer;
9
10impl ViewerBackend for BatViewer {
11    fn name(&self) -> &str {
12        "bat"
13    }
14
15    fn description(&self) -> &str {
16        "Render the proposal in the terminal with bat"
17    }
18
19    fn is_available(&self) -> bool {
20        command_on_path("bat")
21    }
22
23    fn open(&self, content: &str) -> CoreResult<()> {
24        run_with_stdin("bat", &["--language=markdown", "--paging=always"], content)
25    }
26}
27
28pub(crate) fn command_on_path(binary: &str) -> bool {
29    std::env::var_os("PATH")
30        .is_some_and(|paths| std::env::split_paths(&paths).any(|dir| dir.join(binary).is_file()))
31}
32
33pub(crate) fn run_with_stdin(binary: &str, args: &[&str], content: &str) -> CoreResult<()> {
34    if !command_on_path(binary) {
35        return Err(CoreError::not_found(format!(
36            "{binary} is not installed or not on PATH"
37        )));
38    }
39
40    let mut child = Command::new(binary)
41        .args(args)
42        .stdin(Stdio::piped())
43        .spawn()
44        .map_err(|e| CoreError::io(format!("spawning {binary}"), e))?;
45
46    if let Some(mut stdin) = child.stdin.take() {
47        std::io::Write::write_all(&mut stdin, content.as_bytes())
48            .map_err(|e| CoreError::io(format!("writing to {binary} stdin"), e))?;
49    }
50
51    let status = child
52        .wait()
53        .map_err(|e| CoreError::io(format!("waiting for {binary}"), e))?;
54
55    if status.success() {
56        Ok(())
57    } else {
58        Err(CoreError::process(format!(
59            "{binary} exited with status {status}"
60        )))
61    }
62}