diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_assists/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_assists/src/auto_import.rs | 780 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 3 |
4 files changed, 785 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock index 9c65b39f7..40621e891 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -900,6 +900,7 @@ version = "0.1.0" | |||
900 | name = "ra_assists" | 900 | name = "ra_assists" |
901 | version = "0.1.0" | 901 | version = "0.1.0" |
902 | dependencies = [ | 902 | dependencies = [ |
903 | "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
903 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | 904 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
904 | "ra_db 0.1.0", | 905 | "ra_db 0.1.0", |
905 | "ra_fmt 0.1.0", | 906 | "ra_fmt 0.1.0", |
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml index 71ab2dcd2..a5808d5b7 100644 --- a/crates/ra_assists/Cargo.toml +++ b/crates/ra_assists/Cargo.toml | |||
@@ -6,6 +6,7 @@ authors = ["Aleksey Kladov <[email protected]>"] | |||
6 | 6 | ||
7 | [dependencies] | 7 | [dependencies] |
8 | join_to_string = "0.1.3" | 8 | join_to_string = "0.1.3" |
9 | itertools = "0.8.0" | ||
9 | 10 | ||
10 | ra_syntax = { path = "../ra_syntax" } | 11 | ra_syntax = { path = "../ra_syntax" } |
11 | ra_text_edit = { path = "../ra_text_edit" } | 12 | ra_text_edit = { path = "../ra_text_edit" } |
diff --git a/crates/ra_assists/src/auto_import.rs b/crates/ra_assists/src/auto_import.rs new file mode 100644 index 000000000..4cac5a926 --- /dev/null +++ b/crates/ra_assists/src/auto_import.rs | |||
@@ -0,0 +1,780 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast, AstNode, SyntaxNode, Direction, TextRange, | ||
4 | SyntaxKind::{ PATH, PATH_SEGMENT, COLONCOLON, COMMA } | ||
5 | }; | ||
6 | use crate::assist_ctx::{AssistCtx, Assist, AssistBuilder}; | ||
7 | use itertools::{ Itertools, EitherOrBoth }; | ||
8 | |||
9 | // TODO: refactor this before merge | ||
10 | mod formatting { | ||
11 | use ra_syntax::{ | ||
12 | AstNode, SyntaxNode, | ||
13 | ast::{self, AstToken}, | ||
14 | algo::generate, | ||
15 | }; | ||
16 | |||
17 | /// If the node is on the beginning of the line, calculate indent. | ||
18 | pub fn leading_indent(node: &SyntaxNode) -> Option<&str> { | ||
19 | for leaf in prev_leaves(node) { | ||
20 | if let Some(ws) = ast::Whitespace::cast(leaf) { | ||
21 | let ws_text = ws.text(); | ||
22 | if let Some(pos) = ws_text.rfind('\n') { | ||
23 | return Some(&ws_text[pos + 1..]); | ||
24 | } | ||
25 | } | ||
26 | if leaf.leaf_text().unwrap().contains('\n') { | ||
27 | break; | ||
28 | } | ||
29 | } | ||
30 | None | ||
31 | } | ||
32 | |||
33 | fn prev_leaves(node: &SyntaxNode) -> impl Iterator<Item = &SyntaxNode> { | ||
34 | generate(prev_leaf(node), |&node| prev_leaf(node)) | ||
35 | } | ||
36 | |||
37 | fn prev_leaf(node: &SyntaxNode) -> Option<&SyntaxNode> { | ||
38 | generate(node.ancestors().find_map(SyntaxNode::prev_sibling), |it| { | ||
39 | it.last_child() | ||
40 | }) | ||
41 | .last() | ||
42 | } | ||
43 | } | ||
44 | |||
45 | fn collect_path_segments(path: &ast::Path) -> Option<Vec<&ast::PathSegment>> { | ||
46 | let mut v = Vec::new(); | ||
47 | collect_path_segments_raw(&mut v, path)?; | ||
48 | return Some(v); | ||
49 | } | ||
50 | |||
51 | fn collect_path_segments_raw<'b, 'a: 'b>( | ||
52 | segments: &'b mut Vec<&'a ast::PathSegment>, | ||
53 | mut path: &'a ast::Path, | ||
54 | ) -> Option<usize> { | ||
55 | let oldlen = segments.len(); | ||
56 | loop { | ||
57 | let mut children = path.syntax().children(); | ||
58 | let (first, second, third) = ( | ||
59 | children.next().map(|n| (n, n.kind())), | ||
60 | children.next().map(|n| (n, n.kind())), | ||
61 | children.next().map(|n| (n, n.kind())), | ||
62 | ); | ||
63 | match (first, second, third) { | ||
64 | (Some((subpath, PATH)), Some((_, COLONCOLON)), Some((segment, PATH_SEGMENT))) => { | ||
65 | path = ast::Path::cast(subpath)?; | ||
66 | segments.push(ast::PathSegment::cast(segment)?); | ||
67 | } | ||
68 | (Some((segment, PATH_SEGMENT)), _, _) => { | ||
69 | segments.push(ast::PathSegment::cast(segment)?); | ||
70 | break; | ||
71 | } | ||
72 | (_, _, _) => return None, | ||
73 | } | ||
74 | } | ||
75 | // We need to reverse only the new added segments | ||
76 | let only_new_segments = segments.split_at_mut(oldlen).1; | ||
77 | only_new_segments.reverse(); | ||
78 | return Some(segments.len() - oldlen); | ||
79 | } | ||
80 | |||
81 | fn fmt_segments(segments: &[&ast::PathSegment]) -> String { | ||
82 | let mut buf = String::new(); | ||
83 | fmt_segments_raw(segments, &mut buf); | ||
84 | return buf; | ||
85 | } | ||
86 | |||
87 | fn fmt_segments_raw(segments: &[&ast::PathSegment], buf: &mut String) { | ||
88 | let mut first = true; | ||
89 | for s in segments { | ||
90 | if !first { | ||
91 | buf.push_str("::"); | ||
92 | } | ||
93 | match s.kind() { | ||
94 | Some(ast::PathSegmentKind::Name(nameref)) => buf.push_str(nameref.text()), | ||
95 | Some(ast::PathSegmentKind::SelfKw) => buf.push_str("self"), | ||
96 | Some(ast::PathSegmentKind::SuperKw) => buf.push_str("super"), | ||
97 | Some(ast::PathSegmentKind::CrateKw) => buf.push_str("crate"), | ||
98 | None => {} | ||
99 | } | ||
100 | first = false; | ||
101 | } | ||
102 | } | ||
103 | |||
104 | #[derive(Copy, Clone)] | ||
105 | enum PathSegmentsMatch { | ||
106 | // Patch matches exactly | ||
107 | Full, | ||
108 | // None of the segments matched. It's a more explicit Partial(0) | ||
109 | Empty, | ||
110 | // When some of the segments matched | ||
111 | Partial(usize), | ||
112 | // When all the segments of the right path are matched against the left path, | ||
113 | // but the left path is longer. | ||
114 | PartialLeft(usize), | ||
115 | // When all the segments of the left path are matched against the right path, | ||
116 | // but the right path is longer. | ||
117 | PartialRight(usize), | ||
118 | // In all the three cases above we keep track of how many segments matched | ||
119 | } | ||
120 | |||
121 | fn compare_path_segments( | ||
122 | left: &[&ast::PathSegment], | ||
123 | right: &[&ast::PathSegment], | ||
124 | ) -> PathSegmentsMatch { | ||
125 | let mut matching = 0; | ||
126 | for either_or_both in left.iter().zip_longest(right.iter()) { | ||
127 | match either_or_both { | ||
128 | EitherOrBoth::Both(left, right) => { | ||
129 | if compare_path_segment(left, right) { | ||
130 | matching += 1 | ||
131 | } else { | ||
132 | return if matching == 0 { | ||
133 | PathSegmentsMatch::Empty | ||
134 | } else { | ||
135 | PathSegmentsMatch::Partial(matching) | ||
136 | }; | ||
137 | } | ||
138 | } | ||
139 | EitherOrBoth::Left(_) => { | ||
140 | return PathSegmentsMatch::PartialLeft(matching); | ||
141 | } | ||
142 | EitherOrBoth::Right(_) => { | ||
143 | return PathSegmentsMatch::PartialRight(matching); | ||
144 | } | ||
145 | } | ||
146 | } | ||
147 | return PathSegmentsMatch::Full; | ||
148 | } | ||
149 | |||
150 | fn compare_path_segment(a: &ast::PathSegment, b: &ast::PathSegment) -> bool { | ||
151 | if let (Some(ka), Some(kb)) = (a.kind(), b.kind()) { | ||
152 | return match (ka, kb) { | ||
153 | (ast::PathSegmentKind::Name(nameref_a), ast::PathSegmentKind::Name(nameref_b)) => { | ||
154 | nameref_a.text() == nameref_b.text() | ||
155 | } | ||
156 | (ast::PathSegmentKind::SelfKw, ast::PathSegmentKind::SelfKw) => true, | ||
157 | (ast::PathSegmentKind::SuperKw, ast::PathSegmentKind::SuperKw) => true, | ||
158 | (ast::PathSegmentKind::CrateKw, ast::PathSegmentKind::CrateKw) => true, | ||
159 | (_, _) => false, | ||
160 | }; | ||
161 | } else { | ||
162 | false | ||
163 | } | ||
164 | } | ||
165 | |||
166 | fn compare_path_segment_with_name(a: &ast::PathSegment, b: &ast::Name) -> bool { | ||
167 | if let Some(ka) = a.kind() { | ||
168 | return match (ka, b) { | ||
169 | (ast::PathSegmentKind::Name(nameref_a), _) => nameref_a.text() == b.text(), | ||
170 | (_, _) => false, | ||
171 | }; | ||
172 | } else { | ||
173 | false | ||
174 | } | ||
175 | } | ||
176 | |||
177 | #[derive(Copy, Clone)] | ||
178 | enum ImportAction<'a> { | ||
179 | Nothing, | ||
180 | // Add a brand new use statement. | ||
181 | AddNewUse( | ||
182 | Option<&'a SyntaxNode>, // anchor node | ||
183 | bool, // true if we want to add the new statement after the anchor | ||
184 | ), | ||
185 | |||
186 | // In the following actions we keep track of how may segments matched, | ||
187 | // so we can choose the best action to take. | ||
188 | |||
189 | // To split an existing use statement creating a nested import. | ||
190 | AddNestedImport( | ||
191 | usize, | ||
192 | &'a ast::Path, // the complete path we want to split | ||
193 | Option<&'a ast::PathSegment>, // the first segment of path we want to add into the new nested list | ||
194 | bool, // true if we want to add 'self' in addition to the segment | ||
195 | ), | ||
196 | // To add the target path to an existing nested import tree list. | ||
197 | AddInTreeList( | ||
198 | usize, | ||
199 | &'a ast::UseTreeList, | ||
200 | bool, // true if we want to add 'self' | ||
201 | ), | ||
202 | } | ||
203 | |||
204 | impl<'a> ImportAction<'a> { | ||
205 | fn better<'b>(left: &'b ImportAction<'a>, right: &'b ImportAction<'a>) -> &'b ImportAction<'a> { | ||
206 | if left.is_better(right) { | ||
207 | left | ||
208 | } else { | ||
209 | right | ||
210 | } | ||
211 | } | ||
212 | |||
213 | fn is_better(&self, other: &ImportAction) -> bool { | ||
214 | match (self, other) { | ||
215 | (ImportAction::Nothing, _) => true, | ||
216 | (ImportAction::AddInTreeList(..), ImportAction::Nothing) => false, | ||
217 | (ImportAction::AddNestedImport(n, ..), ImportAction::AddInTreeList(m, ..)) => n > m, | ||
218 | (ImportAction::AddInTreeList(n, ..), ImportAction::AddNestedImport(m, ..)) => n > m, | ||
219 | (ImportAction::AddInTreeList(..), _) => true, | ||
220 | (ImportAction::AddNestedImport(..), ImportAction::Nothing) => false, | ||
221 | (ImportAction::AddNestedImport(..), _) => true, | ||
222 | (ImportAction::AddNewUse(..), _) => false, | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | |||
227 | // Find out the best ImportAction to import target path against current_use_tree. | ||
228 | // If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. | ||
229 | fn walk_use_tree_for_best_action<'b, 'c, 'a: 'b + 'c>( | ||
230 | current_path_segments: &'b mut Vec<&'a ast::PathSegment>, // buffer containing path segments | ||
231 | current_parent_use_tree_list: Option<&'a ast::UseTreeList>, // will be Some value if we are in a nested import | ||
232 | current_use_tree: &'a ast::UseTree, // the use tree we are currently examinating | ||
233 | target: &'c [&'a ast::PathSegment], // the path we want to import | ||
234 | ) -> ImportAction<'a> { | ||
235 | // We save the number of segments in the buffer so we can restore the correct segments | ||
236 | // before returning. Recursive call will add segments so we need to delete them. | ||
237 | let prev_len = current_path_segments.len(); | ||
238 | |||
239 | let tree_list = current_use_tree.use_tree_list(); | ||
240 | let alias = current_use_tree.alias(); | ||
241 | |||
242 | let path = match current_use_tree.path() { | ||
243 | Some(path) => path, | ||
244 | None => { | ||
245 | // If the use item don't have a path, it means it's broken (syntax error) | ||
246 | return ImportAction::AddNewUse( | ||
247 | current_use_tree | ||
248 | .syntax() | ||
249 | .ancestors() | ||
250 | .find_map(ast::UseItem::cast) | ||
251 | .map(AstNode::syntax), | ||
252 | true, | ||
253 | ); | ||
254 | } | ||
255 | }; | ||
256 | |||
257 | // This can happen only if current_use_tree is a direct child of a UseItem | ||
258 | if let Some(name) = alias.and_then(ast::NameOwner::name) { | ||
259 | if compare_path_segment_with_name(target[0], name) { | ||
260 | return ImportAction::Nothing; | ||
261 | } | ||
262 | } | ||
263 | |||
264 | collect_path_segments_raw(current_path_segments, path); | ||
265 | |||
266 | // We compare only the new segments added in the line just above. | ||
267 | // The first prev_len segments were already compared in 'parent' recursive calls. | ||
268 | let c = compare_path_segments( | ||
269 | target.split_at(prev_len).1, | ||
270 | current_path_segments.split_at(prev_len).1, | ||
271 | ); | ||
272 | |||
273 | let mut action = match c { | ||
274 | PathSegmentsMatch::Full => { | ||
275 | // e.g: target is std::fmt and we can have | ||
276 | // 1- use std::fmt; | ||
277 | // 2- use std::fmt:{ ... } | ||
278 | if let Some(list) = tree_list { | ||
279 | // In case 2 we need to add self to the nested list | ||
280 | // unless it's already there | ||
281 | let has_self = list.use_trees().map(ast::UseTree::path).any(|p| { | ||
282 | p.and_then(ast::Path::segment) | ||
283 | .and_then(ast::PathSegment::kind) | ||
284 | .filter(|k| *k == ast::PathSegmentKind::SelfKw) | ||
285 | .is_some() | ||
286 | }); | ||
287 | |||
288 | if has_self { | ||
289 | ImportAction::Nothing | ||
290 | } else { | ||
291 | ImportAction::AddInTreeList(current_path_segments.len(), list, true) | ||
292 | } | ||
293 | } else { | ||
294 | // Case 1 | ||
295 | ImportAction::Nothing | ||
296 | } | ||
297 | } | ||
298 | PathSegmentsMatch::Empty => ImportAction::AddNewUse( | ||
299 | // e.g: target is std::fmt and we can have | ||
300 | // use foo::bar | ||
301 | // We add a brand new use statement | ||
302 | current_use_tree | ||
303 | .syntax() | ||
304 | .ancestors() | ||
305 | .find_map(ast::UseItem::cast) | ||
306 | .map(AstNode::syntax), | ||
307 | true, | ||
308 | ), | ||
309 | PathSegmentsMatch::Partial(n) => { | ||
310 | // e.g: target is std::fmt and we have | ||
311 | // use std::io; | ||
312 | // We need to split. | ||
313 | let segments_to_split = current_path_segments.split_at(prev_len + n).1; | ||
314 | ImportAction::AddNestedImport(prev_len + n, path, Some(segments_to_split[0]), false) | ||
315 | } | ||
316 | PathSegmentsMatch::PartialLeft(n) => { | ||
317 | // e.g: target is std::fmt and we can have | ||
318 | // 1- use std; | ||
319 | // 2- use std::{ ... }; | ||
320 | |||
321 | // fallback action | ||
322 | let mut better_action = ImportAction::AddNewUse( | ||
323 | current_use_tree | ||
324 | .syntax() | ||
325 | .ancestors() | ||
326 | .find_map(ast::UseItem::cast) | ||
327 | .map(AstNode::syntax), | ||
328 | true, | ||
329 | ); | ||
330 | if let Some(list) = tree_list { | ||
331 | // Case 2, check recursively if the path is already imported in the nested list | ||
332 | for u in list.use_trees() { | ||
333 | let child_action = | ||
334 | walk_use_tree_for_best_action(current_path_segments, Some(list), u, target); | ||
335 | if child_action.is_better(&better_action) { | ||
336 | better_action = child_action; | ||
337 | if let ImportAction::Nothing = better_action { | ||
338 | return better_action; | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | } else { | ||
343 | // Case 1, split | ||
344 | better_action = ImportAction::AddNestedImport(prev_len + n, path, None, true) | ||
345 | } | ||
346 | better_action | ||
347 | } | ||
348 | PathSegmentsMatch::PartialRight(n) => { | ||
349 | // e.g: target std::fmt and we can have | ||
350 | // use std::fmt::Debug; | ||
351 | let segments_to_split = current_path_segments.split_at(prev_len + n).1; | ||
352 | ImportAction::AddNestedImport(prev_len + n, path, Some(segments_to_split[0]), true) | ||
353 | } | ||
354 | }; | ||
355 | |||
356 | // If we are inside a UseTreeList adding a use statement become adding to the existing | ||
357 | // tree list. | ||
358 | action = match (current_parent_use_tree_list, action) { | ||
359 | (Some(use_tree_list), ImportAction::AddNewUse(..)) => { | ||
360 | ImportAction::AddInTreeList(prev_len, use_tree_list, false) | ||
361 | } | ||
362 | (_, _) => action, | ||
363 | }; | ||
364 | |||
365 | // We remove the segments added | ||
366 | current_path_segments.truncate(prev_len); | ||
367 | return action; | ||
368 | } | ||
369 | |||
370 | fn best_action_for_target<'b, 'a: 'b>( | ||
371 | container: &'a SyntaxNode, | ||
372 | path: &'a ast::Path, | ||
373 | target: &'b [&'a ast::PathSegment], | ||
374 | ) -> ImportAction<'a> { | ||
375 | let mut storage = Vec::with_capacity(16); // this should be the only allocation | ||
376 | let best_action = container | ||
377 | .children() | ||
378 | .filter_map(ast::UseItem::cast) | ||
379 | .filter_map(ast::UseItem::use_tree) | ||
380 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) | ||
381 | .fold(None, |best, a| { | ||
382 | best.and_then(|best| Some(*ImportAction::better(&best, &a))) | ||
383 | .or(Some(a)) | ||
384 | }); | ||
385 | |||
386 | match best_action { | ||
387 | Some(action) => return action, | ||
388 | None => { | ||
389 | // We have no action we no use item was found in container so we find | ||
390 | // another item and we use it as anchor. | ||
391 | // If there are not items, we choose the target path itself as anchor. | ||
392 | let anchor = container | ||
393 | .children() | ||
394 | .find_map(ast::ModuleItem::cast) | ||
395 | .map(AstNode::syntax) | ||
396 | .or(Some(path.syntax())); | ||
397 | |||
398 | return ImportAction::AddNewUse(anchor, false); | ||
399 | } | ||
400 | } | ||
401 | } | ||
402 | |||
403 | fn make_assist(action: &ImportAction, target: &[&ast::PathSegment], edit: &mut AssistBuilder) { | ||
404 | match action { | ||
405 | ImportAction::AddNewUse(anchor, after) => { | ||
406 | make_assist_add_new_use(anchor, *after, target, edit) | ||
407 | } | ||
408 | ImportAction::AddInTreeList(n, tree_list_node, add_self) => { | ||
409 | // We know that the fist n segments already exists in the use statement we want | ||
410 | // to modify, so we want to add only the last target.len() - n segments. | ||
411 | let segments_to_add = target.split_at(*n).1; | ||
412 | make_assist_add_in_tree_list(tree_list_node, segments_to_add, *add_self, edit) | ||
413 | } | ||
414 | ImportAction::AddNestedImport(n, path, first_segment_to_split, add_self) => { | ||
415 | let segments_to_add = target.split_at(*n).1; | ||
416 | make_assist_add_nested_import( | ||
417 | path, | ||
418 | first_segment_to_split, | ||
419 | segments_to_add, | ||
420 | *add_self, | ||
421 | edit, | ||
422 | ) | ||
423 | } | ||
424 | _ => {} | ||
425 | } | ||
426 | } | ||
427 | |||
428 | fn make_assist_add_new_use( | ||
429 | anchor: &Option<&SyntaxNode>, | ||
430 | after: bool, | ||
431 | target: &[&ast::PathSegment], | ||
432 | edit: &mut AssistBuilder, | ||
433 | ) { | ||
434 | if let Some(anchor) = anchor { | ||
435 | let indent = formatting::leading_indent(anchor); | ||
436 | let mut buf = String::new(); | ||
437 | if after { | ||
438 | buf.push_str("\n"); | ||
439 | if let Some(spaces) = indent { | ||
440 | buf.push_str(spaces); | ||
441 | } | ||
442 | } | ||
443 | buf.push_str("use "); | ||
444 | fmt_segments_raw(target, &mut buf); | ||
445 | buf.push_str(";"); | ||
446 | if !after { | ||
447 | buf.push_str("\n\n"); | ||
448 | if let Some(spaces) = indent { | ||
449 | buf.push_str(spaces); | ||
450 | } | ||
451 | } | ||
452 | let position = if after { | ||
453 | anchor.range().end() | ||
454 | } else { | ||
455 | anchor.range().start() | ||
456 | }; | ||
457 | edit.insert(position, buf); | ||
458 | } | ||
459 | } | ||
460 | |||
461 | fn make_assist_add_in_tree_list( | ||
462 | tree_list: &ast::UseTreeList, | ||
463 | target: &[&ast::PathSegment], | ||
464 | add_self: bool, | ||
465 | edit: &mut AssistBuilder, | ||
466 | ) { | ||
467 | let last = tree_list.use_trees().last(); | ||
468 | if let Some(last) = last { | ||
469 | let mut buf = String::new(); | ||
470 | let comma = last | ||
471 | .syntax() | ||
472 | .siblings(Direction::Next) | ||
473 | .find(|n| n.kind() == COMMA); | ||
474 | let offset = if let Some(comma) = comma { | ||
475 | comma.range().end() | ||
476 | } else { | ||
477 | buf.push_str(","); | ||
478 | last.syntax().range().end() | ||
479 | }; | ||
480 | if add_self { | ||
481 | buf.push_str(" self") | ||
482 | } else { | ||
483 | buf.push_str(" "); | ||
484 | } | ||
485 | fmt_segments_raw(target, &mut buf); | ||
486 | edit.insert(offset, buf); | ||
487 | } else { | ||
488 | |||
489 | } | ||
490 | } | ||
491 | |||
492 | fn make_assist_add_nested_import( | ||
493 | path: &ast::Path, | ||
494 | first_segment_to_split: &Option<&ast::PathSegment>, | ||
495 | target: &[&ast::PathSegment], | ||
496 | add_self: bool, | ||
497 | edit: &mut AssistBuilder, | ||
498 | ) { | ||
499 | let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); | ||
500 | if let Some(use_tree) = use_tree { | ||
501 | let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split | ||
502 | { | ||
503 | (first_segment_to_split.syntax().range().start(), false) | ||
504 | } else { | ||
505 | (use_tree.syntax().range().end(), true) | ||
506 | }; | ||
507 | let end = use_tree.syntax().range().end(); | ||
508 | |||
509 | let mut buf = String::new(); | ||
510 | if add_colon_colon { | ||
511 | buf.push_str("::"); | ||
512 | } | ||
513 | buf.push_str("{ "); | ||
514 | if add_self { | ||
515 | buf.push_str("self, "); | ||
516 | } | ||
517 | fmt_segments_raw(target, &mut buf); | ||
518 | if !target.is_empty() { | ||
519 | buf.push_str(", "); | ||
520 | } | ||
521 | edit.insert(start, buf); | ||
522 | edit.insert(end, "}"); | ||
523 | } | ||
524 | } | ||
525 | |||
526 | pub(crate) fn auto_import(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
527 | let node = ctx.covering_node(); | ||
528 | let current_file = node.ancestors().find_map(ast::SourceFile::cast)?; | ||
529 | |||
530 | let path = node.ancestors().find_map(ast::Path::cast)?; | ||
531 | // We don't want to mess with use statements | ||
532 | if path | ||
533 | .syntax() | ||
534 | .ancestors() | ||
535 | .find_map(ast::UseItem::cast) | ||
536 | .is_some() | ||
537 | { | ||
538 | return None; | ||
539 | } | ||
540 | |||
541 | let segments = collect_path_segments(path)?; | ||
542 | if segments.len() < 2 { | ||
543 | return None; | ||
544 | } | ||
545 | |||
546 | ctx.build( | ||
547 | format!("import {} in the current file", fmt_segments(&segments)), | ||
548 | |edit| { | ||
549 | let action = best_action_for_target(current_file.syntax(), path, &segments); | ||
550 | make_assist(&action, segments.as_slice(), edit); | ||
551 | if let Some(last_segment) = path.segment() { | ||
552 | // Here we are assuming the assist will provide a correct use statement | ||
553 | // so we can delete the path qualifier | ||
554 | edit.delete(TextRange::from_to( | ||
555 | path.syntax().range().start(), | ||
556 | last_segment.syntax().range().start(), | ||
557 | )); | ||
558 | } | ||
559 | }, | ||
560 | ) | ||
561 | } | ||
562 | |||
563 | #[cfg(test)] | ||
564 | mod tests { | ||
565 | use super::*; | ||
566 | use crate::helpers::{ check_assist, check_assist_not_applicable }; | ||
567 | |||
568 | #[test] | ||
569 | fn test_auto_import_file_add_use_no_anchor() { | ||
570 | check_assist( | ||
571 | auto_import, | ||
572 | " | ||
573 | std::fmt::Debug<|> | ||
574 | ", | ||
575 | " | ||
576 | use std::fmt::Debug; | ||
577 | |||
578 | Debug<|> | ||
579 | ", | ||
580 | ); | ||
581 | } | ||
582 | |||
583 | #[test] | ||
584 | fn test_auto_import_file_add_use() { | ||
585 | check_assist( | ||
586 | auto_import, | ||
587 | " | ||
588 | use stdx; | ||
589 | |||
590 | impl std::fmt::Debug<|> for Foo { | ||
591 | } | ||
592 | ", | ||
593 | " | ||
594 | use stdx; | ||
595 | use std::fmt::Debug; | ||
596 | |||
597 | impl Debug<|> for Foo { | ||
598 | } | ||
599 | ", | ||
600 | ); | ||
601 | } | ||
602 | |||
603 | #[test] | ||
604 | fn test_auto_import_file_add_use_other_anchor() { | ||
605 | check_assist( | ||
606 | auto_import, | ||
607 | " | ||
608 | impl std::fmt::Debug<|> for Foo { | ||
609 | } | ||
610 | ", | ||
611 | " | ||
612 | use std::fmt::Debug; | ||
613 | |||
614 | impl Debug<|> for Foo { | ||
615 | } | ||
616 | ", | ||
617 | ); | ||
618 | } | ||
619 | |||
620 | #[test] | ||
621 | fn test_auto_import_file_add_use_other_anchor_indent() { | ||
622 | check_assist( | ||
623 | auto_import, | ||
624 | " | ||
625 | impl std::fmt::Debug<|> for Foo { | ||
626 | } | ||
627 | ", | ||
628 | " | ||
629 | use std::fmt::Debug; | ||
630 | |||
631 | impl Debug<|> for Foo { | ||
632 | } | ||
633 | ", | ||
634 | ); | ||
635 | } | ||
636 | |||
637 | #[test] | ||
638 | fn test_auto_import_file_split_different() { | ||
639 | check_assist( | ||
640 | auto_import, | ||
641 | " | ||
642 | use std::fmt; | ||
643 | |||
644 | impl std::io<|> for Foo { | ||
645 | } | ||
646 | ", | ||
647 | " | ||
648 | use std::{ io, fmt}; | ||
649 | |||
650 | impl io<|> for Foo { | ||
651 | } | ||
652 | ", | ||
653 | ); | ||
654 | } | ||
655 | |||
656 | #[test] | ||
657 | fn test_auto_import_file_split_self_for_use() { | ||
658 | check_assist( | ||
659 | auto_import, | ||
660 | " | ||
661 | use std::fmt; | ||
662 | |||
663 | impl std::fmt::Debug<|> for Foo { | ||
664 | } | ||
665 | ", | ||
666 | " | ||
667 | use std::fmt::{ self, Debug, }; | ||
668 | |||
669 | impl Debug<|> for Foo { | ||
670 | } | ||
671 | ", | ||
672 | ); | ||
673 | } | ||
674 | |||
675 | #[test] | ||
676 | fn test_auto_import_file_split_self_for_target() { | ||
677 | check_assist( | ||
678 | auto_import, | ||
679 | " | ||
680 | use std::fmt::Debug; | ||
681 | |||
682 | impl std::fmt<|> for Foo { | ||
683 | } | ||
684 | ", | ||
685 | " | ||
686 | use std::fmt::{ self, Debug}; | ||
687 | |||
688 | impl fmt<|> for Foo { | ||
689 | } | ||
690 | ", | ||
691 | ); | ||
692 | } | ||
693 | |||
694 | #[test] | ||
695 | fn test_auto_import_file_add_to_nested_self_nested() { | ||
696 | check_assist( | ||
697 | auto_import, | ||
698 | " | ||
699 | use std::fmt::{Debug, nested::{Display}}; | ||
700 | |||
701 | impl std::fmt::nested<|> for Foo { | ||
702 | } | ||
703 | ", | ||
704 | " | ||
705 | use std::fmt::{Debug, nested::{Display, self}}; | ||
706 | |||
707 | impl nested<|> for Foo { | ||
708 | } | ||
709 | ", | ||
710 | ); | ||
711 | } | ||
712 | |||
713 | #[test] | ||
714 | fn test_auto_import_file_add_to_nested_self_already_included() { | ||
715 | check_assist( | ||
716 | auto_import, | ||
717 | " | ||
718 | use std::fmt::{Debug, nested::{self, Display}}; | ||
719 | |||
720 | impl std::fmt::nested<|> for Foo { | ||
721 | } | ||
722 | ", | ||
723 | " | ||
724 | use std::fmt::{Debug, nested::{self, Display}}; | ||
725 | |||
726 | impl nested<|> for Foo { | ||
727 | } | ||
728 | ", | ||
729 | ); | ||
730 | } | ||
731 | |||
732 | #[test] | ||
733 | fn test_auto_import_file_add_to_nested_nested() { | ||
734 | check_assist( | ||
735 | auto_import, | ||
736 | " | ||
737 | use std::fmt::{Debug, nested::{Display}}; | ||
738 | |||
739 | impl std::fmt::nested::Debug<|> for Foo { | ||
740 | } | ||
741 | ", | ||
742 | " | ||
743 | use std::fmt::{Debug, nested::{Display, Debug}}; | ||
744 | |||
745 | impl Debug<|> for Foo { | ||
746 | } | ||
747 | ", | ||
748 | ); | ||
749 | } | ||
750 | |||
751 | #[test] | ||
752 | fn test_auto_import_file_alias() { | ||
753 | check_assist( | ||
754 | auto_import, | ||
755 | " | ||
756 | use std::fmt as foo; | ||
757 | |||
758 | impl foo::Debug<|> for Foo { | ||
759 | } | ||
760 | ", | ||
761 | " | ||
762 | use std::fmt as foo; | ||
763 | |||
764 | impl Debug<|> for Foo { | ||
765 | } | ||
766 | ", | ||
767 | ); | ||
768 | } | ||
769 | |||
770 | #[test] | ||
771 | fn test_auto_import_not_applicable_one_segment() { | ||
772 | check_assist_not_applicable( | ||
773 | auto_import, | ||
774 | " | ||
775 | impl foo<|> for Foo { | ||
776 | } | ||
777 | ", | ||
778 | ); | ||
779 | } | ||
780 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 7928b4983..af578893e 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -84,6 +84,8 @@ mod introduce_variable; | |||
84 | mod replace_if_let_with_match; | 84 | mod replace_if_let_with_match; |
85 | mod split_import; | 85 | mod split_import; |
86 | mod remove_dbg; | 86 | mod remove_dbg; |
87 | mod auto_import; | ||
88 | |||
87 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 89 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { |
88 | &[ | 90 | &[ |
89 | add_derive::add_derive, | 91 | add_derive::add_derive, |
@@ -95,6 +97,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis | |||
95 | replace_if_let_with_match::replace_if_let_with_match, | 97 | replace_if_let_with_match::replace_if_let_with_match, |
96 | split_import::split_import, | 98 | split_import::split_import, |
97 | remove_dbg::remove_dbg, | 99 | remove_dbg::remove_dbg, |
100 | auto_import::auto_import, | ||
98 | ] | 101 | ] |
99 | } | 102 | } |
100 | 103 | ||