Skip to main content

ass_editor/core/document/
incremental_edit.rs

1//! Incremental editing entry points backed by core's partial parser
2//!
3//! Implements `edit_incremental` (with error-recovery fallbacks) plus the
4//! insert/delete convenience wrappers. The fast-path helpers live in the
5//! sibling `incremental_fast` module.
6
7use super::EditorDocument;
8use crate::commands::CommandResult;
9use crate::core::errors::Result;
10use crate::core::position::{Position, Range};
11use ass_core::parser::script::ScriptDeltaOwned;
12
13#[cfg(feature = "std")]
14use crate::events::DocumentEvent;
15
16#[cfg(not(feature = "std"))]
17use alloc::{format, string::ToString, vec, vec::Vec};
18
19impl EditorDocument {
20    // === INCREMENTAL PARSING WITH CORE INTEGRATION ===
21
22    /// Perform incremental edit using core's parse_partial for optimal performance
23    ///
24    /// Includes error recovery with fallback strategies:
25    /// 1. Try incremental parsing with Script::parse_partial()
26    /// 2. On failure, fall back to full reparse
27    /// 3. On repeated failures, reset parser state and retry
28    pub fn edit_incremental(&mut self, range: Range, new_text: &str) -> Result<ScriptDeltaOwned> {
29        use crate::core::history::Operation;
30
31        // Fast path for simple edits that don't require full parsing
32        let is_simple_edit = new_text.len() <= 100 && // Small to medium edits
33            !new_text.contains('[') && // No new sections
34            new_text.matches('\n').count() <= 1 && // At most one line break
35            range.len() <= 50; // Small replacements
36
37        if is_simple_edit {
38            return self.edit_fast_path(range, new_text);
39        }
40
41        // Get the old text for undo data
42        #[cfg(feature = "std")]
43        let old_text = self.text_range(range)?;
44        #[cfg(not(feature = "std"))]
45        let _old_text = self.text_range(range)?;
46
47        // Apply change with incremental parsing (includes fallback to full parse)
48        let current_text = self.text();
49        let delta = match self
50            .incremental_parser
51            .apply_change(&current_text, range, new_text)
52        {
53            Ok(delta) => delta,
54            Err(_e) => {
55                // Log the error for debugging
56                #[cfg(feature = "std")]
57                eprintln!("Incremental parsing failed, attempting recovery: {_e}");
58
59                // If incremental parsing fails repeatedly, reset the parser
60                if self.incremental_parser.should_reparse() {
61                    self.incremental_parser.clear_cache();
62                }
63
64                // Try one more time with a fresh parser state
65                match self
66                    .incremental_parser
67                    .apply_change(&current_text, range, new_text)
68                {
69                    Ok(delta) => delta,
70                    Err(_) => {
71                        // Final fallback: return a minimal delta indicating the change
72                        ScriptDeltaOwned {
73                            added: Vec::new(),
74                            modified: vec![(0, "Script modified".to_string())],
75                            removed: Vec::new(),
76                            new_issues: Vec::new(),
77                        }
78                    }
79                }
80            }
81        };
82
83        // Create undo data from the delta (must be captured BEFORE applying changes)
84        let undo_data = self.capture_delta_undo_data(&delta)?;
85
86        // Create delta operation for history
87        let operation = Operation::Delta {
88            forward: delta.clone(),
89            undo_data,
90        };
91
92        // Create command result
93        let result = CommandResult::success_with_change(
94            range,
95            Position::new(range.start.offset + new_text.len()),
96        );
97
98        // Record in history
99        let result_with_delta = result.with_delta(delta.clone());
100        self.history.record_operation(
101            operation,
102            format!("Incremental edit at {}", range.start.offset),
103            &result_with_delta,
104        );
105
106        // Apply the text change
107        self.replace_raw(range, new_text)?;
108
109        // Mark as modified
110        self.modified = true;
111
112        // Emit event
113        #[cfg(feature = "std")]
114        self.emit(DocumentEvent::TextReplaced {
115            range,
116            old_text,
117            new_text: new_text.to_string(),
118        });
119
120        Ok(delta)
121    }
122
123    /// Insert text with incremental parsing (< 1ms target)
124    pub fn insert_incremental(&mut self, pos: Position, text: &str) -> Result<ScriptDeltaOwned> {
125        let range = Range::new(pos, pos); // Zero-length range for insertion
126        self.edit_incremental(range, text)
127    }
128
129    /// Delete text with incremental parsing
130    pub fn delete_incremental(&mut self, range: Range) -> Result<ScriptDeltaOwned> {
131        self.edit_incremental(range, "")
132    }
133}