use anyhow::{Context, Result, bail};
use std::io::{IsTerminal, Write};
use std::path::Path;
use std::process::Command;
pub fn edit(initial: &str) -> Result<String> {
let editor = editor_command()?;
if !std::io::stdin().is_terminal() {
bail!(
"cannot open an editor: input is not a terminal\n\
pass the text with --text instead"
);
}
let directory = tempfile::Builder::new()
.prefix("sefy-")
.tempdir()
.context("cannot create a temporary directory for the editor")?;
let path = directory.path().join("note.txt");
std::fs::write(&path, initial).with_context(|| format!("cannot write {}", path.display()))?;
let status = spawn(&editor, &path)?;
if !status.success() {
shred(&path);
bail!("{editor} exited with {status}; nothing was changed");
}
let edited = std::fs::read_to_string(&path)
.with_context(|| format!("cannot read {} back", path.display()))?;
shred(&path);
Ok(edited.trim_end_matches(['\n', '\r']).to_owned())
}
fn editor_command() -> Result<String> {
for variable in ["VISUAL", "EDITOR"] {
if let Ok(value) = std::env::var(variable) {
let value = value.trim();
if !value.is_empty() {
return Ok(value.to_owned());
}
}
}
bail!(
"no editor configured\n\
set $EDITOR (or $VISUAL), or pass the text with --text"
)
}
fn spawn(editor: &str, path: &Path) -> Result<std::process::ExitStatus> {
let mut parts = editor.split_whitespace();
let program = parts.next().unwrap_or(editor);
Command::new(program)
.args(parts)
.arg(path)
.status()
.with_context(|| format!("cannot run {program}"))
}
fn shred(path: &Path) {
if let Ok(metadata) = std::fs::metadata(path)
&& let Ok(mut file) = std::fs::File::create(path)
{
let _ = file.write_all(&vec![0u8; metadata.len() as usize]);
let _ = file.sync_all();
}
let _ = std::fs::remove_file(path);
}