Skip to main content

ass_editor/commands/
delta_commands.rs

1//! Delta-aware commands for incremental ASS editing
2//!
3//! Commands that use ass-core's Delta tracking for optimal performance
4
5use crate::core::{EditorDocument, Position, Range, Result};
6
7#[cfg(not(feature = "std"))]
8use alloc::{boxed::Box, format, string::String, vec::Vec};
9
10/// A command that tracks deltas for incremental updates
11pub trait DeltaCommand {
12    /// Execute the command and return the result with delta information
13    fn execute_with_delta(&self, document: &mut EditorDocument) -> Result<super::CommandResult>;
14
15    /// Get command description for history
16    fn description(&self) -> String;
17
18    /// Check if this command can be executed incrementally
19    fn supports_incremental(&self) -> bool {
20        cfg!(feature = "stream")
21    }
22}
23
24/// Insert text command with delta tracking
25#[derive(Debug, Clone)]
26pub struct IncrementalInsertCommand {
27    pub position: Position,
28    pub text: String,
29}
30
31impl IncrementalInsertCommand {
32    /// Create a new incremental insert command
33    pub fn new(position: Position, text: String) -> Self {
34        Self { position, text }
35    }
36}
37
38impl DeltaCommand for IncrementalInsertCommand {
39    fn execute_with_delta(&self, document: &mut EditorDocument) -> Result<super::CommandResult> {
40        #[cfg(feature = "stream")]
41        {
42            if self.supports_incremental() {
43                // Use incremental parsing for optimal performance
44                match document.insert_incremental(self.position, &self.text) {
45                    Ok(delta) => {
46                        let result = super::CommandResult {
47                            success: true,
48                            message: Some(format!(
49                                "Inserted text at position {}",
50                                self.position.offset
51                            )),
52                            modified_range: Some(Range::new(
53                                self.position,
54                                Position::new(self.position.offset + self.text.len()),
55                            )),
56                            new_cursor: Some(Position::new(self.position.offset + self.text.len())),
57                            content_changed: true,
58                            script_delta: Some(delta),
59                        };
60                        return Ok(result);
61                    }
62                    Err(_) => {
63                        // Fallback to regular insert
64                    }
65                }
66            }
67        }
68
69        // Regular insert without delta tracking
70        document.insert(self.position, &self.text)?;
71        let result = super::CommandResult::success_with_change(
72            Range::new(
73                self.position,
74                Position::new(self.position.offset + self.text.len()),
75            ),
76            Position::new(self.position.offset + self.text.len()),
77        );
78        Ok(result)
79    }
80
81    fn description(&self) -> String {
82        format!(
83            "Insert '{}' at position {}",
84            self.text, self.position.offset
85        )
86    }
87}
88
89/// Replace text command with delta tracking
90#[derive(Debug, Clone)]
91pub struct IncrementalReplaceCommand {
92    pub range: Range,
93    pub new_text: String,
94}
95
96impl IncrementalReplaceCommand {
97    /// Create a new incremental replace command
98    pub fn new(range: Range, new_text: String) -> Self {
99        Self { range, new_text }
100    }
101}
102
103impl DeltaCommand for IncrementalReplaceCommand {
104    fn execute_with_delta(&self, document: &mut EditorDocument) -> Result<super::CommandResult> {
105        #[cfg(feature = "stream")]
106        {
107            if self.supports_incremental() {
108                // Use incremental parsing
109                match document.edit_incremental(self.range, &self.new_text) {
110                    Ok(delta) => {
111                        let result = super::CommandResult {
112                            success: true,
113                            message: Some(format!(
114                                "Replaced text in range {}..{}",
115                                self.range.start.offset, self.range.end.offset
116                            )),
117                            modified_range: Some(Range::new(
118                                self.range.start,
119                                Position::new(self.range.start.offset + self.new_text.len()),
120                            )),
121                            new_cursor: Some(Position::new(
122                                self.range.start.offset + self.new_text.len(),
123                            )),
124                            content_changed: true,
125                            script_delta: Some(delta),
126                        };
127                        return Ok(result);
128                    }
129                    Err(_) => {
130                        // Fallback to regular replace
131                    }
132                }
133            }
134        }
135
136        // Regular replace
137        document.replace(self.range, &self.new_text)?;
138        let result = super::CommandResult::success_with_change(
139            Range::new(
140                self.range.start,
141                Position::new(self.range.start.offset + self.new_text.len()),
142            ),
143            Position::new(self.range.start.offset + self.new_text.len()),
144        );
145        Ok(result)
146    }
147
148    fn description(&self) -> String {
149        format!(
150            "Replace text in range {}..{} with '{}'",
151            self.range.start.offset, self.range.end.offset, self.new_text
152        )
153    }
154}
155
156/// ASS-aware event edit command with delta tracking
157#[derive(Debug, Clone)]
158pub struct IncrementalEventEditCommand {
159    pub old_text: String,
160    pub new_text: String,
161}
162
163impl IncrementalEventEditCommand {
164    /// Create a new incremental event edit command
165    pub fn new(old_text: String, new_text: String) -> Self {
166        Self { old_text, new_text }
167    }
168}
169
170impl DeltaCommand for IncrementalEventEditCommand {
171    fn execute_with_delta(&self, document: &mut EditorDocument) -> Result<super::CommandResult> {
172        #[cfg(feature = "stream")]
173        {
174            if self.supports_incremental() {
175                // Use ASS-aware incremental editing
176                match document.edit_event_incremental(&self.old_text, &self.new_text) {
177                    Ok(delta) => {
178                        let result = super::CommandResult {
179                            success: true,
180                            message: Some(format!(
181                                "Edited event: '{}' → '{}'",
182                                self.old_text, self.new_text
183                            )),
184                            modified_range: None, // Would need to track position
185                            new_cursor: None,
186                            content_changed: true,
187                            script_delta: Some(delta),
188                        };
189                        return Ok(result);
190                    }
191                    Err(_) => {
192                        // Fallback to regular event edit
193                    }
194                }
195            }
196        }
197
198        // Regular event edit
199        document.edit_event_text(&self.old_text, &self.new_text)?;
200        let result = super::CommandResult {
201            success: true,
202            message: Some(format!(
203                "Edited event: '{}' → '{}'",
204                self.old_text, self.new_text
205            )),
206            modified_range: None,
207            new_cursor: None,
208            content_changed: true,
209            #[cfg(feature = "stream")]
210            script_delta: None,
211        };
212        Ok(result)
213    }
214
215    fn description(&self) -> String {
216        format!("Edit event: '{}' → '{}'", self.old_text, self.new_text)
217    }
218}
219
220/// Batch command that executes multiple delta commands efficiently
221pub struct DeltaBatchCommand {
222    commands: Vec<Box<dyn DeltaCommand>>,
223}
224
225impl DeltaBatchCommand {
226    /// Create a new batch command
227    pub fn new() -> Self {
228        Self {
229            commands: Vec::new(),
230        }
231    }
232
233    /// Add a command to the batch
234    pub fn add_command<T: DeltaCommand + 'static>(mut self, command: T) -> Self {
235        self.commands.push(Box::new(command));
236        self
237    }
238
239    /// Execute all commands in the batch
240    pub fn execute_batch(
241        &self,
242        document: &mut EditorDocument,
243    ) -> Result<Vec<super::CommandResult>> {
244        let mut results = Vec::new();
245
246        for command in &self.commands {
247            let result = command.execute_with_delta(document)?;
248            results.push(result);
249        }
250
251        Ok(results)
252    }
253}
254
255impl Default for DeltaBatchCommand {
256    fn default() -> Self {
257        Self::new()
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use crate::EditorDocument;
265    #[cfg(not(feature = "std"))]
266    use alloc::string::ToString;
267    #[cfg(not(feature = "std"))]
268    #[test]
269    fn test_incremental_insert_command() {
270        let mut doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
271
272        let command = IncrementalInsertCommand::new(
273            Position::new(doc.len_bytes()),
274            "\nAuthor: Test Author".to_string(),
275        );
276
277        let result = command.execute_with_delta(&mut doc).unwrap();
278        assert!(result.success);
279        assert!(result.content_changed);
280        assert!(doc.text().contains("Author: Test Author"));
281    }
282
283    #[test]
284    fn test_incremental_event_edit_command() {
285        let content = r#"[Events]
286Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
287Dialogue: 0,0:00:05.00,0:00:10.00,Default,John,0,0,0,,Hello, world!"#;
288
289        let mut doc = EditorDocument::from_content(content).unwrap();
290
291        let command = IncrementalEventEditCommand::new(
292            "Hello, world!".to_string(),
293            "Hello, ASS-RS!".to_string(),
294        );
295
296        let result = command.execute_with_delta(&mut doc).unwrap();
297        assert!(result.success);
298        assert!(doc.text().contains("Hello, ASS-RS!"));
299    }
300
301    #[test]
302    fn test_delta_batch_command() {
303        let mut doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
304
305        // Simple single command batch to test the infrastructure without complex position calculations
306        let batch = DeltaBatchCommand::new().add_command(IncrementalInsertCommand::new(
307            Position::new(doc.len_bytes()),
308            "\nAuthor: Test".to_string(),
309        ));
310
311        let results = batch.execute_batch(&mut doc).unwrap();
312        assert_eq!(results.len(), 1);
313        assert!(results.iter().all(|r| r.success));
314        assert!(doc.text().contains("Author: Test"));
315    }
316}