Skip to main content

ass_editor/commands/event_commands/
merge.rs

1//! Command to merge two consecutive events into one.
2
3use super::helpers::{collect_event_lines, parse_event_line};
4use crate::commands::{CommandResult, EditorCommand};
5use crate::core::{EditorDocument, EditorError, Position, Range, Result};
6use ass_core::parser::ast::EventType;
7
8#[cfg(not(feature = "std"))]
9use alloc::{
10    format,
11    string::{String, ToString},
12    vec::Vec,
13};
14
15/// Command to merge two consecutive events
16#[derive(Debug, Clone)]
17pub struct MergeEventsCommand {
18    pub first_event_index: usize,
19    pub second_event_index: usize,
20    pub merge_text_separator: String, // Text to put between merged texts
21    pub description: Option<String>,
22}
23
24impl MergeEventsCommand {
25    /// Create a new event merge command
26    pub fn new(first_event_index: usize, second_event_index: usize) -> Self {
27        Self {
28            first_event_index,
29            second_event_index,
30            merge_text_separator: " ".to_string(), // Default space separator
31            description: None,
32        }
33    }
34
35    /// Set the text separator for merged text
36    pub fn with_separator(mut self, separator: String) -> Self {
37        self.merge_text_separator = separator;
38        self
39    }
40
41    /// Set a custom description for this command
42    #[must_use]
43    pub fn with_description(mut self, description: String) -> Self {
44        self.description = Some(description);
45        self
46    }
47}
48
49impl EditorCommand for MergeEventsCommand {
50    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
51        if self.first_event_index >= self.second_event_index {
52            return Err(EditorError::command_failed(
53                "First event index must be less than second event index",
54            ));
55        }
56
57        let content = document.text();
58        let event_lines = collect_event_lines(&content)?;
59        let events: Vec<_> = event_lines
60            .into_iter()
61            .filter(|event_line| {
62                event_line.index == self.first_event_index
63                    || event_line.index == self.second_event_index
64            })
65            .collect();
66
67        if events.len() != 2 {
68            return Err(EditorError::command_failed(
69                "Could not find both events to merge",
70            ));
71        }
72
73        // Parse both events using ass-core's parser
74        let first_event_line = events[0].line;
75        let second_event_line = events[1].line;
76
77        let first_event = parse_event_line(first_event_line)?;
78        let second_event = parse_event_line(second_event_line)?;
79
80        // Use first event's properties, second event's end time, merged text
81        let merged_text = format!(
82            "{}{}{}",
83            first_event.text, self.merge_text_separator, second_event.text
84        );
85
86        // Create merged event
87        let event_type_str = match first_event.event_type {
88            EventType::Dialogue => "Dialogue",
89            EventType::Comment => "Comment",
90            _ => "Dialogue", // Default fallback
91        };
92        let merged_event = format!(
93            "{}: {},{},{},{},{},{},{},{},{},{}",
94            event_type_str,
95            first_event.layer,
96            first_event.start,
97            second_event.end,
98            first_event.style,
99            first_event.name,
100            first_event.margin_l,
101            first_event.margin_r,
102            first_event.margin_v,
103            first_event.effect,
104            merged_text
105        );
106
107        // Replace both events with the merged one
108        let first_start = events[0].start;
109        let second_end = events[1].end_with_newline;
110        let range = Range::new(Position::new(first_start), Position::new(second_end));
111        let replacement = format!("{merged_event}\n");
112
113        document.replace(range, &replacement)?;
114
115        let end_pos = Position::new(first_start + replacement.len());
116        Ok(CommandResult::success_with_change(
117            Range::new(Position::new(first_start), end_pos),
118            end_pos,
119        )
120        .with_message(format!(
121            "Merged events {} and {}",
122            self.first_event_index, self.second_event_index
123        )))
124    }
125
126    fn description(&self) -> &str {
127        self.description.as_deref().unwrap_or("Merge events")
128    }
129
130    fn memory_usage(&self) -> usize {
131        core::mem::size_of::<Self>()
132            + self.merge_text_separator.len()
133            + self.description.as_ref().map_or(0, |d| d.len())
134    }
135}