1 //! Collection of assorted algorithms for syntax trees.
3 use std
::hash
::BuildHasherDefault
;
5 use indexmap
::IndexMap
;
6 use itertools
::Itertools
;
7 use rustc_hash
::FxHashMap
;
8 use text_edit
::TextEditBuilder
;
11 AstNode
, Direction
, NodeOrToken
, SyntaxElement
, SyntaxKind
, SyntaxNode
, SyntaxToken
, TextRange
,
15 /// Returns ancestors of the node at the offset, sorted by length. This should
16 /// do the right thing at an edge, e.g. when searching for expressions at `{
17 /// $0foo }` we will get the name reference instead of the whole block, which
18 /// we would get if we just did `find_token_at_offset(...).flat_map(|t|
19 /// t.parent().ancestors())`.
20 pub fn ancestors_at_offset(
23 ) -> impl Iterator
<Item
= SyntaxNode
> {
24 node
.token_at_offset(offset
)
25 .map(|token
| token
.parent_ancestors())
26 .kmerge_by(|node1
, node2
| node1
.text_range().len() < node2
.text_range().len())
29 /// Finds a node of specific Ast type at offset. Note that this is slightly
30 /// imprecise: if the cursor is strictly between two nodes of the desired type,
34 /// struct Foo {}|struct Bar;
37 /// then the shorter node will be silently preferred.
38 pub fn find_node_at_offset
<N
: AstNode
>(syntax
: &SyntaxNode
, offset
: TextSize
) -> Option
<N
> {
39 ancestors_at_offset(syntax
, offset
).find_map(N
::cast
)
42 pub fn find_node_at_range
<N
: AstNode
>(syntax
: &SyntaxNode
, range
: TextRange
) -> Option
<N
> {
43 syntax
.covering_element(range
).ancestors().find_map(N
::cast
)
46 /// Skip to next non `trivia` token
47 pub fn skip_trivia_token(mut token
: SyntaxToken
, direction
: Direction
) -> Option
<SyntaxToken
> {
48 while token
.kind().is_trivia() {
49 token
= match direction
{
50 Direction
::Next
=> token
.next_token()?
,
51 Direction
::Prev
=> token
.prev_token()?
,
56 /// Skip to next non `whitespace` token
57 pub fn skip_whitespace_token(mut token
: SyntaxToken
, direction
: Direction
) -> Option
<SyntaxToken
> {
58 while token
.kind() == SyntaxKind
::WHITESPACE
{
59 token
= match direction
{
60 Direction
::Next
=> token
.next_token()?
,
61 Direction
::Prev
=> token
.prev_token()?
,
67 /// Finds the first sibling in the given direction which is not `trivia`
68 pub fn non_trivia_sibling(element
: SyntaxElement
, direction
: Direction
) -> Option
<SyntaxElement
> {
69 return match element
{
70 NodeOrToken
::Node(node
) => node
.siblings_with_tokens(direction
).skip(1).find(not_trivia
),
71 NodeOrToken
::Token(token
) => token
.siblings_with_tokens(direction
).skip(1).find(not_trivia
),
74 fn not_trivia(element
: &SyntaxElement
) -> bool
{
76 NodeOrToken
::Node(_
) => true,
77 NodeOrToken
::Token(token
) => !token
.kind().is_trivia(),
82 pub fn least_common_ancestor(u
: &SyntaxNode
, v
: &SyntaxNode
) -> Option
<SyntaxNode
> {
84 return Some(u
.clone());
87 let u_depth
= u
.ancestors().count();
88 let v_depth
= v
.ancestors().count();
89 let keep
= u_depth
.min(v_depth
);
91 let u_candidates
= u
.ancestors().skip(u_depth
- keep
);
92 let v_candidates
= v
.ancestors().skip(v_depth
- keep
);
93 let (res
, _
) = u_candidates
.zip(v_candidates
).find(|(x
, y
)| x
== y
)?
;
97 pub fn neighbor
<T
: AstNode
>(me
: &T
, direction
: Direction
) -> Option
<T
> {
98 me
.syntax().siblings(direction
).skip(1).find_map(T
::cast
)
101 pub fn has_errors(node
: &SyntaxNode
) -> bool
{
102 node
.children().any(|it
| it
.kind() == SyntaxKind
::ERROR
)
105 type FxIndexMap
<K
, V
> = IndexMap
<K
, V
, BuildHasherDefault
<rustc_hash
::FxHasher
>>;
107 #[derive(Debug, Hash, PartialEq, Eq)]
108 enum TreeDiffInsertPos
{
109 After(SyntaxElement
),
110 AsFirstChild(SyntaxElement
),
114 pub struct TreeDiff
{
115 replacements
: FxHashMap
<SyntaxElement
, SyntaxElement
>,
116 deletions
: Vec
<SyntaxElement
>,
117 // the vec as well as the indexmap are both here to preserve order
118 insertions
: FxIndexMap
<TreeDiffInsertPos
, Vec
<SyntaxElement
>>,
122 pub fn into_text_edit(&self, builder
: &mut TextEditBuilder
) {
123 let _p
= profile
::span("into_text_edit");
125 for (anchor
, to
) in &self.insertions
{
126 let offset
= match anchor
{
127 TreeDiffInsertPos
::After(it
) => it
.text_range().end(),
128 TreeDiffInsertPos
::AsFirstChild(it
) => it
.text_range().start(),
130 to
.iter().for_each(|to
| builder
.insert(offset
, to
.to_string()));
132 for (from
, to
) in &self.replacements
{
133 builder
.replace(from
.text_range(), to
.to_string());
135 for text_range
in self.deletions
.iter().map(SyntaxElement
::text_range
) {
136 builder
.delete(text_range
);
140 pub fn is_empty(&self) -> bool
{
141 self.replacements
.is_empty() && self.deletions
.is_empty() && self.insertions
.is_empty()
145 /// Finds a (potentially minimal) diff, which, applied to `from`, will result in `to`.
147 /// Specifically, returns a structure that consists of a replacements, insertions and deletions
148 /// such that applying this map on `from` will result in `to`.
150 /// This function tries to find a fine-grained diff.
151 pub fn diff(from
: &SyntaxNode
, to
: &SyntaxNode
) -> TreeDiff
{
152 let _p
= profile
::span("diff");
154 let mut diff
= TreeDiff
{
155 replacements
: FxHashMap
::default(),
156 insertions
: FxIndexMap
::default(),
157 deletions
: Vec
::new(),
159 let (from
, to
) = (from
.clone().into(), to
.clone().into());
161 if !syntax_element_eq(&from
, &to
) {
162 go(&mut diff
, from
, to
);
166 fn syntax_element_eq(lhs
: &SyntaxElement
, rhs
: &SyntaxElement
) -> bool
{
167 lhs
.kind() == rhs
.kind()
168 && lhs
.text_range().len() == rhs
.text_range().len()
169 && match (&lhs
, &rhs
) {
170 (NodeOrToken
::Node(lhs
), NodeOrToken
::Node(rhs
)) => {
171 lhs
== rhs
|| lhs
.text() == rhs
.text()
173 (NodeOrToken
::Token(lhs
), NodeOrToken
::Token(rhs
)) => lhs
.text() == rhs
.text(),
178 // FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly.
179 fn go(diff
: &mut TreeDiff
, lhs
: SyntaxElement
, rhs
: SyntaxElement
) {
180 let (lhs
, rhs
) = match lhs
.as_node().zip(rhs
.as_node()) {
181 Some((lhs
, rhs
)) => (lhs
, rhs
),
183 cov_mark
::hit
!(diff_node_token_replace
);
184 diff
.replacements
.insert(lhs
, rhs
);
189 let mut look_ahead_scratch
= Vec
::default();
191 let mut rhs_children
= rhs
.children_with_tokens();
192 let mut lhs_children
= lhs
.children_with_tokens();
193 let mut last_lhs
= None
;
195 let lhs_child
= lhs_children
.next();
196 match (lhs_child
.clone(), rhs_children
.next()) {
197 (None
, None
) => break,
198 (None
, Some(element
)) => {
199 let insert_pos
= match last_lhs
.clone() {
201 cov_mark
::hit
!(diff_insert
);
202 TreeDiffInsertPos
::After(prev
)
204 // first iteration, insert into out parent as the first child
206 cov_mark
::hit
!(diff_insert_as_first_child
);
207 TreeDiffInsertPos
::AsFirstChild(lhs
.clone().into())
210 diff
.insertions
.entry(insert_pos
).or_insert_with(Vec
::new
).push(element
);
212 (Some(element
), None
) => {
213 cov_mark
::hit
!(diff_delete
);
214 diff
.deletions
.push(element
);
216 (Some(ref lhs_ele
), Some(ref rhs_ele
)) if syntax_element_eq(lhs_ele
, rhs_ele
) => {}
217 (Some(lhs_ele
), Some(rhs_ele
)) => {
218 // nodes differ, look for lhs_ele in rhs, if its found we can mark everything up
219 // until that element as insertions. This is important to keep the diff minimal
220 // in regards to insertions that have been actually done, this is important for
221 // use insertions as we do not want to replace the entire module node.
222 look_ahead_scratch
.push(rhs_ele
.clone());
223 let mut rhs_children_clone
= rhs_children
.clone();
224 let mut insert
= false;
225 for rhs_child
in &mut rhs_children_clone
{
226 if syntax_element_eq(&lhs_ele
, &rhs_child
) {
227 cov_mark
::hit
!(diff_insertions
);
231 look_ahead_scratch
.push(rhs_child
);
233 let drain
= look_ahead_scratch
.drain(..);
235 let insert_pos
= if let Some(prev
) = last_lhs
.clone().filter(|_
| insert
) {
236 TreeDiffInsertPos
::After(prev
)
238 cov_mark
::hit
!(insert_first_child
);
239 TreeDiffInsertPos
::AsFirstChild(lhs
.clone().into())
242 diff
.insertions
.entry(insert_pos
).or_insert_with(Vec
::new
).extend(drain
);
243 rhs_children
= rhs_children_clone
;
245 go(diff
, lhs_ele
, rhs_ele
);
249 last_lhs
= lhs_child
.or(last_lhs
);
256 use expect_test
::{expect, Expect}
;
257 use itertools
::Itertools
;
258 use parser
::SyntaxKind
;
259 use text_edit
::TextEdit
;
261 use crate::{AstNode, SyntaxElement}
;
264 fn replace_node_token() {
265 cov_mark
::check
!(diff_node_token_replace
);
276 Line 0: Token(USE_KW@0..3 "use") -> ident
288 fn replace_parent() {
289 cov_mark
::check
!(diff_insert_as_first_child
);
296 Line 0: AsFirstChild(Node(SOURCE_FILE@0..0))
312 cov_mark
::check
!(diff_insert
);
324 Line 2: After(Node(USE@10..18))
352 Line 2: After(Token(WHITESPACE@9..10 "\n"))
380 Line 0: After(Token(WHITESPACE@0..1 "\n"))
396 fn first_child_insertion() {
397 cov_mark
::check
!(insert_first_child
);
410 Line 0: AsFirstChild(Node(SOURCE_FILE@0..30))
427 cov_mark
::check
!(diff_delete
);
451 cov_mark
::check
!(diff_insertions
);
454 use expect_test::{expect, Expect};
455 use text_edit::TextEdit;
460 use expect_test::{expect, Expect};
467 Line 1: After(Node(USE@1..35))
469 -> use crate::AstNode;
477 Line 2: use text_edit::TextEdit;
479 Line 4: use crate::AstNode;
489 use text_edit::TextEdit;
503 Line 2: Token(IDENT@5..14 "text_edit") -> crate
504 Line 2: Token(IDENT@16..24 "TextEdit") -> AstNode
505 Line 2: Token(WHITESPACE@25..27 "\n\n") -> "\n"
509 Line 3: use crate::AstNode;
521 hash::BuildHasherDefault,
522 ops::{self, RangeInclusive},
527 use std::hash::BuildHasherDefault;
528 use std::ops::{self, RangeInclusive};
533 Line 2: After(Node(PATH_SEGMENT@5..8))
536 Line 6: After(Token(WHITESPACE@86..87 "\n"))
537 -> use std::hash::BuildHasherDefault;
539 -> use std::ops::{self, RangeInclusive};
544 Line 2: Token(IDENT@5..8 "std") -> std
551 hash::BuildHasherDefault,
552 ops::{self, RangeInclusive},
559 fn early_return_assist() {
563 if let Ok(x) = Err(92) {
570 let x = match Err(92) {
580 Line 3: After(Node(BLOCK_EXPR@40..63))
587 Line 3: After(Node(IF_EXPR@17..63))
593 Line 3: Token(IF_KW@17..19 "if") -> let
594 Line 3: Token(LET_KW@20..23 "let") -> x
595 Line 3: Node(BLOCK_EXPR@40..63) -> =
609 fn check_diff(from
: &str, to
: &str, expected_diff
: Expect
) {
610 let from_node
= crate::SourceFile
::parse(from
).tree().syntax().clone();
611 let to_node
= crate::SourceFile
::parse(to
).tree().syntax().clone();
612 let diff
= super::diff(&from_node
, &to_node
);
615 |syn
: &SyntaxElement
| from
[..syn
.text_range().start().into()].lines().count();
617 let fmt_syntax
= |syn
: &SyntaxElement
| match syn
.kind() {
618 SyntaxKind
::WHITESPACE
=> format
!("{:?}", syn
.to_string()),
619 _
=> format
!("{}", syn
),
623 diff
.insertions
.iter().format_with("\n", |(k
, v
), f
| -> Result
<(), std
::fmt
::Error
> {
625 "Line {}: {:?}\n-> {}",
626 line_number(match k
{
627 super::TreeDiffInsertPos
::After(syn
) => syn
,
628 super::TreeDiffInsertPos
::AsFirstChild(syn
) => syn
,
631 v
.iter().format_with("\n-> ", |v
, f
| f(&fmt_syntax(v
)))
635 let replacements
= diff
638 .sorted_by_key(|(syntax
, _
)| syntax
.text_range().start())
639 .format_with("\n", |(k
, v
), f
| {
640 f(&format
!("Line {}: {:?} -> {}", line_number(k
), k
, fmt_syntax(v
)))
646 .format_with("\n", |v
, f
| f(&format
!("Line {}: {}", line_number(v
), &fmt_syntax(v
))));
648 let actual
= format
!(
649 "insertions:\n\n{}\n\nreplacements:\n\n{}\n\ndeletions:\n\n{}\n",
650 insertions
, replacements
, deletions
652 expected_diff
.assert_eq(&actual
);
654 let mut from
= from
.to_owned();
655 let mut text_edit
= TextEdit
::builder();
656 diff
.into_text_edit(&mut text_edit
);
657 text_edit
.finish().apply(&mut from
);
658 assert_eq
!(&*from
, to
, "diff did not turn `from` to `to`");