1use std::process::{Command, Stdio};
2
3use crate::errors::{CoreError, CoreResult};
4
5use super::ViewerBackend;
6
7pub 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}