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 crate::parser::diagnostics::refine_unicode_diagnostics(&tokens, &mut diagnostics);
642 Some((node.green().to_owned(), diagnostics))
643}
644
645fn toplevel_boundary_stable(stmt_text: &str, forward_ctx: &str, md_default: bool) -> bool {
651 if forward_ctx.is_empty() {
652 return true;
653 }
654 let mut combined = String::with_capacity(stmt_text.len() + forward_ctx.len());
655 combined.push_str(stmt_text);
656 combined.push_str(forward_ctx);
657
658 let tokens = rebalance_brackets(lex_with_md(&combined, md_default));
659 let mut diagnostics = Vec::new();
660 let Some(expr) = parse_expr(&tokens, 0, 0, &mut diagnostics, md_default) else {
661 return false;
662 };
663 expr.end
666 .checked_sub(1)
667 .and_then(|last| tokens.get(last))
668 .is_some_and(|last| last.end == stmt_text.len())
669}
670
671fn parse_block_in_isolation(
676 block_text: &str,
677 md_default: bool,
678) -> Option<(GreenNode, Vec<ParseDiagnostic>)> {
679 let tokens = rebalance_brackets(lex_with_md(block_text, md_default));
680 let mut diagnostics = Vec::new();
681 let expr = parse_expr(&tokens, 0, 0, &mut diagnostics, md_default)?;
682 if expr.start != 0 || expr.end != tokens.len() {
683 return None;
684 }
685
686 let root = build_tree(&tokens, &expr.events);
687 let mut children = root.children();
688 let block = children.next()?;
689 if children.next().is_some() || block.kind() != SyntaxKind::BLOCK_EXPR {
690 return None;
691 }
692
693 let first = block.first_child_or_token().map(|e| e.kind());
694 let last = block.last_child_or_token().map(|e| e.kind());
695 if first != Some(SyntaxKind::LBRACE) || last != Some(SyntaxKind::RBRACE) {
696 return None;
697 }
698
699 crate::parser::diagnostics::refine_unicode_diagnostics(&tokens, &mut diagnostics);
700 Some((block.green().to_owned(), diagnostics))
701}
702
703fn shift(d: &ParseDiagnostic, delta: isize) -> ParseDiagnostic {
704 ParseDiagnostic {
705 message: d.message.clone(),
706 start: (d.start as isize + delta) as usize,
707 end: (d.end as isize + delta) as usize,
708 }
709}
710
711fn text_range(start: usize, end: usize) -> TextRange {
712 TextRange::new(TextSize::new(start as u32), TextSize::new(end as u32))
713}
714
715#[cfg(test)]
716mod verify_tests {
717 use super::*;
718
719 fn edit(start: usize, end: usize, insert: &str) -> Edit {
720 Edit {
721 range: start..end,
722 insert: insert.to_string(),
723 }
724 }
725
726 fn agrees_with_apply(old: &str, e: &Edit) {
729 if e.fits(old) {
730 let applied = e.apply(old);
731 assert!(
732 e.produces(old, &applied),
733 "produces rejected its own apply: {e:?} on {old:?}",
734 );
735 assert!(!e.produces(old, &format!("{applied}~")));
739 assert!(!e.produces(old, &format!("~{applied}")));
740 } else {
741 assert!(!e.produces(old, old), "unfit edit accepted: {e:?}");
742 }
743 }
744
745 #[test]
746 fn produces_agrees_with_apply_across_edit_shapes() {
747 let old = "alpha <- beta\n";
748 for e in [
749 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"), ] {
758 agrees_with_apply(old, &e);
759 }
760 }
761
762 #[test]
763 fn produces_is_total_on_ranges_the_text_cannot_take() {
764 let old = "café <- 1\n";
765 for e in [
766 edit(0, 900, "x"), edit(900, 900, "x"), edit(5, 2, "x"), edit(3, 4, "x"), edit(0, 4, "x"), ] {
772 agrees_with_apply(old, &e);
773 }
774 }
775
776 #[test]
777 fn produces_rejects_a_same_length_but_different_target() {
778 let old = "x <- 1\n";
781 let e = edit(5, 6, "9");
782 assert!(e.produces(old, "x <- 9\n"));
783 assert!(!e.produces(old, "y <- 9\n"));
784 assert!(!e.produces(old, "x <- 8\n"));
785 assert!(!e.produces(old, "x <- 9!"));
786 }
787
788 #[test]
789 fn edits_produce_folds_each_against_prior_text() {
790 let old = "cdef";
792 let edits = [edit(0, 0, "ab"), edit(4, 4, "Z")];
793 assert_eq!(apply_edits(old, &edits), "abcdZef");
794 assert!(edits_produce(old, &edits, "abcdZef"));
795 assert!(!edits_produce(old, &edits, "abcdef"));
796 }
797
798 #[test]
799 fn edits_produce_rejects_a_chain_that_stops_fitting() {
800 let old = "abcdef";
803 let edits = [edit(0, 5, ""), edit(4, 5, "z")];
804 assert!(!edits_produce(old, &edits, "fz"));
805 }
806
807 #[test]
808 fn edits_produce_on_an_empty_slice_is_an_identity_check() {
809 assert!(edits_produce("xyz", &[], "xyz"));
810 assert!(!edits_produce("xyz", &[], "xyw"));
811 }
812}
813
814#[cfg(test)]
815mod map_range_tests {
816 use super::*;
817
818 fn edit(start: usize, end: usize, insert: &str) -> Edit {
819 Edit {
820 range: start..end,
821 insert: insert.to_string(),
822 }
823 }
824
825 #[test]
826 fn node_before_edit_is_unchanged() {
827 let node = text_range(0, 5);
828 assert_eq!(
829 map_range_through_edit(node, &edit(10, 12, "xxxx")),
830 Some(node)
831 );
832 }
833
834 #[test]
835 fn node_after_insertion_shifts_by_delta() {
836 let node = text_range(10, 15);
837 assert_eq!(
839 map_range_through_edit(node, &edit(0, 2, "xxxx")),
840 Some(text_range(12, 17))
841 );
842 }
843
844 #[test]
845 fn node_after_deletion_shifts_back() {
846 let node = text_range(10, 15);
847 assert_eq!(
848 map_range_through_edit(node, &edit(0, 4, "")),
849 Some(text_range(6, 11))
850 );
851 }
852
853 #[test]
854 fn edit_ending_at_node_start_shifts_node() {
855 let node = text_range(10, 15);
857 assert_eq!(
858 map_range_through_edit(node, &edit(8, 10, "abcd")),
859 Some(text_range(12, 17))
860 );
861 }
862
863 #[test]
864 fn insertion_at_node_start_shifts_node() {
865 let node = text_range(10, 15);
866 assert_eq!(
867 map_range_through_edit(node, &edit(10, 10, "ab")),
868 Some(text_range(12, 17))
869 );
870 }
871
872 #[test]
873 fn insertion_at_node_end_leaves_node_unchanged() {
874 let node = text_range(10, 15);
875 assert_eq!(
876 map_range_through_edit(node, &edit(15, 15, "ab")),
877 Some(node)
878 );
879 }
880
881 #[test]
882 fn insertion_strictly_inside_invalidates() {
883 let node = text_range(10, 15);
884 assert_eq!(map_range_through_edit(node, &edit(12, 12, "z")), None);
885 }
886
887 #[test]
888 fn overlapping_deletion_invalidates() {
889 let node = text_range(10, 15);
890 assert_eq!(map_range_through_edit(node, &edit(12, 18, "")), None);
891 }
892
893 #[test]
894 fn zero_length_node_at_insertion_point_is_unchanged() {
895 let node = text_range(10, 10);
898 assert_eq!(
899 map_range_through_edit(node, &edit(10, 10, "ab")),
900 Some(node)
901 );
902 }
903
904 #[test]
905 fn append_at_eof_leaves_earlier_node_unchanged() {
906 let node = text_range(0, 5);
907 assert_eq!(
908 map_range_through_edit(node, &edit(5, 5, "\n# trailing")),
909 Some(node)
910 );
911 }
912
913 #[test]
914 fn map_through_edits_folds_in_order() {
915 let node = text_range(10, 15);
916 let edits = [edit(0, 0, "ab"), edit(0, 0, "cd")];
918 assert_eq!(
919 map_range_through_edits(node, &edits),
920 Some(text_range(14, 19))
921 );
922 }
923
924 #[test]
925 fn map_through_edits_invalidates_on_any_overlap() {
926 let node = text_range(10, 15);
927 let edits = [edit(0, 0, "ab"), edit(13, 13, "z")];
929 assert_eq!(map_range_through_edits(node, &edits), None);
930 }
931
932 #[test]
933 fn apply_edits_folds_each_against_prior_text() {
934 let edits = [edit(0, 0, "ab"), edit(4, 4, "Z")];
938 assert_eq!(apply_edits("cdef", &edits), "abcdZef");
939 }
940
941 #[test]
942 fn apply_edits_empty_is_identity() {
943 assert_eq!(apply_edits("xyz", &[]), "xyz");
944 }
945}