Skip to main content

ass_editor/commands/style_commands/
edit.rs

1//! Style editing command for ASS documents.
2//!
3//! Provides [`EditStyleCommand`] to update individual fields of an existing
4//! style, mapping field names to columns via the section format line.
5
6use crate::commands::{CommandResult, EditorCommand};
7use crate::core::{EditorDocument, EditorError, Position, Range, Result};
8
9#[cfg(not(feature = "std"))]
10use alloc::{
11    format,
12    string::{String, ToString},
13    vec::Vec,
14};
15
16#[cfg(feature = "std")]
17use std::collections::HashMap;
18
19#[cfg(not(feature = "std"))]
20use alloc::collections::BTreeMap as HashMap;
21
22/// Command to edit an existing style
23#[derive(Debug, Clone)]
24pub struct EditStyleCommand {
25    pub style_name: String,
26    pub field_updates: HashMap<String, String>,
27    pub description: Option<String>,
28}
29
30impl EditStyleCommand {
31    /// Create a new style edit command
32    pub fn new(style_name: String) -> Self {
33        Self {
34            style_name,
35            field_updates: HashMap::new(),
36            description: None,
37        }
38    }
39
40    /// Set a field value
41    pub fn set_field(mut self, field: &str, value: String) -> Self {
42        self.field_updates.insert(field.to_string(), value);
43        self
44    }
45
46    /// Set font name
47    pub fn set_font(self, font: &str) -> Self {
48        self.set_field("Fontname", font.to_string())
49    }
50
51    /// Set font size
52    pub fn set_size(self, size: u32) -> Self {
53        self.set_field("Fontsize", size.to_string())
54    }
55
56    /// Set primary color
57    pub fn set_color(self, color: &str) -> Self {
58        self.set_field("PrimaryColour", color.to_string())
59    }
60
61    /// Set bold
62    pub fn set_bold(self, bold: bool) -> Self {
63        self.set_field("Bold", if bold { "-1" } else { "0" }.to_string())
64    }
65
66    /// Set italic
67    pub fn set_italic(self, italic: bool) -> Self {
68        self.set_field("Italic", if italic { "-1" } else { "0" }.to_string())
69    }
70
71    /// Set alignment
72    pub fn set_alignment(self, alignment: u32) -> Self {
73        self.set_field("Alignment", alignment.to_string())
74    }
75
76    /// Set custom description
77    #[must_use]
78    pub fn with_description(mut self, description: String) -> Self {
79        self.description = Some(description);
80        self
81    }
82}
83
84impl EditorCommand for EditStyleCommand {
85    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
86        let content = document.text();
87        let style_pattern = format!("Style: {}", self.style_name);
88
89        if let Some(style_start) = content.find(&style_pattern) {
90            // Find the end of the style line
91            let line_start = content[..style_start]
92                .rfind('\n')
93                .map(|pos| pos + 1)
94                .unwrap_or(0);
95            let line_end = content[style_start..]
96                .find('\n')
97                .map(|pos| style_start + pos)
98                .unwrap_or(content.len());
99
100            let style_line = &content[line_start..line_end];
101            let fields: Vec<&str> = style_line.split(',').collect();
102
103            if fields.len() < 2 {
104                return Err(EditorError::command_failed("Invalid style format"));
105            }
106
107            // Find format line to determine field order
108            let styles_section_start = content[..line_start]
109                .rfind("[V4+ Styles]")
110                .or_else(|| content[..line_start].rfind("[V4 Styles]"))
111                .or_else(|| content[..line_start].rfind("[Styles]"))
112                .ok_or_else(|| EditorError::command_failed("Could not find styles section"))?;
113
114            let format_line_start = content[styles_section_start..]
115                .find("Format:")
116                .map(|pos| styles_section_start + pos)
117                .ok_or_else(|| EditorError::command_failed("Could not find format line"))?;
118
119            let format_line_end = content[format_line_start..]
120                .find('\n')
121                .map(|pos| format_line_start + pos)
122                .unwrap_or(content.len());
123
124            let format_line = &content[format_line_start..format_line_end];
125            let format_fields: Vec<&str> = format_line
126                .strip_prefix("Format: ")
127                .unwrap_or(format_line)
128                .split(", ")
129                .collect();
130
131            // Build updated style line
132            let mut updated_fields = fields
133                .iter()
134                .map(|f| f.to_string())
135                .collect::<Vec<String>>();
136
137            for (field_name, new_value) in &self.field_updates {
138                if let Some(field_index) = format_fields.iter().position(|f| f == field_name) {
139                    if field_index < updated_fields.len() {
140                        updated_fields[field_index] = new_value.clone();
141                    }
142                }
143            }
144
145            let new_style_line = updated_fields.join(",");
146            let range = Range::new(Position::new(line_start), Position::new(line_end));
147
148            document.replace(range, &new_style_line)?;
149
150            let end_pos = Position::new(line_start + new_style_line.len());
151            Ok(CommandResult::success_with_change(
152                Range::new(Position::new(line_start), end_pos),
153                end_pos,
154            )
155            .with_message(format!("Updated style '{}'", self.style_name)))
156        } else {
157            Err(EditorError::command_failed(format!(
158                "Style '{}' not found",
159                self.style_name
160            )))
161        }
162    }
163
164    fn description(&self) -> &str {
165        self.description.as_deref().unwrap_or("Edit style")
166    }
167
168    fn memory_usage(&self) -> usize {
169        core::mem::size_of::<Self>()
170            + self.style_name.len()
171            + self
172                .field_updates
173                .iter()
174                .map(|(k, v)| k.len() + v.len())
175                .sum::<usize>()
176            + self.description.as_ref().map_or(0, |d| d.len())
177    }
178}