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, invalidate_edit_read_permit, plan_edit,
24    plan_revision_edit, write_no_follow_if_unchanged,
25};
26
27#[cfg(feature = "fuzzing")]
28pub(crate) fn fuzz_apply_blocks(original: &str, blocks: &str) {
29    let _ = matcher::apply_blocks_with_unescape_retry(original, blocks);
30}
31
32#[instrument(level = "info", skip(bash_state_arc, file_write_or_edit))]
33pub async fn handle_tool_call(
34    bash_state_arc: &Arc<Mutex<Option<BashState>>>,
35    file_write_or_edit: FileWriteOrEdit,
36) -> Result<String> {
37    let mut bash_state_guard = bash_state_arc.lock().await;
38    {
39        let bash_state = bash_state_guard.as_ref().ok_or(WinxError::BashStateNotInitialized)?;
40        let thread_id = normalize_thread_id(&file_write_or_edit.thread_id);
41        if thread_id != bash_state.current_thread_id {
42            return Err(WinxError::ThreadIdMismatch(thread_id));
43        }
44    }
45
46    let mut state = bash_state_guard.take().ok_or(WinxError::BashStateNotInitialized)?;
47    let recovery_state = state.clone();
48    let joined = tokio::task::spawn_blocking(move || {
49        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
50            let planned = match plan_edit(
51                &state,
52                &file_write_or_edit.file_path,
53                file_write_or_edit.percentage_to_change,
54                &file_write_or_edit.text_or_search_replace_blocks,
55                true,
56            ) {
57                Ok(planned) => planned,
58                Err(error) => {
59                    if error.is_search_match_conflict() {
60                        invalidate_edit_read_permit(&mut state, &file_write_or_edit.file_path);
61                    }
62                    return Err(error);
63                }
64            };
65            commit_edit(&mut state, planned)
66        }))
67        .unwrap_or_else(|_| {
68            Err(WinxError::CommandExecutionError(
69                "FileWriteOrEdit panicked on the blocking worker".to_string(),
70            ))
71        });
72        (state, result)
73    })
74    .await;
75
76    match joined {
77        Ok((state, result)) => {
78            *bash_state_guard = Some(state);
79            result
80        }
81        Err(error) => {
82            *bash_state_guard = Some(recovery_state);
83            Err(WinxError::CommandExecutionError(format!(
84                "FileWriteOrEdit blocking task failed: {error}"
85            )))
86        }
87    }
88}