diff options
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 151 |
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 | ||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
3 | use syntax::{ | 5 | use 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)] |
12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 14 | use 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)] |
15 | pub(crate) enum ImmediatePrevSibling { | 18 | pub(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)] |
23 | pub(crate) enum ImmediateLocation { | 26 | pub(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 | ||
34 | pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { | 47 | pub(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 | ||
80 | pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { | 97 | pub(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 | ||
126 | fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { | 179 | fn 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 | ||
152 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 197 | pub(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 | ||
170 | pub(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] | ||
178 | fn test_is_match_arm() { | ||
179 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm); | ||
180 | } | ||
181 | |||
182 | pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { | 215 | pub(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 | ||
219 | pub(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 | |||
223 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | 252 | fn 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 | ||
235 | fn 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)] |
251 | mod tests { | 265 | mod 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>>) { |