diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-21 22:04:32 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-21 22:04:32 +0000 |
commit | 184665ff9b7b64730ecf481c1914a74e7191a6dd (patch) | |
tree | 4f5e97a0832821a4b06b784591d6cb3a417f1198 /crates/ra_analysis/src/completion/completion_context.rs | |
parent | 2351308d92f4c785d98cc24026a818d28945513e (diff) | |
parent | 2ae87ffc9afe67945e2ad655c3577b589ff640ab (diff) |
Merge #315
315: Split completion into manageable components r=matklad a=matklad
The main idea here is to do completion in two phases:
* first, we figure out surrounding context
* then, we run a series of completers on the given context.
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_analysis/src/completion/completion_context.rs')
-rw-r--r-- | crates/ra_analysis/src/completion/completion_context.rs | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/crates/ra_analysis/src/completion/completion_context.rs b/crates/ra_analysis/src/completion/completion_context.rs new file mode 100644 index 000000000..064fbc6f7 --- /dev/null +++ b/crates/ra_analysis/src/completion/completion_context.rs | |||
@@ -0,0 +1,156 @@ | |||
1 | use ra_editor::find_node_at_offset; | ||
2 | use ra_text_edit::AtomTextEdit; | ||
3 | use ra_syntax::{ | ||
4 | algo::find_leaf_at_offset, | ||
5 | ast, | ||
6 | AstNode, | ||
7 | SyntaxNodeRef, | ||
8 | SourceFileNode, | ||
9 | TextUnit, | ||
10 | SyntaxKind::*, | ||
11 | }; | ||
12 | use hir::source_binder; | ||
13 | |||
14 | use crate::{db, FilePosition, Cancelable}; | ||
15 | |||
16 | /// `CompletionContext` is created early during completion to figure out, where | ||
17 | /// exactly is the cursor, syntax-wise. | ||
18 | #[derive(Debug)] | ||
19 | pub(super) struct CompletionContext<'a> { | ||
20 | pub(super) db: &'a db::RootDatabase, | ||
21 | pub(super) offset: TextUnit, | ||
22 | pub(super) leaf: SyntaxNodeRef<'a>, | ||
23 | pub(super) module: Option<hir::Module>, | ||
24 | pub(super) enclosing_fn: Option<ast::FnDef<'a>>, | ||
25 | pub(super) is_param: bool, | ||
26 | /// A single-indent path, like `foo`. | ||
27 | pub(super) is_trivial_path: bool, | ||
28 | /// If not a trivial, path, the prefix (qualifier). | ||
29 | pub(super) path_prefix: Option<hir::Path>, | ||
30 | pub(super) after_if: bool, | ||
31 | pub(super) is_stmt: bool, | ||
32 | /// Something is typed at the "top" level, in module or impl/trait. | ||
33 | pub(super) is_new_item: bool, | ||
34 | } | ||
35 | |||
36 | impl<'a> CompletionContext<'a> { | ||
37 | pub(super) fn new( | ||
38 | db: &'a db::RootDatabase, | ||
39 | original_file: &'a SourceFileNode, | ||
40 | position: FilePosition, | ||
41 | ) -> Cancelable<Option<CompletionContext<'a>>> { | ||
42 | let module = source_binder::module_from_position(db, position)?; | ||
43 | let leaf = | ||
44 | ctry!(find_leaf_at_offset(original_file.syntax(), position.offset).left_biased()); | ||
45 | let mut ctx = CompletionContext { | ||
46 | db, | ||
47 | leaf, | ||
48 | offset: position.offset, | ||
49 | module, | ||
50 | enclosing_fn: None, | ||
51 | is_param: false, | ||
52 | is_trivial_path: false, | ||
53 | path_prefix: None, | ||
54 | after_if: false, | ||
55 | is_stmt: false, | ||
56 | is_new_item: false, | ||
57 | }; | ||
58 | ctx.fill(original_file, position.offset); | ||
59 | Ok(Some(ctx)) | ||
60 | } | ||
61 | |||
62 | fn fill(&mut self, original_file: &SourceFileNode, offset: TextUnit) { | ||
63 | // Insert a fake ident to get a valid parse tree. We will use this file | ||
64 | // to determine context, though the original_file will be used for | ||
65 | // actual completion. | ||
66 | let file = { | ||
67 | let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); | ||
68 | original_file.reparse(&edit) | ||
69 | }; | ||
70 | |||
71 | // First, let's try to complete a reference to some declaration. | ||
72 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { | ||
73 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
74 | // See RFC#1685. | ||
75 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
76 | self.is_param = true; | ||
77 | return; | ||
78 | } | ||
79 | self.classify_name_ref(&file, name_ref); | ||
80 | } | ||
81 | |||
82 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
83 | // suggest declaration names, see `CompletionKind::Magic`. | ||
84 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | ||
85 | if is_node::<ast::Param>(name.syntax()) { | ||
86 | self.is_param = true; | ||
87 | return; | ||
88 | } | ||
89 | } | ||
90 | } | ||
91 | fn classify_name_ref(&mut self, file: &SourceFileNode, name_ref: ast::NameRef) { | ||
92 | let name_range = name_ref.syntax().range(); | ||
93 | let top_node = name_ref | ||
94 | .syntax() | ||
95 | .ancestors() | ||
96 | .take_while(|it| it.range() == name_range) | ||
97 | .last() | ||
98 | .unwrap(); | ||
99 | |||
100 | match top_node.parent().map(|it| it.kind()) { | ||
101 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | ||
102 | self.is_new_item = true; | ||
103 | return; | ||
104 | } | ||
105 | _ => (), | ||
106 | } | ||
107 | |||
108 | let parent = match name_ref.syntax().parent() { | ||
109 | Some(it) => it, | ||
110 | None => return, | ||
111 | }; | ||
112 | if let Some(segment) = ast::PathSegment::cast(parent) { | ||
113 | let path = segment.parent_path(); | ||
114 | if let Some(mut path) = hir::Path::from_ast(path) { | ||
115 | if !path.is_ident() { | ||
116 | path.segments.pop().unwrap(); | ||
117 | self.path_prefix = Some(path); | ||
118 | return; | ||
119 | } | ||
120 | } | ||
121 | if path.qualifier().is_none() { | ||
122 | self.is_trivial_path = true; | ||
123 | self.enclosing_fn = self | ||
124 | .leaf | ||
125 | .ancestors() | ||
126 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | ||
127 | .find_map(ast::FnDef::cast); | ||
128 | |||
129 | self.is_stmt = match name_ref | ||
130 | .syntax() | ||
131 | .ancestors() | ||
132 | .filter_map(ast::ExprStmt::cast) | ||
133 | .next() | ||
134 | { | ||
135 | None => false, | ||
136 | Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), | ||
137 | }; | ||
138 | |||
139 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | ||
140 | if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { | ||
141 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | ||
142 | self.after_if = true; | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | ||
152 | match node.ancestors().filter_map(N::cast).next() { | ||
153 | None => false, | ||
154 | Some(n) => n.syntax().range() == node.range(), | ||
155 | } | ||
156 | } | ||