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