1use std::error::Error;
9use std::fmt::{Display, Formatter};
10use std::ops::Range;
11use std::sync::Arc;
12
13use crate::cell_stream::{end_span, ends_with_cell_semicolon, frame_recovering};
14use crate::native::{
15 NativeCellState, NativeParseError, incomplete_lex_error, incomplete_parse_error, parse_raw_cell,
16};
17use crate::{
18 Cell, CellStream, LexError, Source, Span, Spanned, TextEdit, TokenStream, lex_str_recovering,
19};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum NativeCellBoundary {
24 EOC,
26 EOF,
28 Unbounded,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct NativeRealtimeEditError;
35
36impl Display for NativeRealtimeEditError {
37 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
38 formatter.write_str("source edit is outside the current realtime buffer")
39 }
40}
41
42impl Error for NativeRealtimeEditError {}
43
44#[derive(Debug)]
51pub struct NativeRealtimeCell {
52 document_range: Range<usize>,
53 source: Span,
54 tokens: TokenStream,
55 syntax: Option<Cell>,
56 lexer_diagnostics: Vec<LexError>,
57 parse_error: Option<NativeParseError>,
58 state: NativeCellState,
59 boundary: NativeCellBoundary,
60}
61
62impl NativeRealtimeCell {
63 pub fn source(&self) -> &str {
69 self.source
70 .text()
71 .expect("realtime cell source spans must resolve to text")
72 }
73
74 pub fn document_range(&self) -> Range<usize> {
76 self.document_range.clone()
77 }
78
79 pub fn tokens(&self) -> &TokenStream {
81 &self.tokens
82 }
83
84 pub fn syntax(&self) -> Option<&Cell> {
86 self.syntax.as_ref()
87 }
88
89 pub fn lexer_diagnostics(&self) -> &[LexError] {
91 &self.lexer_diagnostics
92 }
93
94 pub fn parse_error(&self) -> Option<&NativeParseError> {
96 self.parse_error.as_ref()
97 }
98
99 pub fn state(&self) -> NativeCellState {
101 self.state
102 }
103
104 pub fn boundary(&self) -> NativeCellBoundary {
106 self.boundary
107 }
108}
109
110impl Spanned for NativeRealtimeCell {
111 fn try_span(&self) -> Option<Span> {
112 Some(self.source.clone())
113 }
114}
115
116#[derive(Debug)]
124pub struct NativeRealtimeParser {
125 source: String,
126 cells: Vec<NativeRealtimeCell>,
127 reparsed_cells: Range<usize>,
128}
129
130impl Default for NativeRealtimeParser {
131 fn default() -> Self {
132 Self::new("")
133 }
134}
135
136impl NativeRealtimeParser {
137 pub fn new(source: impl Into<String>) -> Self {
139 let source = source.into();
140 let cells = analyze_region(&source, 0);
141 let reparsed_cells = 0..cells.len();
142 Self {
143 source,
144 cells,
145 reparsed_cells,
146 }
147 }
148
149 pub fn apply_edit(
160 &mut self,
161 edit: &TextEdit,
162 ) -> Result<NativeCellState, NativeRealtimeEditError> {
163 let source = edit.apply(&self.source).ok_or(NativeRealtimeEditError)?;
164 let first_changed = self.first_affected_cell(edit);
165 let region_start = self
166 .cells
167 .get(first_changed)
168 .map_or(self.source.len(), |cell| cell.document_range.start);
169 let reusable_suffix = self.reusable_suffix(edit, &source, first_changed, region_start);
170 let rebuild_end = reusable_suffix
171 .map(|(_, start)| start)
172 .unwrap_or(source.len());
173 let rebuilt = analyze_region(&source[region_start..rebuild_end], region_start);
174
175 let mut previous = std::mem::take(&mut self.cells);
176 let previous_suffix = previous.split_off(first_changed);
177 let rebuilt_len = rebuilt.len();
178 previous.extend(rebuilt);
179 let reuse_from = reusable_suffix
180 .map(|(index, _)| index.saturating_sub(first_changed))
181 .unwrap_or(previous_suffix.len());
182 for mut retained in previous_suffix.into_iter().skip(reuse_from) {
183 retained.document_range = shifted_range(edit, retained.document_range);
184 previous.push(retained);
185 }
186
187 self.source = source;
188 self.cells = previous;
189 self.reparsed_cells = first_changed..first_changed + rebuilt_len;
190 Ok(self.state())
191 }
192
193 pub fn append(&mut self, source: impl Into<String>) -> NativeCellState {
200 let edit = TextEdit::insertion(self.source.len(), source);
201 self.apply_edit(&edit)
202 .expect("an insertion at the current EOF is always valid")
203 }
204
205 pub fn source(&self) -> &str {
207 &self.source
208 }
209
210 pub fn cells(&self) -> &[NativeRealtimeCell] {
212 &self.cells
213 }
214
215 pub fn reparsed_cells(&self) -> Range<usize> {
219 self.reparsed_cells.clone()
220 }
221
222 pub fn state(&self) -> NativeCellState {
224 if self
225 .cells
226 .iter()
227 .any(|cell| cell.state == NativeCellState::Invalid)
228 {
229 NativeCellState::Invalid
230 } else if self
231 .cells
232 .last()
233 .is_some_and(|cell| cell.state == NativeCellState::Incomplete)
234 {
235 NativeCellState::Incomplete
236 } else if self.cells.is_empty() {
237 NativeCellState::Empty
238 } else {
239 NativeCellState::Complete
240 }
241 }
242
243 pub fn cell_stream(&self) -> CellStream {
248 CellStream::from_cells(self.cells.iter().map(|cell| cell.tokens.clone()))
249 }
250
251 fn first_affected_cell(&self, edit: &TextEdit) -> usize {
252 let start = match edit {
253 TextEdit::Insertion { at, .. } => *at,
254 TextEdit::Deletion { start, .. } => *start,
255 };
256 self.cells
257 .iter()
258 .position(|cell| {
259 start < cell.document_range.end
260 || (start == cell.document_range.end
261 && cell.boundary != NativeCellBoundary::EOC)
262 })
263 .unwrap_or(self.cells.len())
264 }
265
266 fn reusable_suffix(
267 &self,
268 edit: &TextEdit,
269 source: &str,
270 first_changed: usize,
271 region_start: usize,
272 ) -> Option<(usize, usize)> {
273 for index in first_changed.saturating_add(1)..self.cells.len() {
274 let old_start = self.cells[index].document_range.start;
275 let new_start = shifted_offset(edit, old_start);
276 if new_start < region_start
277 || self.source.get(old_start..) != source.get(new_start..)
278 || !ends_at_confirmed_boundary(&source[region_start..new_start])
279 {
280 continue;
281 }
282 return Some((index, new_start));
283 }
284 None
285 }
286}
287
288fn shifted_range(edit: &TextEdit, range: Range<usize>) -> Range<usize> {
289 shifted_offset(edit, range.start)..shifted_offset(edit, range.end)
290}
291
292fn shifted_offset(edit: &TextEdit, offset: usize) -> usize {
293 match edit {
294 TextEdit::Insertion { at, text } if offset >= *at => offset + text.len(),
295 TextEdit::Insertion { .. } => offset,
296 TextEdit::Deletion { start, .. } if offset <= *start => offset,
297 TextEdit::Deletion { start, end } if offset <= *end => *start,
298 TextEdit::Deletion { start, end } => offset - (end - start),
299 }
300}
301
302fn ends_at_confirmed_boundary(source: &str) -> bool {
303 if source.trim().is_empty() {
304 return true;
305 }
306 let Ok(recovered) = lex_str_recovering(source) else {
307 return false;
308 };
309 let framed = frame_recovering(recovered.value);
310 if framed.incomplete.is_some() {
311 return false;
312 }
313 let Some(last) = framed.cells.last() else {
314 return true;
315 };
316 ends_with_cell_semicolon(last) || source.ends_with('\n')
317}
318
319fn analyze_region(source: &str, document_start: usize) -> Vec<NativeRealtimeCell> {
320 let recovered = match lex_str_recovering(source) {
321 Ok(recovered) => recovered,
322 Err(error) => {
323 if source.is_empty() {
324 return Vec::new();
325 }
326 return vec![cell_from_lex_error(
327 source,
328 document_start..document_start + source.len(),
329 error,
330 )];
331 }
332 };
333 let framed = frame_recovering(recovered.value);
334 let complete_count = framed.cells.len();
335 let has_incomplete = framed.incomplete.is_some();
336 let mut start = 0;
337 let mut cells = Vec::with_capacity(complete_count + usize::from(has_incomplete));
338
339 for (index, tokens) in framed.cells.into_iter().enumerate() {
340 let has_following = index + 1 < complete_count || has_incomplete;
341 let mut end = end_span(&tokens)
342 .end()
343 .unwrap_or(source.len())
344 .min(source.len());
345 if !has_following && end < source.len() {
346 end = source.len();
347 }
348 let boundary = if has_following
349 || ends_with_cell_semicolon(&tokens)
350 || source[start..end].ends_with('\n')
351 {
352 NativeCellBoundary::EOC
353 } else {
354 NativeCellBoundary::EOF
355 };
356 cells.push(analyze_cell(
357 &source[start..end],
358 document_start + start..document_start + end,
359 boundary,
360 ));
361 start = end;
362 }
363
364 if framed.incomplete.is_some() {
365 cells.push(analyze_cell(
366 &source[start..],
367 document_start + start..document_start + source.len(),
368 NativeCellBoundary::Unbounded,
369 ));
370 }
371 cells
372}
373
374fn analyze_cell(
375 source: &str,
376 document_range: Range<usize>,
377 boundary: NativeCellBoundary,
378) -> NativeRealtimeCell {
379 let recovered = match lex_str_recovering(source) {
380 Ok(recovered) => recovered,
381 Err(error) => return cell_from_lex_error(source, document_range, error),
382 };
383 let diagnostics = recovered.diagnostics;
384 let framed = frame_recovering(recovered.value);
385 let mut streams = framed.cells;
386 streams.extend(framed.incomplete);
387 let tokens = match streams.len() {
388 0 => TokenStream::new(),
389 1 => streams.pop().expect("one framed stream is present"),
390 _ => CellStream::from_cells(streams).into_token_stream(),
391 };
392 let source_owner = tokens.source().unwrap_or_else(|| Source::new(source));
393 let source_span = Span::ranged(source_owner, 0..source.len());
394
395 let (syntax, parse_error, state) = match parse_raw_cell(tokens.clone()) {
396 Ok(syntax) => {
397 let state = if boundary == NativeCellBoundary::Unbounded || !diagnostics.is_empty() {
398 NativeCellState::Incomplete
399 } else {
400 NativeCellState::Complete
401 };
402 (syntax, None, state)
403 }
404 Err(error) => {
405 let state = if boundary != NativeCellBoundary::EOC && incomplete_parse_error(&error) {
406 NativeCellState::Incomplete
407 } else {
408 NativeCellState::Invalid
409 };
410 (None, Some(error), state)
411 }
412 };
413
414 NativeRealtimeCell {
415 document_range,
416 source: source_span,
417 tokens,
418 syntax,
419 lexer_diagnostics: diagnostics,
420 parse_error,
421 state,
422 boundary,
423 }
424}
425
426fn cell_from_lex_error(
427 source: &str,
428 document_range: Range<usize>,
429 error: LexError,
430) -> NativeRealtimeCell {
431 let state = if incomplete_lex_error(error.kind()) {
432 NativeCellState::Incomplete
433 } else {
434 NativeCellState::Invalid
435 };
436 let source_owner = Arc::clone(error.span().source());
437 NativeRealtimeCell {
438 document_range,
439 source: Span::ranged(source_owner, 0..source.len()),
440 tokens: TokenStream::new(),
441 syntax: None,
442 lexer_diagnostics: vec![error],
443 parse_error: None,
444 state,
445 boundary: NativeCellBoundary::Unbounded,
446 }
447}