aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/patterns.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r--crates/ide_completion/src/patterns.rs151
1 files changed, 87 insertions, 64 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index caf0ef39f..26516046b 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -1,15 +1,18 @@
1//! Patterns telling us certain facts about current syntax element, they are used in completion context 1//! Patterns telling us certain facts about current syntax element, they are used in completion context
2 2
3use hir::Semantics;
4use ide_db::RootDatabase;
3use syntax::{ 5use syntax::{
4 algo::non_trivia_sibling, 6 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner}, 7 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 8 match_ast, AstNode, Direction, SyntaxElement,
7 SyntaxKind::*, 9 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, T, 10 SyntaxNode, SyntaxToken, TextSize, T,
9}; 11};
10 12
11#[cfg(test)] 13#[cfg(test)]
12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 14use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
15
13/// Direct parent container of the cursor position 16/// Direct parent container of the cursor position
14#[derive(Copy, Clone, Debug, PartialEq, Eq)] 17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub(crate) enum ImmediatePrevSibling { 18pub(crate) enum ImmediatePrevSibling {
@@ -19,7 +22,7 @@ pub(crate) enum ImmediatePrevSibling {
19} 22}
20 23
21/// Direct parent container of the cursor position 24/// Direct parent container of the cursor position
22#[derive(Copy, Clone, Debug, PartialEq, Eq)] 25#[derive(Clone, Debug, PartialEq, Eq)]
23pub(crate) enum ImmediateLocation { 26pub(crate) enum ImmediateLocation {
24 Use, 27 Use,
25 Impl, 28 Impl,
@@ -29,10 +32,24 @@ pub(crate) enum ImmediateLocation {
29 IdentPat, 32 IdentPat,
30 BlockExpr, 33 BlockExpr,
31 ItemList, 34 ItemList,
35 // Fake file ast node
36 Attribute(ast::Attr),
37 // Fake file ast node
38 ModDeclaration(ast::Module),
39 // Original file ast node
40 /// The record expr of the field name we are completing
41 RecordExpr(ast::RecordExpr),
42 // Original file ast node
43 /// The record pat of the field name we are completing
44 RecordPat(ast::RecordPat),
32} 45}
33 46
34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { 47pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
35 let node = maximize_name_ref(name_like)?; 48 let node = match name_like {
49 ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
50 ast::NameLike::Name(n) => n.syntax().clone(),
51 ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
52 };
36 let node = match node.parent().and_then(ast::MacroCall::cast) { 53 let node = match node.parent().and_then(ast::MacroCall::cast) {
37 // When a path is being typed after the name of a trait/type of an impl it is being 54 // When a path is being typed after the name of a trait/type of an impl it is being
38 // parsed as a macro, so when the trait/impl has a block following it an we are between the 55 // parsed as a macro, so when the trait/impl has a block following it an we are between the
@@ -77,8 +94,37 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi
77 Some(res) 94 Some(res)
78} 95}
79 96
80pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { 97pub(crate) fn determine_location(
81 let node = maximize_name_ref(name_like)?; 98 sema: &Semantics<RootDatabase>,
99 original_file: &SyntaxNode,
100 offset: TextSize,
101 name_like: &ast::NameLike,
102) -> Option<ImmediateLocation> {
103 let node = match name_like {
104 ast::NameLike::NameRef(name_ref) => {
105 if ast::RecordExprField::for_field_name(&name_ref).is_some() {
106 return sema
107 .find_node_at_offset_with_macros(original_file, offset)
108 .map(ImmediateLocation::RecordExpr);
109 }
110 if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() {
111 return sema
112 .find_node_at_offset_with_macros(original_file, offset)
113 .map(ImmediateLocation::RecordPat);
114 }
115 maximize_name_ref(name_ref)
116 }
117 ast::NameLike::Name(name) => {
118 if ast::RecordPatField::for_field_name(&name).is_some() {
119 return sema
120 .find_node_at_offset_with_macros(original_file, offset)
121 .map(ImmediateLocation::RecordPat);
122 }
123 name.syntax().clone()
124 }
125 ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
126 };
127
82 let parent = match node.parent() { 128 let parent = match node.parent() {
83 Some(parent) => match ast::MacroCall::cast(parent.clone()) { 129 Some(parent) => match ast::MacroCall::cast(parent.clone()) {
84 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. 130 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
@@ -103,6 +149,7 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL
103 } 149 }
104 } 150 }
105 }; 151 };
152
106 let res = match_ast! { 153 let res = match_ast! {
107 match parent { 154 match parent {
108 ast::IdentPat(_it) => ImmediateLocation::IdentPat, 155 ast::IdentPat(_it) => ImmediateLocation::IdentPat,
@@ -117,36 +164,34 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL
117 Some(TRAIT) => ImmediateLocation::Trait, 164 Some(TRAIT) => ImmediateLocation::Trait,
118 _ => return None, 165 _ => return None,
119 }, 166 },
167 ast::Module(it) => if it.item_list().is_none() {
168 ImmediateLocation::ModDeclaration(it)
169 } else {
170 return None
171 },
172 ast::Attr(it) => ImmediateLocation::Attribute(it),
120 _ => return None, 173 _ => return None,
121 } 174 }
122 }; 175 };
123 Some(res) 176 Some(res)
124} 177}
125 178
126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { 179fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode {
127 // First walk the element we are completing up to its highest node that has the same text range 180 // Maximize a nameref to its enclosing path if its the last segment of said path
128 // as the element so that we can check in what context it immediately lies. We only do this for 181 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
129 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. 182 let p = segment.parent_path();
130 // We only wanna do this if the NameRef is the last segment of the path. 183 if p.parent_path().is_none() {
131 let node = match name_like { 184 if let Some(it) = p
132 ast::NameLike::NameRef(name_ref) => { 185 .syntax()
133 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { 186 .ancestors()
134 let p = segment.parent_path(); 187 .take_while(|it| it.text_range() == p.syntax().text_range())
135 if p.parent_path().is_none() { 188 .last()
136 p.syntax() 189 {
137 .ancestors() 190 return it;
138 .take_while(|it| it.text_range() == p.syntax().text_range())
139 .last()?
140 } else {
141 return None;
142 }
143 } else {
144 return None;
145 } 191 }
146 } 192 }
147 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), 193 }
148 }; 194 name_ref.syntax().clone()
149 Some(node)
150} 195}
151 196
152pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 197pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
@@ -167,18 +212,6 @@ fn test_inside_impl_trait_block() {
167 check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); 212 check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
168} 213}
169 214
170pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
171 not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
172 && previous_sibling_or_ancestor_sibling(element)
173 .and_then(|it| it.into_token())
174 .filter(|it| it.kind() == FAT_ARROW)
175 .is_some()
176}
177#[test]
178fn test_is_match_arm() {
179 check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm);
180}
181
182pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { 215pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
183 element.into_token().and_then(|it| previous_non_trivia_token(it)) 216 element.into_token().and_then(|it| previous_non_trivia_token(it))
184} 217}
@@ -216,10 +249,6 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
216 .is_some() 249 .is_some()
217} 250}
218 251
219pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
220 element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next()
221}
222
223fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 252fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
224 let mut token = token.prev_token(); 253 let mut token = token.prev_token();
225 while let Some(inner) = token.clone() { 254 while let Some(inner) = token.clone() {
@@ -232,31 +261,25 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
232 None 261 None
233} 262}
234 263
235fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> {
236 let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev);
237 if let Some(sibling) = token_sibling {
238 Some(sibling)
239 } else {
240 // if not trying to find first ancestor which has such a sibling
241 let range = element.text_range();
242 let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?;
243 let prev_sibling_node = top_node.ancestors().find(|it| {
244 non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some()
245 })?;
246 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
247 }
248}
249
250#[cfg(test)] 264#[cfg(test)]
251mod tests { 265mod tests {
266 use syntax::algo::find_node_at_offset;
267
268 use crate::test_utils::position;
269
252 use super::*; 270 use super::*;
253 271
254 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) { 272 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
255 check_pattern_is_applicable(code, |e| { 273 let (db, pos) = position(code);
256 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); 274
257 assert_eq!(determine_location(name), loc.into()); 275 let sema = Semantics::new(&db);
258 true 276 let original_file = sema.parse(pos.file_id);
259 }); 277
278 let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap();
279 assert_eq!(
280 determine_location(&sema, original_file.syntax(), pos.offset, &name_like),
281 loc.into()
282 );
260 } 283 }
261 284
262 fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { 285 fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {