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