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