1use crate::frontmatter::ComposeError;
2use std::path::Path;
3use tokio::process::Command;
4
5pub fn resolve_editor(config_editor: Option<&str>) -> String {
8 std::env::var("EDITOR")
9 .or_else(|_| std::env::var("VISUAL"))
10 .unwrap_or_else(|_| {
11 config_editor
12 .map(|s| s.to_string())
13 .unwrap_or_else(|| "vi".to_string())
14 })
15}
16
17pub async fn spawn_editor(
20 editor: &str,
21 file_path: &Path,
22 cursor_line: Option<usize>,
23) -> Result<bool, ComposeError> {
24 let mut cmd = Command::new(editor);
25
26 if let Some(line) = cursor_line {
28 let editor_lower = editor.to_lowercase();
29 if editor_lower.contains("vim") || editor_lower == "vi" || editor_lower.contains("nvim") {
30 cmd.arg(format!("+{line}"));
31 } else if editor_lower.contains("hx") || editor_lower.contains("helix") {
32 let path_str = format!("{}:{line}", file_path.display());
33 let status = Command::new(editor)
34 .arg(&path_str)
35 .status()
36 .await
37 .map_err(|e| ComposeError::EditorFailed(e.to_string()))?;
38 return Ok(status.success());
39 }
40 }
41
42 cmd.arg(file_path);
43
44 let status = cmd
45 .status()
46 .await
47 .map_err(|e| ComposeError::EditorFailed(e.to_string()))?;
48
49 Ok(status.success())
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use std::sync::Mutex;
56
57 static ENV_LOCK: Mutex<()> = Mutex::new(());
59
60 #[test]
61 fn resolve_editor_env_var() {
62 let _guard = ENV_LOCK.lock().unwrap();
63 let prev_editor = std::env::var("EDITOR").ok();
64 let prev_visual = std::env::var("VISUAL").ok();
65
66 unsafe { std::env::set_var("EDITOR", "nvim") };
67 let result = resolve_editor(None);
68
69 match prev_editor {
71 Some(v) => unsafe { std::env::set_var("EDITOR", v) },
72 None => unsafe { std::env::remove_var("EDITOR") },
73 }
74 match prev_visual {
75 Some(v) => unsafe { std::env::set_var("VISUAL", v) },
76 None => unsafe { std::env::remove_var("VISUAL") },
77 }
78
79 assert_eq!(result, "nvim");
80 }
81
82 #[test]
83 fn resolve_editor_fallback() {
84 let _guard = ENV_LOCK.lock().unwrap();
85 let prev_editor = std::env::var("EDITOR").ok();
86 let prev_visual = std::env::var("VISUAL").ok();
87
88 unsafe { std::env::remove_var("EDITOR") };
89 unsafe { std::env::remove_var("VISUAL") };
90 let result = resolve_editor(None);
91
92 if let Some(v) = prev_editor {
94 unsafe { std::env::set_var("EDITOR", v) }
95 }
96 if let Some(v) = prev_visual {
97 unsafe { std::env::set_var("VISUAL", v) }
98 }
99
100 assert_eq!(result, "vi");
101 }
102
103 #[test]
104 fn resolve_editor_config() {
105 let _guard = ENV_LOCK.lock().unwrap();
106 let prev_editor = std::env::var("EDITOR").ok();
107 let prev_visual = std::env::var("VISUAL").ok();
108
109 unsafe { std::env::remove_var("EDITOR") };
110 unsafe { std::env::remove_var("VISUAL") };
111 let result = resolve_editor(Some("nano"));
112
113 if let Some(v) = prev_editor {
115 unsafe { std::env::set_var("EDITOR", v) }
116 }
117 if let Some(v) = prev_visual {
118 unsafe { std::env::set_var("VISUAL", v) }
119 }
120
121 assert_eq!(result, "nano");
122 }
123}