editor_input/
lib.rs

1use std::{
2    env::var,
3    error,
4    fs::File,
5    io::{self, Read, Write},
6    process::{Command, ExitStatus},
7};
8use tempfile::NamedTempFile;
9
10#[derive(Debug)]
11pub enum EditorInputError {
12    ExitStatus(ExitStatus),
13    IO(io::Error),
14}
15
16impl std::fmt::Display for EditorInputError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            EditorInputError::ExitStatus(ex) => {
20                write!(f, "Editor exited with unsuccessful exit status {}", ex)
21            }
22            EditorInputError::IO(io) => io.fmt(f),
23        }
24    }
25}
26
27impl error::Error for EditorInputError {
28    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
29        match *self {
30            EditorInputError::ExitStatus(_) => None,
31            EditorInputError::IO(ref e) => Some(e),
32        }
33    }
34}
35
36impl From<io::Error> for EditorInputError {
37    fn from(e: io::Error) -> Self {
38        Self::IO(e)
39    }
40}
41
42/// Opens the editor specified by the $EDITOR environment variable (fallback `vi`)
43/// and returns the saved text when the editor is closed.
44///
45/// # Arguments
46///
47///  * `placeholder` - Text that will be present in the temporary file being edited
48///
49pub fn input_from_editor(placeholder: &str) -> Result<String, EditorInputError> {
50    let editor = var("EDITOR").unwrap_or("vi".to_string());
51
52    let mut tmpfile = NamedTempFile::new()?;
53    tmpfile.write_all(placeholder.as_bytes())?;
54    let file_path = tmpfile.into_temp_path();
55
56    let status = Command::new(editor).arg(&file_path).status()?;
57    if !status.success() {
58        return Err(EditorInputError::ExitStatus(status));
59    }
60
61    let mut editable = String::new();
62    File::open(file_path)?.read_to_string(&mut editable)?;
63
64    Ok(editable)
65}