Skip to content

Commit 173e45f

Browse files
bors[bot]Veykril
andauthored
Merge #6365
6365: Do insertion lookahead in algo::diff r=matklad a=Veykril This is the last blocker for #6287 after this I can update that PR to properly fix things through using `SyntaxRewriter`. This PR also shuffles tests around a bit and adds some more. Ideally this is just a hack until we implement a "proper" diff algorithm that approximates a minimal diff. Maybe something like [gumtree](https://github.com/GumTreeDiff/gumtree)? Co-authored-by: Lukas Wirth <[email protected]>
2 parents d021dbe + 750ab54 commit 173e45f

File tree

2 files changed

+121
-41
lines changed

2 files changed

+121
-41
lines changed

crates/ide/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ fn main() {
613613
pub struct Foo { pub a: i32, pub b: i32 }
614614
"#,
615615
r#"
616-
fn some(, b: ()} {}
616+
fn some(, b: ()) {}
617617
fn items() {}
618618
fn here() {}
619619

crates/syntax/src/algo.rs

Lines changed: 120 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl TreeDiff {
137137
}
138138
}
139139

140-
/// Finds minimal the diff, which, applied to `from`, will result in `to`.
140+
/// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`.
141141
///
142142
/// Specifically, returns a structure that consists of a replacements, insertions and deletions
143143
/// such that applying this map on `from` will result in `to`.
@@ -151,7 +151,6 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
151151
};
152152
let (from, to) = (from.clone().into(), to.clone().into());
153153

154-
// FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly.
155154
if !syntax_element_eq(&from, &to) {
156155
go(&mut diff, from, to);
157156
}
@@ -169,6 +168,7 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
169168
}
170169
}
171170

171+
// FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly.
172172
fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
173173
let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
174174
Some((lhs, rhs)) => (lhs, rhs),
@@ -179,6 +179,8 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
179179
}
180180
};
181181

182+
let mut look_ahead_scratch = Vec::default();
183+
182184
let mut rhs_children = rhs.children_with_tokens();
183185
let mut lhs_children = lhs.children_with_tokens();
184186
let mut last_lhs = None;
@@ -204,7 +206,31 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
204206
diff.deletions.push(element);
205207
}
206208
(Some(ref lhs_ele), Some(ref rhs_ele)) if syntax_element_eq(lhs_ele, rhs_ele) => {}
207-
(Some(lhs_ele), Some(rhs_ele)) => go(diff, lhs_ele, rhs_ele),
209+
(Some(lhs_ele), Some(rhs_ele)) => {
210+
// nodes differ, look for lhs_ele in rhs, if its found we can mark everything up
211+
// until that element as insertions. This is important to keep the diff minimal
212+
// in regards to insertions that have been actually done, this is important for
213+
// use insertions as we do not want to replace the entire module node.
214+
look_ahead_scratch.push(rhs_ele.clone());
215+
let mut rhs_children_clone = rhs_children.clone();
216+
let mut insert = false;
217+
while let Some(rhs_child) = rhs_children_clone.next() {
218+
if syntax_element_eq(&lhs_ele, &rhs_child) {
219+
mark::hit!(diff_insertions);
220+
insert = true;
221+
break;
222+
} else {
223+
look_ahead_scratch.push(rhs_child);
224+
}
225+
}
226+
let drain = look_ahead_scratch.drain(..);
227+
if let Some(prev) = last_lhs.clone().filter(|_| insert) {
228+
diff.insertions.entry(prev).or_insert_with(Vec::new).extend(drain);
229+
rhs_children = rhs_children_clone;
230+
} else {
231+
go(diff, lhs_ele, rhs_ele)
232+
}
233+
}
208234
}
209235
last_lhs = lhs_child.or(last_lhs);
210236
}
@@ -292,7 +318,6 @@ fn _replace_children(
292318
#[derive(Debug, PartialEq, Eq, Hash)]
293319
enum InsertPos {
294320
FirstChildOf(SyntaxNode),
295-
// Before(SyntaxElement),
296321
After(SyntaxElement),
297322
}
298323

@@ -603,18 +628,44 @@ mod tests {
603628
}
604629

605630
#[test]
606-
fn insert() {
631+
fn replace_parent() {
632+
mark::check!(diff_replace_parent);
633+
check_diff(
634+
r#""#,
635+
r#"use foo::bar;"#,
636+
expect![[r#"
637+
insertions:
638+
639+
640+
641+
replacements:
642+
643+
Line 0: Node([email protected]) -> use foo::bar;
644+
645+
deletions:
646+
647+
648+
"#]],
649+
);
650+
}
651+
652+
#[test]
653+
fn insert_last() {
607654
mark::check!(diff_insert);
608655
check_diff(
609-
r#"use foo;"#,
610-
r#"use foo;
656+
r#"
657+
use foo;
611658
use bar;"#,
659+
r#"
660+
use foo;
661+
use bar;
662+
use baz;"#,
612663
expect![[r#"
613664
insertions:
614665
615-
Line 0: Node(USE@0..8)
666+
Line 2: Node(USE@10..18)
616667
-> "\n"
617-
-> use bar;
668+
-> use baz;
618669
619670
replacements:
620671
@@ -628,29 +679,63 @@ use bar;"#,
628679
}
629680

630681
#[test]
631-
fn replace_parent() {
632-
mark::check!(diff_replace_parent);
682+
fn insert_middle() {
633683
check_diff(
634-
r#""#,
635-
r#"use foo::bar;"#,
684+
r#"
685+
use foo;
686+
use baz;"#,
687+
r#"
688+
use foo;
689+
use bar;
690+
use baz;"#,
636691
expect![[r#"
637692
insertions:
638693
694+
Line 2: Token([email protected] "\n")
695+
-> use bar;
696+
-> "\n"
697+
698+
replacements:
699+
700+
701+
702+
deletions:
703+
704+
705+
"#]],
706+
)
707+
}
708+
709+
#[test]
710+
fn insert_first() {
711+
check_diff(
712+
r#"
713+
use bar;
714+
use baz;"#,
715+
r#"
716+
use foo;
717+
use bar;
718+
use baz;"#,
719+
expect![[r#"
720+
insertions:
639721
722+
Line 0: Token([email protected] "\n")
723+
-> use foo;
724+
-> "\n"
640725
641726
replacements:
642727
643-
Line 0: Node([email protected]) -> use foo::bar;
728+
644729
645730
deletions:
646731
647732
648733
"#]],
649-
);
734+
)
650735
}
651736

652737
#[test]
653-
fn delete() {
738+
fn delete_last() {
654739
mark::check!(diff_delete);
655740
check_diff(
656741
r#"use foo;
@@ -674,52 +759,50 @@ use bar;"#,
674759
}
675760

676761
#[test]
677-
fn insert_use() {
762+
fn delete_middle() {
763+
mark::check!(diff_insertions);
678764
check_diff(
679765
r#"
680766
use expect_test::{expect, Expect};
767+
use text_edit::TextEdit;
681768
682769
use crate::AstNode;
683770
"#,
684771
r#"
685772
use expect_test::{expect, Expect};
686-
use text_edit::TextEdit;
687773
688774
use crate::AstNode;
689775
"#,
690776
expect![[r#"
691777
insertions:
692778
693-
Line 4: Token([email protected] "\n")
779+
Line 1: Node([email protected])
780+
-> "\n\n"
694781
-> use crate::AstNode;
695-
-> "\n"
696782
697783
replacements:
698784
699-
Line 2: Token([email protected] "\n\n") -> "\n"
700-
Line 4: Token([email protected] "crate") -> text_edit
701-
Line 4: Token([email protected] "AstNode") -> TextEdit
702-
Line 4: Token([email protected] "\n") -> "\n\n"
703785
704-
deletions:
705786
787+
deletions:
706788
789+
Line 2: use text_edit::TextEdit;
790+
Line 3: "\n\n"
791+
Line 4: use crate::AstNode;
792+
Line 5: "\n"
707793
"#]],
708794
)
709795
}
710796

711797
#[test]
712-
fn remove_use() {
798+
fn delete_first() {
713799
check_diff(
714800
r#"
715-
use expect_test::{expect, Expect};
716801
use text_edit::TextEdit;
717802
718803
use crate::AstNode;
719804
"#,
720805
r#"
721-
use expect_test::{expect, Expect};
722-
723806
use crate::AstNode;
724807
"#,
725808
expect![[r#"
@@ -729,15 +812,14 @@ use crate::AstNode;
729812
730813
replacements:
731814
732-
Line 2: Token([email protected] "\n") -> "\n\n"
733-
Line 3: Node([email protected]) -> crate
734-
Line 3: Token([email protected] "TextEdit") -> AstNode
735-
Line 3: Token([email protected] "\n\n") -> "\n"
815+
Line 2: Node([email protected]) -> crate
816+
Line 2: Token([email protected] "TextEdit") -> AstNode
817+
Line 2: Token([email protected] "\n\n") -> "\n"
736818
737819
deletions:
738820
739-
Line 4: use crate::AstNode;
740-
Line 5: "\n"
821+
Line 3: use crate::AstNode;
822+
Line 4: "\n"
741823
"#]],
742824
)
743825
}
@@ -814,17 +896,15 @@ fn main() {
814896
_ => return,
815897
}
816898
-> ;
817-
Line 5: Token([email protected] "}")
818-
-> "\n"
819-
-> }
899+
Line 3: Node([email protected])
900+
-> "\n "
901+
-> foo(x);
820902
821903
replacements:
822904
823905
Line 3: Token([email protected] "if") -> let
824906
Line 3: Token([email protected] "let") -> x
825907
Line 3: Node([email protected]) -> =
826-
Line 5: Token([email protected] "\n") -> "\n "
827-
Line 5: Token([email protected] "}") -> foo(x);
828908
829909
deletions:
830910

0 commit comments

Comments
 (0)