diff options
author | Kirill Bulatov <[email protected]> | 2019-12-24 00:19:09 +0000 |
---|---|---|
committer | Kirill Bulatov <[email protected]> | 2020-01-26 22:16:29 +0000 |
commit | 316795e074dff8f627f8c70c85d236420ecfb3a6 (patch) | |
tree | da6e266139563ef314d0563a01723ae2264609d2 /crates/ra_assists/src/assists | |
parent | d1330a4a65f0113c687716a5a679239af4df9c11 (diff) |
Initial auto import action implementation
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r-- | crates/ra_assists/src/assists/auto_import.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs new file mode 100644 index 000000000..fe226521e --- /dev/null +++ b/crates/ra_assists/src/assists/auto_import.rs | |||
@@ -0,0 +1,181 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode}, | ||
4 | SmolStr, SyntaxElement, | ||
5 | SyntaxKind::{NAME_REF, USE_ITEM}, | ||
6 | SyntaxNode, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_ctx::{ActionBuilder, Assist, AssistCtx}, | ||
11 | auto_import_text_edit, AssistId, ImportsLocator, | ||
12 | }; | ||
13 | |||
14 | // Assist: auto_import | ||
15 | // | ||
16 | // If the name is unresolved, provides all possible imports for it. | ||
17 | // | ||
18 | // ``` | ||
19 | // fn main() { | ||
20 | // let map = HashMap<|>::new(); | ||
21 | // } | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // use std::collections::HashMap; | ||
26 | // | ||
27 | // fn main() { | ||
28 | // let map = HashMap<|>::new(); | ||
29 | // } | ||
30 | // ``` | ||
31 | pub(crate) fn auto_import<'a, F: ImportsLocator<'a>>( | ||
32 | ctx: AssistCtx<impl HirDatabase>, | ||
33 | imports_locator: &mut F, | ||
34 | ) -> Option<Assist> { | ||
35 | let path: ast::Path = ctx.find_node_at_offset()?; | ||
36 | let module = path.syntax().ancestors().find_map(ast::Module::cast); | ||
37 | let position = match module.and_then(|it| it.item_list()) { | ||
38 | Some(item_list) => item_list.syntax().clone(), | ||
39 | None => { | ||
40 | let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?; | ||
41 | current_file.syntax().clone() | ||
42 | } | ||
43 | }; | ||
44 | |||
45 | let module_with_name_to_import = ctx.source_analyzer(&position, None).module()?; | ||
46 | let name_to_import = hir::InFile { | ||
47 | file_id: ctx.frange.file_id.into(), | ||
48 | value: &find_applicable_name_ref(ctx.covering_element())?, | ||
49 | }; | ||
50 | |||
51 | let proposed_imports = | ||
52 | imports_locator.find_imports(name_to_import, module_with_name_to_import)?; | ||
53 | if proposed_imports.is_empty() { | ||
54 | return None; | ||
55 | } | ||
56 | |||
57 | ctx.add_assist_group(AssistId("auto_import"), "auto import", || { | ||
58 | proposed_imports | ||
59 | .into_iter() | ||
60 | .map(|import| import_to_action(import.to_string(), &position, &path)) | ||
61 | .collect() | ||
62 | }) | ||
63 | } | ||
64 | |||
65 | fn find_applicable_name_ref(element: SyntaxElement) -> Option<ast::NameRef> { | ||
66 | if element.ancestors().find(|ancestor| ancestor.kind() == USE_ITEM).is_some() { | ||
67 | None | ||
68 | } else if element.kind() == NAME_REF { | ||
69 | Some(element.as_node().cloned().and_then(ast::NameRef::cast)?) | ||
70 | } else { | ||
71 | let parent = element.parent()?; | ||
72 | if parent.kind() == NAME_REF { | ||
73 | Some(ast::NameRef::cast(parent)?) | ||
74 | } else { | ||
75 | None | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | fn import_to_action(import: String, position: &SyntaxNode, path: &ast::Path) -> ActionBuilder { | ||
81 | let mut action_builder = ActionBuilder::default(); | ||
82 | action_builder.label(format!("Import `{}`", &import)); | ||
83 | auto_import_text_edit( | ||
84 | position, | ||
85 | &path.syntax().clone(), | ||
86 | &[SmolStr::new(import)], | ||
87 | action_builder.text_edit_builder(), | ||
88 | ); | ||
89 | action_builder | ||
90 | } | ||
91 | |||
92 | #[cfg(test)] | ||
93 | mod tests { | ||
94 | use super::*; | ||
95 | use crate::helpers::{ | ||
96 | check_assist_with_imports_locator, check_assist_with_imports_locator_not_applicable, | ||
97 | }; | ||
98 | use hir::Name; | ||
99 | |||
100 | #[derive(Clone)] | ||
101 | struct TestImportsLocator<'a> { | ||
102 | import_path: &'a [Name], | ||
103 | } | ||
104 | |||
105 | impl<'a> TestImportsLocator<'a> { | ||
106 | fn new(import_path: &'a [Name]) -> Self { | ||
107 | TestImportsLocator { import_path } | ||
108 | } | ||
109 | } | ||
110 | |||
111 | impl<'a> ImportsLocator<'_> for TestImportsLocator<'_> { | ||
112 | fn find_imports( | ||
113 | &mut self, | ||
114 | _: hir::InFile<&ast::NameRef>, | ||
115 | _: hir::Module, | ||
116 | ) -> Option<Vec<hir::ModPath>> { | ||
117 | if self.import_path.is_empty() { | ||
118 | None | ||
119 | } else { | ||
120 | Some(vec![hir::ModPath { | ||
121 | kind: hir::PathKind::Plain, | ||
122 | segments: self.import_path.to_owned(), | ||
123 | }]) | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | #[test] | ||
129 | fn applicable_when_found_an_import() { | ||
130 | let import_path = &[hir::name::known::std, hir::name::known::ops, hir::name::known::Debug]; | ||
131 | let mut imports_locator = TestImportsLocator::new(import_path); | ||
132 | check_assist_with_imports_locator( | ||
133 | auto_import, | ||
134 | &mut imports_locator, | ||
135 | " | ||
136 | fn main() { | ||
137 | } | ||
138 | |||
139 | Debug<|>", | ||
140 | &format!( | ||
141 | " | ||
142 | use {}; | ||
143 | |||
144 | fn main() {{ | ||
145 | }} | ||
146 | |||
147 | Debug<|>", | ||
148 | import_path | ||
149 | .into_iter() | ||
150 | .map(|name| name.to_string()) | ||
151 | .collect::<Vec<String>>() | ||
152 | .join("::") | ||
153 | ), | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn not_applicable_when_no_imports_found() { | ||
159 | let mut imports_locator = TestImportsLocator::new(&[]); | ||
160 | check_assist_with_imports_locator_not_applicable( | ||
161 | auto_import, | ||
162 | &mut imports_locator, | ||
163 | " | ||
164 | fn main() { | ||
165 | } | ||
166 | |||
167 | Debug<|>", | ||
168 | ); | ||
169 | } | ||
170 | |||
171 | #[test] | ||
172 | fn not_applicable_in_import_statements() { | ||
173 | let import_path = &[hir::name::known::std, hir::name::known::ops, hir::name::known::Debug]; | ||
174 | let mut imports_locator = TestImportsLocator::new(import_path); | ||
175 | check_assist_with_imports_locator_not_applicable( | ||
176 | auto_import, | ||
177 | &mut imports_locator, | ||
178 | "use Debug<|>;", | ||
179 | ); | ||
180 | } | ||
181 | } | ||