Skip to main content

ass_editor/commands/style_commands/
clone.rs

1//! Style cloning command for ASS documents.
2//!
3//! Provides [`CloneStyleCommand`] to duplicate an existing style under a new
4//! name, inserting the clone after the source style line.
5
6use crate::commands::{CommandResult, EditorCommand};
7use crate::core::{EditorDocument, EditorError, Position, Range, Result};
8
9#[cfg(not(feature = "std"))]
10use alloc::{format, string::String};
11
12/// Command to clone an existing style with a new name
13#[derive(Debug, Clone)]
14pub struct CloneStyleCommand {
15    pub source_style: String,
16    pub target_style: String,
17    pub description: Option<String>,
18}
19
20impl CloneStyleCommand {
21    /// Create a new style cloning command
22    pub fn new(source_style: String, target_style: String) -> Self {
23        Self {
24            source_style,
25            target_style,
26            description: None,
27        }
28    }
29
30    /// Set custom description
31    #[must_use]
32    pub fn with_description(mut self, description: String) -> Self {
33        self.description = Some(description);
34        self
35    }
36}
37
38impl EditorCommand for CloneStyleCommand {
39    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
40        let content = document.text();
41        let source_pattern = format!("Style: {}", self.source_style);
42
43        // Check if target style already exists
44        let target_pattern = format!("Style: {}", self.target_style);
45        if content.contains(&target_pattern) {
46            return Err(EditorError::command_failed(format!(
47                "Style '{}' already exists",
48                self.target_style
49            )));
50        }
51
52        if let Some(source_start) = content.find(&source_pattern) {
53            // Find the complete source style line
54            let line_start = content[..source_start]
55                .rfind('\n')
56                .map(|pos| pos + 1)
57                .unwrap_or(0);
58            let line_end = content[source_start..]
59                .find('\n')
60                .map(|pos| source_start + pos)
61                .unwrap_or(content.len());
62
63            let source_line = &content[line_start..line_end];
64
65            // Replace the style name in the cloned line
66            let cloned_line = source_line.replace(
67                &format!("Style: {}", self.source_style),
68                &format!("Style: {}", self.target_style),
69            );
70
71            // Find where to insert the new style (after the source style)
72            let insert_pos = line_end;
73            let insert_text = format!("\n{cloned_line}");
74
75            document.insert(Position::new(insert_pos), &insert_text)?;
76
77            let end_pos = Position::new(insert_pos + insert_text.len());
78            Ok(CommandResult::success_with_change(
79                Range::new(Position::new(insert_pos), end_pos),
80                end_pos,
81            )
82            .with_message(format!(
83                "Cloned style '{}' to '{}'",
84                self.source_style, self.target_style
85            )))
86        } else {
87            Err(EditorError::command_failed(format!(
88                "Source style '{}' not found",
89                self.source_style
90            )))
91        }
92    }
93
94    fn description(&self) -> &str {
95        self.description.as_deref().unwrap_or("Clone style")
96    }
97
98    fn memory_usage(&self) -> usize {
99        core::mem::size_of::<Self>()
100            + self.source_style.len()
101            + self.target_style.len()
102            + self.description.as_ref().map_or(0, |d| d.len())
103    }
104}