use anyhow::{bail, Context, Result};
pub fn edit_text(initial: &str, scratch_name: &str) -> Result<String> {
let scratch = std::env::temp_dir().join(scratch_name);
std::fs::write(&scratch, initial).with_context(|| format!("writing {}", scratch.display()))?;
let editor = std::env::var("VISUAL")
.or_else(|_| std::env::var("EDITOR"))
.unwrap_or_else(|_| "vi".to_string());
let status = std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"{editor} {}",
shell_quote(&scratch.to_string_lossy())
))
.status()
.with_context(|| format!("launching {editor}"))?;
if !status.success() {
let _ = std::fs::remove_file(&scratch);
bail!("{editor} exited with {status}");
}
let text = std::fs::read_to_string(&scratch)?;
let _ = std::fs::remove_file(&scratch);
Ok(text)
}
pub fn shell_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', r"'\''"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_quoted_path_survives_single_quotes() {
assert_eq!(shell_quote("plain"), "'plain'");
assert_eq!(shell_quote("it's"), r"'it'\''s'");
}
#[test]
fn a_scripted_editor_round_trips_the_text() {
std::env::set_var("VISUAL", "true");
let text = edit_text("hello\nworld", "mecha-editor-test.txt").unwrap();
assert_eq!(text, "hello\nworld");
std::env::set_var("VISUAL", "false");
assert!(edit_text("x", "mecha-editor-test-2.txt").is_err());
}
}