Skip to main content

winx_code_agent/tools/
file_write_or_edit.rs

1//! `FileWriteOrEdit` facade.
2//!
3//! Parsing, fuzzy matching, atomic commit, and result rendering live in focused
4//! submodules under `file_write_or_edit/`.
5
6mod commit;
7mod matcher;
8mod parser;
9mod report;
10
11#[cfg(test)]
12mod tests;
13
14use std::sync::Arc;
15
16use tokio::sync::Mutex;
17use tracing::instrument;
18
19use crate::errors::{Result, WinxError};
20use crate::state::bash_state::BashState;
21use crate::types::{normalize_thread_id, FileWriteOrEdit};
22pub(crate) use commit::{
23    commit_edit, ensure_parent_dirs, hash_content, plan_edit, write_no_follow,
24};
25
26#[cfg(feature = "fuzzing")]
27pub(crate) fn fuzz_apply_blocks(original: &str, blocks: &str) {
28    let _ = matcher::apply_blocks_with_unescape_retry(original, blocks);
29}
30
31#[instrument(level = "info", skip(bash_state_arc, file_write_or_edit))]
32pub async fn handle_tool_call(
33    bash_state_arc: &Arc<Mutex<Option<BashState>>>,
34    file_write_or_edit: FileWriteOrEdit,
35) -> Result<String> {
36    let mut bash_state_guard = bash_state_arc.lock().await;
37    {
38        let bash_state = bash_state_guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
39        let thread_id = normalize_thread_id(&file_write_or_edit.thread_id);
40        if thread_id != bash_state.current_thread_id {
41            return Err(WinxError::ThreadIdMismatch(thread_id));
42        }
43    }
44
45    let mut state = bash_state_guard.take().ok_or(WinxError::BashStateNotInitialized)?;
46    let recovery_state = state.clone();
47    let joined = tokio::task::spawn_blocking(move || {
48        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
49            let planned = plan_edit(
50                &state,
51                &file_write_or_edit.file_path,
52                file_write_or_edit.percentage_to_change,
53                &file_write_or_edit.text_or_search_replace_blocks,
54                true,
55            )?;
56            commit_edit(&mut state, planned)
57        }))
58        .unwrap_or_else(|_| {
59            Err(WinxError::CommandExecutionError(
60                "FileWriteOrEdit panicked on the blocking worker".to_string(),
61            ))
62        });
63        (state, result)
64    })
65    .await;
66
67    match joined {
68        Ok((state, result)) => {
69            *bash_state_guard = Some(state);
70            result
71        }
72        Err(error) => {
73            *bash_state_guard = Some(recovery_state);
74            Err(WinxError::CommandExecutionError(format!(
75                "FileWriteOrEdit blocking task failed: {error}"
76            )))
77        }
78    }
79}