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, CellTokenStream, LexError, Source, Span, Spanned, TextEdit,
19 lex_str_recovering,
20};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub enum NativeCellBoundary {
25 EOC,
27 EOF,
29 Unbounded,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct NativeRealtimeEditError;
36
37impl Display for NativeRealtimeEditError {
38 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
39 formatter.write_str("source edit is outside the current realtime buffer")
40 }
41}
42
43impl Error for NativeRealtimeEditError {}
44
45#[derive(Debug)]
52pub struct NativeRealtimeCell {
53 document_range: Range<usize>,
55 source: Span,
57 tokens: CellTokenStream,
59 syntax: Option<Cell>,
61 lexer_diagnostics: Vec<LexError>,
63 parse_error: Option<NativeParseError>,
65 state: NativeCellState,
67 boundary: NativeCellBoundary,
69}
70
71impl NativeRealtimeCell {
72 pub fn source(&self) -> &str {
78 self.source
79 .text()
80 .expect("realtime cell source spans must resolve to text")
81 }
82
83 pub fn document_range(&self) -> Range<usize> {
85 self.document_range.clone()
86 }
87
88 pub fn tokens(&self) -> &CellTokenStream {
90 &self.tokens
91 }
92
93 pub fn syntax(&self) -> Option<&Cell> {
95 self.syntax.as_ref()
96 }
97
98 pub fn lexer_diagnostics(&self) -> &[LexError] {
100 &self.lexer_diagnostics
101 }
102
103 pub fn parse_error(&self) -> Option<&NativeParseError> {
105 self.parse_error.as_ref()
106 }
107
108 pub fn state(&self) -> NativeCellState {
110 self.state
111 }
112
113 pub fn boundary(&self) -> NativeCellBoundary {
115 self.boundary
116 }
117}
118
119impl Spanned for NativeRealtimeCell {
120 fn try_span(&self) -> Option<Span> {
121 Some(self.source.clone())
122 }
123}
124
125#[derive(Debug)]
133pub struct NativeRealtimeParser {
134 source: String,
136 cells: Vec<NativeRealtimeCell>,
138 reparsed_cells: Range<usize>,
140}
141
142impl Default for NativeRealtimeParser {
143 fn default() -> Self {
144 Self::new("")
145 }
146}
147
148impl NativeRealtimeParser {
149 pub fn new(source: impl Into<String>) -> Self {
151 let source = source.into();
152 let cells = analyze_region(&source, 0);
153 let reparsed_cells = 0..cells.len();
154 Self {
155 source,
156 cells,
157 reparsed_cells,
158 }
159 }
160
161 pub fn apply_edit(
172 &mut self,
173 edit: &TextEdit,
174 ) -> Result<NativeCellState, NativeRealtimeEditError> {
175 let source = edit.apply(&self.source).ok_or(NativeRealtimeEditError)?;
176 let first_changed = self.first_affected_cell(edit);
177 let region_start = self
178 .cells
179 .get(first_changed)
180 .map_or(self.source.len(), |cell| cell.document_range.start);
181 let reusable_suffix = self.reusable_suffix(edit, &source, first_changed, region_start);
182 let rebuild_end = reusable_suffix
183 .map(|(_, start)| start)
184 .unwrap_or(source.len());
185 let rebuilt = analyze_region(&source[region_start..rebuild_end], region_start);
186
187 let mut previous = std::mem::take(&mut self.cells);
188 let previous_suffix = previous.split_off(first_changed);
189 let rebuilt_len = rebuilt.len();
190 previous.extend(rebuilt);
191 let reuse_from = reusable_suffix
192 .map(|(index, _)| index.saturating_sub(first_changed))
193 .unwrap_or(previous_suffix.len());
194 for mut retained in previous_suffix.into_iter().skip(reuse_from) {
195 retained.document_range = shifted_range(edit, retained.document_range);
196 previous.push(retained);
197 }
198
199 self.source = source;
200 self.cells = previous;
201 self.reparsed_cells = first_changed..first_changed + rebuilt_len;
202 Ok(self.state())
203 }
204
205 pub fn append(&mut self, source: impl Into<String>) -> NativeCellState {
212 let edit = TextEdit::insertion(self.source.len(), source);
213 self.apply_edit(&edit)
214 .expect("an insertion at the current EOF is always valid")
215 }
216
217 pub fn source(&self) -> &str {
219 &self.source
220 }
221
222 pub fn cells(&self) -> &[NativeRealtimeCell] {
224 &self.cells
225 }
226
227 pub fn reparsed_cells(&self) -> Range<usize> {
231 self.reparsed_cells.clone()
232 }
233
234 pub fn state(&self) -> NativeCellState {
236 if self
237 .cells
238 .iter()
239 .any(|cell| cell.state == NativeCellState::Invalid)
240 {
241 NativeCellState::Invalid
242 } else if self
243 .cells
244 .last()
245 .is_some_and(|cell| cell.state == NativeCellState::Incomplete)
246 {
247 NativeCellState::Incomplete
248 } else if self.cells.is_empty() {
249 NativeCellState::Empty
250 } else {
251 NativeCellState::Complete
252 }
253 }
254
255 pub fn cell_stream(&self) -> CellStream {
260 CellStream::from_cells(self.cells.iter().map(|cell| cell.tokens.clone()))
261 }
262
263 fn first_affected_cell(&self, edit: &TextEdit) -> usize {
265 let start = match edit {
266 TextEdit::Insertion { at, .. } => *at,
267 TextEdit::Deletion { start, .. } => *start,
268 };
269 self.cells
270 .iter()
271 .position(|cell| {
272 start < cell.document_range.end
273 || (start == cell.document_range.end
274 && cell.boundary != NativeCellBoundary::EOC)
275 })
276 .unwrap_or(self.cells.len())
277 }
278
279 fn reusable_suffix(
281 &self,
282 edit: &TextEdit,
283 source: &str,
284 first_changed: usize,
285 region_start: usize,
286 ) -> Option<(usize, usize)> {
287 for index in first_changed.saturating_add(1)..self.cells.len() {
288 let old_start = self.cells[index].document_range.start;
289 let new_start = shifted_offset(edit, old_start);
290 if new_start < region_start
291 || self.source.get(old_start..) != source.get(new_start..)
292 || !ends_at_confirmed_boundary(&source[region_start..new_start])
293 {
294 continue;
295 }
296 return Some((index, new_start));
297 }
298 None
299 }
300}
301
302fn shifted_range(edit: &TextEdit, range: Range<usize>) -> Range<usize> {
304 shifted_offset(edit, range.start)..shifted_offset(edit, range.end)
305}
306
307fn shifted_offset(edit: &TextEdit, offset: usize) -> usize {
309 match edit {
310 TextEdit::Insertion { at, text } if offset >= *at => offset + text.len(),
311 TextEdit::Insertion { .. } => offset,
312 TextEdit::Deletion { start, .. } if offset <= *start => offset,
313 TextEdit::Deletion { start, end } if offset <= *end => *start,
314 TextEdit::Deletion { start, end } => offset - (end - start),
315 }
316}
317
318fn ends_at_confirmed_boundary(source: &str) -> bool {
320 if source.trim().is_empty() {
321 return true;
322 }
323 let Ok(recovered) = lex_str_recovering(source) else {
324 return false;
325 };
326 let framed = frame_recovering(recovered.value);
327 if framed.incomplete.is_some() {
328 return false;
329 }
330 let Some(last) = framed.cells.last() else {
331 return true;
332 };
333 ends_with_cell_semicolon(last) || source.ends_with('\n')
334}
335
336fn analyze_region(source: &str, document_start: usize) -> Vec<NativeRealtimeCell> {
338 let recovered = match lex_str_recovering(source) {
339 Ok(recovered) => recovered,
340 Err(error) => {
341 if source.is_empty() {
342 return Vec::new();
343 }
344 return vec![cell_from_lex_error(
345 source,
346 document_start..document_start + source.len(),
347 error,
348 )];
349 }
350 };
351 let framed = frame_recovering(recovered.value);
352 let complete_count = framed.cells.len();
353 let has_incomplete = framed.incomplete.is_some();
354 let mut start = 0;
355 let mut cells = Vec::with_capacity(complete_count + usize::from(has_incomplete));
356
357 for (index, tokens) in framed.cells.into_iter().enumerate() {
358 let has_following = index + 1 < complete_count || has_incomplete;
359 let mut end = end_span(&tokens)
360 .end()
361 .unwrap_or(source.len())
362 .min(source.len());
363 if !has_following && end < source.len() {
364 end = source.len();
365 }
366 let boundary = if has_following
367 || ends_with_cell_semicolon(&tokens)
368 || source[start..end].ends_with('\n')
369 {
370 NativeCellBoundary::EOC
371 } else {
372 NativeCellBoundary::EOF
373 };
374 cells.push(analyze_cell(
375 &source[start..end],
376 document_start + start..document_start + end,
377 boundary,
378 ));
379 start = end;
380 }
381
382 if framed.incomplete.is_some() {
383 cells.push(analyze_cell(
384 &source[start..],
385 document_start + start..document_start + source.len(),
386 NativeCellBoundary::Unbounded,
387 ));
388 }
389 cells
390}
391
392fn analyze_cell(
394 source: &str,
395 document_range: Range<usize>,
396 boundary: NativeCellBoundary,
397) -> NativeRealtimeCell {
398 let recovered = match lex_str_recovering(source) {
399 Ok(recovered) => recovered,
400 Err(error) => return cell_from_lex_error(source, document_range, error),
401 };
402 let diagnostics = recovered.diagnostics;
403 let framed = frame_recovering(recovered.value);
404 let mut streams = framed.cells;
405 streams.extend(framed.incomplete);
406 let tokens = match streams.len() {
407 0 => CellTokenStream::default(),
408 1 => streams.pop().expect("one framed stream is present"),
409 _ => CellTokenStream::from_framed_cells(streams),
410 };
411 let source_owner = tokens.source().unwrap_or_else(|| Source::new(source));
412 let source_span = Span::ranged(source_owner, 0..source.len());
413
414 let (syntax, parse_error, state) = match parse_raw_cell(tokens.clone()) {
415 Ok(syntax) => {
416 let state = if boundary == NativeCellBoundary::Unbounded || !diagnostics.is_empty() {
417 NativeCellState::Incomplete
418 } else {
419 NativeCellState::Complete
420 };
421 (syntax, None, state)
422 }
423 Err(error) => {
424 let state = if boundary != NativeCellBoundary::EOC && incomplete_parse_error(&error) {
425 NativeCellState::Incomplete
426 } else {
427 NativeCellState::Invalid
428 };
429 (None, Some(error), state)
430 }
431 };
432
433 NativeRealtimeCell {
434 document_range,
435 source: source_span,
436 tokens,
437 syntax,
438 lexer_diagnostics: diagnostics,
439 parse_error,
440 state,
441 boundary,
442 }
443}
444
445fn cell_from_lex_error(
447 source: &str,
448 document_range: Range<usize>,
449 error: LexError,
450) -> NativeRealtimeCell {
451 let state = if incomplete_lex_error(error.kind()) {
452 NativeCellState::Incomplete
453 } else {
454 NativeCellState::Invalid
455 };
456 let source_owner = Arc::clone(error.span().source());
457 NativeRealtimeCell {
458 document_range,
459 source: Span::ranged(source_owner, 0..source.len()),
460 tokens: CellTokenStream::default(),
461 syntax: None,
462 lexer_diagnostics: vec![error],
463 parse_error: None,
464 state,
465 boundary: NativeCellBoundary::Unbounded,
466 }
467}