Skip to main content

ass_core/parser/script/
update.rs

1//! In-place line updates and format mutation.
2//!
3//! Implements [`Script::update_line_at_offset`], which locates the section and
4//! line at a byte offset and replaces it while recording change-tracking
5//! deltas, alongside the styles/events format setters.
6
7use alloc::{boxed::Box, vec::Vec};
8
9use crate::parser::ast::Section;
10use crate::parser::errors::ParseError;
11
12use super::types::{Change, LineContent};
13use super::Script;
14
15impl<'a> Script<'a> {
16    /// Update a line in the script at the given byte offset
17    ///
18    /// Finds the section containing the offset and updates the appropriate line.
19    /// Returns the old line content if successful.
20    ///
21    /// # Arguments
22    ///
23    /// * `offset` - Byte offset of the line to update
24    /// * `new_line` - New line content
25    /// * `line_number` - Line number for error reporting
26    ///
27    /// # Returns
28    ///
29    /// The old line content if successful, or error if update failed
30    ///
31    /// # Errors
32    ///
33    /// Returns error if offset is invalid or line cannot be parsed
34    pub fn update_line_at_offset(
35        &mut self,
36        offset: usize,
37        new_line: &'a str,
38        line_number: u32,
39    ) -> core::result::Result<LineContent<'a>, ParseError> {
40        // Find which section contains this offset
41        let section_index = self
42            .sections
43            .iter()
44            .position(|s| {
45                s.span()
46                    .is_some_and(|span| span.start <= offset && offset < span.end)
47            })
48            .ok_or(ParseError::SectionNotFound)?;
49
50        // Parse the new line to determine its type
51        let (_, new_content) = self.parse_line_auto(new_line, line_number)?;
52
53        // Update the appropriate section
54        let result = match (&mut self.sections[section_index], new_content.clone()) {
55            (Section::Styles(styles), LineContent::Style(new_style)) => {
56                // Find the style at this offset
57                styles
58                    .iter()
59                    .position(|s| s.span.start <= offset && offset < s.span.end)
60                    .map_or(Err(ParseError::IndexOutOfBounds), |style_index| {
61                        let old_style = styles[style_index].clone();
62                        styles[style_index] = *new_style;
63                        Ok(LineContent::Style(Box::new(old_style)))
64                    })
65            }
66            (Section::Events(events), LineContent::Event(new_event)) => {
67                // Find the event at this offset
68                events
69                    .iter()
70                    .position(|e| e.span.start <= offset && offset < e.span.end)
71                    .map_or(Err(ParseError::IndexOutOfBounds), |event_index| {
72                        let old_event = events[event_index].clone();
73                        events[event_index] = *new_event;
74                        Ok(LineContent::Event(Box::new(old_event)))
75                    })
76            }
77            (Section::ScriptInfo(info), LineContent::Field(key, value)) => {
78                // Find and update the field
79                if let Some(field_index) = info.fields.iter().position(|(k, _)| *k == key) {
80                    let old_value = info.fields[field_index].1;
81                    info.fields[field_index] = (key, value);
82                    Ok(LineContent::Field(key, old_value))
83                } else {
84                    // Add new field if not found
85                    info.fields.push((key, value));
86                    // Record as addition
87                    self.change_tracker.record(Change::Added {
88                        offset,
89                        content: LineContent::Field(key, value),
90                        line_number,
91                    });
92                    Ok(LineContent::Field(key, ""))
93                }
94            }
95            _ => Err(ParseError::InvalidFieldFormat {
96                line: line_number as usize,
97            }),
98        };
99
100        // Record change if successful
101        if let Ok(old_content) = &result {
102            if !matches!(old_content, LineContent::Field(_, "")) {
103                // This was a modification, not an addition
104                self.change_tracker.record(Change::Modified {
105                    offset,
106                    old_content: old_content.clone(),
107                    new_content,
108                    line_number,
109                });
110            }
111        }
112
113        result
114    }
115
116    /// Update format for styles section
117    pub fn set_styles_format(&mut self, format: Vec<&'a str>) {
118        self.styles_format = Some(format);
119    }
120
121    /// Update format for events section
122    pub fn set_events_format(&mut self, format: Vec<&'a str>) {
123        self.events_format = Some(format);
124    }
125}