aico/commands/
edit.rs

1use crate::exceptions::AicoError;
2use crate::session::Session;
3use std::env;
4use std::fs;
5use std::io::IsTerminal;
6use std::io::{Read, Write};
7use std::process::Command;
8
9fn run_editor(content: &str) -> Result<String, AicoError> {
10    // 1. Create temp file
11    let mut temp = tempfile::Builder::new().suffix(".md").tempfile()?;
12    temp.write_all(content.as_bytes())?;
13    let temp_path = temp.path().to_path_buf();
14
15    // 2. Open Editor
16    let editor = env::var("EDITOR").unwrap_or_else(|_| "vi".into());
17    let parts = shlex::split(&editor).ok_or_else(|| {
18        AicoError::Configuration(format!("Failed to parse EDITOR variable: '{}'", editor))
19    })?;
20
21    if parts.is_empty() {
22        return Err(AicoError::Configuration(
23            "EDITOR environment variable is empty".into(),
24        ));
25    }
26
27    let status = Command::new(&parts[0])
28        .args(&parts[1..])
29        .arg(&temp_path)
30        .status()
31        .map_err(|e| {
32            if e.kind() == std::io::ErrorKind::NotFound {
33                AicoError::InvalidInput(format!(
34                    "Editor '{}' not found. Please set $EDITOR.",
35                    parts[0]
36                ))
37            } else {
38                AicoError::Io(e)
39            }
40        })?;
41
42    if !status.success() {
43        return Err(AicoError::InvalidInput(format!(
44            "Editor closed with exit code {}. Aborting.",
45            status.code().unwrap_or(1)
46        )));
47    }
48
49    // 3. Read back
50    let mut buffer = String::new();
51    fs::File::open(&temp_path)?.read_to_string(&mut buffer)?;
52    Ok(buffer)
53}
54
55pub fn run(index_str: String, prompt_flag: bool) -> Result<(), AicoError> {
56    let mut session = Session::load_active()?;
57    let pair_idx = session.resolve_pair_index(&index_str)?;
58
59    // Calculate which message within the pair to edit
60    let msg_idx = if prompt_flag {
61        pair_idx * 2
62    } else {
63        pair_idx * 2 + 1
64    };
65
66    let global_idx = session.view.message_indices[msg_idx];
67    let records = session.store.read_many(&[global_idx])?;
68    let record = records
69        .first()
70        .ok_or_else(|| AicoError::SessionIntegrity("Record not found".into()))?;
71
72    let original_content = &record.content;
73
74    let is_piped = !std::io::stdin().is_terminal();
75    let force_editor = std::env::var("AICO_FORCE_EDITOR").is_ok();
76
77    let new_content = if is_piped && !force_editor {
78        let mut buffer = String::new();
79        std::io::stdin().read_to_string(&mut buffer)?;
80        buffer
81    } else {
82        run_editor(original_content)?
83    };
84
85    // Normalize for comparison
86    let norm_new = new_content.replace("\r\n", "\n");
87    let norm_old = original_content.replace("\r\n", "\n");
88
89    if norm_new.trim().is_empty() || norm_new == norm_old {
90        println!("No changes detected. Aborting.");
91        return Ok(());
92    }
93
94    // 4. Update session
95    session.edit_message(msg_idx, norm_new)?;
96
97    let target = if prompt_flag { "prompt" } else { "response" };
98    println!("Updated {} for message pair {}.", target, pair_idx);
99
100    Ok(())
101}