aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/utils/insert_use.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/utils/insert_use.rs')
-rw-r--r--crates/assists/src/utils/insert_use.rs547
1 files changed, 547 insertions, 0 deletions
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
new file mode 100644
index 000000000..50a62ee82
--- /dev/null
+++ b/crates/assists/src/utils/insert_use.rs
@@ -0,0 +1,547 @@
1//! Handle syntactic aspects of inserting a new `use`.
2// FIXME: rewrite according to the plan, outlined in
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4
5use std::iter::successors;
6
7use either::Either;
8use hir::{self, ModPath};
9use syntax::{
10 ast::{self, NameOwner, VisibilityOwner},
11 AstNode, AstToken, Direction, SmolStr,
12 SyntaxKind::{PATH, PATH_SEGMENT},
13 SyntaxNode, SyntaxToken, T,
14};
15use text_edit::TextEditBuilder;
16
17use crate::assist_context::AssistContext;
18
19/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
20pub(crate) fn find_insert_use_container(
21 position: &SyntaxNode,
22 ctx: &AssistContext,
23) -> Option<Either<ast::ItemList, ast::SourceFile>> {
24 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
25 if let Some(module) = ast::Module::cast(n.clone()) {
26 return module.item_list().map(|it| Either::Left(it));
27 }
28 Some(Either::Right(ast::SourceFile::cast(n)?))
29 })
30}
31
32/// Creates and inserts a use statement for the given path to import.
33/// The use statement is inserted in the scope most appropriate to the
34/// the cursor position given, additionally merged with the existing use imports.
35pub(crate) fn insert_use_statement(
36 // Ideally the position of the cursor, used to
37 position: &SyntaxNode,
38 path_to_import: &ModPath,
39 ctx: &AssistContext,
40 builder: &mut TextEditBuilder,
41) {
42 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
43 let container = find_insert_use_container(position, ctx);
44
45 if let Some(container) = container {
46 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
47 let action = best_action_for_target(syntax, position.clone(), &target);
48 make_assist(&action, &target, builder);
49 }
50}
51
52fn collect_path_segments_raw(
53 segments: &mut Vec<ast::PathSegment>,
54 mut path: ast::Path,
55) -> Option<usize> {
56 let oldlen = segments.len();
57 loop {
58 let mut children = path.syntax().children_with_tokens();
59 let (first, second, third) = (
60 children.next().map(|n| (n.clone(), n.kind())),
61 children.next().map(|n| (n.clone(), n.kind())),
62 children.next().map(|n| (n.clone(), n.kind())),
63 );
64 match (first, second, third) {
65 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
66 path = ast::Path::cast(subpath.as_node()?.clone())?;
67 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
68 }
69 (Some((segment, PATH_SEGMENT)), _, _) => {
70 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
71 break;
72 }
73 (_, _, _) => return None,
74 }
75 }
76 // We need to reverse only the new added segments
77 let only_new_segments = segments.split_at_mut(oldlen).1;
78 only_new_segments.reverse();
79 Some(segments.len() - oldlen)
80}
81
82fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
83 let mut iter = segments.iter();
84 if let Some(s) = iter.next() {
85 buf.push_str(s);
86 }
87 for s in iter {
88 buf.push_str("::");
89 buf.push_str(s);
90 }
91}
92
93/// Returns the number of common segments.
94fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
95 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
96}
97
98fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool {
99 if let Some(kb) = b.kind() {
100 match kb {
101 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(),
102 ast::PathSegmentKind::SelfKw => a == "self",
103 ast::PathSegmentKind::SuperKw => a == "super",
104 ast::PathSegmentKind::CrateKw => a == "crate",
105 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
106 }
107 } else {
108 false
109 }
110}
111
112fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
113 a == b.text()
114}
115
116#[derive(Clone, Debug)]
117enum ImportAction {
118 Nothing,
119 // Add a brand new use statement.
120 AddNewUse {
121 anchor: Option<SyntaxNode>, // anchor node
122 add_after_anchor: bool,
123 },
124
125 // To split an existing use statement creating a nested import.
126 AddNestedImport {
127 // how may segments matched with the target path
128 common_segments: usize,
129 path_to_split: ast::Path,
130 // the first segment of path_to_split we want to add into the new nested list
131 first_segment_to_split: Option<ast::PathSegment>,
132 // Wether to add 'self' in addition to the target path
133 add_self: bool,
134 },
135 // To add the target path to an existing nested import tree list.
136 AddInTreeList {
137 common_segments: usize,
138 // The UseTreeList where to add the target path
139 tree_list: ast::UseTreeList,
140 add_self: bool,
141 },
142}
143
144impl ImportAction {
145 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
146 ImportAction::AddNewUse { anchor, add_after_anchor }
147 }
148
149 fn add_nested_import(
150 common_segments: usize,
151 path_to_split: ast::Path,
152 first_segment_to_split: Option<ast::PathSegment>,
153 add_self: bool,
154 ) -> Self {
155 ImportAction::AddNestedImport {
156 common_segments,
157 path_to_split,
158 first_segment_to_split,
159 add_self,
160 }
161 }
162
163 fn add_in_tree_list(
164 common_segments: usize,
165 tree_list: ast::UseTreeList,
166 add_self: bool,
167 ) -> Self {
168 ImportAction::AddInTreeList { common_segments, tree_list, add_self }
169 }
170
171 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
172 if left.is_better(&right) {
173 left
174 } else {
175 right
176 }
177 }
178
179 fn is_better(&self, other: &ImportAction) -> bool {
180 match (self, other) {
181 (ImportAction::Nothing, _) => true,
182 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false,
183 (
184 ImportAction::AddNestedImport { common_segments: n, .. },
185 ImportAction::AddInTreeList { common_segments: m, .. },
186 )
187 | (
188 ImportAction::AddInTreeList { common_segments: n, .. },
189 ImportAction::AddNestedImport { common_segments: m, .. },
190 )
191 | (
192 ImportAction::AddInTreeList { common_segments: n, .. },
193 ImportAction::AddInTreeList { common_segments: m, .. },
194 )
195 | (
196 ImportAction::AddNestedImport { common_segments: n, .. },
197 ImportAction::AddNestedImport { common_segments: m, .. },
198 ) => n > m,
199 (ImportAction::AddInTreeList { .. }, _) => true,
200 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
201 (ImportAction::AddNestedImport { .. }, _) => true,
202 (ImportAction::AddNewUse { .. }, _) => false,
203 }
204 }
205}
206
207// Find out the best ImportAction to import target path against current_use_tree.
208// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
209fn walk_use_tree_for_best_action(
210 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments
211 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import
212 current_use_tree: ast::UseTree, // the use tree we are currently examinating
213 target: &[SmolStr], // the path we want to import
214) -> ImportAction {
215 // We save the number of segments in the buffer so we can restore the correct segments
216 // before returning. Recursive call will add segments so we need to delete them.
217 let prev_len = current_path_segments.len();
218
219 let tree_list = current_use_tree.use_tree_list();
220 let alias = current_use_tree.rename();
221
222 let path = match current_use_tree.path() {
223 Some(path) => path,
224 None => {
225 // If the use item don't have a path, it means it's broken (syntax error)
226 return ImportAction::add_new_use(
227 current_use_tree
228 .syntax()
229 .ancestors()
230 .find_map(ast::Use::cast)
231 .map(|it| it.syntax().clone()),
232 true,
233 );
234 }
235 };
236
237 // This can happen only if current_use_tree is a direct child of a UseItem
238 if let Some(name) = alias.and_then(|it| it.name()) {
239 if compare_path_segment_with_name(&target[0], &name) {
240 return ImportAction::Nothing;
241 }
242 }
243
244 collect_path_segments_raw(current_path_segments, path.clone());
245
246 // We compare only the new segments added in the line just above.
247 // The first prev_len segments were already compared in 'parent' recursive calls.
248 let left = target.split_at(prev_len).1;
249 let right = current_path_segments.split_at(prev_len).1;
250 let common = compare_path_segments(left, &right);
251 let mut action = match common {
252 0 => ImportAction::add_new_use(
253 // e.g: target is std::fmt and we can have
254 // use foo::bar
255 // We add a brand new use statement
256 current_use_tree
257 .syntax()
258 .ancestors()
259 .find_map(ast::Use::cast)
260 .map(|it| it.syntax().clone()),
261 true,
262 ),
263 common if common == left.len() && left.len() == right.len() => {
264 // e.g: target is std::fmt and we can have
265 // 1- use std::fmt;
266 // 2- use std::fmt::{ ... }
267 if let Some(list) = tree_list {
268 // In case 2 we need to add self to the nested list
269 // unless it's already there
270 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
271 p.and_then(|it| it.segment())
272 .and_then(|it| it.kind())
273 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
274 .is_some()
275 });
276
277 if has_self {
278 ImportAction::Nothing
279 } else {
280 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
281 }
282 } else {
283 // Case 1
284 ImportAction::Nothing
285 }
286 }
287 common if common != left.len() && left.len() == right.len() => {
288 // e.g: target is std::fmt and we have
289 // use std::io;
290 // We need to split.
291 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
292 ImportAction::add_nested_import(
293 prev_len + common,
294 path,
295 Some(segments_to_split[0].clone()),
296 false,
297 )
298 }
299 common if common == right.len() && left.len() > right.len() => {
300 // e.g: target is std::fmt and we can have
301 // 1- use std;
302 // 2- use std::{ ... };
303
304 // fallback action
305 let mut better_action = ImportAction::add_new_use(
306 current_use_tree
307 .syntax()
308 .ancestors()
309 .find_map(ast::Use::cast)
310 .map(|it| it.syntax().clone()),
311 true,
312 );
313 if let Some(list) = tree_list {
314 // Case 2, check recursively if the path is already imported in the nested list
315 for u in list.use_trees() {
316 let child_action = walk_use_tree_for_best_action(
317 current_path_segments,
318 Some(list.clone()),
319 u,
320 target,
321 );
322 if child_action.is_better(&better_action) {
323 better_action = child_action;
324 if let ImportAction::Nothing = better_action {
325 return better_action;
326 }
327 }
328 }
329 } else {
330 // Case 1, split adding self
331 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
332 }
333 better_action
334 }
335 common if common == left.len() && left.len() < right.len() => {
336 // e.g: target is std::fmt and we can have
337 // use std::fmt::Debug;
338 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
339 ImportAction::add_nested_import(
340 prev_len + common,
341 path,
342 Some(segments_to_split[0].clone()),
343 true,
344 )
345 }
346 common if common < left.len() && common < right.len() => {
347 // e.g: target is std::fmt::nested::Debug
348 // use std::fmt::Display
349 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
350 ImportAction::add_nested_import(
351 prev_len + common,
352 path,
353 Some(segments_to_split[0].clone()),
354 false,
355 )
356 }
357 _ => unreachable!(),
358 };
359
360 // If we are inside a UseTreeList adding a use statement become adding to the existing
361 // tree list.
362 action = match (current_parent_use_tree_list, action.clone()) {
363 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
364 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
365 }
366 (_, _) => action,
367 };
368
369 // We remove the segments added
370 current_path_segments.truncate(prev_len);
371 action
372}
373
374fn best_action_for_target(
375 container: SyntaxNode,
376 anchor: SyntaxNode,
377 target: &[SmolStr],
378) -> ImportAction {
379 let mut storage = Vec::with_capacity(16); // this should be the only allocation
380 let best_action = container
381 .children()
382 .filter_map(ast::Use::cast)
383 .filter(|u| u.visibility().is_none())
384 .filter_map(|it| it.use_tree())
385 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
386 .fold(None, |best, a| match best {
387 Some(best) => Some(ImportAction::better(best, a)),
388 None => Some(a),
389 });
390
391 match best_action {
392 Some(action) => action,
393 None => {
394 // We have no action and no UseItem was found in container so we find
395 // another item and we use it as anchor.
396 // If there are no items above, we choose the target path itself as anchor.
397 // todo: we should include even whitespace blocks as anchor candidates
398 let anchor = container.children().next().or_else(|| Some(anchor));
399
400 let add_after_anchor = anchor
401 .clone()
402 .and_then(ast::Attr::cast)
403 .map(|attr| attr.kind() == ast::AttrKind::Inner)
404 .unwrap_or(false);
405 ImportAction::add_new_use(anchor, add_after_anchor)
406 }
407 }
408}
409
410fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) {
411 match action {
412 ImportAction::AddNewUse { anchor, add_after_anchor } => {
413 make_assist_add_new_use(anchor, *add_after_anchor, target, edit)
414 }
415 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => {
416 // We know that the fist n segments already exists in the use statement we want
417 // to modify, so we want to add only the last target.len() - n segments.
418 let segments_to_add = target.split_at(*common_segments).1;
419 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
420 }
421 ImportAction::AddNestedImport {
422 common_segments,
423 path_to_split,
424 first_segment_to_split,
425 add_self,
426 } => {
427 let segments_to_add = target.split_at(*common_segments).1;
428 make_assist_add_nested_import(
429 path_to_split,
430 first_segment_to_split,
431 segments_to_add,
432 *add_self,
433 edit,
434 )
435 }
436 _ => {}
437 }
438}
439
440fn make_assist_add_new_use(
441 anchor: &Option<SyntaxNode>,
442 after: bool,
443 target: &[SmolStr],
444 edit: &mut TextEditBuilder,
445) {
446 if let Some(anchor) = anchor {
447 let indent = leading_indent(anchor);
448 let mut buf = String::new();
449 if after {
450 buf.push_str("\n");
451 if let Some(spaces) = &indent {
452 buf.push_str(spaces);
453 }
454 }
455 buf.push_str("use ");
456 fmt_segments_raw(target, &mut buf);
457 buf.push_str(";");
458 if !after {
459 buf.push_str("\n\n");
460 if let Some(spaces) = &indent {
461 buf.push_str(&spaces);
462 }
463 }
464 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
465 edit.insert(position, buf);
466 }
467}
468
469fn make_assist_add_in_tree_list(
470 tree_list: &ast::UseTreeList,
471 target: &[SmolStr],
472 add_self: bool,
473 edit: &mut TextEditBuilder,
474) {
475 let last = tree_list.use_trees().last();
476 if let Some(last) = last {
477 let mut buf = String::new();
478 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
479 let offset = if let Some(comma) = comma {
480 comma.text_range().end()
481 } else {
482 buf.push_str(",");
483 last.syntax().text_range().end()
484 };
485 if add_self {
486 buf.push_str(" self")
487 } else {
488 buf.push_str(" ");
489 }
490 fmt_segments_raw(target, &mut buf);
491 edit.insert(offset, buf);
492 } else {
493 }
494}
495
496fn make_assist_add_nested_import(
497 path: &ast::Path,
498 first_segment_to_split: &Option<ast::PathSegment>,
499 target: &[SmolStr],
500 add_self: bool,
501 edit: &mut TextEditBuilder,
502) {
503 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast);
504 if let Some(use_tree) = use_tree {
505 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
506 {
507 (first_segment_to_split.syntax().text_range().start(), false)
508 } else {
509 (use_tree.syntax().text_range().end(), true)
510 };
511 let end = use_tree.syntax().text_range().end();
512
513 let mut buf = String::new();
514 if add_colon_colon {
515 buf.push_str("::");
516 }
517 buf.push_str("{");
518 if add_self {
519 buf.push_str("self, ");
520 }
521 fmt_segments_raw(target, &mut buf);
522 if !target.is_empty() {
523 buf.push_str(", ");
524 }
525 edit.insert(start, buf);
526 edit.insert(end, "}".to_string());
527 }
528}
529
530/// If the node is on the beginning of the line, calculate indent.
531fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> {
532 for token in prev_tokens(node.first_token()?) {
533 if let Some(ws) = ast::Whitespace::cast(token.clone()) {
534 let ws_text = ws.text();
535 if let Some(pos) = ws_text.rfind('\n') {
536 return Some(ws_text[pos + 1..].into());
537 }
538 }
539 if token.text().contains('\n') {
540 break;
541 }
542 }
543 return None;
544 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> {
545 successors(token.prev_token(), |token| token.prev_token())
546 }
547}