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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Range-based partial reparse and span adjustment helpers.
//!
//! Hosts the streaming [`Script::parse_partial`] entry point used by editors for
//! sub-2ms edits, together with the `adjust_section_spans` helper that shifts
//! unchanged section spans after a text change.
use alloc::vec::Vec;
#[cfg(feature = "stream")]
use alloc::{format, string::ToString};
#[cfg(feature = "stream")]
use core::ops::Range;
use crate::parser::ast::Section;
#[cfg(feature = "stream")]
use crate::parser::streaming;
#[cfg(feature = "stream")]
use crate::Result;
#[cfg(feature = "stream")]
use super::delta::{calculate_delta, ScriptDeltaOwned};
use super::Script;
impl<'a> Script<'a> {
/// Parse incrementally with range-based updates for editors
///
/// Updates only the specified range, keeping other sections unchanged.
/// Enables <2ms edit responsiveness for interactive editing.
///
/// # Arguments
///
/// * `range` - Byte range in source to re-parse
/// * `new_text` - Replacement text for the range
///
/// # Returns
///
/// Delta containing changes that can be applied to existing script.
///
/// # Errors
///
/// Returns an error if the new text contains malformed section headers or
/// other unrecoverable syntax errors in the specified range.
#[cfg(feature = "stream")]
pub fn parse_partial(&self, range: Range<usize>, new_text: &str) -> Result<ScriptDeltaOwned> {
// Build the modified source
let modified_source =
streaming::build_modified_source(self.source, range.clone(), new_text);
// Create a TextChange for incremental parsing
let change = crate::parser::incremental::TextChange {
range: range.clone(),
new_text: new_text.to_string(),
line_range: crate::parser::incremental::calculate_line_range(self.source, range),
};
// Parse incrementally
let new_script = self.parse_incremental(&modified_source, &change)?;
// Calculate delta
let delta = calculate_delta(self, &new_script);
// Convert to owned format
let mut owned_delta = ScriptDeltaOwned {
added: Vec::new(),
modified: Vec::new(),
removed: Vec::new(),
new_issues: Vec::new(),
};
// Convert added sections
for section in delta.added {
owned_delta.added.push(format!("{section:?}"));
}
// Convert modified sections
for (idx, section) in delta.modified {
owned_delta.modified.push((idx, format!("{section:?}")));
}
// Convert removed sections
owned_delta.removed = delta.removed;
// Convert new issues
owned_delta.new_issues = delta.new_issues;
Ok(owned_delta)
}
/// Adjust section spans for unchanged sections after a text change
pub(super) fn adjust_section_spans(
section: &Section<'a>,
change: &crate::parser::incremental::TextChange,
) -> Section<'a> {
use crate::parser::ast::Span;
// Calculate the offset caused by the change
let new_len = change.new_text.len();
let old_len = change.range.end - change.range.start;
// Helper to adjust a span using safe arithmetic
let adjust_span = |span: &Span| -> Span {
let new_start = if new_len >= old_len {
span.start + (new_len - old_len)
} else {
span.start.saturating_sub(old_len - new_len)
};
let new_end = if new_len >= old_len {
span.end + (new_len - old_len)
} else {
span.end.saturating_sub(old_len - new_len)
};
Span::new(new_start, new_end, span.line, span.column)
};
// Adjust all spans in the section
match section {
Section::ScriptInfo(info) => {
let mut new_info = info.clone();
new_info.span = adjust_span(&info.span);
Section::ScriptInfo(new_info)
}
Section::Styles(styles) => {
let new_styles: Vec<_> = styles
.iter()
.map(|style| {
let mut new_style = style.clone();
new_style.span = adjust_span(&style.span);
new_style
})
.collect();
Section::Styles(new_styles)
}
Section::Events(events) => {
let new_events: Vec<_> = events
.iter()
.map(|event| {
let mut new_event = event.clone();
new_event.span = adjust_span(&event.span);
new_event
})
.collect();
Section::Events(new_events)
}
Section::Fonts(fonts) => {
let new_fonts: Vec<_> = fonts
.iter()
.map(|font| {
let mut new_font = font.clone();
new_font.span = adjust_span(&font.span);
new_font
})
.collect();
Section::Fonts(new_fonts)
}
Section::Graphics(graphics) => {
let new_graphics: Vec<_> = graphics
.iter()
.map(|graphic| {
let mut new_graphic = graphic.clone();
new_graphic.span = adjust_span(&graphic.span);
new_graphic
})
.collect();
Section::Graphics(new_graphics)
}
}
}
}