spawn_editor/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::borrow::Cow;
4use std::convert::AsRef;
5use core::fmt;
6use std::env::{self, VarError};
7use std::process::{Command, ExitStatus};
8
9/// Get the default editor for the current environment
10// orignally taken from the crate `default-editor`
11pub fn get_editor(override_editor: Option<Cow<str>>) -> Result<Cow<str>, VarError> {
12    if let Some(z) = override_editor {
13        return Ok(z);
14    }
15
16    match env::var("VISUAL") {
17        Ok(result) => return Ok(result.into()),
18        Err(VarError::NotPresent) => {},
19        Err(error) => return Err(error),
20    }
21
22    match env::var("EDITOR") {
23        Ok(result) => return Ok(result.into()),
24        Err(VarError::NotPresent) => {},
25        Err(error) => return Err(error),
26    }
27
28    Ok("vi".into())
29}
30
31#[derive(Debug)]
32pub enum SEError {
33    Process(std::io::Error),
34    Var(VarError),
35}
36
37impl std::error::Error for SEError {
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39        match self {
40            SEError::Process(source) => Some(&*source),
41            SEError::Var(source) => Some(&*source),
42        }
43    }
44}
45
46impl fmt::Display for SEError {
47    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
48        match self {
49            SEError::Process(_) => {
50                formatter.write_str("editor spawning/waiting failed")
51            }
52            SEError::Var(_) => {
53                formatter.write_str("got invalid environment variable")
54            }
55        }
56    }
57}
58
59type SEResult = Result<ExitStatus, SEError>;
60
61/// This function either uses the `override_editor` argument as an editor
62/// or tries to get this information from the environment variables.
63/// A file to edit can be provided via `extra_args`
64///
65/// Example usage:
66/// ```no_run
67/// spawn_editor::spawn_editor(Some("nano"), &["src/lib.rs"]);
68/// ```
69pub fn spawn_editor(override_editor: Option<&str>, extra_args: &[&str]) -> SEResult {
70    let editor: std::borrow::Cow<str> = get_editor(override_editor.map(Into::into)).map_err(SEError::Var)?;
71
72    Ok(Command::new(&*editor)
73        .args(extra_args)
74        .spawn()
75        .and_then(|mut c| c.wait())
76        .map_err(SEError::Process)?)
77}
78
79/// This function is a convenient wrapper around [`spawn_editor`],
80/// in case that the arguments aren't simple string slices
81pub fn spawn_editor_generic<Ta, Tb>(override_editor: Option<Ta>, extra_args: &[Tb]) -> SEResult
82where
83    Ta: AsRef<str>,
84    Tb: AsRef<str>,
85{
86    let real_oore = override_editor.as_ref().map(|x| x.as_ref());
87    let xar: Vec<_> = extra_args.iter().map(|x| x.as_ref()).collect();
88    spawn_editor(real_oore, &xar[..])
89}
90
91/// This function is a convenient wrapper around [`spawn_editor_generic`],
92/// in case that `override_editor == None`
93///
94/// Example usage:
95/// ```no_run
96/// spawn_editor::spawn_editor_with_args(&["src/lib.rs"]);
97/// ```
98#[inline]
99pub fn spawn_editor_with_args<Tb: AsRef<str>>(extra_args: &[Tb]) -> SEResult {
100    spawn_editor_generic::<&str, Tb>(None, extra_args)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    // tests taken from `default-editor v0.1.0`
108    mod default_editor {
109        use std::env;
110
111        fn it_falls_back_to_vi() {
112            env::remove_var("VISUAL");
113            env::remove_var("EDITOR");
114
115            assert_eq!(crate::get_editor(None), Ok("vi".into()));
116        }
117
118        fn it_returns_visual() {
119            env::set_var("VISUAL", "test1");
120            env::remove_var("EDITOR");
121
122            assert_eq!(crate::get_editor(None), Ok("test1".to_string().into()));
123        }
124
125        fn it_returns_editor() {
126            env::remove_var("VISUAL");
127            env::set_var("EDITOR", "test2");
128
129            assert_eq!(crate::get_editor(None), Ok("test2".to_string().into()));
130        }
131
132        fn it_returns_visual_before_editor() {
133            env::set_var("VISUAL", "test3");
134            env::set_var("EDITOR", "test4");
135
136            assert_eq!(crate::get_editor(None), Ok("test3".to_string().into()));
137        }
138
139        #[test]
140        fn all_tests() {
141            // Wrap all tests in another function since they cannot be run in parallel
142            it_falls_back_to_vi();
143            it_returns_visual();
144            it_returns_editor();
145            it_returns_visual_before_editor();
146        }
147    }
148
149    #[test]
150    #[ignore]
151    fn testit() {
152        let _ = spawn_editor_with_args(&["src/lib.rs"]);
153    }
154}