ass_editor/core/document/
delta_apply.rs1use super::EditorDocument;
8use crate::commands::CommandResult;
9use crate::core::errors::{EditorError, Result};
10use crate::core::position::{Position, Range};
11use ass_core::parser::ast::Section;
12use ass_core::parser::script::ScriptDeltaOwned;
13use ass_core::parser::Script;
14
15#[cfg(not(feature = "std"))]
16use alloc::string::ToString;
17
18impl EditorDocument {
19 pub fn apply_script_delta(&mut self, delta: ScriptDeltaOwned) -> Result<()> {
21 use crate::core::history::Operation;
22
23 let undo_data = self.capture_delta_undo_data(&delta)?;
25
26 self.apply_script_delta_internal(delta.clone())?;
28
29 let operation = Operation::Delta {
31 forward: delta,
32 undo_data,
33 };
34
35 let result = CommandResult::success();
36 self.history
37 .record_operation(operation, "Apply delta".to_string(), &result);
38
39 Ok(())
40 }
41
42 fn apply_script_delta_internal(&mut self, delta: ScriptDeltaOwned) -> Result<()> {
44 let current_content = self.text();
46 let script = Script::parse(¤t_content).map_err(EditorError::from)?;
47
48 let mut removed_indices = delta.removed.clone();
50 removed_indices.sort_by(|a, b| b.cmp(a)); for index in removed_indices {
53 if index < script.sections().len() {
54 let section = &script.sections()[index];
56 let start_offset = self.find_section_start(section)?;
57 let end_offset = self.find_section_end(section)?;
58
59 self.delete_raw(Range::new(
60 Position::new(start_offset),
61 Position::new(end_offset),
62 ))?;
63 }
64 }
65
66 for (index, new_section_text) in delta.modified {
68 if index < script.sections().len() {
69 let section = &script.sections()[index];
71 let start_offset = self.find_section_start(section)?;
72 let end_offset = self.find_section_end(section)?;
73
74 self.replace_raw(
76 Range::new(Position::new(start_offset), Position::new(end_offset)),
77 &new_section_text,
78 )?;
79 }
80 }
81
82 for section_text in delta.added {
84 let end_pos = Position::new(self.len_bytes());
86
87 if self.len_bytes() > 0 && !self.text().ends_with('\n') {
89 self.insert_raw(end_pos, "\n")?;
90 }
91
92 self.insert_raw(Position::new(self.len_bytes()), §ion_text)?;
93
94 if !section_text.ends_with('\n') {
96 self.insert_raw(Position::new(self.len_bytes()), "\n")?;
97 }
98 }
99
100 let _ = Script::parse(&self.text()).map_err(EditorError::from)?;
102
103 Ok(())
104 }
105
106 fn find_section_start(&self, section: &Section) -> Result<usize> {
108 let header = match section {
110 Section::ScriptInfo(_) => "[Script Info]",
111 Section::Styles(_) => "[V4+ Styles]",
112 Section::Events(_) => "[Events]",
113 Section::Fonts(_) => "[Fonts]",
114 Section::Graphics(_) => "[Graphics]",
115 };
116
117 if let Some(pos) = self.text().find(header) {
119 Ok(pos)
120 } else {
121 Err(EditorError::SectionNotFound {
122 section: header.to_string(),
123 })
124 }
125 }
126
127 fn find_section_end(&self, section: &Section) -> Result<usize> {
129 let start = self.find_section_start(section)?;
130 let content = &self.text()[start..];
131
132 let section_headers = [
134 "[Script Info]",
135 "[V4+ Styles]",
136 "[Events]",
137 "[Fonts]",
138 "[Graphics]",
139 ];
140
141 let mut end_offset = content.len();
142 for header in §ion_headers {
143 if let Some(pos) = content.find(header) {
144 if pos > 0 {
145 end_offset = end_offset.min(pos);
146 }
147 }
148 }
149
150 Ok(start + end_offset)
151 }
152}