1use super::{CommandResult, EditorCommand};
7use crate::core::{EditorDocument, EditorError, Position, Range, Result};
8use ass_core::parser::ast::{Event, EventType, Span};
9use ass_core::utils::{format_ass_time, parse_ass_time};
10
11fn parse_event_line(line: &str) -> core::result::Result<Event, EditorError> {
14 let colon_pos = line
16 .find(':')
17 .ok_or_else(|| EditorError::command_failed("Invalid event format: missing colon"))?;
18 let event_type_str = &line[..colon_pos];
19 let fields_part = line[colon_pos + 1..].trim();
20
21 let event_type = match event_type_str {
22 "Dialogue" => EventType::Dialogue,
23 "Comment" => EventType::Comment,
24 _ => return Err(EditorError::command_failed("Unknown event type")),
25 };
26
27 let parts: Vec<&str> = fields_part.splitn(10, ',').collect();
30 if parts.len() < 10 {
31 return Err(EditorError::command_failed(
32 "Invalid event format: insufficient fields",
33 ));
34 }
35
36 let layer = parts[0].trim();
41 let start = parts[1].trim();
42 let end = parts[2].trim();
43 let style = parts[3].trim();
44 let name = parts[4].trim();
45 let margin_l = parts[5].trim();
46 let margin_r = parts[6].trim();
47 let margin_v = parts[7].trim();
48
49 let prefix_len = parts[0..8].iter().map(|s| s.len()).sum::<usize>() + 8; let remaining = &fields_part[prefix_len..];
55
56 let mut split_point = None;
58 let chars: Vec<char> = remaining.chars().collect();
59 let mut paren_depth = 0;
60
61 for (i, &ch) in chars.iter().enumerate().rev() {
62 match ch {
63 ')' => paren_depth += 1,
64 '(' => paren_depth -= 1,
65 ',' if paren_depth == 0 => {
66 split_point = Some(i);
67 break;
68 }
69 _ => {}
70 }
71 }
72
73 let (effect, text) = if let Some(split) = split_point {
74 let effect_part = remaining[..split].trim();
75 let text_part = remaining[split + 1..].trim();
76 (effect_part, text_part)
77 } else {
78 (remaining.trim(), "")
80 };
81
82 Ok(Event {
83 event_type,
84 layer,
85 start,
86 end,
87 style,
88 name,
89 margin_l,
90 margin_r,
91 margin_v,
92 margin_t: None,
93 margin_b: None,
94 effect,
95 text,
96 span: Span::new(0, line.len(), 1, 1), })
98}
99
100#[cfg(not(feature = "std"))]
101use alloc::{
102 format,
103 string::{String, ToString},
104 vec,
105 vec::Vec,
106};
107
108#[derive(Debug, Clone)]
110pub struct SplitEventCommand {
111 pub event_index: usize,
112 pub split_time: String, pub description: Option<String>,
114}
115
116impl SplitEventCommand {
117 pub fn new(event_index: usize, split_time: String) -> Self {
119 Self {
120 event_index,
121 split_time,
122 description: None,
123 }
124 }
125
126 #[must_use]
128 pub fn with_description(mut self, description: String) -> Self {
129 self.description = Some(description);
130 self
131 }
132}
133
134impl EditorCommand for SplitEventCommand {
135 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
136 let split_time_cs = parse_ass_time(&self.split_time).map_err(|_| {
138 EditorError::command_failed(format!("Invalid time format: {}", self.split_time))
139 })?;
140
141 let content = document.text();
143 let events_start = content
144 .find("[Events]")
145 .ok_or_else(|| EditorError::command_failed("Events section not found"))?;
146
147 let events_content = &content[events_start..];
149 let format_line_end = events_content
150 .find("Format:")
151 .and_then(|format_pos| {
152 events_content[format_pos..]
153 .find('\n')
154 .map(|newline_pos| events_start + format_pos + newline_pos + 1)
155 })
156 .ok_or_else(|| EditorError::command_failed("Invalid events section format"))?;
157
158 let mut current_index = 0;
160 let mut event_start = format_line_end;
161
162 while event_start < content.len() {
163 let line_end = content[event_start..]
164 .find('\n')
165 .map(|pos| event_start + pos)
166 .unwrap_or(content.len());
167
168 if event_start >= line_end {
169 break;
170 }
171
172 let line = &content[event_start..line_end];
173
174 if line.starts_with("Dialogue:") || line.starts_with("Comment:") {
176 if current_index == self.event_index {
177 let event = parse_event_line(line)?;
179
180 let start_time_cs = event
182 .start_time_cs()
183 .map_err(|_| EditorError::command_failed("Invalid start time in event"))?;
184 let end_time_cs = event
185 .end_time_cs()
186 .map_err(|_| EditorError::command_failed("Invalid end time in event"))?;
187
188 if split_time_cs <= start_time_cs || split_time_cs >= end_time_cs {
189 return Err(EditorError::command_failed(
190 "Split time must be between event start and end times",
191 ));
192 }
193
194 let event_type_str = match event.event_type {
196 EventType::Dialogue => "Dialogue",
197 EventType::Comment => "Comment",
198 _ => "Dialogue", };
200 let first_event = format!(
201 "{}: {},{},{},{},{},{},{},{},{},{}",
202 event_type_str,
203 event.layer,
204 event.start,
205 self.split_time,
206 event.style,
207 event.name,
208 event.margin_l,
209 event.margin_r,
210 event.margin_v,
211 event.effect,
212 event.text
213 );
214
215 let second_event = format!(
216 "{}: {},{},{},{},{},{},{},{},{},{}",
217 event_type_str,
218 event.layer,
219 self.split_time,
220 event.end,
221 event.style,
222 event.name,
223 event.margin_l,
224 event.margin_r,
225 event.margin_v,
226 event.effect,
227 event.text
228 );
229
230 let replacement = format!("{first_event}\n{second_event}");
232 let range = Range::new(Position::new(event_start), Position::new(line_end));
233 document.replace(range, &replacement)?;
234
235 let end_pos = Position::new(event_start + replacement.len());
236 return Ok(CommandResult::success_with_change(
237 Range::new(Position::new(event_start), end_pos),
238 end_pos,
239 )
240 .with_message(format!(
241 "Split event {} at time {}",
242 self.event_index, self.split_time
243 )));
244 }
245 current_index += 1;
246 } else if line.starts_with('[') {
247 break;
249 }
250
251 event_start = line_end + 1;
252 }
253
254 Err(EditorError::command_failed(format!(
255 "Event index {} not found",
256 self.event_index
257 )))
258 }
259
260 fn description(&self) -> &str {
261 self.description.as_deref().unwrap_or("Split event")
262 }
263
264 fn memory_usage(&self) -> usize {
265 core::mem::size_of::<Self>()
266 + self.split_time.len()
267 + self.description.as_ref().map_or(0, |d| d.len())
268 }
269}
270
271#[derive(Debug, Clone)]
273pub struct MergeEventsCommand {
274 pub first_event_index: usize,
275 pub second_event_index: usize,
276 pub merge_text_separator: String, pub description: Option<String>,
278}
279
280impl MergeEventsCommand {
281 pub fn new(first_event_index: usize, second_event_index: usize) -> Self {
283 Self {
284 first_event_index,
285 second_event_index,
286 merge_text_separator: " ".to_string(), description: None,
288 }
289 }
290
291 pub fn with_separator(mut self, separator: String) -> Self {
293 self.merge_text_separator = separator;
294 self
295 }
296
297 #[must_use]
299 pub fn with_description(mut self, description: String) -> Self {
300 self.description = Some(description);
301 self
302 }
303}
304
305impl EditorCommand for MergeEventsCommand {
306 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
307 if self.first_event_index >= self.second_event_index {
308 return Err(EditorError::command_failed(
309 "First event index must be less than second event index",
310 ));
311 }
312
313 let content = document.text();
314 let events_start = content
315 .find("[Events]")
316 .ok_or_else(|| EditorError::command_failed("Events section not found"))?;
317
318 let events_content = &content[events_start..];
320 let format_line_end = events_content
321 .find("Format:")
322 .and_then(|format_pos| {
323 events_content[format_pos..]
324 .find('\n')
325 .map(|newline_pos| events_start + format_pos + newline_pos + 1)
326 })
327 .ok_or_else(|| EditorError::command_failed("Invalid events section format"))?;
328
329 let mut events = Vec::new();
331 let mut current_index = 0;
332 let mut event_start = format_line_end;
333
334 while event_start < content.len() {
335 let line_end = content[event_start..]
336 .find('\n')
337 .map(|pos| event_start + pos)
338 .unwrap_or(content.len());
339
340 if event_start >= line_end {
341 break;
342 }
343
344 let line = &content[event_start..line_end];
345
346 if line.starts_with("Dialogue:") || line.starts_with("Comment:") {
347 if current_index == self.first_event_index
348 || current_index == self.second_event_index
349 {
350 events.push((current_index, event_start, line_end, line.to_string()));
351 }
352 current_index += 1;
353 } else if line.starts_with('[') {
354 break;
355 }
356
357 event_start = line_end + 1;
358 }
359
360 if events.len() != 2 {
361 return Err(EditorError::command_failed(
362 "Could not find both events to merge",
363 ));
364 }
365
366 let first_event_line = &events[0].3;
368 let second_event_line = &events[1].3;
369
370 let first_event = parse_event_line(first_event_line)?;
371 let second_event = parse_event_line(second_event_line)?;
372
373 let merged_text = format!(
375 "{}{}{}",
376 first_event.text, self.merge_text_separator, second_event.text
377 );
378
379 let event_type_str = match first_event.event_type {
381 EventType::Dialogue => "Dialogue",
382 EventType::Comment => "Comment",
383 _ => "Dialogue", };
385 let merged_event = format!(
386 "{}: {},{},{},{},{},{},{},{},{},{}",
387 event_type_str,
388 first_event.layer,
389 first_event.start,
390 second_event.end,
391 first_event.style,
392 first_event.name,
393 first_event.margin_l,
394 first_event.margin_r,
395 first_event.margin_v,
396 first_event.effect,
397 merged_text
398 );
399
400 let first_start = events[0].1;
402 let second_end = events[1].2 + 1; let range = Range::new(Position::new(first_start), Position::new(second_end));
404 let replacement = format!("{merged_event}\n");
405
406 document.replace(range, &replacement)?;
407
408 let end_pos = Position::new(first_start + replacement.len());
409 Ok(CommandResult::success_with_change(
410 Range::new(Position::new(first_start), end_pos),
411 end_pos,
412 )
413 .with_message(format!(
414 "Merged events {} and {}",
415 self.first_event_index, self.second_event_index
416 )))
417 }
418
419 fn description(&self) -> &str {
420 self.description.as_deref().unwrap_or("Merge events")
421 }
422
423 fn memory_usage(&self) -> usize {
424 core::mem::size_of::<Self>()
425 + self.merge_text_separator.len()
426 + self.description.as_ref().map_or(0, |d| d.len())
427 }
428}
429
430#[derive(Debug, Clone)]
432pub struct TimingAdjustCommand {
433 pub event_indices: Vec<usize>, pub start_offset_cs: i32, pub end_offset_cs: i32, pub description: Option<String>,
437}
438
439impl TimingAdjustCommand {
440 pub fn new(event_indices: Vec<usize>, start_offset_cs: i32, end_offset_cs: i32) -> Self {
442 Self {
443 event_indices,
444 start_offset_cs,
445 end_offset_cs,
446 description: None,
447 }
448 }
449
450 pub fn all_events(start_offset_cs: i32, end_offset_cs: i32) -> Self {
452 Self {
453 event_indices: Vec::new(), start_offset_cs,
455 end_offset_cs,
456 description: None,
457 }
458 }
459
460 pub fn shift_start(event_indices: Vec<usize>, offset_cs: i32) -> Self {
462 Self::new(event_indices, offset_cs, offset_cs)
463 }
464
465 pub fn shift_end(event_indices: Vec<usize>, offset_cs: i32) -> Self {
467 Self::new(event_indices, 0, offset_cs)
468 }
469
470 pub fn scale_duration(event_indices: Vec<usize>, factor: f64) -> Self {
472 let offset = (factor * 100.0) as i32 - 100; Self::new(event_indices, 0, offset)
475 }
476
477 #[must_use]
479 pub fn with_description(mut self, description: String) -> Self {
480 self.description = Some(description);
481 self
482 }
483}
484
485impl EditorCommand for TimingAdjustCommand {
486 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
487 let mut content = document.text();
488 let events_start = content
489 .find("[Events]")
490 .ok_or_else(|| EditorError::command_failed("Events section not found"))?;
491
492 let events_content = &content[events_start..];
493 let format_line_end = events_content
494 .find("Format:")
495 .and_then(|format_pos| {
496 events_content[format_pos..]
497 .find('\n')
498 .map(|newline_pos| events_start + format_pos + newline_pos + 1)
499 })
500 .ok_or_else(|| EditorError::command_failed("Invalid events section format"))?;
501
502 let mut changes_made = 0;
503 let mut current_index = 0;
504 let mut event_start = format_line_end;
505 let mut total_range: Option<Range> = None;
506
507 while event_start < content.len() {
508 let line_end = content[event_start..]
509 .find('\n')
510 .map(|pos| event_start + pos)
511 .unwrap_or(content.len());
512
513 if event_start >= line_end {
514 break;
515 }
516
517 let line = &content[event_start..line_end];
518
519 if line.starts_with("Dialogue:") || line.starts_with("Comment:") {
520 let should_adjust =
521 self.event_indices.is_empty() || self.event_indices.contains(¤t_index);
522
523 if should_adjust {
524 if let Ok(event) = parse_event_line(line) {
526 if let (Ok(start_cs), Ok(end_cs)) =
528 (event.start_time_cs(), event.end_time_cs())
529 {
530 let new_start_cs =
532 (start_cs as i32 + self.start_offset_cs).max(0) as u32;
533 let new_end_cs = (end_cs as i32 + self.end_offset_cs).max(0) as u32;
534
535 let final_end_cs = new_end_cs.max(new_start_cs + 1);
537
538 let new_start_time = format_ass_time(new_start_cs);
539 let new_end_time = format_ass_time(final_end_cs);
540
541 let event_type_str = match event.event_type {
543 EventType::Dialogue => "Dialogue",
544 EventType::Comment => "Comment",
545 _ => "Dialogue", };
547 let new_line = format!(
548 "{}: {},{},{},{},{},{},{},{},{},{}",
549 event_type_str,
550 event.layer,
551 new_start_time,
552 new_end_time,
553 event.style,
554 event.name,
555 event.margin_l,
556 event.margin_r,
557 event.margin_v,
558 event.effect,
559 event.text
560 );
561
562 let range =
564 Range::new(Position::new(event_start), Position::new(line_end));
565 document.replace(range, &new_line)?;
566
567 content = document.text();
569
570 let change_range = Range::new(
572 Position::new(event_start),
573 Position::new(event_start + new_line.len()),
574 );
575 total_range = Some(match total_range {
576 Some(existing) => existing.union(&change_range),
577 None => change_range,
578 });
579
580 changes_made += 1;
581 }
582 }
583 }
584 current_index += 1;
585 } else if line.starts_with('[') {
586 break;
587 }
588
589 event_start = line_end + 1;
590 }
591
592 if changes_made > 0 {
593 Ok(CommandResult::success_with_change(
594 total_range.unwrap_or(Range::new(Position::new(0), Position::new(0))),
595 Position::new(content.len()),
596 )
597 .with_message(format!("Adjusted timing for {changes_made} events")))
598 } else {
599 Ok(CommandResult::success().with_message("No events were adjusted".to_string()))
600 }
601 }
602
603 fn description(&self) -> &str {
604 self.description.as_deref().unwrap_or("Adjust event timing")
605 }
606
607 fn memory_usage(&self) -> usize {
608 core::mem::size_of::<Self>()
609 + self.event_indices.len() * core::mem::size_of::<usize>()
610 + self.description.as_ref().map_or(0, |d| d.len())
611 }
612}
613
614#[derive(Debug, Clone)]
616pub struct ToggleEventTypeCommand {
617 pub event_indices: Vec<usize>,
618 pub description: Option<String>,
619}
620
621impl ToggleEventTypeCommand {
622 pub fn new(event_indices: Vec<usize>) -> Self {
624 Self {
625 event_indices,
626 description: None,
627 }
628 }
629
630 pub fn single(event_index: usize) -> Self {
632 Self::new(vec![event_index])
633 }
634
635 pub fn all() -> Self {
637 Self::new(Vec::new()) }
639
640 #[must_use]
642 pub fn with_description(mut self, description: String) -> Self {
643 self.description = Some(description);
644 self
645 }
646}
647
648impl EditorCommand for ToggleEventTypeCommand {
649 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
650 let mut content = document.text();
651 let events_start = content
652 .find("[Events]")
653 .ok_or_else(|| EditorError::command_failed("Events section not found"))?;
654
655 let events_content = &content[events_start..];
656 let format_line_end = events_content
657 .find("Format:")
658 .and_then(|format_pos| {
659 events_content[format_pos..]
660 .find('\n')
661 .map(|newline_pos| events_start + format_pos + newline_pos + 1)
662 })
663 .ok_or_else(|| EditorError::command_failed("Invalid events section format"))?;
664
665 let mut changes_made = 0;
666 let mut current_index = 0;
667 let mut event_start = format_line_end;
668 let mut total_range: Option<Range> = None;
669
670 while event_start < content.len() {
671 let line_end = content[event_start..]
672 .find('\n')
673 .map(|pos| event_start + pos)
674 .unwrap_or(content.len());
675
676 if event_start >= line_end {
677 break;
678 }
679
680 let line = &content[event_start..line_end];
681
682 if line.starts_with("Dialogue:") || line.starts_with("Comment:") {
683 let should_toggle =
684 self.event_indices.is_empty() || self.event_indices.contains(¤t_index);
685
686 if should_toggle {
687 let new_line = if line.starts_with("Dialogue:") {
688 line.replacen("Dialogue:", "Comment:", 1)
689 } else {
690 line.replacen("Comment:", "Dialogue:", 1)
691 };
692
693 let range = Range::new(Position::new(event_start), Position::new(line_end));
694 document.replace(range, &new_line)?;
695
696 content = document.text();
698
699 let change_range = Range::new(
701 Position::new(event_start),
702 Position::new(event_start + new_line.len()),
703 );
704 total_range = Some(match total_range {
705 Some(existing) => existing.union(&change_range),
706 None => change_range,
707 });
708
709 changes_made += 1;
710 }
711 current_index += 1;
712 } else if line.starts_with('[') {
713 break;
714 }
715
716 event_start = line_end + 1;
717 }
718
719 if changes_made > 0 {
720 Ok(CommandResult::success_with_change(
721 total_range.unwrap_or(Range::new(Position::new(0), Position::new(0))),
722 Position::new(content.len()),
723 )
724 .with_message(format!("Toggled type for {changes_made} events")))
725 } else {
726 Ok(CommandResult::success().with_message("No events were toggled".to_string()))
727 }
728 }
729
730 fn description(&self) -> &str {
731 self.description.as_deref().unwrap_or("Toggle event type")
732 }
733
734 fn memory_usage(&self) -> usize {
735 core::mem::size_of::<Self>()
736 + self.event_indices.len() * core::mem::size_of::<usize>()
737 + self.description.as_ref().map_or(0, |d| d.len())
738 }
739}
740
741#[derive(Debug, Clone)]
743pub struct EventEffectCommand {
744 pub event_indices: Vec<usize>,
745 pub effect: String,
746 pub operation: EffectOperation,
747 pub description: Option<String>,
748}
749
750#[derive(Debug, Clone, PartialEq, Eq)]
751pub enum EffectOperation {
752 Set, Append, Prepend, Clear, }
757
758impl EventEffectCommand {
759 pub fn new(event_indices: Vec<usize>, effect: String, operation: EffectOperation) -> Self {
761 Self {
762 event_indices,
763 effect,
764 operation,
765 description: None,
766 }
767 }
768
769 pub fn set_effect(event_indices: Vec<usize>, effect: String) -> Self {
771 Self::new(event_indices, effect, EffectOperation::Set)
772 }
773
774 pub fn clear_effect(event_indices: Vec<usize>) -> Self {
776 Self::new(event_indices, String::new(), EffectOperation::Clear)
777 }
778
779 pub fn append_effect(event_indices: Vec<usize>, effect: String) -> Self {
781 Self::new(event_indices, effect, EffectOperation::Append)
782 }
783
784 #[must_use]
786 pub fn with_description(mut self, description: String) -> Self {
787 self.description = Some(description);
788 self
789 }
790}
791
792impl EditorCommand for EventEffectCommand {
793 fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
794 let mut content = document.text();
795 let events_start = content
796 .find("[Events]")
797 .ok_or_else(|| EditorError::command_failed("Events section not found"))?;
798
799 let events_content = &content[events_start..];
800 let format_line_end = events_content
801 .find("Format:")
802 .and_then(|format_pos| {
803 events_content[format_pos..]
804 .find('\n')
805 .map(|newline_pos| events_start + format_pos + newline_pos + 1)
806 })
807 .ok_or_else(|| EditorError::command_failed("Invalid events section format"))?;
808
809 let mut changes_made = 0;
810 let mut current_index = 0;
811 let mut event_start = format_line_end;
812 let mut total_range: Option<Range> = None;
813
814 while event_start < content.len() {
815 let line_end = content[event_start..]
816 .find('\n')
817 .map(|pos| event_start + pos)
818 .unwrap_or(content.len());
819
820 if event_start >= line_end {
821 break;
822 }
823
824 let line = &content[event_start..line_end];
825
826 if line.starts_with("Dialogue:") || line.starts_with("Comment:") {
827 let should_modify =
828 self.event_indices.is_empty() || self.event_indices.contains(¤t_index);
829
830 if should_modify {
831 if let Ok(event) = parse_event_line(line) {
833 let new_effect = match self.operation {
834 EffectOperation::Set => self.effect.clone(),
835 EffectOperation::Clear => String::new(),
836 EffectOperation::Append => {
837 if event.effect.is_empty() {
838 self.effect.clone()
839 } else {
840 format!("{} {}", event.effect, self.effect)
841 }
842 }
843 EffectOperation::Prepend => {
844 if event.effect.is_empty() {
845 self.effect.clone()
846 } else {
847 format!("{} {}", self.effect, event.effect)
848 }
849 }
850 };
851
852 let event_type_str = match event.event_type {
854 EventType::Dialogue => "Dialogue",
855 EventType::Comment => "Comment",
856 _ => "Dialogue", };
858 let new_line = format!(
859 "{}: {},{},{},{},{},{},{},{},{},{}",
860 event_type_str,
861 event.layer,
862 event.start,
863 event.end,
864 event.style,
865 event.name,
866 event.margin_l,
867 event.margin_r,
868 event.margin_v,
869 new_effect,
870 event.text
871 );
872
873 let range = Range::new(Position::new(event_start), Position::new(line_end));
874 document.replace(range, &new_line)?;
875
876 content = document.text();
878
879 let change_range = Range::new(
881 Position::new(event_start),
882 Position::new(event_start + new_line.len()),
883 );
884 total_range = Some(match total_range {
885 Some(existing) => existing.union(&change_range),
886 None => change_range,
887 });
888
889 changes_made += 1;
890 }
891 }
892 current_index += 1;
893 } else if line.starts_with('[') {
894 break;
895 }
896
897 event_start = line_end + 1;
898 }
899
900 if changes_made > 0 {
901 let operation_name = match self.operation {
902 EffectOperation::Set => "set",
903 EffectOperation::Clear => "cleared",
904 EffectOperation::Append => "appended",
905 EffectOperation::Prepend => "prepended",
906 };
907
908 Ok(CommandResult::success_with_change(
909 total_range.unwrap_or(Range::new(Position::new(0), Position::new(0))),
910 Position::new(content.len()),
911 )
912 .with_message(format!("Effect {operation_name} for {changes_made} events")))
913 } else {
914 Ok(CommandResult::success().with_message("No events were modified".to_string()))
915 }
916 }
917
918 fn description(&self) -> &str {
919 self.description.as_deref().unwrap_or("Modify event effect")
920 }
921
922 fn memory_usage(&self) -> usize {
923 core::mem::size_of::<Self>()
924 + self.event_indices.len() * core::mem::size_of::<usize>()
925 + self.effect.len()
926 + self.description.as_ref().map_or(0, |d| d.len())
927 }
928}
929
930#[cfg(test)]
931mod tests {
932 use super::*;
933 use crate::core::EditorDocument;
934 #[cfg(not(feature = "std"))]
935 use alloc::string::ToString;
936 #[cfg(not(feature = "std"))]
937 use alloc::vec;
938 const TEST_CONTENT: &str = r#"[Script Info]
939Title: Event Commands Test
940
941[V4+ Styles]
942Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
943Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
944
945[Events]
946Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
947Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
948Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
949Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
950"#;
951
952 #[test]
953 fn test_split_event_command() {
954 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
955
956 let command = SplitEventCommand::new(0, "0:00:03.00".to_string());
957 let result = command.execute(&mut doc).unwrap();
958
959 assert!(result.success);
960 assert!(result.content_changed);
961
962 let events_count = doc
964 .text()
965 .lines()
966 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
967 .count();
968 assert_eq!(events_count, 4);
969
970 assert!(doc.text().contains("0:00:01.00,0:00:03.00"));
972 assert!(doc.text().contains("0:00:03.00,0:00:05.00"));
973 }
974
975 #[test]
976 fn test_merge_events_command() {
977 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
978
979 let command = MergeEventsCommand::new(0, 1).with_separator(" | ".to_string());
980 let result = command.execute(&mut doc).unwrap();
981
982 assert!(result.success);
983 assert!(result.content_changed);
984
985 let events_count = doc
987 .text()
988 .lines()
989 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
990 .count();
991 assert_eq!(events_count, 2);
992
993 assert!(doc.text().contains("First event | Second event"));
995 assert!(doc.text().contains("0:00:01.00,0:00:10.00")); }
997
998 #[test]
999 fn test_timing_adjust_command() {
1000 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1001
1002 let command = TimingAdjustCommand::all_events(200, 200);
1004 let result = command.execute(&mut doc).unwrap();
1005
1006 assert!(result.success);
1007 assert!(result.content_changed);
1008
1009 assert!(doc.text().contains("0:00:03.00,0:00:07.00")); assert!(doc.text().contains("0:00:07.00,0:00:12.00")); assert!(doc.text().contains("0:00:12.00,0:00:17.00")); }
1014
1015 #[test]
1016 fn test_toggle_event_type_command() {
1017 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1018
1019 let command = ToggleEventTypeCommand::single(0);
1020 let result = command.execute(&mut doc).unwrap();
1021
1022 assert!(result.success);
1023 assert!(result.content_changed);
1024
1025 let text = doc.text();
1027 let lines: Vec<&str> = text.lines().collect();
1028 let event_lines: Vec<&str> = lines
1029 .iter()
1030 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
1031 .copied()
1032 .collect();
1033
1034 assert_eq!(event_lines.len(), 3);
1035 assert!(event_lines[0].starts_with("Comment:")); assert!(event_lines[1].starts_with("Dialogue:")); assert!(event_lines[2].starts_with("Comment:")); }
1039
1040 #[test]
1041 fn test_event_effect_command() {
1042 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1043
1044 let command = EventEffectCommand::set_effect(vec![0, 1], "Fade(255,0)".to_string());
1045 let result = command.execute(&mut doc).unwrap();
1046
1047 assert!(result.success);
1048 assert!(result.content_changed);
1049
1050 let content = doc.text();
1052 let lines: Vec<&str> = content.lines().collect();
1053 let event_lines: Vec<&str> = lines
1054 .iter()
1055 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
1056 .copied()
1057 .collect();
1058
1059 assert!(event_lines[0].contains("Fade(255,0)"));
1060 assert!(event_lines[1].contains("Fade(255,0)"));
1061 assert!(!event_lines[2].contains("Fade(255,0)")); }
1063
1064 #[test]
1065 fn test_split_event_invalid_time() {
1066 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1067
1068 let command = SplitEventCommand::new(0, "0:00:00.50".to_string()); let result = command.execute(&mut doc);
1071
1072 assert!(result.is_err());
1073 }
1074
1075 #[test]
1076 fn test_merge_events_invalid_indices() {
1077 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1078
1079 let command = MergeEventsCommand::new(1, 0); let result = command.execute(&mut doc);
1082
1083 assert!(result.is_err());
1084 }
1085
1086 #[test]
1087 fn test_timing_adjust_with_specific_events() {
1088 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1089
1090 let command = TimingAdjustCommand::new(vec![0], 100, 100); let result = command.execute(&mut doc).unwrap();
1093
1094 assert!(result.success);
1095
1096 assert!(doc.text().contains("0:00:02.00,0:00:06.00")); assert!(doc.text().contains("0:00:05.00,0:00:10.00")); }
1100
1101 #[test]
1102 fn test_effect_operations() {
1103 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1104
1105 let set_cmd = EventEffectCommand::set_effect(vec![0], "Fade(255,0)".to_string());
1107 set_cmd.execute(&mut doc).unwrap();
1108
1109 let append_cmd = EventEffectCommand::append_effect(vec![0], "Move(100,200)".to_string());
1111 append_cmd.execute(&mut doc).unwrap();
1112
1113 assert!(doc.text().contains("Fade(255,0) Move(100,200)"));
1116
1117 let clear_cmd = EventEffectCommand::clear_effect(vec![0]);
1119 clear_cmd.execute(&mut doc).unwrap();
1120
1121 let text = doc.text();
1123 let lines: Vec<&str> = text.lines().collect();
1124 let first_event = lines
1125 .iter()
1126 .find(|line| line.starts_with("Dialogue:"))
1127 .unwrap();
1128 let parts: Vec<&str> = first_event.split(',').collect();
1129 assert_eq!(parts[8].trim(), ""); }
1131}