1use crate::cx::Env;
5use crate::error::{Error, Result};
6
7pub fn resolve_editor(config_editor: Option<&str>, env: &Env) -> Result<String> {
9 if let Some(editor) = config_editor.filter(|e| !e.is_empty()) {
10 return Ok(editor.to_string());
11 }
12 if let Some(visual) = env.get("VISUAL").filter(|v| !v.is_empty()) {
13 return Ok(visual.to_string());
14 }
15 if let Some(editor) = env.get("EDITOR").filter(|e| !e.is_empty()) {
16 return Ok(editor.to_string());
17 }
18 Err(Error::operation(
19 "no editor configured; set the `editor` config key, $VISUAL, or $EDITOR",
20 ))
21}
22
23pub fn editor_argv(command: &str) -> Vec<String> {
25 shell_words::split(command).unwrap_or_else(|_| vec![command.to_string()])
26}
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31 use std::collections::HashMap;
32
33 fn env(pairs: &[(&str, &str)]) -> Env {
34 Env::from_map(
35 pairs
36 .iter()
37 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
38 .collect::<HashMap<_, _>>(),
39 )
40 }
41
42 #[test]
43 fn config_editor_wins() {
44 let e = env(&[("VISUAL", "vim"), ("EDITOR", "nano")]);
45 assert_eq!(resolve_editor(Some("hx"), &e).unwrap(), "hx");
46 }
47
48 #[test]
49 fn falls_back_to_visual_then_editor() {
50 let e = env(&[("VISUAL", "vim"), ("EDITOR", "nano")]);
51 assert_eq!(resolve_editor(None, &e).unwrap(), "vim");
52 let e2 = env(&[("EDITOR", "nano")]);
53 assert_eq!(resolve_editor(None, &e2).unwrap(), "nano");
54 }
55
56 #[test]
57 fn empty_values_are_skipped() {
58 let e = env(&[("VISUAL", ""), ("EDITOR", "nano")]);
59 assert_eq!(resolve_editor(Some(""), &e).unwrap(), "nano");
60 }
61
62 #[test]
63 fn errors_when_unset() {
64 assert!(resolve_editor(None, &env(&[])).is_err());
65 }
66
67 #[test]
68 fn argv_splits_quoted_command() {
69 assert_eq!(editor_argv("code --wait"), vec!["code", "--wait"]);
70 assert_eq!(editor_argv("\"my editor\" -n"), vec!["my editor", "-n"]);
71 }
72}