1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4
5pub struct Editor {
7 pub bin: PathBuf,
8 pub args: Vec<String>,
9}
10
11pub fn resolve() -> Result<Editor> {
14 let env_editor = std::env::var("PODBOX_EDITOR").ok();
15 let env_visual = std::env::var("VISUAL").ok();
16 let env_editor_generic = std::env::var("EDITOR").ok();
17
18 let candidates: Vec<(&str, Option<&str>)> = vec![
19 ("$PODBOX_EDITOR", env_editor.as_deref()),
20 ("$VISUAL", env_visual.as_deref()),
21 ("$EDITOR", env_editor_generic.as_deref()),
22 ("nvim", None),
23 ("neovim", None),
24 ("hx", None),
25 ("helix", None),
26 ("code", None),
27 ("nano", None),
28 ("vi", None),
29 ];
30
31 for (name, env_val) in &candidates {
32 let path = match env_val {
33 Some(val) => {
34 let parts = shell_words::split(val).unwrap_or_else(|_| vec![val.to_string()]);
35 let bin_part = parts.first().map(|s| s.as_str()).unwrap_or(val);
36 let p = PathBuf::from(bin_part);
37 if p.is_absolute() && p.exists() {
38 p
39 } else {
40 match which::which(bin_part) {
41 Ok(p) => p,
42 Err(_) => continue,
43 }
44 }
45 }
46 None => match which::which(name) {
47 Ok(p) => p,
48 Err(_) => continue,
49 },
50 };
51
52 let mut args = editor_args(name);
53 if let Some(val) = env_val {
54 if let Ok(words) = shell_words::split(val) {
55 args.extend(words.into_iter().skip(1));
56 }
57 }
58 let editor = Editor { bin: path, args };
59 return Ok(editor);
60 }
61
62 anyhow::bail!(
63 "no editor found.\n\
64 Set $VISUAL, $EDITOR, or $PODBOX_EDITOR to your preferred editor.\n\
65 Example: export EDITOR=nano"
66 );
67}
68
69fn editor_args(name: &str) -> Vec<String> {
72 let base = Path::new(name)
73 .file_name()
74 .map(|s| s.to_string_lossy().to_string())
75 .unwrap_or_else(|| name.to_string());
76 if base == "code" || base == "code-insiders" {
77 vec!["--wait".into()]
78 } else {
79 vec![]
80 }
81}
82
83pub fn open(editor: &Editor, path: &Path) -> Result<()> {
85 let status = std::process::Command::new(&editor.bin)
86 .args(&editor.args)
87 .arg(path)
88 .status()?;
89
90 if !status.success() {
91 let code = status.code().unwrap_or(-1);
92 anyhow::bail!("editor exited with code {}", code);
93 }
94 Ok(())
95}