1use crate::prelude::*;
2
3use std::io::{Read as _, Write as _};
4
5pub fn edit(contents: &str, help: &str) -> Result<String> {
6 if !rustix::termios::isatty(std::io::stdin()) {
7 return match std::io::read_to_string(std::io::stdin()) {
9 Err(e) => Err(Error::FailedToReadFromStdin { err: e }),
10 Ok(res) => Ok(res),
11 };
12 }
13
14 let mut var = "VISUAL";
15 let editor = std::env::var_os(var).unwrap_or_else(|| {
16 var = "EDITOR";
17 std::env::var_os(var).unwrap_or_else(|| "/usr/bin/vim".into())
18 });
19
20 let dir = tempfile::tempdir().unwrap();
21 let file = dir.path().join("bwx");
22 let mut fh = std::fs::File::create(&file).unwrap();
23 fh.write_all(contents.as_bytes()).unwrap();
24 fh.write_all(help.as_bytes()).unwrap();
25 drop(fh);
26
27 let (cmd, args) = if contains_shell_metacharacters(&editor) {
28 let mut cmdline = std::ffi::OsString::new();
29 cmdline.extend([
30 editor.as_ref(),
31 std::ffi::OsStr::new(" "),
32 file.as_os_str(),
33 ]);
34
35 let editor_args = vec![std::ffi::OsString::from("-c"), cmdline];
36 (std::path::Path::new("/bin/sh"), editor_args)
37 } else {
38 let editor = std::path::Path::new(&editor);
39 let mut editor_args = vec![];
40
41 #[allow(clippy::single_match_else)] match editor.file_name() {
43 Some(editor) => match editor.to_str() {
44 Some("vim" | "nvim") => {
45 editor_args.push(std::ffi::OsString::from("-ni"));
47 editor_args.push(std::ffi::OsString::from("NONE"));
48 }
49 _ => {
50 }
52 },
53 None => {
54 return Err(Error::InvalidEditor {
55 var: var.to_string(),
56 editor: editor.as_os_str().to_os_string(),
57 })
58 }
59 }
60 editor_args.push(file.clone().into_os_string());
61 (editor, editor_args)
62 };
63
64 let res = std::process::Command::new(cmd).args(&args).status();
65 match res {
66 Ok(res) => {
67 if !res.success() {
68 return Err(Error::FailedToRunEditor {
69 editor: cmd.to_owned(),
70 args,
71 res,
72 });
73 }
74 }
75 Err(err) => {
76 return Err(Error::FailedToFindEditor {
77 editor: cmd.to_owned(),
78 err,
79 })
80 }
81 }
82
83 let mut fh = std::fs::File::open(&file).unwrap();
84 let mut contents = String::new();
85 fh.read_to_string(&mut contents).unwrap();
86 drop(fh);
87
88 Ok(contents)
89}
90
91fn contains_shell_metacharacters(cmd: &std::ffi::OsStr) -> bool {
92 cmd.to_str()
93 .is_some_and(|s| s.contains(&[' ', '$', '\'', '"'][..]))
94}