diff options
Diffstat (limited to 'crates/ra_ide_api/src/completion')
-rw-r--r-- | crates/ra_ide_api/src/completion/complete_scope.rs | 120 | ||||
-rw-r--r-- | crates/ra_ide_api/src/completion/completion_context.rs | 4 |
2 files changed, 118 insertions, 6 deletions
diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs index fd256fc3b..2473e58b4 100644 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ b/crates/ra_ide_api/src/completion/complete_scope.rs | |||
@@ -1,12 +1,122 @@ | |||
1 | use crate::completion::{Completions, CompletionContext}; | 1 | use rustc_hash::FxHashMap; |
2 | use ra_text_edit::TextEditBuilder; | ||
3 | use ra_syntax::{SmolStr, ast, AstNode}; | ||
4 | use ra_assists::auto_import; | ||
5 | |||
6 | use crate::completion::{CompletionItem, Completions, CompletionKind, CompletionContext}; | ||
2 | 7 | ||
3 | pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { | 8 | pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { |
4 | if !ctx.is_trivial_path { | 9 | if ctx.is_trivial_path { |
5 | return; | 10 | let names = ctx.analyzer.all_names(ctx.db); |
11 | names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); | ||
12 | |||
13 | // auto-import | ||
14 | // We fetch ident from the original file, because we need to pre-filter auto-imports | ||
15 | if ast::NameRef::cast(ctx.token.parent()).is_some() { | ||
16 | let import_resolver = ImportResolver::new(); | ||
17 | let import_names = import_resolver.all_names(ctx.token.text()); | ||
18 | import_names.into_iter().for_each(|(name, path)| { | ||
19 | let edit = { | ||
20 | let mut builder = TextEditBuilder::default(); | ||
21 | builder.replace(ctx.source_range(), name.to_string()); | ||
22 | auto_import::auto_import_text_edit( | ||
23 | ctx.token.parent(), | ||
24 | ctx.token.parent(), | ||
25 | &path, | ||
26 | &mut builder, | ||
27 | ); | ||
28 | builder.finish() | ||
29 | }; | ||
30 | |||
31 | // Hack: copied this check form conv.rs beacause auto import can produce edits | ||
32 | // that invalidate assert in conv_with. | ||
33 | if edit | ||
34 | .as_atoms() | ||
35 | .iter() | ||
36 | .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) | ||
37 | .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) | ||
38 | { | ||
39 | CompletionItem::new( | ||
40 | CompletionKind::Reference, | ||
41 | ctx.source_range(), | ||
42 | build_import_label(&name, &path), | ||
43 | ) | ||
44 | .text_edit(edit) | ||
45 | .add_to(acc); | ||
46 | } | ||
47 | }); | ||
48 | } | ||
6 | } | 49 | } |
7 | let names = ctx.analyzer.all_names(ctx.db); | 50 | } |
8 | 51 | ||
9 | names.into_iter().for_each(|(name, res)| acc.add_resolution(ctx, name.to_string(), &res)); | 52 | fn build_import_label(name: &str, path: &Vec<SmolStr>) -> String { |
53 | let mut buf = String::with_capacity(64); | ||
54 | buf.push_str(name); | ||
55 | buf.push_str(" ("); | ||
56 | fmt_import_path(path, &mut buf); | ||
57 | buf.push_str(")"); | ||
58 | buf | ||
59 | } | ||
60 | |||
61 | fn fmt_import_path(path: &Vec<SmolStr>, buf: &mut String) { | ||
62 | let mut segments = path.iter(); | ||
63 | if let Some(s) = segments.next() { | ||
64 | buf.push_str(&s); | ||
65 | } | ||
66 | for s in segments { | ||
67 | buf.push_str("::"); | ||
68 | buf.push_str(&s); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | #[derive(Debug, Clone, Default)] | ||
73 | pub(crate) struct ImportResolver { | ||
74 | // todo: use fst crate or something like that | ||
75 | dummy_names: Vec<(SmolStr, Vec<SmolStr>)>, | ||
76 | } | ||
77 | |||
78 | impl ImportResolver { | ||
79 | pub(crate) fn new() -> Self { | ||
80 | let dummy_names = vec![ | ||
81 | (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]), | ||
82 | (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]), | ||
83 | (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]), | ||
84 | (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]), | ||
85 | ( | ||
86 | SmolStr::new("Debug"), | ||
87 | vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")], | ||
88 | ), | ||
89 | ( | ||
90 | SmolStr::new("Display"), | ||
91 | vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")], | ||
92 | ), | ||
93 | ( | ||
94 | SmolStr::new("Hash"), | ||
95 | vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")], | ||
96 | ), | ||
97 | ( | ||
98 | SmolStr::new("Hasher"), | ||
99 | vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")], | ||
100 | ), | ||
101 | ( | ||
102 | SmolStr::new("Iterator"), | ||
103 | vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")], | ||
104 | ), | ||
105 | ]; | ||
106 | |||
107 | ImportResolver { dummy_names } | ||
108 | } | ||
109 | |||
110 | // Returns a map of importable items filtered by name. | ||
111 | // The map associates item name with its full path. | ||
112 | // todo: should return Resolutions | ||
113 | pub(crate) fn all_names(&self, name: &str) -> FxHashMap<SmolStr, Vec<SmolStr>> { | ||
114 | if name.len() > 1 { | ||
115 | self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect() | ||
116 | } else { | ||
117 | FxHashMap::default() | ||
118 | } | ||
119 | } | ||
10 | } | 120 | } |
11 | 121 | ||
12 | #[cfg(test)] | 122 | #[cfg(test)] |
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs index 359f2cffa..a8c8cc7b0 100644 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ b/crates/ra_ide_api/src/completion/completion_context.rs | |||
@@ -27,7 +27,7 @@ pub(crate) struct CompletionContext<'a> { | |||
27 | pub(super) is_pat_binding: bool, | 27 | pub(super) is_pat_binding: bool, |
28 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | 28 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. |
29 | pub(super) is_trivial_path: bool, | 29 | pub(super) is_trivial_path: bool, |
30 | /// If not a trivial, path, the prefix (qualifier). | 30 | /// If not a trivial path, the prefix (qualifier). |
31 | pub(super) path_prefix: Option<hir::Path>, | 31 | pub(super) path_prefix: Option<hir::Path>, |
32 | pub(super) after_if: bool, | 32 | pub(super) after_if: bool, |
33 | /// `true` if we are a statement or a last expr in the block. | 33 | /// `true` if we are a statement or a last expr in the block. |
@@ -151,6 +151,7 @@ impl<'a> CompletionContext<'a> { | |||
151 | Some(it) => it, | 151 | Some(it) => it, |
152 | None => return, | 152 | None => return, |
153 | }; | 153 | }; |
154 | |||
154 | if let Some(segment) = ast::PathSegment::cast(parent) { | 155 | if let Some(segment) = ast::PathSegment::cast(parent) { |
155 | let path = segment.parent_path(); | 156 | let path = segment.parent_path(); |
156 | self.is_call = path | 157 | self.is_call = path |
@@ -167,6 +168,7 @@ impl<'a> CompletionContext<'a> { | |||
167 | return; | 168 | return; |
168 | } | 169 | } |
169 | } | 170 | } |
171 | |||
170 | if path.qualifier().is_none() { | 172 | if path.qualifier().is_none() { |
171 | self.is_trivial_path = true; | 173 | self.is_trivial_path = true; |
172 | 174 | ||