aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists
diff options
context:
space:
mode:
authorKirill Bulatov <[email protected]>2019-12-24 00:19:09 +0000
committerKirill Bulatov <[email protected]>2020-01-26 22:16:29 +0000
commit316795e074dff8f627f8c70c85d236420ecfb3a6 (patch)
treeda6e266139563ef314d0563a01723ae2264609d2 /crates/ra_assists/src/assists
parentd1330a4a65f0113c687716a5a679239af4df9c11 (diff)
Initial auto import action implementation
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r--crates/ra_assists/src/assists/auto_import.rs181
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 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode},
4 SmolStr, SyntaxElement,
5 SyntaxKind::{NAME_REF, USE_ITEM},
6 SyntaxNode,
7};
8
9use 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// ```
31pub(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
65fn 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
80fn 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)]
93mod 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}