1use std::borrow::Cow;
34use std::ops::Range;
35
36use rowan::{GreenNode, GreenToken, TextRange, TextSize};
37
38use crate::parser::bracket_balancer::rebalance_brackets;
39use crate::parser::core::ParseOptions;
40use crate::parser::diagnostics::ParseDiagnostic;
41use crate::parser::expr::parse_expr;
42use crate::parser::lexer::lex_with_md;
43use crate::parser::tree_builder::{build_tree, syntax_kind_for};
44use crate::syntax::{SyntaxKind, SyntaxNode};
45
46#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Edit {
50 pub range: Range<usize>,
51 pub insert: String,
52}
53
54impl Edit {
55 fn delta(&self) -> isize {
57 self.insert.len() as isize - (self.range.end - self.range.start) as isize
58 }
59
60 pub fn apply(&self, old: &str) -> String {
67 let mut out =
68 String::with_capacity(old.len().saturating_sub(self.range.len()) + self.insert.len());
69 out.push_str(&old[..self.range.start]);
70 out.push_str(&self.insert);
71 out.push_str(&old[self.range.end..]);
72 out
73 }
74
75 fn fits(&self, old: &str) -> bool {
77 self.range.start <= self.range.end
78 && self.range.end <= old.len()
79 && old.is_char_boundary(self.range.start)
80 && old.is_char_boundary(self.range.end)
81 }
82
83 fn apply_checked(&self, old: &str) -> Option<String> {
86 self.fits(old).then(|| self.apply(old))
87 }
88
89 fn produces(&self, old: &str, target: &str) -> bool {
99 if !self.fits(old) {
100 return false;
101 }
102 let (start, end) = (self.range.start, self.range.end);
103 let insert = self.insert.as_bytes();
104 let (old, target) = (old.as_bytes(), target.as_bytes());
105
106 if target.len() != old.len() - (end - start) + insert.len() {
109 return false;
110 }
111 let tail = start + insert.len();
113 old[..start] == target[..start]
114 && target[start..tail] == *insert
115 && old[end..] == target[tail..]
116 }
117}
118
119pub fn apply_edits(old: &str, edits: &[Edit]) -> String {
127 edits.iter().fold(old.to_string(), |t, e| e.apply(&t))
128}
129
130pub fn edits_produce(old: &str, edits: &[Edit], target: &str) -> bool {
141 let Some((last, init)) = edits.split_last() else {
142 return old == target;
143 };
144 let mut text = Cow::Borrowed(old);
145 for edit in init {
146 let Some(next) = edit.apply_checked(&text) else {
147 return false;
148 };
149 text = Cow::Owned(next);
150 }
151 last.produces(&text, target)
152}
153
154pub fn map_range_through_edit(range: TextRange, edit: &Edit) -> Option<TextRange> {
173 let node_start = usize::from(range.start());
174 let node_end = usize::from(range.end());
175
176 if node_end <= edit.range.start {
177 Some(range)
179 } else if node_start >= edit.range.end {
180 let start = (node_start as isize + edit.delta()) as usize;
182 let end = (node_end as isize + edit.delta()) as usize;
183 Some(text_range(start, end))
184 } else {
185 None
187 }
188}
189
190pub fn map_range_through_edits(range: TextRange, edits: &[Edit]) -> Option<TextRange> {
195 edits.iter().try_fold(range, map_range_through_edit)
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub enum ReparseKind {
201 Token,
202 Block,
203 TopLevel,
204 Multi,
208}
209
210#[derive(Debug, Clone)]
213pub struct Reparsed {
214 pub green: GreenNode,
215 pub diagnostics: Vec<ParseDiagnostic>,
216 pub kind: ReparseKind,
217}
218
219pub fn diff_edit(old: &str, new: &str) -> Edit {
224 let ob = old.as_bytes();
225 let nb = new.as_bytes();
226
227 let mut prefix = 0;
228 let max_prefix = ob.len().min(nb.len());
229 while prefix < max_prefix && ob[prefix] == nb[prefix] {
230 prefix += 1;
231 }
232 while prefix > 0 && !old.is_char_boundary(prefix) {
233 prefix -= 1;
234 }
235
236 let mut suffix = 0;
237 let max_suffix = (ob.len() - prefix).min(nb.len() - prefix);
238 while suffix < max_suffix && ob[ob.len() - 1 - suffix] == nb[nb.len() - 1 - suffix] {
239 suffix += 1;
240 }
241 while suffix > 0
242 && (!old.is_char_boundary(old.len() - suffix) || !new.is_char_boundary(new.len() - suffix))
243 {
244 suffix -= 1;
245 }
246
247 Edit {
248 range: prefix..(old.len() - suffix),
249 insert: new[prefix..(new.len() - suffix)].to_string(),
250 }
251}
252
253pub fn reparse(
258 old_root: &SyntaxNode,
259 old_text: &str,
260 old_diags: &[ParseDiagnostic],
261 edit: &Edit,
262) -> Option<Reparsed> {
263 reparse_with_options(
264 old_root,
265 old_text,
266 old_diags,
267 edit,
268 &ParseOptions::default(),
269 )
270}
271
272pub fn reparse_with_options(
278 old_root: &SyntaxNode,
279 old_text: &str,
280 old_diags: &[ParseDiagnostic],
281 edit: &Edit,
282 options: &ParseOptions,
283) -> Option<Reparsed> {
284 if !edit.fits(old_text) {
285 return None;
286 }
287
288 let md = options.roxygen_markdown_default;
289 let result = reparse_token(old_root, old_text, old_diags, edit, md)
290 .or_else(|| reparse_block(old_root, old_text, old_diags, edit, md))
291 .or_else(|| reparse_toplevel(old_root, old_text, old_diags, edit, md));
292
293 if let Some(reparsed) = &result {
294 debug_assert_eq!(
295 SyntaxNode::new_root(reparsed.green.clone())
296 .text()
297 .to_string(),
298 edit.apply(old_text),
299 "incremental reparse ({:?}) is not lossless",
300 reparsed.kind,
301 );
302 }
303 result
304}
305
306pub fn reparse_edits(
323 old_root: &SyntaxNode,
324 old_text: &str,
325 old_diags: &[ParseDiagnostic],
326 edits: &[Edit],
327 target: &str,
328) -> Option<Reparsed> {
329 reparse_edits_with_options(
330 old_root,
331 old_text,
332 old_diags,
333 edits,
334 target,
335 &ParseOptions::default(),
336 )
337}
338
339pub fn reparse_edits_with_options(
342 old_root: &SyntaxNode,
343 old_text: &str,
344 old_diags: &[ParseDiagnostic],
345 edits: &[Edit],
346 target: &str,
347 options: &ParseOptions,
348) -> Option<Reparsed> {
349 let (last, init) = edits.split_last()?;
354
355 let mut root = old_root.clone();
357 let mut text = Cow::Borrowed(old_text);
358 let mut diags = Cow::Borrowed(old_diags);
359 for edit in init {
360 let reparsed = reparse_with_options(&root, &text, &diags, edit, options)?;
361 text = Cow::Owned(edit.apply_checked(&text)?);
363 root = SyntaxNode::new_root(reparsed.green);
364 diags = Cow::Owned(reparsed.diagnostics);
365 }
366
367 if !last.produces(&text, target) {
368 return None;
369 }
370
371 let mut reparsed = reparse_with_options(&root, &text, &diags, last, options)?;
372 reparsed.kind = ReparseKind::Multi;
373 Some(reparsed)
374}
375
376const TOKEN_REPARSE_KINDS: &[SyntaxKind] = &[
377 SyntaxKind::IDENT,
378 SyntaxKind::STRING,
379 SyntaxKind::COMMENT,
380 SyntaxKind::WHITESPACE,
381 ];
387
388fn reparse_token(
389 old_root: &SyntaxNode,
390 old_text: &str,
391 old_diags: &[ParseDiagnostic],
392 edit: &Edit,
393 md_default: bool,
394) -> Option<Reparsed> {
395 let (s, e) = (edit.range.start, edit.range.end);
396 let elem = old_root.covering_element(text_range(s, e));
397 let token = elem.into_token()?;
398 if !TOKEN_REPARSE_KINDS.contains(&token.kind()) {
399 return None;
400 }
401
402 let tr = token.text_range();
403 let (t0, t1) = (usize::from(tr.start()), usize::from(tr.end()));
404 if s == e && !(t0 < s && s < t1) {
409 return None;
410 }
411
412 let mut new_text = token.text().to_string();
414 new_text.replace_range((s - t0)..(e - t0), &edit.insert);
415
416 let relexed = lex_with_md(&new_text, md_default);
418 let [only] = relexed.as_slice() else {
419 return None;
420 };
421 if only.start != 0 || only.end != new_text.len() || syntax_kind_for(&only.kind) != token.kind()
422 {
423 return None;
424 }
425
426 if let Some(next_char) = old_text[t1..].chars().next() {
430 let mut probe = new_text.clone();
431 probe.push(next_char);
432 let probe_toks = lex_with_md(&probe, md_default);
433 let first = probe_toks.first()?;
434 if first.end != new_text.len() || syntax_kind_for(&first.kind) != token.kind() {
435 return None;
436 }
437 }
438
439 if old_diags.iter().any(|d| d.start < t1 && d.end > t0) {
442 return None;
443 }
444 let delta = edit.delta();
445 let diagnostics = old_diags
446 .iter()
447 .map(|d| {
448 if d.start >= t1 {
449 shift(d, delta)
450 } else {
451 d.clone()
452 }
453 })
454 .collect();
455
456 let green_token = GreenToken::new(rowan::SyntaxKind::from(token.kind()), &new_text);
457 let green = token.replace_with(green_token);
458 Some(Reparsed {
459 green,
460 diagnostics,
461 kind: ReparseKind::Token,
462 })
463}
464
465fn reparse_block(
466 old_root: &SyntaxNode,
467 old_text: &str,
468 old_diags: &[ParseDiagnostic],
469 edit: &Edit,
470 md_default: bool,
471) -> Option<Reparsed> {
472 let (s, e) = (edit.range.start, edit.range.end);
473 let elem = old_root.covering_element(text_range(s, e));
474 let start_node = match elem {
475 rowan::NodeOrToken::Node(n) => n,
476 rowan::NodeOrToken::Token(t) => t.parent()?,
477 };
478
479 let block = start_node.ancestors().find(|node| {
482 if node.kind() != SyntaxKind::BLOCK_EXPR {
483 return false;
484 }
485 let r = node.text_range();
486 let (bstart, bend) = (usize::from(r.start()), usize::from(r.end()));
487 bend.saturating_sub(bstart) >= 2 && s > bstart && e < bend
489 })?;
490
491 let r = block.text_range();
492 let (bstart, bend) = (usize::from(r.start()), usize::from(r.end()));
493
494 let mut block_text = old_text[bstart..bend].to_string();
497 block_text.replace_range((s - bstart)..(e - bstart), &edit.insert);
498
499 let (block_green, block_diags) = parse_block_in_isolation(&block_text, md_default)?;
500
501 let delta = edit.delta();
502 let mut diagnostics: Vec<ParseDiagnostic> =
503 Vec::with_capacity(old_diags.len() + block_diags.len());
504 for d in old_diags {
505 if d.end <= bstart {
506 diagnostics.push(d.clone());
507 } else if d.start >= bend {
508 diagnostics.push(shift(d, delta));
509 }
510 }
512 for d in &block_diags {
513 diagnostics.push(ParseDiagnostic {
514 message: d.message.clone(),
515 start: d.start + bstart,
516 end: d.end + bstart,
517 });
518 }
519 diagnostics.sort_by_key(|d| (d.start, d.end));
520
521 let green = block.replace_with(block_green);
522 Some(Reparsed {
523 green,
524 diagnostics,
525 kind: ReparseKind::Block,
526 })
527}
528
529fn reparse_toplevel(
532 old_root: &SyntaxNode,
533 old_text: &str,
534 old_diags: &[ParseDiagnostic],
535 edit: &Edit,
536 md_default: bool,
537) -> Option<Reparsed> {
538 let (s, e) = (edit.range.start, edit.range.end);
539 let elem = old_root.covering_element(text_range(s, e));
540 let start_node = match elem {
541 rowan::NodeOrToken::Node(n) => n,
542 rowan::NodeOrToken::Token(t) => t.parent()?,
543 };
544
545 let stmt = start_node
550 .ancestors()
551 .find(|node| node.parent().map(|p| p.kind()) == Some(SyntaxKind::ROOT))?;
552
553 if stmt.kind() == SyntaxKind::ROXYGEN_BLOCK {
557 return None;
558 }
559
560 let r = stmt.text_range();
561 let (ss, se) = (usize::from(r.start()), usize::from(r.end()));
562 debug_assert!(ss <= s && e <= se);
564
565 let mut stmt_text = old_text[ss..se].to_string();
569 stmt_text.replace_range((s - ss)..(e - ss), &edit.insert);
570
571 let (stmt_green, stmt_diags) = parse_stmt_in_isolation(&stmt_text, stmt.kind(), md_default)?;
575
576 let forward_end = match stmt.next_sibling() {
584 Some(next) => usize::from(next.text_range().end()),
585 None => old_text.len(),
586 };
587 if !toplevel_boundary_stable(&stmt_text, &old_text[se..forward_end], md_default) {
588 return None;
589 }
590
591 let delta = edit.delta();
592 let mut diagnostics: Vec<ParseDiagnostic> =
593 Vec::with_capacity(old_diags.len() + stmt_diags.len());
594 for d in old_diags {
595 if d.end <= ss {
596 diagnostics.push(d.clone());
597 } else if d.start >= se {
598 diagnostics.push(shift(d, delta));
599 }
600 }
602 for d in &stmt_diags {
603 diagnostics.push(ParseDiagnostic {
604 message: d.message.clone(),
605 start: d.start + ss,
606 end: d.end + ss,
607 });
608 }
609 diagnostics.sort_by_key(|d| (d.start, d.end));
610
611 let green = stmt.replace_with(stmt_green);
612 Some(Reparsed {
613 green,
614 diagnostics,
615 kind: ReparseKind::TopLevel,
616 })
617}
618
619fn parse_stmt_in_isolation(
624 stmt_text: &str,
625 expected_kind: SyntaxKind,
626 md_default: bool,
627) -> Option<(GreenNode, Vec<ParseDiagnostic>)> {
628 let tokens = rebalance_brackets(lex_with_md(stmt_text, md_default));
629 let mut diagnostics = Vec::new();
630 let expr = parse_expr(&tokens, 0, 0, &mut diagnostics, md_default)?;
631 if expr.start != 0 || expr.end != tokens.len() {
632 return None;
633 }
634
635 let root = build_tree(&tokens, &expr.events);
636 let mut children = root.children();
637 let node = children.next()?;
638 if children.next().is_some() || node.kind() != expected_kind {
639 return None;
640 }
641 Some((node.green().to_owned(), diagnostics))
642}
643
644fn toplevel_boundary_stable(stmt_text: &str, forward_ctx: &str, md_default: bool) -> bool {
650 if forward_ctx.is_empty() {
651 return true;
652 }
653 let mut combined = String::with_capacity(stmt_text.len() + forward_ctx.len());
654 combined.push_str(stmt_text);
655 combined.push_str(forward_ctx);
656
657 let tokens = rebalance_brackets(lex_with_md(&combined, md_default));
658 let mut diagnostics = Vec::new();
659 let Some(expr) = parse_expr(&tokens, 0, 0, &mut diagnostics, md_default) else {
660 return false;
661 };
662 expr.end
665 .checked_sub(1)
666 .and_then(|last| tokens.get(last))
667 .is_some_and(|last| last.end == stmt_text.len())
668}
669
670fn parse_block_in_isolation(
675 block_text: &str,
676 md_default: bool,
677) -> Option<(GreenNode, Vec<ParseDiagnostic>)> {
678 let tokens = rebalance_brackets(lex_with_md(block_text, md_default));
679 let mut diagnostics = Vec::new();
680 let expr = parse_expr(&tokens, 0, 0, &mut diagnostics, md_default)?;
681 if expr.start != 0 || expr.end != tokens.len() {
682 return None;
683 }
684
685 let root = build_tree(&tokens, &expr.events);
686 let mut children = root.children();
687 let block = children.next()?;
688 if children.next().is_some() || block.kind() != SyntaxKind::BLOCK_EXPR {
689 return None;
690 }
691
692 let first = block.first_child_or_token().map(|e| e.kind());
693 let last = block.last_child_or_token().map(|e| e.kind());
694 if first != Some(SyntaxKind::LBRACE) || last != Some(SyntaxKind::RBRACE) {
695 return None;
696 }
697
698 Some((block.green().to_owned(), diagnostics))
699}
700
701fn shift(d: &ParseDiagnostic, delta: isize) -> ParseDiagnostic {
702 ParseDiagnostic {
703 message: d.message.clone(),
704 start: (d.start as isize + delta) as usize,
705 end: (d.end as isize + delta) as usize,
706 }
707}
708
709fn text_range(start: usize, end: usize) -> TextRange {
710 TextRange::new(TextSize::new(start as u32), TextSize::new(end as u32))
711}
712
713#[cfg(test)]
714mod verify_tests {
715 use super::*;
716
717 fn edit(start: usize, end: usize, insert: &str) -> Edit {
718 Edit {
719 range: start..end,
720 insert: insert.to_string(),
721 }
722 }
723
724 fn agrees_with_apply(old: &str, e: &Edit) {
727 if e.fits(old) {
728 let applied = e.apply(old);
729 assert!(
730 e.produces(old, &applied),
731 "produces rejected its own apply: {e:?} on {old:?}",
732 );
733 assert!(!e.produces(old, &format!("{applied}~")));
737 assert!(!e.produces(old, &format!("~{applied}")));
738 } else {
739 assert!(!e.produces(old, old), "unfit edit accepted: {e:?}");
740 }
741 }
742
743 #[test]
744 fn produces_agrees_with_apply_across_edit_shapes() {
745 let old = "alpha <- beta\n";
746 for e in [
747 edit(0, 0, "x"), edit(old.len(), old.len(), "x"), edit(6, 6, ""), edit(0, old.len(), "wholly new"), edit(0, old.len(), ""), edit(9, 13, ""), edit(9, 13, "gamma"), edit(9, 13, "g"), ] {
756 agrees_with_apply(old, &e);
757 }
758 }
759
760 #[test]
761 fn produces_is_total_on_ranges_the_text_cannot_take() {
762 let old = "café <- 1\n";
763 for e in [
764 edit(0, 900, "x"), edit(900, 900, "x"), edit(5, 2, "x"), edit(3, 4, "x"), edit(0, 4, "x"), ] {
770 agrees_with_apply(old, &e);
771 }
772 }
773
774 #[test]
775 fn produces_rejects_a_same_length_but_different_target() {
776 let old = "x <- 1\n";
779 let e = edit(5, 6, "9");
780 assert!(e.produces(old, "x <- 9\n"));
781 assert!(!e.produces(old, "y <- 9\n"));
782 assert!(!e.produces(old, "x <- 8\n"));
783 assert!(!e.produces(old, "x <- 9!"));
784 }
785
786 #[test]
787 fn edits_produce_folds_each_against_prior_text() {
788 let old = "cdef";
790 let edits = [edit(0, 0, "ab"), edit(4, 4, "Z")];
791 assert_eq!(apply_edits(old, &edits), "abcdZef");
792 assert!(edits_produce(old, &edits, "abcdZef"));
793 assert!(!edits_produce(old, &edits, "abcdef"));
794 }
795
796 #[test]
797 fn edits_produce_rejects_a_chain_that_stops_fitting() {
798 let old = "abcdef";
801 let edits = [edit(0, 5, ""), edit(4, 5, "z")];
802 assert!(!edits_produce(old, &edits, "fz"));
803 }
804
805 #[test]
806 fn edits_produce_on_an_empty_slice_is_an_identity_check() {
807 assert!(edits_produce("xyz", &[], "xyz"));
808 assert!(!edits_produce("xyz", &[], "xyw"));
809 }
810}
811
812#[cfg(test)]
813mod map_range_tests {
814 use super::*;
815
816 fn edit(start: usize, end: usize, insert: &str) -> Edit {
817 Edit {
818 range: start..end,
819 insert: insert.to_string(),
820 }
821 }
822
823 #[test]
824 fn node_before_edit_is_unchanged() {
825 let node = text_range(0, 5);
826 assert_eq!(
827 map_range_through_edit(node, &edit(10, 12, "xxxx")),
828 Some(node)
829 );
830 }
831
832 #[test]
833 fn node_after_insertion_shifts_by_delta() {
834 let node = text_range(10, 15);
835 assert_eq!(
837 map_range_through_edit(node, &edit(0, 2, "xxxx")),
838 Some(text_range(12, 17))
839 );
840 }
841
842 #[test]
843 fn node_after_deletion_shifts_back() {
844 let node = text_range(10, 15);
845 assert_eq!(
846 map_range_through_edit(node, &edit(0, 4, "")),
847 Some(text_range(6, 11))
848 );
849 }
850
851 #[test]
852 fn edit_ending_at_node_start_shifts_node() {
853 let node = text_range(10, 15);
855 assert_eq!(
856 map_range_through_edit(node, &edit(8, 10, "abcd")),
857 Some(text_range(12, 17))
858 );
859 }
860
861 #[test]
862 fn insertion_at_node_start_shifts_node() {
863 let node = text_range(10, 15);
864 assert_eq!(
865 map_range_through_edit(node, &edit(10, 10, "ab")),
866 Some(text_range(12, 17))
867 );
868 }
869
870 #[test]
871 fn insertion_at_node_end_leaves_node_unchanged() {
872 let node = text_range(10, 15);
873 assert_eq!(
874 map_range_through_edit(node, &edit(15, 15, "ab")),
875 Some(node)
876 );
877 }
878
879 #[test]
880 fn insertion_strictly_inside_invalidates() {
881 let node = text_range(10, 15);
882 assert_eq!(map_range_through_edit(node, &edit(12, 12, "z")), None);
883 }
884
885 #[test]
886 fn overlapping_deletion_invalidates() {
887 let node = text_range(10, 15);
888 assert_eq!(map_range_through_edit(node, &edit(12, 18, "")), None);
889 }
890
891 #[test]
892 fn zero_length_node_at_insertion_point_is_unchanged() {
893 let node = text_range(10, 10);
896 assert_eq!(
897 map_range_through_edit(node, &edit(10, 10, "ab")),
898 Some(node)
899 );
900 }
901
902 #[test]
903 fn append_at_eof_leaves_earlier_node_unchanged() {
904 let node = text_range(0, 5);
905 assert_eq!(
906 map_range_through_edit(node, &edit(5, 5, "\n# trailing")),
907 Some(node)
908 );
909 }
910
911 #[test]
912 fn map_through_edits_folds_in_order() {
913 let node = text_range(10, 15);
914 let edits = [edit(0, 0, "ab"), edit(0, 0, "cd")];
916 assert_eq!(
917 map_range_through_edits(node, &edits),
918 Some(text_range(14, 19))
919 );
920 }
921
922 #[test]
923 fn map_through_edits_invalidates_on_any_overlap() {
924 let node = text_range(10, 15);
925 let edits = [edit(0, 0, "ab"), edit(13, 13, "z")];
927 assert_eq!(map_range_through_edits(node, &edits), None);
928 }
929
930 #[test]
931 fn apply_edits_folds_each_against_prior_text() {
932 let edits = [edit(0, 0, "ab"), edit(4, 4, "Z")];
936 assert_eq!(apply_edits("cdef", &edits), "abcdZef");
937 }
938
939 #[test]
940 fn apply_edits_empty_is_identity() {
941 assert_eq!(apply_edits("xyz", &[]), "xyz");
942 }
943}