1use super::{EditorDocument, Position, Range, Result, StyleBuilder};
15use crate::commands::{
16 AdjustKaraokeCommand, ApplyKaraokeCommand, ApplyStyleCommand, CloneStyleCommand,
17 CreateStyleCommand, DeleteStyleCommand, EditStyleCommand, EditorCommand, EffectOperation,
18 EventEffectCommand, GenerateKaraokeCommand, InsertTagCommand, KaraokeType, MergeEventsCommand,
19 ParseTagCommand, ParsedTag, RemoveTagCommand, ReplaceTagCommand, SplitEventCommand,
20 SplitKaraokeCommand, TimingAdjustCommand, ToggleEventTypeCommand, WrapTagCommand,
21};
22use crate::core::errors::EditorError;
23use ass_core::parser::ast::{Event, EventType, Section};
24use core::cmp::Ordering;
25
26#[cfg(not(feature = "std"))]
27use alloc::{
28 string::{String, ToString},
29 vec,
30 vec::Vec,
31};
32
33#[cfg(feature = "std")]
34use std::vec;
35
36#[derive(Debug, Clone, PartialEq)]
42pub struct EventInfo {
43 pub index: usize,
45 pub event: OwnedEvent,
47 pub line_number: usize,
49 pub range: Range,
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub struct OwnedEvent {
56 pub event_type: EventType,
58 pub layer: String,
60 pub start: String,
62 pub end: String,
64 pub style: String,
66 pub name: String,
68 pub margin_l: String,
70 pub margin_r: String,
72 pub margin_v: String,
74 pub margin_t: Option<String>,
76 pub margin_b: Option<String>,
78 pub effect: String,
80 pub text: String,
82}
83
84impl<'a> From<&Event<'a>> for OwnedEvent {
85 fn from(event: &Event<'a>) -> Self {
86 Self {
87 event_type: event.event_type,
88 layer: event.layer.to_string(),
89 start: event.start.to_string(),
90 end: event.end.to_string(),
91 style: event.style.to_string(),
92 name: event.name.to_string(),
93 margin_l: event.margin_l.to_string(),
94 margin_r: event.margin_r.to_string(),
95 margin_v: event.margin_v.to_string(),
96 margin_t: event.margin_t.map(|s| s.to_string()),
97 margin_b: event.margin_b.map(|s| s.to_string()),
98 effect: event.effect.to_string(),
99 text: event.text.to_string(),
100 }
101 }
102}
103
104#[derive(Debug, Clone, Default)]
106pub struct EventFilter {
107 pub event_type: Option<EventType>,
109 pub style_pattern: Option<String>,
111 pub speaker_pattern: Option<String>,
113 pub text_pattern: Option<String>,
115 pub time_range: Option<(u32, u32)>,
117 pub layer: Option<u32>,
119 pub effect_pattern: Option<String>,
121 pub use_regex: bool,
123 pub case_sensitive: bool,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
129pub enum EventSortCriteria {
130 StartTime,
132 EndTime,
134 Duration,
136 Style,
138 Speaker,
140 Layer,
142 Index,
144 Text,
146}
147
148#[derive(Debug, Clone)]
150pub struct EventSortOptions {
151 pub criteria: EventSortCriteria,
153 pub secondary: Option<EventSortCriteria>,
155 pub ascending: bool,
157}
158
159impl Default for EventSortOptions {
160 fn default() -> Self {
161 Self {
162 criteria: EventSortCriteria::Index,
163 secondary: None,
164 ascending: true,
165 }
166 }
167}
168
169pub struct AtPosition<'a> {
175 document: &'a mut EditorDocument,
176 position: Position,
177}
178
179impl<'a> AtPosition<'a> {
180 pub(crate) fn new(document: &'a mut EditorDocument, position: Position) -> Self {
182 Self { document, position }
183 }
184
185 pub fn insert_text(self, text: &str) -> Result<&'a mut EditorDocument> {
187 let range = Range::empty(self.position);
188 self.document.replace(range, text)?;
189 Ok(self.document)
190 }
191
192 pub fn insert_line(self) -> Result<&'a mut EditorDocument> {
194 self.insert_text("\n")
195 }
196
197 pub fn delete(self, count: usize) -> Result<&'a mut EditorDocument> {
199 let end = self.position.advance(count);
200 let range = Range::new(self.position, end);
201 self.document.delete(range)?;
202 Ok(self.document)
203 }
204
205 pub fn backspace(self, count: usize) -> Result<&'a mut EditorDocument> {
207 let start = self.position.retreat(count);
208 let range = Range::new(start, self.position);
209 self.document.delete(range)?;
210 Ok(self.document)
211 }
212
213 pub fn replace_to_line_end(self, text: &str) -> Result<&'a mut EditorDocument> {
215 #[cfg(feature = "rope")]
216 {
217 let rope = self.document.rope();
218 let line_idx = rope.byte_to_line(self.position.offset);
219 let line_end_byte = if line_idx + 1 < rope.len_lines() {
220 rope.line_to_byte(line_idx + 1).saturating_sub(1)
221 } else {
222 rope.len_bytes()
223 };
224 let range = Range::new(self.position, Position::new(line_end_byte));
225 self.document.replace(range, text)?;
226 Ok(self.document)
227 }
228
229 #[cfg(not(feature = "rope"))]
230 {
231 Err(EditorError::FeatureNotEnabled {
232 feature: "line-based operations".to_string(),
233 required_feature: "rope".to_string(),
234 })
235 }
236 }
237
238 pub const fn position(&self) -> Position {
240 self.position
241 }
242
243 #[cfg(feature = "rope")]
245 pub fn to_line_column(&self) -> Result<(usize, usize)> {
246 let rope = self.document.rope();
247 let line_idx = rope.byte_to_line(self.position.offset);
248 let line_start = rope.line_to_byte(line_idx);
249 let col_offset = self.position.offset - line_start;
250
251 let line = rope.line(line_idx);
253 let mut char_col = 0;
254 let mut byte_count = 0;
255
256 for ch in line.chars() {
257 if byte_count >= col_offset {
258 break;
259 }
260 byte_count += ch.len_utf8();
261 char_col += 1;
262 }
263
264 Ok((line_idx + 1, char_col + 1)) }
266}
267
268pub struct SelectRange<'a> {
270 document: &'a mut EditorDocument,
271 range: Range,
272}
273
274impl<'a> SelectRange<'a> {
275 pub(crate) fn new(document: &'a mut EditorDocument, range: Range) -> Self {
277 Self { document, range }
278 }
279
280 pub fn replace_with(self, text: &str) -> Result<&'a mut EditorDocument> {
282 self.document.replace(self.range, text)?;
283 Ok(self.document)
284 }
285
286 pub fn delete(self) -> Result<&'a mut EditorDocument> {
288 self.document.delete(self.range)?;
289 Ok(self.document)
290 }
291
292 pub fn wrap_with_tag(self, open_tag: &str, close_tag: &str) -> Result<&'a mut EditorDocument> {
294 let selected = self
296 .document
297 .rope()
298 .byte_slice(self.range.start.offset..self.range.end.offset);
299 let mut wrapped =
300 String::with_capacity(open_tag.len() + selected.len_bytes() + close_tag.len());
301 wrapped.push_str(open_tag);
302 wrapped.push_str(&selected.to_string());
303 wrapped.push_str(close_tag);
304
305 self.document.replace(self.range, &wrapped)?;
306 Ok(self.document)
307 }
308
309 #[cfg(feature = "rope")]
311 pub fn indent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
312 let start_line = self.document.rope().byte_to_line(self.range.start.offset);
314 let end_line = self.document.rope().byte_to_line(self.range.end.offset);
315 let indent = " ".repeat(spaces);
316
317 let mut line_positions = Vec::new();
319 for line_idx in (start_line..=end_line).rev() {
320 let line_start = self.document.rope().line_to_byte(line_idx);
321 line_positions.push(line_start);
322 }
323
324 for line_start in line_positions {
326 let pos = Position::new(line_start);
327 let range = Range::empty(pos);
328 self.document.replace(range, &indent)?;
329 }
330
331 Ok(self.document)
332 }
333
334 #[cfg(feature = "rope")]
336 pub fn unindent(self, spaces: usize) -> Result<&'a mut EditorDocument> {
337 let start_line = self.document.rope().byte_to_line(self.range.start.offset);
339 let end_line = self.document.rope().byte_to_line(self.range.end.offset);
340
341 let mut unindent_ops = Vec::new();
343 for line_idx in (start_line..=end_line).rev() {
344 let line_start = self.document.rope().line_to_byte(line_idx);
345 let line = self.document.rope().line(line_idx);
346
347 let mut space_count = 0;
349 for ch in line.chars().take(spaces) {
350 if ch == ' ' {
351 space_count += 1;
352 } else {
353 break;
354 }
355 }
356
357 if space_count > 0 {
358 unindent_ops.push((line_start, space_count));
359 }
360 }
361
362 for (line_start, space_count) in unindent_ops {
364 let range = Range::new(
365 Position::new(line_start),
366 Position::new(line_start + space_count),
367 );
368 self.document.delete(range)?;
369 }
370
371 Ok(self.document)
372 }
373
374 pub fn text(&self) -> String {
376 self.document
377 .rope()
378 .byte_slice(self.range.start.offset..self.range.end.offset)
379 .to_string()
380 }
381
382 pub const fn range(&self) -> Range {
384 self.range
385 }
386}
387
388pub struct StyleOps<'a> {
390 document: &'a mut EditorDocument,
391}
392
393impl<'a> StyleOps<'a> {
394 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
396 Self { document }
397 }
398
399 pub fn create(self, name: &str, builder: StyleBuilder) -> Result<&'a mut EditorDocument> {
401 let command = CreateStyleCommand::new(name.to_string(), builder);
402 command.execute(self.document)?;
403 Ok(self.document)
404 }
405
406 pub fn edit(self, name: &str) -> StyleEditor<'a> {
408 StyleEditor::new(self.document, name.to_string())
409 }
410
411 pub fn delete(self, name: &str) -> Result<&'a mut EditorDocument> {
413 let command = DeleteStyleCommand::new(name.to_string());
414 command.execute(self.document)?;
415 Ok(self.document)
416 }
417
418 pub fn clone(self, source: &str, target: &str) -> Result<&'a mut EditorDocument> {
420 let command = CloneStyleCommand::new(source.to_string(), target.to_string());
421 command.execute(self.document)?;
422 Ok(self.document)
423 }
424
425 pub fn apply(self, old_style: &str, new_style: &str) -> StyleApplicator<'a> {
427 StyleApplicator::new(self.document, old_style.to_string(), new_style.to_string())
428 }
429}
430
431pub struct StyleEditor<'a> {
433 document: &'a mut EditorDocument,
434 command: EditStyleCommand,
435}
436
437impl<'a> StyleEditor<'a> {
438 pub(crate) fn new(document: &'a mut EditorDocument, style_name: String) -> Self {
440 let command = EditStyleCommand::new(style_name);
441 Self { document, command }
442 }
443
444 pub fn font(mut self, font: &str) -> Self {
446 self.command = self.command.set_font(font);
447 self
448 }
449
450 pub fn size(mut self, size: u32) -> Self {
452 self.command = self.command.set_size(size);
453 self
454 }
455
456 pub fn color(mut self, color: &str) -> Self {
458 self.command = self.command.set_color(color);
459 self
460 }
461
462 pub fn bold(mut self, bold: bool) -> Self {
464 self.command = self.command.set_bold(bold);
465 self
466 }
467
468 pub fn italic(mut self, italic: bool) -> Self {
470 self.command = self.command.set_italic(italic);
471 self
472 }
473
474 pub fn alignment(mut self, alignment: u32) -> Self {
476 self.command = self.command.set_alignment(alignment);
477 self
478 }
479
480 pub fn field(mut self, name: &str, value: &str) -> Self {
482 self.command = self.command.set_field(name, value.to_string());
483 self
484 }
485
486 pub fn apply(self) -> Result<&'a mut EditorDocument> {
488 self.command.execute(self.document)?;
489 Ok(self.document)
490 }
491}
492
493pub struct StyleApplicator<'a> {
495 document: &'a mut EditorDocument,
496 command: ApplyStyleCommand,
497}
498
499impl<'a> StyleApplicator<'a> {
500 pub(crate) fn new(
502 document: &'a mut EditorDocument,
503 old_style: String,
504 new_style: String,
505 ) -> Self {
506 let command = ApplyStyleCommand::new(old_style, new_style);
507 Self { document, command }
508 }
509
510 pub fn with_filter(mut self, filter: &str) -> Self {
512 self.command = self.command.with_filter(filter.to_string());
513 self
514 }
515
516 pub fn apply(self) -> Result<&'a mut EditorDocument> {
518 self.command.execute(self.document)?;
519 Ok(self.document)
520 }
521}
522
523pub struct EventOps<'a> {
525 document: &'a mut EditorDocument,
526}
527
528impl<'a> EventOps<'a> {
529 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
531 Self { document }
532 }
533
534 pub fn split(self, event_index: usize, split_time: &str) -> Result<&'a mut EditorDocument> {
536 let command = SplitEventCommand::new(event_index, split_time.to_string());
537 command.execute(self.document)?;
538 Ok(self.document)
539 }
540
541 pub fn merge(self, first_index: usize, second_index: usize) -> EventMerger<'a> {
543 EventMerger::new(self.document, first_index, second_index)
544 }
545
546 pub fn timing(self) -> EventTimer<'a> {
548 EventTimer::new(self.document)
549 }
550
551 pub fn toggle_type(self) -> EventToggler<'a> {
553 EventToggler::new(self.document)
554 }
555
556 pub fn effects(self) -> EventEffector<'a> {
558 EventEffector::new(self.document)
559 }
560
561 pub fn get(self, index: usize) -> Result<Option<EventInfo>> {
567 self.document
568 .parse_script_with(|script| -> Result<Option<EventInfo>> {
569 let mut current_index = 0;
570
571 for section in script.sections() {
572 if let Section::Events(events) = section {
573 for event in events {
574 if current_index == index {
575 let event_info = EventInfo {
576 index,
577 event: OwnedEvent::from(event),
578 line_number: self.find_line_number_for_event(event)?,
579 range: self.find_range_for_event(event)?,
580 };
581 return Ok(Some(event_info));
582 }
583 current_index += 1;
584 }
585 }
586 }
587
588 Ok(None)
589 })?
590 }
591
592 pub fn event(self, index: usize) -> EventAccessor<'a> {
594 EventAccessor::new(self.document, index)
595 }
596
597 pub fn all(self) -> Result<Vec<EventInfo>> {
599 EventQuery::new(self.document).execute()
600 }
601
602 pub fn count(self) -> Result<usize> {
604 self.document.parse_script_with(|script| {
605 let mut count = 0;
606
607 for section in script.sections() {
608 if let Section::Events(events) = section {
609 count += events.len();
610 }
611 }
612
613 count
614 })
615 }
616
617 pub fn query(self) -> EventQuery<'a> {
623 EventQuery::new(self.document)
624 }
625
626 pub fn dialogues(self) -> EventQuery<'a> {
628 EventQuery::new(self.document).filter_by_type(EventType::Dialogue)
629 }
630
631 pub fn comments(self) -> EventQuery<'a> {
632 EventQuery::new(self.document).filter_by_type(EventType::Comment)
633 }
634
635 pub fn in_time_range(self, start_cs: u32, end_cs: u32) -> EventQuery<'a> {
636 EventQuery::new(self.document).filter_by_time_range(start_cs, end_cs)
637 }
638
639 pub fn with_style(self, pattern: &str) -> EventQuery<'a> {
640 EventQuery::new(self.document).filter_by_style(pattern)
641 }
642
643 pub fn containing(self, text: &str) -> EventQuery<'a> {
645 EventQuery::new(self.document).filter_by_text(text)
646 }
647
648 pub fn in_order(self) -> EventQuery<'a> {
650 EventQuery::new(self.document).sort(EventSortCriteria::Index)
651 }
652
653 pub fn by_time(self) -> EventQuery<'a> {
655 EventQuery::new(self.document).sort_by_time()
656 }
657
658 fn find_line_number_for_event(&self, _event: &Event) -> Result<usize> {
663 Ok(1)
666 }
667
668 fn find_range_for_event(&self, _event: &Event) -> Result<Range> {
669 Ok(Range::new(Position::new(0), Position::new(0)))
672 }
673}
674
675pub struct EventMerger<'a> {
677 document: &'a mut EditorDocument,
678 first_index: usize,
679 second_index: usize,
680 separator: String,
681}
682
683impl<'a> EventMerger<'a> {
684 pub(crate) fn new(
686 document: &'a mut EditorDocument,
687 first_index: usize,
688 second_index: usize,
689 ) -> Self {
690 Self {
691 document,
692 first_index,
693 second_index,
694 separator: " ".to_string(),
695 }
696 }
697
698 pub fn with_separator(mut self, separator: &str) -> Self {
700 self.separator = separator.to_string();
701 self
702 }
703
704 pub fn apply(self) -> Result<&'a mut EditorDocument> {
706 let command = MergeEventsCommand::new(self.first_index, self.second_index)
707 .with_separator(self.separator);
708 command.execute(self.document)?;
709 Ok(self.document)
710 }
711}
712
713pub struct EventTimer<'a> {
715 document: &'a mut EditorDocument,
716 event_indices: Vec<usize>,
717}
718
719impl<'a> EventTimer<'a> {
720 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
722 Self {
723 document,
724 event_indices: Vec::new(), }
726 }
727
728 pub fn events(mut self, indices: Vec<usize>) -> Self {
730 self.event_indices = indices;
731 self
732 }
733
734 pub fn event(mut self, index: usize) -> Self {
736 self.event_indices = vec![index];
737 self
738 }
739
740 pub fn shift(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
742 let command = TimingAdjustCommand::new(self.event_indices, offset_cs, offset_cs);
743 command.execute(self.document)?;
744 Ok(self.document)
745 }
746
747 pub fn shift_start(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
749 let command = TimingAdjustCommand::new(self.event_indices, offset_cs, 0);
750 command.execute(self.document)?;
751 Ok(self.document)
752 }
753
754 pub fn shift_end(self, offset_cs: i32) -> Result<&'a mut EditorDocument> {
756 let command = TimingAdjustCommand::new(self.event_indices, 0, offset_cs);
757 command.execute(self.document)?;
758 Ok(self.document)
759 }
760
761 pub fn scale_duration(self, factor: f64) -> Result<&'a mut EditorDocument> {
763 let command = TimingAdjustCommand::scale_duration(self.event_indices, factor);
764 command.execute(self.document)?;
765 Ok(self.document)
766 }
767
768 pub fn adjust(
770 self,
771 start_offset_cs: i32,
772 end_offset_cs: i32,
773 ) -> Result<&'a mut EditorDocument> {
774 let command = TimingAdjustCommand::new(self.event_indices, start_offset_cs, end_offset_cs);
775 command.execute(self.document)?;
776 Ok(self.document)
777 }
778}
779
780pub struct EventToggler<'a> {
782 document: &'a mut EditorDocument,
783 event_indices: Vec<usize>,
784}
785
786impl<'a> EventToggler<'a> {
787 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
789 Self {
790 document,
791 event_indices: Vec::new(), }
793 }
794
795 pub fn events(mut self, indices: Vec<usize>) -> Self {
797 self.event_indices = indices;
798 self
799 }
800
801 pub fn event(mut self, index: usize) -> Self {
803 self.event_indices = vec![index];
804 self
805 }
806
807 pub fn apply(self) -> Result<&'a mut EditorDocument> {
809 let command = ToggleEventTypeCommand::new(self.event_indices);
810 command.execute(self.document)?;
811 Ok(self.document)
812 }
813}
814
815pub struct EventEffector<'a> {
817 document: &'a mut EditorDocument,
818 event_indices: Vec<usize>,
819}
820
821impl<'a> EventEffector<'a> {
822 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
824 Self {
825 document,
826 event_indices: Vec::new(), }
828 }
829
830 pub fn events(mut self, indices: Vec<usize>) -> Self {
832 self.event_indices = indices;
833 self
834 }
835
836 pub fn event(mut self, index: usize) -> Self {
838 self.event_indices = vec![index];
839 self
840 }
841
842 pub fn set(self, effect: &str) -> Result<&'a mut EditorDocument> {
844 let command = EventEffectCommand::set_effect(self.event_indices, effect.to_string());
845 command.execute(self.document)?;
846 Ok(self.document)
847 }
848
849 pub fn clear(self) -> Result<&'a mut EditorDocument> {
851 let command = EventEffectCommand::clear_effect(self.event_indices);
852 command.execute(self.document)?;
853 Ok(self.document)
854 }
855
856 pub fn append(self, effect: &str) -> Result<&'a mut EditorDocument> {
858 let command = EventEffectCommand::append_effect(self.event_indices, effect.to_string());
859 command.execute(self.document)?;
860 Ok(self.document)
861 }
862
863 pub fn prepend(self, effect: &str) -> Result<&'a mut EditorDocument> {
865 let command = EventEffectCommand::new(
866 self.event_indices,
867 effect.to_string(),
868 EffectOperation::Prepend,
869 );
870 command.execute(self.document)?;
871 Ok(self.document)
872 }
873}
874
875pub struct TagOps<'a> {
877 document: &'a mut EditorDocument,
878 range: Option<Range>,
879 position: Option<Position>,
880}
881
882impl<'a> TagOps<'a> {
883 fn new(document: &'a mut EditorDocument) -> Self {
885 Self {
886 document,
887 range: None,
888 position: None,
889 }
890 }
891
892 #[must_use]
894 pub fn at(mut self, position: Position) -> Self {
895 self.position = Some(position);
896 self
897 }
898
899 #[must_use]
901 pub fn in_range(mut self, range: Range) -> Self {
902 self.range = Some(range);
903 self
904 }
905
906 pub fn insert(self, tag: &str) -> Result<&'a mut EditorDocument> {
908 let position = self
909 .position
910 .ok_or_else(|| EditorError::command_failed("Position required for tag insertion"))?;
911
912 let command = InsertTagCommand::new(position, tag.to_string());
913 command.execute(self.document)?;
914 Ok(self.document)
915 }
916
917 pub fn insert_raw(self, tag: &str) -> Result<&'a mut EditorDocument> {
919 let position = self
920 .position
921 .ok_or_else(|| EditorError::command_failed("Position required for tag insertion"))?;
922
923 let command = InsertTagCommand::new(position, tag.to_string()).no_auto_wrap();
924 command.execute(self.document)?;
925 Ok(self.document)
926 }
927
928 pub fn remove_all(self) -> Result<&'a mut EditorDocument> {
930 let range = self
931 .range
932 .ok_or_else(|| EditorError::command_failed("Range required for tag removal"))?;
933
934 let command = RemoveTagCommand::new(range);
935 command.execute(self.document)?;
936 Ok(self.document)
937 }
938
939 pub fn remove_pattern(self, pattern: &str) -> Result<&'a mut EditorDocument> {
941 let range = self
942 .range
943 .ok_or_else(|| EditorError::command_failed("Range required for tag removal"))?;
944
945 let command = RemoveTagCommand::new(range).pattern(pattern.to_string());
946 command.execute(self.document)?;
947 Ok(self.document)
948 }
949
950 pub fn replace(self, find_pattern: &str, replace_with: &str) -> Result<&'a mut EditorDocument> {
952 let range = self
953 .range
954 .ok_or_else(|| EditorError::command_failed("Range required for tag replacement"))?;
955
956 let command =
957 ReplaceTagCommand::new(range, find_pattern.to_string(), replace_with.to_string());
958 command.execute(self.document)?;
959 Ok(self.document)
960 }
961
962 pub fn replace_all(
964 self,
965 find_pattern: &str,
966 replace_with: &str,
967 ) -> Result<&'a mut EditorDocument> {
968 let range = self
969 .range
970 .ok_or_else(|| EditorError::command_failed("Range required for tag replacement"))?;
971
972 let command =
973 ReplaceTagCommand::new(range, find_pattern.to_string(), replace_with.to_string()).all();
974 command.execute(self.document)?;
975 Ok(self.document)
976 }
977
978 pub fn wrap(self, opening_tag: &str) -> Result<&'a mut EditorDocument> {
980 let range = self
981 .range
982 .ok_or_else(|| EditorError::command_failed("Range required for tag wrapping"))?;
983
984 let command = WrapTagCommand::new(range, opening_tag.to_string());
985 command.execute(self.document)?;
986 Ok(self.document)
987 }
988
989 pub fn wrap_with(self, opening_tag: &str, closing_tag: &str) -> Result<&'a mut EditorDocument> {
991 let range = self
992 .range
993 .ok_or_else(|| EditorError::command_failed("Range required for tag wrapping"))?;
994
995 let command = WrapTagCommand::new(range, opening_tag.to_string())
996 .closing_tag(closing_tag.to_string());
997 command.execute(self.document)?;
998 Ok(self.document)
999 }
1000
1001 pub fn parse(self) -> Result<Vec<ParsedTag>> {
1003 let range = self.range.unwrap_or_else(|| {
1004 Range::new(Position::new(0), Position::new(self.document.text().len()))
1005 });
1006
1007 let command = ParseTagCommand::new(range).with_positions();
1008 let text = self.document.text_range(range)?;
1009 command.parse_tags_from_text(&text)
1010 }
1011}
1012
1013pub struct KaraokeOps<'a> {
1015 document: &'a mut EditorDocument,
1016 range: Option<Range>,
1017}
1018
1019impl<'a> KaraokeOps<'a> {
1020 fn new(document: &'a mut EditorDocument) -> Self {
1022 Self {
1023 document,
1024 range: None,
1025 }
1026 }
1027
1028 #[must_use]
1030 pub fn in_range(mut self, range: Range) -> Self {
1031 self.range = Some(range);
1032 self
1033 }
1034
1035 pub fn generate(self, default_duration: u32) -> KaraokeGenerator<'a> {
1037 let default_range = if self.range.is_none() {
1038 let doc_len = self.document.text().len();
1039 Range::new(Position::new(0), Position::new(doc_len))
1040 } else {
1041 Range::new(Position::new(0), Position::new(0)) };
1043
1044 KaraokeGenerator {
1045 document: self.document,
1046 range: self.range.unwrap_or(default_range),
1047 default_duration,
1048 karaoke_type: KaraokeType::Standard,
1049 auto_detect_syllables: true,
1050 }
1051 }
1052
1053 pub fn split(self, split_positions: Vec<usize>) -> KaraokeSplitter<'a> {
1055 let default_range = if self.range.is_none() {
1056 let doc_len = self.document.text().len();
1057 Range::new(Position::new(0), Position::new(doc_len))
1058 } else {
1059 Range::new(Position::new(0), Position::new(0)) };
1061
1062 KaraokeSplitter {
1063 document: self.document,
1064 range: self.range.unwrap_or(default_range),
1065 split_positions,
1066 new_duration: None,
1067 }
1068 }
1069
1070 pub fn adjust(self) -> KaraokeAdjuster<'a> {
1072 let default_range = if self.range.is_none() {
1073 let doc_len = self.document.text().len();
1074 Range::new(Position::new(0), Position::new(doc_len))
1075 } else {
1076 Range::new(Position::new(0), Position::new(0)) };
1078
1079 KaraokeAdjuster {
1080 document: self.document,
1081 range: self.range.unwrap_or(default_range),
1082 }
1083 }
1084
1085 pub fn apply(self) -> KaraokeApplicator<'a> {
1087 let default_range = if self.range.is_none() {
1088 let doc_len = self.document.text().len();
1089 Range::new(Position::new(0), Position::new(doc_len))
1090 } else {
1091 Range::new(Position::new(0), Position::new(0)) };
1093
1094 KaraokeApplicator {
1095 document: self.document,
1096 range: self.range.unwrap_or(default_range),
1097 }
1098 }
1099}
1100
1101pub struct KaraokeGenerator<'a> {
1103 document: &'a mut EditorDocument,
1104 range: Range,
1105 default_duration: u32,
1106 karaoke_type: KaraokeType,
1107 auto_detect_syllables: bool,
1108}
1109
1110impl<'a> KaraokeGenerator<'a> {
1111 #[must_use]
1113 pub fn karaoke_type(mut self, karaoke_type: KaraokeType) -> Self {
1114 self.karaoke_type = karaoke_type;
1115 self
1116 }
1117
1118 #[must_use]
1120 pub fn manual_syllables(mut self) -> Self {
1121 self.auto_detect_syllables = false;
1122 self
1123 }
1124
1125 pub fn execute(self) -> Result<&'a mut EditorDocument> {
1127 let mut command = GenerateKaraokeCommand::new(self.range, self.default_duration)
1128 .karaoke_type(self.karaoke_type);
1129
1130 if !self.auto_detect_syllables {
1131 command = command.manual_syllables();
1132 }
1133
1134 command.execute(self.document)?;
1135 Ok(self.document)
1136 }
1137}
1138
1139pub struct KaraokeSplitter<'a> {
1141 document: &'a mut EditorDocument,
1142 range: Range,
1143 split_positions: Vec<usize>,
1144 new_duration: Option<u32>,
1145}
1146
1147impl<'a> KaraokeSplitter<'a> {
1148 #[must_use]
1150 pub fn duration(mut self, duration: u32) -> Self {
1151 self.new_duration = Some(duration);
1152 self
1153 }
1154
1155 pub fn execute(self) -> Result<&'a mut EditorDocument> {
1157 let mut command = SplitKaraokeCommand::new(self.range, self.split_positions);
1158
1159 if let Some(duration) = self.new_duration {
1160 command = command.duration(duration);
1161 }
1162
1163 command.execute(self.document)?;
1164 Ok(self.document)
1165 }
1166}
1167
1168pub struct KaraokeAdjuster<'a> {
1170 document: &'a mut EditorDocument,
1171 range: Range,
1172}
1173
1174impl<'a> KaraokeAdjuster<'a> {
1175 pub fn scale(self, factor: f32) -> Result<&'a mut EditorDocument> {
1177 let command = AdjustKaraokeCommand::scale(self.range, factor);
1178 command.execute(self.document)?;
1179 Ok(self.document)
1180 }
1181
1182 pub fn offset(self, offset: i32) -> Result<&'a mut EditorDocument> {
1184 let command = AdjustKaraokeCommand::offset(self.range, offset);
1185 command.execute(self.document)?;
1186 Ok(self.document)
1187 }
1188
1189 pub fn set_all(self, duration: u32) -> Result<&'a mut EditorDocument> {
1191 let command = AdjustKaraokeCommand::set_all(self.range, duration);
1192 command.execute(self.document)?;
1193 Ok(self.document)
1194 }
1195
1196 pub fn custom(self, timings: Vec<u32>) -> Result<&'a mut EditorDocument> {
1198 let command = AdjustKaraokeCommand::custom(self.range, timings);
1199 command.execute(self.document)?;
1200 Ok(self.document)
1201 }
1202}
1203
1204pub struct KaraokeApplicator<'a> {
1206 document: &'a mut EditorDocument,
1207 range: Range,
1208}
1209
1210impl<'a> KaraokeApplicator<'a> {
1211 pub fn equal(self, duration: u32, karaoke_type: KaraokeType) -> Result<&'a mut EditorDocument> {
1213 let command = ApplyKaraokeCommand::equal(self.range, duration, karaoke_type);
1214 command.execute(self.document)?;
1215 Ok(self.document)
1216 }
1217
1218 pub fn beat(
1220 self,
1221 bpm: u32,
1222 beats_per_syllable: f32,
1223 karaoke_type: KaraokeType,
1224 ) -> Result<&'a mut EditorDocument> {
1225 let command = ApplyKaraokeCommand::beat(self.range, bpm, beats_per_syllable, karaoke_type);
1226 command.execute(self.document)?;
1227 Ok(self.document)
1228 }
1229
1230 pub fn pattern(
1232 self,
1233 durations: Vec<u32>,
1234 karaoke_type: KaraokeType,
1235 ) -> Result<&'a mut EditorDocument> {
1236 let command = ApplyKaraokeCommand::pattern(self.range, durations, karaoke_type);
1237 command.execute(self.document)?;
1238 Ok(self.document)
1239 }
1240
1241 pub fn import_from(self, source_event_index: usize) -> Result<&'a mut EditorDocument> {
1243 let command = ApplyKaraokeCommand::import_from(self.range, source_event_index);
1244 command.execute(self.document)?;
1245 Ok(self.document)
1246 }
1247}
1248
1249impl EditorDocument {
1251 pub fn at_pos(&mut self, position: Position) -> AtPosition<'_> {
1253 AtPosition::new(self, position)
1254 }
1255
1256 #[cfg(feature = "rope")]
1258 pub fn at_line(&mut self, line: usize) -> Result<AtPosition<'_>> {
1259 let line_idx = line.saturating_sub(1);
1260 if line_idx >= self.rope().len_lines() {
1261 return Err(EditorError::InvalidPosition { line, column: 1 });
1262 }
1263
1264 let byte_pos = self.rope().line_to_byte(line_idx);
1265 Ok(AtPosition::new(self, Position::new(byte_pos)))
1266 }
1267
1268 pub fn at_start(&mut self) -> AtPosition<'_> {
1270 AtPosition::new(self, Position::start())
1271 }
1272
1273 pub fn at_end(&mut self) -> AtPosition<'_> {
1275 let end_pos = Position::new(self.len());
1276 AtPosition::new(self, end_pos)
1277 }
1278
1279 pub fn select(&mut self, range: Range) -> SelectRange<'_> {
1281 SelectRange::new(self, range)
1282 }
1283
1284 pub fn styles(&mut self) -> StyleOps<'_> {
1286 StyleOps::new(self)
1287 }
1288
1289 pub fn events(&mut self) -> EventOps<'_> {
1291 EventOps::new(self)
1292 }
1293
1294 pub fn tags(&mut self) -> TagOps<'_> {
1296 TagOps::new(self)
1297 }
1298
1299 pub fn karaoke(&mut self) -> KaraokeOps<'_> {
1301 KaraokeOps::new(self)
1302 }
1303
1304 #[cfg(feature = "rope")]
1306 pub fn position_to_line_col(&self, pos: Position) -> Result<(usize, usize)> {
1307 if pos.offset > self.len() {
1308 return Err(EditorError::PositionOutOfBounds {
1309 position: pos.offset,
1310 length: self.len(),
1311 });
1312 }
1313
1314 let line_idx = self.rope().byte_to_line(pos.offset);
1315 let line_start = self.rope().line_to_byte(line_idx);
1316 let col_offset = pos.offset - line_start;
1317
1318 let line = self.rope().line(line_idx);
1320 let mut char_col = 0;
1321 let mut byte_count = 0;
1322
1323 for ch in line.chars() {
1324 if byte_count >= col_offset {
1325 break;
1326 }
1327 byte_count += ch.len_utf8();
1328 char_col += 1;
1329 }
1330
1331 Ok((line_idx + 1, char_col + 1)) }
1333
1334 #[cfg(feature = "rope")]
1336 pub fn line_column_to_position(&self, line: usize, column: usize) -> Result<Position> {
1337 use super::PositionBuilder;
1338
1339 PositionBuilder::new()
1340 .line(line)
1341 .column(column)
1342 .build(self.rope())
1343 }
1344}
1345
1346pub struct EventAccessor<'a> {
1352 document: &'a mut EditorDocument,
1353 index: usize,
1354}
1355
1356impl<'a> EventAccessor<'a> {
1357 pub(crate) fn new(document: &'a mut EditorDocument, index: usize) -> Self {
1358 Self { document, index }
1359 }
1360
1361 pub fn get(self) -> Result<Option<EventInfo>> {
1363 EventOps::new(self.document).get(self.index)
1364 }
1365
1366 pub fn text(self) -> Result<Option<String>> {
1368 Ok(self.get()?.map(|info| info.event.text))
1369 }
1370
1371 pub fn style(self) -> Result<Option<String>> {
1373 Ok(self.get()?.map(|info| info.event.style))
1374 }
1375
1376 pub fn speaker(self) -> Result<Option<String>> {
1378 Ok(self.get()?.map(|info| info.event.name))
1379 }
1380
1381 pub fn timing(self) -> Result<Option<(String, String)>> {
1383 Ok(self.get()?.map(|info| (info.event.start, info.event.end)))
1384 }
1385
1386 pub fn start_time(self) -> Result<Option<String>> {
1388 Ok(self.get()?.map(|info| info.event.start))
1389 }
1390
1391 pub fn end_time(self) -> Result<Option<String>> {
1393 Ok(self.get()?.map(|info| info.event.end))
1394 }
1395
1396 pub fn layer(self) -> Result<Option<String>> {
1398 Ok(self.get()?.map(|info| info.event.layer))
1399 }
1400
1401 pub fn effect(self) -> Result<Option<String>> {
1403 Ok(self.get()?.map(|info| info.event.effect))
1404 }
1405
1406 pub fn event_type(self) -> Result<Option<EventType>> {
1408 Ok(self.get()?.map(|info| info.event.event_type))
1409 }
1410
1411 pub fn exists(self) -> Result<bool> {
1413 Ok(self.get()?.is_some())
1414 }
1415
1416 pub fn margins(self) -> Result<Option<(String, String, String)>> {
1418 Ok(self.get()?.map(|info| {
1419 (
1420 info.event.margin_l,
1421 info.event.margin_r,
1422 info.event.margin_v,
1423 )
1424 }))
1425 }
1426
1427 pub fn timing_ops(self) -> EventTimer<'a> {
1429 EventTimer::new(self.document).event(self.index)
1430 }
1431
1432 pub fn toggle_ops(self) -> EventToggler<'a> {
1434 EventToggler::new(self.document).event(self.index)
1435 }
1436
1437 pub fn effect_ops(self) -> EventEffector<'a> {
1439 EventEffector::new(self.document).event(self.index)
1440 }
1441}
1442
1443pub struct EventQuery<'a> {
1449 document: &'a mut EditorDocument,
1450 filters: EventFilter,
1451 sort_options: Option<EventSortOptions>,
1452 limit: Option<usize>,
1453}
1454
1455impl<'a> EventQuery<'a> {
1456 pub(crate) fn new(document: &'a mut EditorDocument) -> Self {
1457 Self {
1458 document,
1459 filters: EventFilter::default(),
1460 sort_options: None,
1461 limit: None,
1462 }
1463 }
1464
1465 pub fn filter(mut self, filter: EventFilter) -> Self {
1467 self.filters = filter;
1468 self
1469 }
1470
1471 pub fn filter_by_type(mut self, event_type: EventType) -> Self {
1472 self.filters.event_type = Some(event_type);
1473 self
1474 }
1475
1476 pub fn filter_by_style(mut self, pattern: &str) -> Self {
1477 self.filters.style_pattern = Some(pattern.to_string());
1478 self
1479 }
1480
1481 pub fn filter_by_speaker(mut self, pattern: &str) -> Self {
1482 self.filters.speaker_pattern = Some(pattern.to_string());
1483 self
1484 }
1485
1486 pub fn filter_by_text(mut self, pattern: &str) -> Self {
1487 self.filters.text_pattern = Some(pattern.to_string());
1488 self
1489 }
1490
1491 pub fn filter_by_time_range(mut self, start_cs: u32, end_cs: u32) -> Self {
1492 self.filters.time_range = Some((start_cs, end_cs));
1493 self
1494 }
1495
1496 pub fn filter_by_layer(mut self, layer: u32) -> Self {
1497 self.filters.layer = Some(layer);
1498 self
1499 }
1500
1501 pub fn filter_by_effect(mut self, pattern: &str) -> Self {
1502 self.filters.effect_pattern = Some(pattern.to_string());
1503 self
1504 }
1505
1506 pub fn with_regex(mut self, use_regex: bool) -> Self {
1507 self.filters.use_regex = use_regex;
1508 self
1509 }
1510
1511 pub fn case_sensitive(mut self, case_sensitive: bool) -> Self {
1512 self.filters.case_sensitive = case_sensitive;
1513 self
1514 }
1515
1516 pub fn sort(mut self, criteria: EventSortCriteria) -> Self {
1518 self.sort_options = Some(EventSortOptions {
1519 criteria,
1520 secondary: None,
1521 ascending: true,
1522 });
1523 self
1524 }
1525
1526 pub fn sort_by(mut self, options: EventSortOptions) -> Self {
1527 self.sort_options = Some(options);
1528 self
1529 }
1530
1531 pub fn sort_by_time(self) -> Self {
1532 self.sort(EventSortCriteria::StartTime)
1533 }
1534
1535 pub fn sort_by_style(self) -> Self {
1536 self.sort(EventSortCriteria::Style)
1537 }
1538
1539 pub fn sort_by_duration(self) -> Self {
1540 self.sort(EventSortCriteria::Duration)
1541 }
1542
1543 pub fn descending(mut self) -> Self {
1544 if let Some(ref mut options) = self.sort_options {
1545 options.ascending = false;
1546 }
1547 self
1548 }
1549
1550 pub fn then_by(mut self, criteria: EventSortCriteria) -> Self {
1551 if let Some(ref mut options) = self.sort_options {
1552 options.secondary = Some(criteria);
1553 }
1554 self
1555 }
1556
1557 pub fn limit(mut self, count: usize) -> Self {
1559 self.limit = Some(count);
1560 self
1561 }
1562
1563 pub fn take(self, count: usize) -> Self {
1564 self.limit(count)
1565 }
1566
1567 pub fn execute(self) -> Result<Vec<EventInfo>> {
1569 let mut results = self.collect_events()?;
1570
1571 results = self.apply_filters(results)?;
1573
1574 if let Some(ref sort_options) = self.sort_options {
1576 self.apply_sort(&mut results, sort_options);
1577 }
1578
1579 if let Some(limit) = self.limit {
1581 results.truncate(limit);
1582 }
1583
1584 Ok(results)
1585 }
1586
1587 pub fn indices(self) -> Result<Vec<usize>> {
1589 Ok(self.execute()?.into_iter().map(|info| info.index).collect())
1590 }
1591
1592 pub fn with_indices(self) -> Result<Vec<(usize, OwnedEvent)>> {
1594 Ok(self
1595 .execute()?
1596 .into_iter()
1597 .map(|info| (info.index, info.event))
1598 .collect())
1599 }
1600
1601 pub fn first(self) -> Result<Option<EventInfo>> {
1603 let mut results = self.limit(1).execute()?;
1604 Ok(results.pop())
1605 }
1606
1607 pub fn count(self) -> Result<usize> {
1609 Ok(self.execute()?.len())
1610 }
1611
1612 pub fn timing(self) -> Result<EventTimer<'a>> {
1614 let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1615 Err(EditorError::command_failed(
1618 "Cannot chain timing operations after query execution - use indices() first",
1619 ))
1620 }
1621
1622 pub fn toggle_type(self) -> Result<EventToggler<'a>> {
1623 let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1624 Err(EditorError::command_failed(
1625 "Cannot chain toggle operations after query execution - use indices() first",
1626 ))
1627 }
1628
1629 pub fn effects(self) -> Result<EventEffector<'a>> {
1630 let _indices: Vec<usize> = self.execute()?.into_iter().map(|info| info.index).collect();
1631 Err(EditorError::command_failed(
1632 "Cannot chain effect operations after query execution - use indices() first",
1633 ))
1634 }
1635
1636 fn collect_events(&self) -> Result<Vec<EventInfo>> {
1641 self.document
1642 .parse_script_with(|script| -> Result<Vec<EventInfo>> {
1643 let mut events = Vec::new();
1644 let mut event_index = 0;
1645
1646 for section in script.sections() {
1647 if let Section::Events(section_events) = section {
1648 for event in section_events {
1649 let event_info = EventInfo {
1651 index: event_index,
1652 event: OwnedEvent::from(event),
1653 line_number: self.find_line_number(event)?,
1654 range: self.find_event_range(event)?,
1655 };
1656 events.push(event_info);
1657 event_index += 1;
1658 }
1659 }
1660 }
1661
1662 Ok(events)
1663 })?
1664 }
1665
1666 fn apply_filters(&self, events: Vec<EventInfo>) -> Result<Vec<EventInfo>> {
1667 let mut filtered = Vec::new();
1668
1669 for event_info in events {
1670 if self.matches_filter(&event_info)? {
1671 filtered.push(event_info);
1672 }
1673 }
1674
1675 Ok(filtered)
1676 }
1677
1678 fn matches_filter(&self, event_info: &EventInfo) -> Result<bool> {
1679 if let Some(event_type) = self.filters.event_type {
1681 if event_info.event.event_type != event_type {
1682 return Ok(false);
1683 }
1684 }
1685
1686 if let Some(ref pattern) = self.filters.style_pattern {
1687 if !self.matches_pattern(&event_info.event.style, pattern)? {
1688 return Ok(false);
1689 }
1690 }
1691
1692 if let Some(ref pattern) = self.filters.text_pattern {
1693 if !self.matches_pattern(&event_info.event.text, pattern)? {
1694 return Ok(false);
1695 }
1696 }
1697
1698 if let Some(ref pattern) = self.filters.speaker_pattern {
1699 if !self.matches_pattern(&event_info.event.name, pattern)? {
1700 return Ok(false);
1701 }
1702 }
1703
1704 if let Some(ref pattern) = self.filters.effect_pattern {
1705 if !self.matches_pattern(&event_info.event.effect, pattern)? {
1706 return Ok(false);
1707 }
1708 }
1709
1710 if let Some(layer) = self.filters.layer {
1711 if let Ok(event_layer) = event_info.event.layer.parse::<u32>() {
1712 if event_layer != layer {
1713 return Ok(false);
1714 }
1715 } else {
1716 return Ok(false);
1717 }
1718 }
1719
1720 if let Some((start_cs, end_cs)) = self.filters.time_range {
1721 if let (Ok(event_start), Ok(event_end)) = (
1724 self.parse_time_to_cs(&event_info.event.start),
1725 self.parse_time_to_cs(&event_info.event.end),
1726 ) {
1727 if event_start < start_cs || event_end > end_cs {
1728 return Ok(false);
1729 }
1730 } else {
1731 return Ok(false);
1732 }
1733 }
1734
1735 Ok(true)
1736 }
1737
1738 fn matches_pattern(&self, text: &str, pattern: &str) -> Result<bool> {
1739 if self.filters.use_regex {
1740 Ok(if self.filters.case_sensitive {
1743 text.contains(pattern)
1744 } else {
1745 text.to_lowercase().contains(&pattern.to_lowercase())
1746 })
1747 } else {
1748 Ok(if self.filters.case_sensitive {
1749 text.contains(pattern)
1750 } else {
1751 text.to_lowercase().contains(&pattern.to_lowercase())
1752 })
1753 }
1754 }
1755
1756 fn parse_time_to_cs(&self, time_str: &str) -> Result<u32> {
1757 let parts: Vec<&str> = time_str.split(':').collect();
1760 if parts.len() != 3 {
1761 return Err(EditorError::command_failed("Invalid time format"));
1762 }
1763
1764 let hours: u32 = parts[0]
1765 .parse()
1766 .map_err(|_| EditorError::command_failed("Invalid hours"))?;
1767 let minutes: u32 = parts[1]
1768 .parse()
1769 .map_err(|_| EditorError::command_failed("Invalid minutes"))?;
1770
1771 let sec_cs_parts: Vec<&str> = parts[2].split('.').collect();
1772 if sec_cs_parts.len() != 2 {
1773 return Err(EditorError::command_failed("Invalid seconds format"));
1774 }
1775
1776 let seconds: u32 = sec_cs_parts[0]
1777 .parse()
1778 .map_err(|_| EditorError::command_failed("Invalid seconds"))?;
1779 let centiseconds: u32 = sec_cs_parts[1]
1780 .parse()
1781 .map_err(|_| EditorError::command_failed("Invalid centiseconds"))?;
1782
1783 Ok(hours * 360000 + minutes * 6000 + seconds * 100 + centiseconds)
1784 }
1785
1786 fn apply_sort(&self, events: &mut [EventInfo], options: &EventSortOptions) {
1787 events.sort_by(|a, b| {
1788 let primary_cmp = self.compare_by_criteria(a, b, &options.criteria);
1789
1790 match primary_cmp {
1791 Ordering::Equal => {
1792 if let Some(secondary) = &options.secondary {
1793 let secondary_cmp = self.compare_by_criteria(a, b, secondary);
1794 if options.ascending {
1795 secondary_cmp
1796 } else {
1797 secondary_cmp.reverse()
1798 }
1799 } else {
1800 Ordering::Equal
1801 }
1802 }
1803 other => {
1804 if options.ascending {
1805 other
1806 } else {
1807 other.reverse()
1808 }
1809 }
1810 }
1811 });
1812 }
1813
1814 fn compare_by_criteria(
1815 &self,
1816 a: &EventInfo,
1817 b: &EventInfo,
1818 criteria: &EventSortCriteria,
1819 ) -> Ordering {
1820 match criteria {
1821 EventSortCriteria::StartTime => {
1822 let a_time = self.parse_time_to_cs(&a.event.start).unwrap_or(0);
1823 let b_time = self.parse_time_to_cs(&b.event.start).unwrap_or(0);
1824 a_time.cmp(&b_time)
1825 }
1826 EventSortCriteria::EndTime => {
1827 let a_time = self.parse_time_to_cs(&a.event.end).unwrap_or(0);
1828 let b_time = self.parse_time_to_cs(&b.event.end).unwrap_or(0);
1829 a_time.cmp(&b_time)
1830 }
1831 EventSortCriteria::Duration => {
1832 let a_start = self.parse_time_to_cs(&a.event.start).unwrap_or(0);
1833 let a_end = self.parse_time_to_cs(&a.event.end).unwrap_or(0);
1834 let b_start = self.parse_time_to_cs(&b.event.start).unwrap_or(0);
1835 let b_end = self.parse_time_to_cs(&b.event.end).unwrap_or(0);
1836 let a_duration = a_end.saturating_sub(a_start);
1837 let b_duration = b_end.saturating_sub(b_start);
1838 a_duration.cmp(&b_duration)
1839 }
1840 EventSortCriteria::Style => a.event.style.cmp(&b.event.style),
1841 EventSortCriteria::Speaker => a.event.name.cmp(&b.event.name),
1842 EventSortCriteria::Layer => {
1843 let a_layer = a.event.layer.parse::<u32>().unwrap_or(0);
1844 let b_layer = b.event.layer.parse::<u32>().unwrap_or(0);
1845 a_layer.cmp(&b_layer)
1846 }
1847 EventSortCriteria::Index => a.index.cmp(&b.index),
1848 EventSortCriteria::Text => a.event.text.cmp(&b.event.text),
1849 }
1850 }
1851
1852 fn find_line_number(&self, _event: &Event) -> Result<usize> {
1853 Ok(1)
1856 }
1857
1858 fn find_event_range(&self, _event: &Event) -> Result<Range> {
1859 Ok(Range::new(Position::new(0), Position::new(0)))
1862 }
1863}
1864
1865#[cfg(test)]
1866mod tests {
1867 use super::*;
1868 #[cfg(not(feature = "std"))]
1869 use alloc::string::ToString;
1870 #[cfg(not(feature = "std"))]
1871 use alloc::vec;
1872
1873 #[test]
1874 #[cfg(feature = "rope")]
1875 fn test_fluent_insert() {
1876 let mut doc = EditorDocument::new();
1877 doc.at_start().insert_text("Hello, ").unwrap();
1878 doc.at_end().insert_text("World!").unwrap();
1879
1880 assert_eq!(doc.text(), "Hello, World!");
1881 }
1882
1883 #[test]
1884 #[cfg(feature = "rope")]
1885 fn test_fluent_line_operations() {
1886 let mut doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1887
1888 doc.at_line(2).unwrap().insert_text("Start: ").unwrap();
1890 assert_eq!(doc.text(), "Line 1\nStart: Line 2\nLine 3");
1891
1892 doc.at_line(2)
1894 .unwrap()
1895 .replace_to_line_end("New Line 2")
1896 .unwrap();
1897 assert_eq!(doc.text(), "Line 1\nNew Line 2\nLine 3");
1898 }
1899
1900 #[test]
1901 #[cfg(feature = "rope")]
1902 fn test_fluent_selection() {
1903 let mut doc = EditorDocument::from_content("Hello World").unwrap();
1904
1905 let range = Range::new(Position::new(6), Position::new(11));
1906 doc.select(range).replace_with("Rust").unwrap();
1907 assert_eq!(doc.text(), "Hello Rust");
1908
1909 let range = Range::new(Position::new(6), Position::new(10));
1911 doc.select(range).wrap_with_tag("{\\b1}", "{\\b0}").unwrap();
1912 assert_eq!(doc.text(), "Hello {\\b1}Rust{\\b0}");
1913 }
1914
1915 #[test]
1916 #[cfg(feature = "rope")]
1917 fn test_position_conversion() {
1918 let doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1919
1920 let pos = Position::new(7); let (line, col) = doc.position_to_line_col(pos).unwrap();
1923 assert_eq!((line, col), (2, 1));
1924
1925 let pos2 = doc.line_column_to_position(2, 1).unwrap();
1927 assert_eq!(pos2.offset, 7);
1928 }
1929
1930 #[test]
1931 #[cfg(feature = "rope")]
1932 fn test_indent_unindent() {
1933 let mut doc = EditorDocument::from_content("Line 1\nLine 2\nLine 3").unwrap();
1934
1935 let range = Range::new(Position::start(), Position::new(doc.len()));
1937 doc.select(range).indent(2).unwrap();
1938 assert_eq!(doc.text(), " Line 1\n Line 2\n Line 3");
1939
1940 let range = Range::new(Position::start(), Position::new(doc.len()));
1942 doc.select(range).unindent(2).unwrap();
1943 assert_eq!(doc.text(), "Line 1\nLine 2\nLine 3");
1944 }
1945
1946 #[test]
1947 fn test_fluent_style_operations() {
1948 const TEST_CONTENT: &str = r#"[V4+ Styles]
1949Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
1950Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
1951
1952[Events]
1953Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
1954Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Hello world!
1955"#;
1956
1957 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
1958
1959 doc.styles()
1961 .create(
1962 "NewStyle",
1963 StyleBuilder::new()
1964 .font("Comic Sans MS")
1965 .size(24)
1966 .bold(true),
1967 )
1968 .unwrap();
1969
1970 assert!(doc.text().contains("Style: NewStyle"));
1971 assert!(doc.text().contains("Comic Sans MS"));
1972
1973 doc.styles()
1975 .edit("Default")
1976 .font("Helvetica")
1977 .size(18)
1978 .bold(true)
1979 .apply()
1980 .unwrap();
1981
1982 assert!(doc.text().contains("Helvetica"));
1983 assert!(doc.text().contains("18"));
1984
1985 doc.styles().clone("Default", "DefaultCopy").unwrap();
1987
1988 assert!(doc.text().contains("Style: DefaultCopy"));
1989
1990 doc.styles().apply("Default", "NewStyle").apply().unwrap();
1992
1993 let text = doc.text();
1995 let events_section = text.split("[Events]").nth(1).unwrap();
1996 assert!(events_section.contains("NewStyle"));
1997 }
1998
1999 #[test]
2000 fn test_fluent_style_delete() {
2001 const TEST_CONTENT: &str = r#"[V4+ Styles]
2002Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2003Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2004Style: ToDelete,Times,22,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2005"#;
2006
2007 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2008
2009 assert!(doc.text().contains("Style: ToDelete"));
2011
2012 doc.styles().delete("ToDelete").unwrap();
2014
2015 assert!(!doc.text().contains("Style: ToDelete"));
2017 assert!(doc.text().contains("Style: Default")); }
2019
2020 #[test]
2021 fn test_fluent_style_apply_with_filter() {
2022 const TEST_CONTENT: &str = r#"[V4+ Styles]
2023Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2024Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2025Style: FilterStyle,Times,22,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2026
2027[Events]
2028Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2029Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Hello world!
2030Dialogue: 0,0:00:06.00,0:00:10.00,Default,Speaker,0,0,0,,Goodbye world!
2031"#;
2032
2033 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2034
2035 doc.styles()
2037 .apply("Default", "FilterStyle")
2038 .with_filter("Hello")
2039 .apply()
2040 .unwrap();
2041
2042 let content = doc.text();
2043 let lines: Vec<&str> = content.lines().collect();
2044
2045 let hello_line = lines.iter().find(|line| line.contains("Hello")).unwrap();
2047 let goodbye_line = lines.iter().find(|line| line.contains("Goodbye")).unwrap();
2048
2049 assert!(hello_line.contains("FilterStyle"));
2051 assert!(goodbye_line.contains("Default")); }
2053
2054 #[test]
2055 fn test_fluent_event_operations() {
2056 const TEST_CONTENT: &str = r#"[V4+ Styles]
2057Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2058Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2059
2060[Events]
2061Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2062Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2063Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2064Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2065"#;
2066
2067 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2068
2069 doc.events().split(0, "0:00:03.00").unwrap();
2071
2072 let events_count = doc
2074 .text()
2075 .lines()
2076 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2077 .count();
2078 assert_eq!(events_count, 4);
2079 assert!(doc.text().contains("0:00:01.00,0:00:03.00"));
2080 assert!(doc.text().contains("0:00:03.00,0:00:05.00"));
2081 }
2082
2083 #[test]
2084 fn test_fluent_event_merge() {
2085 const TEST_CONTENT: &str = r#"[Events]
2086Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2087Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2088Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2089Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2090"#;
2091
2092 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2093
2094 doc.events()
2096 .merge(0, 1)
2097 .with_separator(" | ")
2098 .apply()
2099 .unwrap();
2100
2101 let events_count = doc
2103 .text()
2104 .lines()
2105 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2106 .count();
2107 assert_eq!(events_count, 2);
2108 assert!(doc.text().contains("First event | Second event"));
2109 assert!(doc.text().contains("0:00:01.00,0:00:10.00")); }
2111
2112 #[test]
2113 fn test_fluent_event_timing() {
2114 const TEST_CONTENT: &str = r#"[Events]
2115Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2116Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2117Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2118"#;
2119
2120 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2121
2122 doc.events().timing().shift(200).unwrap();
2124
2125 assert!(doc.text().contains("0:00:03.00,0:00:07.00")); assert!(doc.text().contains("0:00:07.00,0:00:12.00")); }
2128
2129 #[test]
2130 fn test_fluent_event_timing_specific() {
2131 const TEST_CONTENT: &str = r#"[Events]
2132Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2133Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2134Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2135"#;
2136
2137 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2138
2139 doc.events()
2141 .timing()
2142 .event(0)
2143 .shift_start(100) .unwrap();
2145
2146 assert!(doc.text().contains("0:00:02.00,0:00:05.00")); assert!(doc.text().contains("0:00:05.00,0:00:10.00")); }
2150
2151 #[test]
2152 fn test_fluent_event_toggle() {
2153 const TEST_CONTENT: &str = r#"[Events]
2154Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2155Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2156Comment: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2157"#;
2158
2159 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2160
2161 doc.events().toggle_type().event(0).apply().unwrap();
2163
2164 let text = doc.text();
2165 let lines: Vec<&str> = text.lines().collect();
2166 let event_lines: Vec<&str> = lines
2167 .iter()
2168 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2169 .copied()
2170 .collect();
2171
2172 assert_eq!(event_lines.len(), 2);
2174 assert!(event_lines[0].starts_with("Comment:"));
2175 assert!(event_lines[1].starts_with("Comment:")); }
2177
2178 #[test]
2179 fn test_fluent_event_effects() {
2180 const TEST_CONTENT: &str = r#"[Events]
2181Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2182Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2183Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2184"#;
2185
2186 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2187
2188 doc.events()
2190 .effects()
2191 .events(vec![0, 1])
2192 .set("Fade(255,0)")
2193 .unwrap();
2194
2195 let text = doc.text();
2197 let event_lines: Vec<&str> = text
2198 .lines()
2199 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2200 .collect();
2201
2202 assert!(event_lines[0].contains("Fade(255,0)"));
2203 assert!(event_lines[1].contains("Fade(255,0)"));
2204 }
2205
2206 #[test]
2207 fn test_fluent_event_effects_chaining() {
2208 const TEST_CONTENT: &str = r#"[Events]
2209Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2210Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2211"#;
2212
2213 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2214
2215 doc.events().effects().event(0).set("Fade(255,0)").unwrap();
2217
2218 doc.events()
2219 .effects()
2220 .event(0)
2221 .append("Move(100,200)")
2222 .unwrap();
2223
2224 assert!(doc.text().contains("Fade(255,0) Move(100,200)"));
2226
2227 doc.events().effects().event(0).clear().unwrap();
2229
2230 let text = doc.text();
2232 let event_line = text
2233 .lines()
2234 .find(|line| line.starts_with("Dialogue:"))
2235 .unwrap();
2236 let parts: Vec<&str> = event_line.split(',').collect();
2237 assert_eq!(parts[8].trim(), ""); }
2239
2240 #[test]
2241 fn test_fluent_event_complex_workflow() {
2242 const TEST_CONTENT: &str = r#"[Events]
2243Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2244Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Long event that needs splitting
2245Dialogue: 0,0:00:05.00,0:00:07.00,Default,Speaker,0,0,0,,Short event
2246Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Comment to toggle
2247"#;
2248
2249 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2250
2251 doc.events().split(0, "0:00:03.00").unwrap();
2255
2256 doc.events()
2260 .timing()
2261 .shift(100) .unwrap();
2263
2264 doc.events().toggle_type().event(3).apply().unwrap();
2266
2267 doc.events().effects().set("Fade(255,0)").unwrap();
2269
2270 let content = doc.text();
2271
2272 let event_lines: Vec<&str> = content
2274 .lines()
2275 .filter(|line| line.starts_with("Dialogue:") || line.starts_with("Comment:"))
2276 .collect();
2277
2278 assert_eq!(event_lines.len(), 4);
2280 assert!(event_lines.iter().all(|line| line.starts_with("Dialogue:")));
2281
2282 assert!(content.contains("0:00:02.00,0:00:04.00")); assert!(content.contains("0:00:04.00,0:00:06.00")); assert!(content.contains("0:00:06.00,0:00:08.00")); assert!(content.contains("0:00:11.00,0:00:16.00")); assert!(event_lines.iter().all(|line| line.contains("Fade(255,0)")));
2290 }
2291
2292 #[test]
2293 fn tag_operations() {
2294 let mut doc = EditorDocument::from_content("Hello World").unwrap();
2295
2296 doc.tags().at(Position::new(5)).insert("\\b1").unwrap();
2298 assert_eq!(doc.text(), "Hello{\\b1} World");
2299
2300 doc.tags().at(Position::new(12)).insert_raw("\\i1").unwrap();
2302 assert_eq!(doc.text(), "Hello{\\b1} W\\i1orld");
2303 }
2304
2305 #[test]
2306 fn tag_removal() {
2307 let mut doc =
2308 EditorDocument::from_content("Hello {\\b1\\i1}World{\\c&H00FF00&} test").unwrap();
2309 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2310
2311 doc.tags().in_range(range).remove_pattern("\\b").unwrap();
2313 assert_eq!(doc.text(), "Hello {\\i1}World{\\c&H00FF00&} test");
2314
2315 let full_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2317 doc.tags().in_range(full_range).remove_all().unwrap();
2318 assert_eq!(doc.text(), "Hello World test");
2319 }
2320
2321 #[test]
2322 fn tag_replacement() {
2323 let mut doc = EditorDocument::from_content("Hello {\\b1}World{\\b1} test").unwrap();
2324 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2325
2326 doc.tags()
2328 .in_range(range)
2329 .replace_all("\\b1", "\\i1")
2330 .unwrap();
2331 assert_eq!(doc.text(), "Hello {\\i1}World{\\i1} test");
2332 }
2333
2334 #[test]
2335 fn tag_wrapping() {
2336 let mut doc = EditorDocument::from_content("Hello World").unwrap();
2337 let range = Range::new(Position::new(6), Position::new(11));
2338
2339 doc.tags().in_range(range).wrap("\\b1").unwrap();
2341 assert_eq!(doc.text(), "Hello {\\b1}World{\\b0}");
2342
2343 let mut doc2 = EditorDocument::from_content("Hello World").unwrap();
2345 let range2 = Range::new(Position::new(6), Position::new(11));
2346 doc2.tags()
2347 .in_range(range2)
2348 .wrap_with("\\c&HFF0000&", "\\c")
2349 .unwrap();
2350 assert_eq!(doc2.text(), "Hello {\\c&HFF0000&}World{\\c}");
2351 }
2352
2353 #[test]
2354 fn tag_parsing() {
2355 let mut doc =
2356 EditorDocument::from_content("Hello {\\b1\\c&H00FF00&\\pos(100,200)}World").unwrap();
2357 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2358
2359 let parsed_tags = doc.tags().in_range(range).parse().unwrap();
2360
2361 assert_eq!(parsed_tags.len(), 3);
2362 assert_eq!(parsed_tags[0].tag, "\\b1");
2363 assert_eq!(parsed_tags[1].tag, "\\c&H00FF00&");
2364 assert_eq!(parsed_tags[2].tag, "\\pos");
2365 assert_eq!(parsed_tags[2].parameters.len(), 2);
2366 assert_eq!(parsed_tags[2].parameters[0], "100");
2367 assert_eq!(parsed_tags[2].parameters[1], "200");
2368 }
2369
2370 #[test]
2371 fn karaoke_generate() {
2372 let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2373 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2374
2375 doc.karaoke()
2377 .in_range(range)
2378 .generate(50)
2379 .manual_syllables()
2380 .execute()
2381 .unwrap();
2382
2383 let text = doc.text();
2384 assert!(text.contains("\\k50"));
2385 assert!(text.contains("Hello World Test"));
2387 }
2388
2389 #[test]
2390 fn karaoke_generate_with_types() {
2391 let mut doc = EditorDocument::from_content("Test Text").unwrap();
2392 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2393
2394 doc.karaoke()
2396 .in_range(range)
2397 .generate(40)
2398 .karaoke_type(KaraokeType::Fill)
2399 .execute()
2400 .unwrap();
2401
2402 assert!(doc.text().contains("\\kf40"));
2403
2404 let mut doc2 = EditorDocument::from_content("Test Text").unwrap();
2406 let range2 = Range::new(Position::new(0), Position::new(doc2.text().len()));
2407
2408 doc2.karaoke()
2409 .in_range(range2)
2410 .generate(30)
2411 .karaoke_type(KaraokeType::Outline)
2412 .execute()
2413 .unwrap();
2414
2415 assert!(doc2.text().contains("\\ko30"));
2416 }
2417
2418 #[test]
2419 fn karaoke_generate_manual_syllables() {
2420 let mut doc = EditorDocument::from_content("Syllable Test").unwrap();
2421 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2422
2423 doc.karaoke()
2425 .in_range(range)
2426 .generate(60)
2427 .manual_syllables()
2428 .execute()
2429 .unwrap();
2430
2431 let text = doc.text();
2432 assert!(text.contains("\\k60"));
2433 assert!(text.contains("Syllable Test"));
2434 }
2435
2436 #[test]
2437 fn karaoke_split() {
2438 let mut doc = EditorDocument::from_content("{\\k100}Hello World").unwrap();
2439 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2440
2441 doc.karaoke()
2443 .in_range(range)
2444 .split(vec![5])
2445 .duration(25)
2446 .execute()
2447 .unwrap();
2448
2449 let text = doc.text();
2450 assert!(text.contains("\\k25"));
2451 }
2452
2453 #[test]
2454 fn karaoke_adjust_scale() {
2455 let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2456 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2457
2458 doc.karaoke().in_range(range).adjust().scale(2.0).unwrap();
2460
2461 let text = doc.text();
2462 assert!(text.contains("\\k100")); assert!(text.contains("\\k60")); }
2465
2466 #[test]
2467 fn karaoke_adjust_offset() {
2468 let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2469 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2470
2471 doc.karaoke().in_range(range).adjust().offset(20).unwrap();
2473
2474 let text = doc.text();
2475 assert!(text.contains("\\k70")); assert!(text.contains("\\k50")); }
2478
2479 #[test]
2480 fn karaoke_adjust_set_all() {
2481 let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2482 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2483
2484 doc.karaoke().in_range(range).adjust().set_all(45).unwrap();
2486
2487 let text = doc.text();
2488 assert!(text.contains("\\k45"));
2489 assert_eq!(text.matches("\\k45").count(), 2);
2491 }
2492
2493 #[test]
2494 fn karaoke_adjust_custom() {
2495 let mut doc = EditorDocument::from_content("{\\k50}Hello {\\k30}World").unwrap();
2496 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2497
2498 doc.karaoke()
2500 .in_range(range)
2501 .adjust()
2502 .custom(vec![80, 40])
2503 .unwrap();
2504
2505 let text = doc.text();
2506 assert!(text.contains("\\k80"));
2507 assert!(text.contains("\\k40"));
2508 }
2509
2510 #[test]
2511 fn karaoke_apply_equal() {
2512 let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2513 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2514
2515 doc.karaoke()
2517 .in_range(range)
2518 .apply()
2519 .equal(35, KaraokeType::Fill)
2520 .unwrap();
2521
2522 let text = doc.text();
2523 assert!(text.contains("\\kf35"));
2524 assert!(text.contains("Hello"));
2525 assert!(text.contains("World"));
2526 assert!(text.contains("Test"));
2527 }
2528
2529 #[test]
2530 fn karaoke_apply_beat() {
2531 let mut doc = EditorDocument::from_content("Hello World").unwrap();
2532 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2533
2534 doc.karaoke()
2537 .in_range(range)
2538 .apply()
2539 .beat(120, 0.5, KaraokeType::Standard)
2540 .unwrap();
2541
2542 let text = doc.text();
2543 assert!(text.contains("\\k25"));
2544 }
2545
2546 #[test]
2547 fn karaoke_apply_pattern() {
2548 let mut doc = EditorDocument::from_content("Hello World Test").unwrap();
2549 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2550
2551 doc.karaoke()
2553 .in_range(range)
2554 .apply()
2555 .pattern(vec![40, 60], KaraokeType::Outline)
2556 .unwrap();
2557
2558 let text = doc.text();
2559 assert!(text.contains("\\ko40"));
2560 assert!(text.contains("\\ko60"));
2561 }
2562
2563 #[test]
2564 fn karaoke_apply_import() {
2565 let mut doc = EditorDocument::from_content("Source text for import").unwrap();
2566 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2567
2568 doc.karaoke()
2570 .in_range(range)
2571 .apply()
2572 .import_from(0)
2573 .unwrap();
2574
2575 assert!(doc.text().contains("Source text for import"));
2577 }
2578
2579 #[test]
2580 fn karaoke_complex_workflow() {
2581 let mut doc =
2582 EditorDocument::from_content("Complex karaoke test with multiple words").unwrap();
2583 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2584
2585 doc.karaoke()
2587 .in_range(range)
2588 .generate(50)
2589 .karaoke_type(KaraokeType::Standard)
2590 .manual_syllables()
2591 .execute()
2592 .unwrap();
2593
2594 let mut text = doc.text();
2595 assert!(text.contains("\\k50"));
2596
2597 let current_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2599 doc.karaoke()
2600 .in_range(current_range)
2601 .adjust()
2602 .scale(1.5)
2603 .unwrap();
2604
2605 text = doc.text();
2606 assert!(text.contains("\\k75")); let final_range = Range::new(Position::new(0), Position::new(doc.text().len()));
2610 doc.karaoke()
2611 .in_range(final_range)
2612 .adjust()
2613 .offset(10)
2614 .unwrap();
2615
2616 text = doc.text();
2617 assert!(text.contains("\\k85")); assert!(text.contains("Complex karaoke test with multiple words"));
2621 }
2622
2623 #[test]
2624 fn karaoke_different_types_workflow() {
2625 let test_text = "Test karaoke types";
2627
2628 let mut doc1 = EditorDocument::from_content(test_text).unwrap();
2630 let range1 = Range::new(Position::new(0), Position::new(doc1.text().len()));
2631 doc1.karaoke()
2632 .in_range(range1)
2633 .generate(30)
2634 .karaoke_type(KaraokeType::Standard)
2635 .execute()
2636 .unwrap();
2637 assert!(doc1.text().contains("\\k30"));
2638
2639 let mut doc2 = EditorDocument::from_content(test_text).unwrap();
2641 let range2 = Range::new(Position::new(0), Position::new(doc2.text().len()));
2642 doc2.karaoke()
2643 .in_range(range2)
2644 .generate(40)
2645 .karaoke_type(KaraokeType::Fill)
2646 .execute()
2647 .unwrap();
2648 assert!(doc2.text().contains("\\kf40"));
2649
2650 let mut doc3 = EditorDocument::from_content(test_text).unwrap();
2652 let range3 = Range::new(Position::new(0), Position::new(doc3.text().len()));
2653 doc3.karaoke()
2654 .in_range(range3)
2655 .generate(50)
2656 .karaoke_type(KaraokeType::Outline)
2657 .execute()
2658 .unwrap();
2659 assert!(doc3.text().contains("\\ko50"));
2660
2661 let mut doc4 = EditorDocument::from_content(test_text).unwrap();
2663 let range4 = Range::new(Position::new(0), Position::new(doc4.text().len()));
2664 doc4.karaoke()
2665 .in_range(range4)
2666 .generate(60)
2667 .karaoke_type(KaraokeType::Transition)
2668 .execute()
2669 .unwrap();
2670 assert!(doc4.text().contains("\\kt60"));
2671 }
2672
2673 #[test]
2674 fn karaoke_error_conditions() {
2675 let mut doc = EditorDocument::from_content("Hello {\\b1}World{\\b0}").unwrap();
2677 let range = Range::new(Position::new(0), Position::new(doc.text().len()));
2678
2679 let result = doc.karaoke().in_range(range).generate(50).execute();
2680
2681 assert!(result.is_err());
2683 }
2684
2685 #[test]
2686 fn karaoke_edge_cases() {
2687 let mut doc = EditorDocument::from_content("").unwrap();
2689 let range = Range::new(Position::new(0), Position::new(0));
2690
2691 let result = doc.karaoke().in_range(range).generate(50).execute();
2692
2693 assert!(result.is_ok());
2695
2696 let mut doc2 = EditorDocument::from_content("A").unwrap();
2698 let range2 = Range::new(Position::new(0), Position::new(1));
2699
2700 doc2.karaoke()
2701 .in_range(range2)
2702 .generate(25)
2703 .execute()
2704 .unwrap();
2705
2706 assert!(doc2.text().contains("\\k25"));
2707 assert!(doc2.text().contains("A"));
2708 }
2709
2710 #[test]
2711 fn test_new_event_api_direct_access() {
2712 const TEST_CONTENT: &str = r#"[V4+ Styles]
2713Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2714Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2715
2716[Events]
2717Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2718Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First event
2719Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second event
2720Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third event
2721"#;
2722
2723 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2724
2725 let event_info = doc.events().get(0).unwrap();
2727 assert!(event_info.is_some());
2728 let info = event_info.unwrap();
2729 assert_eq!(info.index, 0);
2730 assert_eq!(info.event.text, "First event");
2731 assert_eq!(info.event.event_type, EventType::Dialogue);
2732
2733 let count = doc.events().count().unwrap();
2735 assert_eq!(count, 3);
2736
2737 let text = doc.events().event(1).text().unwrap();
2739 assert_eq!(text, Some("Second event".to_string()));
2740
2741 let style = doc.events().event(1).style().unwrap();
2742 assert_eq!(style, Some("Default".to_string()));
2743
2744 let exists = doc.events().event(5).exists().unwrap();
2745 assert!(!exists);
2746 }
2747
2748 #[test]
2749 fn test_new_event_api_filtering() {
2750 const TEST_CONTENT: &str = r#"[V4+ Styles]
2751Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2752Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2753
2754[Events]
2755Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2756Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First dialogue
2757Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second dialogue
2758Comment: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,First comment
2759Comment: 0,0:00:15.00,0:00:20.00,Default,Speaker,0,0,0,,Second comment
2760"#;
2761
2762 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2763
2764 let dialogues = doc.events().dialogues().execute().unwrap();
2766 assert_eq!(dialogues.len(), 2);
2767 assert!(dialogues
2768 .iter()
2769 .all(|info| info.event.event_type == EventType::Dialogue));
2770
2771 let comments = doc.events().comments().execute().unwrap();
2772 assert_eq!(comments.len(), 2);
2773 assert!(comments
2774 .iter()
2775 .all(|info| info.event.event_type == EventType::Comment));
2776
2777 let with_first = doc
2779 .events()
2780 .query()
2781 .filter_by_text("First")
2782 .execute()
2783 .unwrap();
2784 assert_eq!(with_first.len(), 2);
2785 assert!(with_first[0].event.text.contains("First"));
2786 assert!(with_first[1].event.text.contains("First"));
2787
2788 let with_first_insensitive = doc
2790 .events()
2791 .query()
2792 .filter_by_text("first")
2793 .case_sensitive(false)
2794 .execute()
2795 .unwrap();
2796 assert_eq!(with_first_insensitive.len(), 2);
2797 }
2798
2799 #[test]
2800 fn test_new_event_api_sorting() {
2801 const TEST_CONTENT: &str = r#"[V4+ Styles]
2802Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2803Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2804
2805[Events]
2806Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2807Dialogue: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Third by time
2808Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,First by time
2809Dialogue: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Second by time
2810"#;
2811
2812 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2813
2814 let by_time = doc.events().by_time().execute().unwrap();
2816 assert_eq!(by_time.len(), 3);
2817 assert_eq!(by_time[0].event.text, "First by time");
2818 assert_eq!(by_time[1].event.text, "Second by time");
2819 assert_eq!(by_time[2].event.text, "Third by time");
2820
2821 let in_order = doc.events().in_order().execute().unwrap();
2823 assert_eq!(in_order.len(), 3);
2824 assert_eq!(in_order[0].event.text, "Third by time");
2825 assert_eq!(in_order[1].event.text, "First by time");
2826 assert_eq!(in_order[2].event.text, "Second by time");
2827
2828 let by_time_desc = doc
2830 .events()
2831 .query()
2832 .sort_by_time()
2833 .descending()
2834 .execute()
2835 .unwrap();
2836 assert_eq!(by_time_desc[0].event.text, "Third by time");
2837 assert_eq!(by_time_desc[1].event.text, "Second by time");
2838 assert_eq!(by_time_desc[2].event.text, "First by time");
2839 }
2840
2841 #[test]
2842 fn test_new_event_api_combined_operations() {
2843 const TEST_CONTENT: &str = r#"[V4+ Styles]
2844Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
2845Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
2846
2847[Events]
2848Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
2849Dialogue: 0,0:00:10.00,0:00:15.00,Default,Speaker,0,0,0,,Important dialogue
2850Dialogue: 0,0:00:01.00,0:00:05.00,Default,Speaker,0,0,0,,Another dialogue
2851Comment: 0,0:00:05.00,0:00:10.00,Default,Speaker,0,0,0,,Important comment
2852Dialogue: 0,0:00:15.00,0:00:20.00,Default,Speaker,0,0,0,,Final dialogue
2853"#;
2854
2855 let mut doc = EditorDocument::from_content(TEST_CONTENT).unwrap();
2856
2857 let important_dialogues = doc
2859 .events()
2860 .query()
2861 .filter_by_type(EventType::Dialogue)
2862 .filter_by_text("Important")
2863 .sort_by_time()
2864 .limit(1)
2865 .execute()
2866 .unwrap();
2867
2868 assert_eq!(important_dialogues.len(), 1);
2869 assert_eq!(important_dialogues[0].event.text, "Important dialogue");
2870 assert_eq!(important_dialogues[0].event.event_type, EventType::Dialogue);
2871
2872 let dialogue_indices = doc.events().dialogues().sort_by_time().indices().unwrap();
2874
2875 assert_eq!(dialogue_indices.len(), 3);
2876 assert_eq!(dialogue_indices, vec![1, 0, 3]);
2878
2879 let dialogue_count = doc.events().dialogues().count().unwrap();
2881 assert_eq!(dialogue_count, 3);
2882
2883 let first_dialogue = doc.events().dialogues().sort_by_time().first().unwrap();
2885
2886 assert!(first_dialogue.is_some());
2887 let first = first_dialogue.unwrap();
2888 assert_eq!(first.event.text, "Another dialogue");
2889 }
2890
2891 #[test]
2892 fn karaoke_chaining_operations() {
2893 let mut doc = EditorDocument::from_content("Chain test").unwrap();
2894
2895 doc.at_pos(Position::new(0))
2897 .insert_text("Prefix: ")
2898 .unwrap();
2899
2900 assert_eq!(doc.text(), "Prefix: Chain test");
2901
2902 let karaoke_range = Range::new(Position::new(8), Position::new(doc.text().len()));
2904 doc.karaoke()
2905 .in_range(karaoke_range)
2906 .generate(45)
2907 .manual_syllables()
2908 .execute()
2909 .unwrap();
2910
2911 let text = doc.text();
2912 assert!(text.starts_with("Prefix: "));
2913 assert!(text.contains("\\k45"));
2914 assert!(text.contains("Chain test"));
2916 }
2917}