edit_rs/
lib.rs

1use std::env;
2use std::ffi::OsString;
3use std::fs::OpenOptions;
4use std::io;
5use std::io::prelude::*;
6
7
8extern crate subprocess;
9use subprocess::{Exec, Redirection};
10
11// This crate tries to get multi-line string input from your user's favorite
12// text editor. Its logic is based on how Git command-line chooses an editor:
13// https://github.com/git/git/blob/936d1b989416a95f593bf81ccae8ac62cd83f279/editor.c
14
15#[derive(Debug)]
16pub enum Error {
17    NoEditor(&'static str),
18    IOError(io::Error),
19    SubprocessError(subprocess::PopenError),
20}
21
22impl From<io::Error> for Error {
23    fn from(e: io::Error) -> Self {
24        Error::IOError(e)
25    }
26}
27
28impl From<subprocess::PopenError> for Error {
29    fn from(e: subprocess::PopenError) -> Self {
30        Error::SubprocessError(e)
31    }
32}
33
34fn is_terminal_dumb() -> bool {
35    match env::var("TERM") {
36        Err(_) => true,
37        Ok(term) => term == "dumb".to_string(),
38    }
39}
40
41pub fn text_editor() -> Result<OsString, Error> {
42    let mut editor = env::var_os("EDITRS_EDITOR");
43    let terminal_is_dumb = is_terminal_dumb();
44
45    if editor == None && !terminal_is_dumb {
46        editor = env::var_os("VISUAL");
47    }
48    if editor == None {
49        editor = env::var_os("EDITOR");
50    }
51
52    if editor == None && terminal_is_dumb {
53        return Err(Error::NoEditor("Terminal is dumb, but EDITOR unset"));
54    }
55
56    let editor = editor.unwrap_or_else(|| {
57        println!("Using vi as default text editor. To change this behavior, set the EDITRS_EDITOR environment variable.");
58        OsString::from("vi".to_string())
59    });
60    Ok(editor)
61}
62
63pub fn get_input(default_value: &str) -> Result<String, Error> {
64    let file = OpenOptions::new().write(true).create(true).open(".EDITRS_EDITOR_INPUT")?;
65    // If a default value is given, write it to the file before opening it
66    file.set_len(0)?;
67    Exec::cmd("echo").arg(default_value).stdout(Redirection::File(file)).join()?;
68
69
70    // Open the user's text editor and wait for them to close it
71    Exec::cmd(text_editor()?).arg(".EDITRS_EDITOR_INPUT").join()?;
72    let mut file = OpenOptions::new().read(true).open(".EDITRS_EDITOR_INPUT")?;
73    let mut file_buffer = String::new();
74    file.read_to_string(&mut file_buffer)?;
75    Ok(file_buffer.trim().to_string())
76}