Skip to main content

oe/
main.rs

1//! Open files (optionally at line:col) in existing Nvim / Helix pane.
2//!
3//! # Errors
4//! - Argument parsing, pane discovery, or command execution fails.
5
6use core::str::FromStr;
7
8use rootcause::prelude::ResultExt as _;
9use rootcause::report;
10use ytil_editor::Editor;
11use ytil_editor::FileToOpen;
12use ytil_sys::cli::Args;
13
14/// Wrapper for environment variables.
15struct Env {
16    /// The name of the environment variable (static string).
17    name: &'static str,
18    /// The value of the environment variable (dynamically constructed string).
19    value: String,
20}
21
22impl Env {
23    /// Returns environment variable as tuple.
24    pub fn by_ref(&self) -> (&'static str, &str) {
25        (self.name, &self.value)
26    }
27}
28
29/// Creates enriched PATH for `WezTerm` integration.
30///
31/// # Errors
32/// - A required environment variable is missing or invalid Unicode.
33fn get_enriched_path_env() -> rootcause::Result<Env> {
34    let enriched_path = [
35        &std::env::var("PATH").unwrap_or_else(|_| String::new()),
36        "/opt/homebrew/bin",
37        &ytil_sys::dir::build_home_path(&[".local", "bin"])?.to_string_lossy(),
38    ]
39    .join(":");
40
41    Ok(Env {
42        name: "PATH",
43        value: enriched_path,
44    })
45}
46
47/// Escape single quotes for safe embedding in shell single-quoted strings.
48///
49/// Replaces each `'` with `'\''` (end quote, escaped quote, begin quote).
50fn escape_single_quotes(s: &str) -> String {
51    s.replace('\'', "'\\''")
52}
53
54/// Open files (optionally at line:col) in existing Nvim / Helix pane.
55#[ytil_sys::main]
56fn main() -> rootcause::Result<()> {
57    let enriched_path_env = get_enriched_path_env()?;
58    let args = ytil_sys::cli::get();
59
60    if args.has_help() {
61        println!("{}", include_str!("../help.txt"));
62        return Ok(());
63    }
64
65    let Some(editor) = args.first().map(|x| Editor::from_str(x)).transpose()? else {
66        return Err(report!("missing editor arg")).attach_with(|| format!("args={args:#?}"));
67    };
68
69    let Some(file_to_open) = args.get(1) else {
70        return Err(report!("missing file arg")).attach_with(|| format!("args={args:#?}"));
71    };
72
73    let pane_id = match args.get(2) {
74        Some(x) => x.parse()?,
75        None => ytil_wezterm::get_current_pane_id()?,
76    };
77
78    let panes = ytil_wezterm::get_all_panes(&[enriched_path_env.by_ref()])?;
79
80    let file_to_open = FileToOpen::try_from((file_to_open.as_str(), pane_id, panes.as_slice()))?;
81
82    let editor_pane_id =
83        ytil_wezterm::get_sibling_pane_with_titles(&panes, pane_id, editor.pane_titles()).map(|x| x.pane_id)?;
84
85    let open_file_cmd = editor.open_file_cmd(&file_to_open);
86    let escaped_open_file_cmd = escape_single_quotes(&open_file_cmd);
87
88    ytil_cmd::silent_cmd("sh")
89        .args([
90            "-c",
91            &format!(
92                "{} && {} && {} && {}",
93                // `wezterm cli send-text $'\e'` sends the "ESC" to `WezTerm` to exit from insert mode
94                // https://github.com/wez/wezterm/discussions/3945
95                ytil_wezterm::send_text_to_pane_cmd(r"$'\e'", editor_pane_id),
96                ytil_wezterm::send_text_to_pane_cmd(&format!("'{escaped_open_file_cmd}'"), editor_pane_id),
97                ytil_wezterm::submit_pane_cmd(editor_pane_id),
98                ytil_wezterm::activate_pane_cmd(editor_pane_id),
99            ),
100        ])
101        .envs(std::iter::once(enriched_path_env.by_ref()))
102        .spawn()?;
103
104    Ok(())
105}
106
107#[cfg(test)]
108mod tests {
109    use rstest::rstest;
110
111    use super::*;
112
113    #[rstest]
114    #[case::no_quotes("hello world", "hello world")]
115    #[case::single_quote("it's here", "it'\\''s here")]
116    #[case::multiple_quotes("a'b'c", "a'\\''b'\\''c")]
117    #[case::only_quote("'", "'\\''")]
118    #[case::empty("", "")]
119    fn test_escape_single_quotes_produces_expected_output(#[case] input: &str, #[case] expected: &str) {
120        pretty_assertions::assert_eq!(escape_single_quotes(input), expected);
121    }
122}