1pub mod delta_commands;
8pub mod event_commands;
9pub mod karaoke_commands;
10pub mod macros;
11pub mod style_commands;
12pub mod tag_commands;
13
14use crate::core::{EditorDocument, EditorError, Position, Range, Result};
15
16#[cfg(feature = "stream")]
17use ass_core::parser::ScriptDeltaOwned;
18
19pub use delta_commands::*;
21pub use event_commands::*;
22pub use karaoke_commands::*;
23pub use style_commands::*;
24pub use tag_commands::*;
25
26#[cfg(not(feature = "std"))]
27use alloc::{
28 boxed::Box,
29 format,
30 string::{String, ToString},
31 vec::Vec,
32};
33
34#[derive(Debug, Clone)]
39pub struct CommandResult {
40 pub success: bool,
42
43 pub message: Option<String>,
45
46 pub modified_range: Option<Range>,
48
49 pub new_cursor: Option<Position>,
51
52 pub content_changed: bool,
54
55 #[cfg(feature = "stream")]
57 pub script_delta: Option<ScriptDeltaOwned>,
58}
59
60impl CommandResult {
61 pub fn success() -> Self {
63 Self {
64 success: true,
65 message: None,
66 modified_range: None,
67 new_cursor: None,
68 content_changed: false,
69 #[cfg(feature = "stream")]
70 script_delta: None,
71 }
72 }
73
74 pub fn success_with_change(range: Range, cursor: Position) -> Self {
76 Self {
77 success: true,
78 message: None,
79 modified_range: Some(range),
80 new_cursor: Some(cursor),
81 content_changed: true,
82 #[cfg(feature = "stream")]
83 script_delta: None,
84 }
85 }
86
87 pub fn failure(message: String) -> Self {
89 Self {
90 success: false,
91 message: Some(message),
92 modified_range: None,
93 new_cursor: None,
94 content_changed: false,
95 #[cfg(feature = "stream")]
96 script_delta: None,
97 }
98 }
99
100 #[cfg(feature = "stream")]
102 #[must_use]
103 pub fn with_delta(mut self, delta: ScriptDeltaOwned) -> Self {
104 self.script_delta = Some(delta);
105 self
106 }
107
108 #[must_use]
110 pub fn with_message(mut self, message: String) -> Self {
111 self.message = Some(message);
112 self
113 }
114}
115
116pub trait EditorCommand: core::fmt::Debug + Send + Sync {
155 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult>;
160
161 fn description(&self) -> &str;
163
164 fn modifies_content(&self) -> bool {
169 true
170 }
171
172 fn memory_usage(&self) -> usize {
177 64 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct InsertTextCommand {
184 pub position: Position,
186 pub text: String,
188 pub description: Option<String>,
190}
191
192impl InsertTextCommand {
193 pub fn new(position: Position, text: String) -> Self {
208 Self {
209 position,
210 text,
211 description: None,
212 }
213 }
214
215 #[must_use]
217 pub fn with_description(mut self, description: String) -> Self {
218 self.description = Some(description);
219 self
220 }
221}
222
223impl EditorCommand for InsertTextCommand {
224 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
225 document.insert_raw(self.position, &self.text)?;
226
227 let end_pos = Position::new(self.position.offset + self.text.len());
228 let range = Range::new(self.position, end_pos);
229
230 Ok(CommandResult::success_with_change(range, end_pos))
231 }
232
233 fn description(&self) -> &str {
234 self.description.as_deref().unwrap_or("Insert text")
235 }
236
237 fn memory_usage(&self) -> usize {
238 core::mem::size_of::<Self>()
239 + self.text.len()
240 + self.description.as_ref().map_or(0, |d| d.len())
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct DeleteTextCommand {
247 pub range: Range,
249 pub description: Option<String>,
251}
252
253impl DeleteTextCommand {
254 pub fn new(range: Range) -> Self {
256 Self {
257 range,
258 description: None,
259 }
260 }
261
262 #[must_use]
264 pub fn with_description(mut self, description: String) -> Self {
265 self.description = Some(description);
266 self
267 }
268}
269
270impl EditorCommand for DeleteTextCommand {
271 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
272 document.delete_raw(self.range)?;
273
274 let cursor_pos = self.range.start;
275 let range = Range::new(self.range.start, self.range.start);
276
277 Ok(CommandResult::success_with_change(range, cursor_pos))
278 }
279
280 fn description(&self) -> &str {
281 self.description.as_deref().unwrap_or("Delete text")
282 }
283}
284
285#[derive(Debug, Clone, PartialEq, Eq)]
287pub struct ReplaceTextCommand {
288 pub range: Range,
290 pub new_text: String,
292 pub description: Option<String>,
294}
295
296impl ReplaceTextCommand {
297 pub fn new(range: Range, new_text: String) -> Self {
299 Self {
300 range,
301 new_text,
302 description: None,
303 }
304 }
305
306 #[must_use]
308 pub fn with_description(mut self, description: String) -> Self {
309 self.description = Some(description);
310 self
311 }
312}
313
314impl EditorCommand for ReplaceTextCommand {
315 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
316 document.replace_raw(self.range, &self.new_text)?;
317
318 let end_pos = Position::new(self.range.start.offset + self.new_text.len());
319 let range = Range::new(self.range.start, end_pos);
320
321 Ok(CommandResult::success_with_change(range, end_pos))
322 }
323
324 fn description(&self) -> &str {
325 self.description.as_deref().unwrap_or("Replace text")
326 }
327
328 fn memory_usage(&self) -> usize {
329 core::mem::size_of::<Self>()
330 + self.new_text.len()
331 + self.description.as_ref().map_or(0, |d| d.len())
332 }
333}
334
335#[derive(Debug)]
337pub struct BatchCommand {
338 pub commands: Vec<Box<dyn EditorCommand>>,
340 pub description: String,
342}
343
344impl BatchCommand {
345 pub fn new(description: String) -> Self {
363 Self {
364 commands: Vec::new(),
365 description,
366 }
367 }
368
369 pub fn add_command(mut self, command: Box<dyn EditorCommand>) -> Self {
371 self.commands.push(command);
372 self
373 }
374
375 pub fn add_commands(mut self, commands: Vec<Box<dyn EditorCommand>>) -> Self {
377 self.commands.extend(commands);
378 self
379 }
380}
381
382impl EditorCommand for BatchCommand {
383 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
384 let mut overall_result = CommandResult::success();
385 let mut first_range: Option<Range> = None;
386 let mut last_cursor: Option<Position> = None;
387
388 for command in &self.commands {
389 let result = command.execute(document)?;
390
391 if !result.success {
392 return Ok(CommandResult::failure(format!(
393 "Batch command failed at: {}",
394 command.description()
395 )));
396 }
397
398 if let Some(range) = result.modified_range {
400 first_range = Some(match first_range {
401 Some(existing) => existing.union(&range),
402 None => range,
403 });
404 }
405
406 if result.new_cursor.is_some() {
407 last_cursor = result.new_cursor;
408 }
409
410 if result.content_changed {
411 overall_result.content_changed = true;
412 }
413 }
414
415 overall_result.modified_range = first_range;
416 overall_result.new_cursor = last_cursor;
417
418 Ok(overall_result)
419 }
420
421 fn description(&self) -> &str {
422 &self.description
423 }
424
425 fn memory_usage(&self) -> usize {
426 core::mem::size_of::<Self>()
427 + self.description.len()
428 + self
429 .commands
430 .iter()
431 .map(|c| c.memory_usage())
432 .sum::<usize>()
433 }
434}
435
436pub struct TextCommand<'a> {
453 document: &'a mut EditorDocument,
454 position: Option<Position>,
455 range: Option<Range>,
456}
457
458impl<'a> TextCommand<'a> {
459 pub fn new(document: &'a mut EditorDocument) -> Self {
461 Self {
462 document,
463 position: None,
464 range: None,
465 }
466 }
467
468 #[must_use]
470 pub fn at(mut self, position: Position) -> Self {
471 self.position = Some(position);
472 self
473 }
474
475 #[must_use]
477 pub fn range(mut self, range: Range) -> Self {
478 self.range = Some(range);
479 self
480 }
481
482 pub fn insert(self, text: &str) -> Result<CommandResult> {
484 let position = self
485 .position
486 .ok_or_else(|| EditorError::command_failed("Position not set for insert operation"))?;
487
488 let command = InsertTextCommand::new(position, text.to_string());
489 command.execute(self.document)
490 }
491
492 pub fn delete(self) -> Result<CommandResult> {
494 let range = self
495 .range
496 .ok_or_else(|| EditorError::command_failed("Range not set for delete operation"))?;
497
498 let command = DeleteTextCommand::new(range);
499 command.execute(self.document)
500 }
501
502 pub fn replace(self, new_text: &str) -> Result<CommandResult> {
504 let range = self
505 .range
506 .ok_or_else(|| EditorError::command_failed("Range not set for replace operation"))?;
507
508 let command = ReplaceTextCommand::new(range, new_text.to_string());
509 command.execute(self.document)
510 }
511}
512
513pub trait DocumentCommandExt {
515 fn command(&mut self) -> TextCommand<'_>;
517
518 fn insert_at(&mut self, position: Position, text: &str) -> Result<CommandResult>;
520
521 fn delete_range(&mut self, range: Range) -> Result<CommandResult>;
523
524 fn replace_range(&mut self, range: Range, text: &str) -> Result<CommandResult>;
526}
527
528impl DocumentCommandExt for EditorDocument {
529 fn command(&mut self) -> TextCommand<'_> {
530 TextCommand::new(self)
531 }
532
533 fn insert_at(&mut self, position: Position, text: &str) -> Result<CommandResult> {
534 let command = InsertTextCommand::new(position, text.to_string());
535 command.execute(self)
536 }
537
538 fn delete_range(&mut self, range: Range) -> Result<CommandResult> {
539 let command = DeleteTextCommand::new(range);
540 command.execute(self)
541 }
542
543 fn replace_range(&mut self, range: Range, text: &str) -> Result<CommandResult> {
544 let command = ReplaceTextCommand::new(range, text.to_string());
545 command.execute(self)
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552 use crate::core::EditorDocument;
553 #[cfg(not(feature = "std"))]
554 use alloc::string::ToString;
555 #[cfg(not(feature = "std"))]
556 #[test]
557 fn insert_command_execution() {
558 let mut doc = EditorDocument::new();
559 let command = InsertTextCommand::new(Position::new(0), "Hello".to_string());
560
561 let result = command.execute(&mut doc).unwrap();
562 assert!(result.success);
563 assert!(result.content_changed);
564 assert_eq!(doc.text(), "Hello");
565 }
566
567 #[test]
568 fn delete_command_execution() {
569 let mut doc = EditorDocument::from_content("Hello World").unwrap();
570 let range = Range::new(Position::new(6), Position::new(11));
571 let command = DeleteTextCommand::new(range);
572
573 let result = command.execute(&mut doc).unwrap();
574 assert!(result.success);
575 assert!(result.content_changed);
576 assert_eq!(doc.text(), "Hello ");
577 }
578
579 #[test]
580 fn replace_command_execution() {
581 let mut doc = EditorDocument::from_content("Hello World").unwrap();
582 let range = Range::new(Position::new(6), Position::new(11));
583 let command = ReplaceTextCommand::new(range, "Rust".to_string());
584
585 let result = command.execute(&mut doc).unwrap();
586 assert!(result.success);
587 assert!(result.content_changed);
588 assert_eq!(doc.text(), "Hello Rust");
589 }
590
591 #[test]
592 fn batch_command_execution() {
593 let mut doc = EditorDocument::from_content("Hello").unwrap();
594
595 let batch = BatchCommand::new("Insert and replace".to_string())
596 .add_command(Box::new(InsertTextCommand::new(
597 Position::new(5),
598 " World".to_string(),
599 )))
600 .add_command(Box::new(ReplaceTextCommand::new(
601 Range::new(Position::new(0), Position::new(5)),
602 "Hi".to_string(),
603 )));
604
605 let result = batch.execute(&mut doc).unwrap();
606 assert!(result.success);
607 assert!(result.content_changed);
608 assert_eq!(doc.text(), "Hi World");
609 }
610
611 #[test]
612 fn fluent_api_usage() {
613 let mut doc = EditorDocument::new();
614
615 let result = doc.command().at(Position::new(0)).insert("Hello").unwrap();
617
618 assert!(result.success);
619 assert_eq!(doc.text(), "Hello");
620
621 let range = Range::new(Position::new(0), Position::new(5));
623 let result = doc.command().range(range).replace("Hi").unwrap();
624
625 assert!(result.success);
626 assert_eq!(doc.text(), "Hi");
627 }
628
629 #[test]
630 fn document_extension_methods() {
631 let mut doc = EditorDocument::new();
632
633 doc.insert_at(Position::new(0), "Hello").unwrap();
635 assert_eq!(doc.text(), "Hello");
636
637 let range = Range::new(Position::new(0), Position::new(5));
639 doc.replace_range(range, "Hi").unwrap();
640 assert_eq!(doc.text(), "Hi");
641
642 let range = Range::new(Position::new(0), Position::new(2));
644 doc.delete_range(range).unwrap();
645 assert_eq!(doc.text(), "");
646 }
647
648 #[test]
649 fn command_memory_usage() {
650 let insert_cmd = InsertTextCommand::new(Position::new(0), "Hello".to_string());
651 let usage = insert_cmd.memory_usage();
652
653 assert!(usage >= core::mem::size_of::<InsertTextCommand>() + 5);
655 }
656}