aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_syntax/src/algo.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_syntax/src/algo.rs')
-rw-r--r--crates/ra_syntax/src/algo.rs130
1 files changed, 111 insertions, 19 deletions
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index f47e11e66..45f624810 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -1,18 +1,12 @@
1pub mod visit; 1pub mod visit;
2 2
3use itertools::Itertools; 3use std::ops::RangeInclusive;
4
5use crate::{AstNode, Direction, SyntaxElement, SyntaxNode, SyntaxToken, TextRange, TextUnit};
6 4
7pub use rowan::TokenAtOffset; 5use itertools::Itertools;
8 6
9pub fn find_token_at_offset(node: &SyntaxNode, offset: TextUnit) -> TokenAtOffset<SyntaxToken> { 7use crate::{
10 match node.0.token_at_offset(offset) { 8 AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxNodePtr, TextRange, TextUnit,
11 TokenAtOffset::None => TokenAtOffset::None, 9};
12 TokenAtOffset::Single(n) => TokenAtOffset::Single(SyntaxToken(n)),
13 TokenAtOffset::Between(l, r) => TokenAtOffset::Between(SyntaxToken(l), SyntaxToken(r)),
14 }
15}
16 10
17/// Returns ancestors of the node at the offset, sorted by length. This should 11/// Returns ancestors of the node at the offset, sorted by length. This should
18/// do the right thing at an edge, e.g. when searching for expressions at `{ 12/// do the right thing at an edge, e.g. when searching for expressions at `{
@@ -23,7 +17,7 @@ pub fn ancestors_at_offset(
23 node: &SyntaxNode, 17 node: &SyntaxNode,
24 offset: TextUnit, 18 offset: TextUnit,
25) -> impl Iterator<Item = SyntaxNode> { 19) -> impl Iterator<Item = SyntaxNode> {
26 find_token_at_offset(node, offset) 20 node.token_at_offset(offset)
27 .map(|token| token.parent().ancestors()) 21 .map(|token| token.parent().ancestors())
28 .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len()) 22 .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len())
29} 23}
@@ -44,20 +38,118 @@ pub fn find_node_at_offset<N: AstNode>(syntax: &SyntaxNode, offset: TextUnit) ->
44/// Finds the first sibling in the given direction which is not `trivia` 38/// Finds the first sibling in the given direction which is not `trivia`
45pub fn non_trivia_sibling(element: SyntaxElement, direction: Direction) -> Option<SyntaxElement> { 39pub fn non_trivia_sibling(element: SyntaxElement, direction: Direction) -> Option<SyntaxElement> {
46 return match element { 40 return match element {
47 SyntaxElement::Node(node) => node.siblings_with_tokens(direction).skip(1).find(not_trivia), 41 NodeOrToken::Node(node) => node.siblings_with_tokens(direction).skip(1).find(not_trivia),
48 SyntaxElement::Token(token) => { 42 NodeOrToken::Token(token) => token.siblings_with_tokens(direction).skip(1).find(not_trivia),
49 token.siblings_with_tokens(direction).skip(1).find(not_trivia)
50 }
51 }; 43 };
52 44
53 fn not_trivia(element: &SyntaxElement) -> bool { 45 fn not_trivia(element: &SyntaxElement) -> bool {
54 match element { 46 match element {
55 SyntaxElement::Node(_) => true, 47 NodeOrToken::Node(_) => true,
56 SyntaxElement::Token(token) => !token.kind().is_trivia(), 48 NodeOrToken::Token(token) => !token.kind().is_trivia(),
57 } 49 }
58 } 50 }
59} 51}
60 52
61pub fn find_covering_element(root: &SyntaxNode, range: TextRange) -> SyntaxElement { 53pub fn find_covering_element(root: &SyntaxNode, range: TextRange) -> SyntaxElement {
62 SyntaxElement::new(root.0.covering_node(range)) 54 root.covering_element(range)
55}
56
57#[derive(Debug, PartialEq, Eq, Clone, Copy)]
58pub enum InsertPosition<T> {
59 First,
60 Last,
61 Before(T),
62 After(T),
63}
64
65/// Adds specified children (tokens or nodes) to the current node at the
66/// specific position.
67///
68/// This is a type-unsafe low-level editing API, if you need to use it,
69/// prefer to create a type-safe abstraction on top of it instead.
70pub fn insert_children(
71 parent: &SyntaxNode,
72 position: InsertPosition<SyntaxElement>,
73 to_insert: impl Iterator<Item = SyntaxElement>,
74) -> SyntaxNode {
75 let mut delta = TextUnit::default();
76 let to_insert = to_insert.map(|element| {
77 delta += element.text_range().len();
78 to_green_element(element)
79 });
80
81 let old_children = parent.green().children();
82
83 let new_children = match &position {
84 InsertPosition::First => {
85 to_insert.chain(old_children.iter().cloned()).collect::<Box<[_]>>()
86 }
87 InsertPosition::Last => old_children.iter().cloned().chain(to_insert).collect::<Box<[_]>>(),
88 InsertPosition::Before(anchor) | InsertPosition::After(anchor) => {
89 let take_anchor = if let InsertPosition::After(_) = position { 1 } else { 0 };
90 let split_at = position_of_child(parent, anchor.clone()) + take_anchor;
91 let (before, after) = old_children.split_at(split_at);
92 before
93 .iter()
94 .cloned()
95 .chain(to_insert)
96 .chain(after.iter().cloned())
97 .collect::<Box<[_]>>()
98 }
99 };
100
101 with_children(parent, new_children)
102}
103
104/// Replaces all nodes in `to_delete` with nodes from `to_insert`
105///
106/// This is a type-unsafe low-level editing API, if you need to use it,
107/// prefer to create a type-safe abstraction on top of it instead.
108pub fn replace_children(
109 parent: &SyntaxNode,
110 to_delete: RangeInclusive<SyntaxElement>,
111 to_insert: impl Iterator<Item = SyntaxElement>,
112) -> SyntaxNode {
113 let start = position_of_child(parent, to_delete.start().clone());
114 let end = position_of_child(parent, to_delete.end().clone());
115 let old_children = parent.green().children();
116
117 let new_children = old_children[..start]
118 .iter()
119 .cloned()
120 .chain(to_insert.map(to_green_element))
121 .chain(old_children[end + 1..].iter().cloned())
122 .collect::<Box<[_]>>();
123 with_children(parent, new_children)
124}
125
126fn with_children(
127 parent: &SyntaxNode,
128 new_children: Box<[NodeOrToken<rowan::GreenNode, rowan::GreenToken>]>,
129) -> SyntaxNode {
130 let len = new_children.iter().map(|it| it.text_len()).sum::<TextUnit>();
131 let new_node =
132 rowan::GreenNode::new(rowan::cursor::SyntaxKind(parent.kind() as u16), new_children);
133 let new_root_node = parent.replace_with(new_node);
134 let new_root_node = SyntaxNode::new_root(new_root_node);
135
136 // FIXME: use a more elegant way to re-fetch the node (#1185), make
137 // `range` private afterwards
138 let mut ptr = SyntaxNodePtr::new(parent);
139 ptr.range = TextRange::offset_len(ptr.range().start(), len);
140 ptr.to_node(&new_root_node)
141}
142
143fn position_of_child(parent: &SyntaxNode, child: SyntaxElement) -> usize {
144 parent
145 .children_with_tokens()
146 .position(|it| it == child)
147 .expect("element is not a child of current element")
148}
149
150fn to_green_element(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, rowan::GreenToken> {
151 match element {
152 NodeOrToken::Node(it) => it.green().clone().into(),
153 NodeOrToken::Token(it) => it.green().clone().into(),
154 }
63} 155}