use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use crate::error::{Result, SparcliError};
pub fn edit_file(command: Option<&str>, path: &Path) -> Result<()> {
let resolved = resolve_command(command);
let mut parts = resolved.split_whitespace();
let program = parts
.next()
.ok_or_else(|| SparcliError::Config("empty editor command".into()))?;
Command::new(program).args(parts).arg(path).status()?;
Ok(())
}
pub(crate) fn edit_text(
command: Option<&str>,
initial: &str,
suffix: &str,
) -> Result<String> {
let path = temp_path(suffix);
fs::write(&path, initial)?;
let result = edit_file(command, &path);
let contents = fs::read_to_string(&path);
if let Err(error) = fs::remove_file(&path) {
log::debug!("could not remove temp file {}: {error}", path.display());
}
result?;
Ok(contents?)
}
fn resolve_command(command: Option<&str>) -> String {
if let Some(command) = command
&& !command.trim().is_empty()
{
return command.to_string();
}
for key in ["VISUAL", "EDITOR"] {
if let Ok(value) = env::var(key)
&& !value.trim().is_empty()
{
return value;
}
}
default_editor().to_string()
}
fn default_editor() -> &'static str {
if cfg!(windows) { "notepad" } else { "vi" }
}
fn temp_path(suffix: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let name = format!("sparcli-{}-{nanos}{suffix}", std::process::id());
env::temp_dir().join(name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_prefers_explicit_command() {
assert_eq!(resolve_command(Some("nano")), "nano");
assert!(!resolve_command(Some(" ")).trim().is_empty());
}
#[cfg(unix)]
#[test]
fn edit_text_round_trips_through_a_noop_editor() {
let result = edit_text(Some("true"), "hello", ".txt").unwrap();
assert_eq!(result, "hello");
}
}