Skip to main content

ito_core/viewer/
tmux_nvim.rs

1use std::process::Command;
2
3use crate::errors::{CoreError, CoreResult};
4
5use super::ViewerBackend;
6use super::util::command_on_path;
7
8/// Render markdown inside a tmux popup running Neovim in read-only mode.
9pub struct TmuxNvimViewer;
10
11impl ViewerBackend for TmuxNvimViewer {
12    fn name(&self) -> &str {
13        "tmux-nvim"
14    }
15
16    fn description(&self) -> &str {
17        "Open the proposal in a tmux popup with Neovim"
18    }
19
20    fn is_available(&self) -> bool {
21        std::env::var_os("TMUX").is_some() && command_on_path("tmux") && command_on_path("nvim")
22    }
23
24    fn open(&self, content: &str) -> CoreResult<()> {
25        if std::env::var_os("TMUX").is_none() {
26            return Err(CoreError::validation(
27                "tmux-nvim viewer requires an active tmux session",
28            ));
29        }
30        if !command_on_path("nvim") {
31            return Err(CoreError::not_found("nvim is not installed or not on PATH"));
32        }
33        if !command_on_path("tmux") {
34            return Err(CoreError::not_found("tmux is not installed or not on PATH"));
35        }
36
37        let mut temp_file = tempfile::Builder::new()
38            .prefix("ito-viewer-")
39            .suffix(".md")
40            .tempfile()
41            .map_err(|e| CoreError::io("creating temporary viewer file", e))?;
42        std::io::Write::write_all(&mut temp_file, content.as_bytes())
43            .map_err(|e| CoreError::io("writing temporary viewer file", e))?;
44
45        // temp_file is deleted when it goes out of scope; `status()` waits for
46        // nvim to exit, so the file remains valid for the entire nvim session.
47        let status = Command::new("tmux")
48            .arg("display-popup")
49            .arg("-E")
50            .arg("nvim")
51            .arg("-R")
52            .arg(temp_file.path())
53            .status()
54            .map_err(|e| CoreError::io("spawning tmux display-popup", e))?;
55
56        if status.success() {
57            Ok(())
58        } else {
59            Err(CoreError::process(format!(
60                "tmux display-popup exited with status {status}"
61            )))
62        }
63    }
64}