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.rs1347
1 files changed, 867 insertions, 480 deletions
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 49096a67c..09f4a2224 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,546 +1,933 @@
1//! Handle syntactic aspects of inserting a new `use`. 1//! Handle syntactic aspects of inserting a new `use`.
2// FIXME: rewrite according to the plan, outlined in 2use std::{
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553 3 cmp::Ordering,
4 4 iter::{self, successors},
5use std::iter::successors; 5};
6 6
7use either::Either; 7use ast::{
8 edit::{AstNodeEdit, IndentLevel},
9 PathSegmentKind, VisibilityOwner,
10};
8use syntax::{ 11use syntax::{
9 ast::{self, NameOwner, VisibilityOwner}, 12 algo,
10 AstNode, AstToken, Direction, SmolStr, 13 ast::{self, make, AstNode},
11 SyntaxKind::{PATH, PATH_SEGMENT}, 14 InsertPosition, SyntaxElement, SyntaxNode,
12 SyntaxNode, SyntaxToken, T,
13}; 15};
14use text_edit::TextEditBuilder;
15
16use crate::assist_context::AssistContext;
17
18/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
19pub(crate) fn find_insert_use_container(
20 position: &SyntaxNode,
21 ctx: &AssistContext,
22) -> Option<Either<ast::ItemList, ast::SourceFile>> {
23 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
24 if let Some(module) = ast::Module::cast(n.clone()) {
25 return module.item_list().map(|it| Either::Left(it));
26 }
27 Some(Either::Right(ast::SourceFile::cast(n)?))
28 })
29}
30 16
31/// Creates and inserts a use statement for the given path to import. 17#[derive(Debug)]
32/// The use statement is inserted in the scope most appropriate to the 18pub enum ImportScope {
33/// the cursor position given, additionally merged with the existing use imports. 19 File(ast::SourceFile),
34pub(crate) fn insert_use_statement( 20 Module(ast::ItemList),
35 // Ideally the position of the cursor, used to
36 position: &SyntaxNode,
37 path_to_import: &str,
38 ctx: &AssistContext,
39 builder: &mut TextEditBuilder,
40) {
41 let target = path_to_import.split("::").map(SmolStr::new).collect::<Vec<_>>();
42 let container = find_insert_use_container(position, ctx);
43
44 if let Some(container) = container {
45 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
46 let action = best_action_for_target(syntax, position.clone(), &target);
47 make_assist(&action, &target, builder);
48 }
49} 21}
50 22
51fn collect_path_segments_raw( 23impl ImportScope {
52 segments: &mut Vec<ast::PathSegment>, 24 pub fn from(syntax: SyntaxNode) -> Option<Self> {
53 mut path: ast::Path, 25 if let Some(module) = ast::Module::cast(syntax.clone()) {
54) -> Option<usize> { 26 module.item_list().map(ImportScope::Module)
55 let oldlen = segments.len(); 27 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
56 loop { 28 this.map(ImportScope::File)
57 let mut children = path.syntax().children_with_tokens(); 29 } else {
58 let (first, second, third) = ( 30 ast::ItemList::cast(syntax).map(ImportScope::Module)
59 children.next().map(|n| (n.clone(), n.kind())),
60 children.next().map(|n| (n.clone(), n.kind())),
61 children.next().map(|n| (n.clone(), n.kind())),
62 );
63 match (first, second, third) {
64 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
65 path = ast::Path::cast(subpath.as_node()?.clone())?;
66 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
67 }
68 (Some((segment, PATH_SEGMENT)), _, _) => {
69 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
70 break;
71 }
72 (_, _, _) => return None,
73 } 31 }
74 } 32 }
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 Some(segments.len() - oldlen)
79}
80 33
81fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 34 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
82 let mut iter = segments.iter(); 35 pub(crate) fn find_insert_use_container(
83 if let Some(s) = iter.next() { 36 position: &SyntaxNode,
84 buf.push_str(s); 37 ctx: &crate::assist_context::AssistContext,
85 } 38 ) -> Option<Self> {
86 for s in iter { 39 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
87 buf.push_str("::");
88 buf.push_str(s);
89 } 40 }
90}
91
92/// Returns the number of common segments.
93fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
94 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count()
95}
96 41
97fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 42 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
98 if let Some(kb) = b.kind() { 43 match self {
99 match kb { 44 ImportScope::File(file) => file.syntax(),
100 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), 45 ImportScope::Module(item_list) => item_list.syntax(),
101 ast::PathSegmentKind::SelfKw => a == "self",
102 ast::PathSegmentKind::SuperKw => a == "super",
103 ast::PathSegmentKind::CrateKw => a == "crate",
104 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
105 } 46 }
106 } else {
107 false
108 } 47 }
109}
110
111fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
112 a == b.text()
113}
114 48
115#[derive(Clone, Debug)] 49 fn indent_level(&self) -> IndentLevel {
116enum ImportAction { 50 match self {
117 Nothing, 51 ImportScope::File(file) => file.indent_level(),
118 // Add a brand new use statement. 52 ImportScope::Module(item_list) => item_list.indent_level() + 1,
119 AddNewUse {
120 anchor: Option<SyntaxNode>, // anchor node
121 add_after_anchor: bool,
122 },
123
124 // To split an existing use statement creating a nested import.
125 AddNestedImport {
126 // how may segments matched with the target path
127 common_segments: usize,
128 path_to_split: ast::Path,
129 // the first segment of path_to_split we want to add into the new nested list
130 first_segment_to_split: Option<ast::PathSegment>,
131 // Wether to add 'self' in addition to the target path
132 add_self: bool,
133 },
134 // To add the target path to an existing nested import tree list.
135 AddInTreeList {
136 common_segments: usize,
137 // The UseTreeList where to add the target path
138 tree_list: ast::UseTreeList,
139 add_self: bool,
140 },
141}
142
143impl ImportAction {
144 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
145 ImportAction::AddNewUse { anchor, add_after_anchor }
146 }
147
148 fn add_nested_import(
149 common_segments: usize,
150 path_to_split: ast::Path,
151 first_segment_to_split: Option<ast::PathSegment>,
152 add_self: bool,
153 ) -> Self {
154 ImportAction::AddNestedImport {
155 common_segments,
156 path_to_split,
157 first_segment_to_split,
158 add_self,
159 } 53 }
160 } 54 }
161 55
162 fn add_in_tree_list( 56 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
163 common_segments: usize, 57 match self {
164 tree_list: ast::UseTreeList, 58 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
165 add_self: bool, 59 // don't insert the imports before the item list's opening curly brace
166 ) -> Self { 60 ImportScope::Module(item_list) => item_list
167 ImportAction::AddInTreeList { common_segments, tree_list, add_self } 61 .l_curly_token()
168 } 62 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
169 63 .unwrap_or((InsertPosition::First, AddBlankLine::AfterTwice)),
170 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
171 if left.is_better(&right) {
172 left
173 } else {
174 right
175 } 64 }
176 } 65 }
177 66
178 fn is_better(&self, other: &ImportAction) -> bool { 67 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
179 match (self, other) { 68 // check if the scope has inner attributes, we dont want to insert in front of them
180 (ImportAction::Nothing, _) => true, 69 match self
181 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, 70 .as_syntax_node()
182 ( 71 .children()
183 ImportAction::AddNestedImport { common_segments: n, .. }, 72 // no flat_map here cause we want to short circuit the iterator
184 ImportAction::AddInTreeList { common_segments: m, .. }, 73 .map(ast::Attr::cast)
185 ) 74 .take_while(|attr| {
186 | ( 75 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
187 ImportAction::AddInTreeList { common_segments: n, .. }, 76 })
188 ImportAction::AddNestedImport { common_segments: m, .. }, 77 .last()
189 ) 78 .flatten()
190 | ( 79 {
191 ImportAction::AddInTreeList { common_segments: n, .. }, 80 Some(attr) => {
192 ImportAction::AddInTreeList { common_segments: m, .. }, 81 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
193 ) 82 }
194 | ( 83 None => self.first_insert_pos(),
195 ImportAction::AddNestedImport { common_segments: n, .. },
196 ImportAction::AddNestedImport { common_segments: m, .. },
197 ) => n > m,
198 (ImportAction::AddInTreeList { .. }, _) => true,
199 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
200 (ImportAction::AddNestedImport { .. }, _) => true,
201 (ImportAction::AddNewUse { .. }, _) => false,
202 } 84 }
203 } 85 }
204} 86}
205 87
206// Find out the best ImportAction to import target path against current_use_tree. 88/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
207// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. 89pub(crate) fn insert_use(
208fn walk_use_tree_for_best_action( 90 scope: &ImportScope,
209 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments 91 path: ast::Path,
210 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import 92 merge: Option<MergeBehaviour>,
211 current_use_tree: ast::UseTree, // the use tree we are currently examinating 93) -> SyntaxNode {
212 target: &[SmolStr], // the path we want to import 94 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
213) -> ImportAction { 95 // merge into existing imports if possible
214 // We save the number of segments in the buffer so we can restore the correct segments 96 if let Some(mb) = merge {
215 // before returning. Recursive call will add segments so we need to delete them. 97 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
216 let prev_len = current_path_segments.len(); 98 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
217 99 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
218 let tree_list = current_use_tree.use_tree_list(); 100 let to_delete = to_delete.clone()..=to_delete;
219 let alias = current_use_tree.rename(); 101 let to_insert = iter::once(merged.syntax().clone().into());
220 102 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
221 let path = match current_use_tree.path() { 103 }
222 Some(path) => path,
223 None => {
224 // If the use item don't have a path, it means it's broken (syntax error)
225 return ImportAction::add_new_use(
226 current_use_tree
227 .syntax()
228 .ancestors()
229 .find_map(ast::Use::cast)
230 .map(|it| it.syntax().clone()),
231 true,
232 );
233 }
234 };
235
236 // This can happen only if current_use_tree is a direct child of a UseItem
237 if let Some(name) = alias.and_then(|it| it.name()) {
238 if compare_path_segment_with_name(&target[0], &name) {
239 return ImportAction::Nothing;
240 } 104 }
241 } 105 }
242 106
243 collect_path_segments_raw(current_path_segments, path.clone()); 107 // either we weren't allowed to merge or there is no import that fits the merge conditions
244 108 // so look for the place we have to insert to
245 // We compare only the new segments added in the line just above. 109 let (insert_position, add_blank) = find_insert_position(scope, path);
246 // The first prev_len segments were already compared in 'parent' recursive calls. 110
247 let left = target.split_at(prev_len).1; 111 let to_insert: Vec<SyntaxElement> = {
248 let right = current_path_segments.split_at(prev_len).1; 112 let mut buf = Vec::new();
249 let common = compare_path_segments(left, &right); 113
250 let mut action = match common { 114 match add_blank {
251 0 => ImportAction::add_new_use( 115 AddBlankLine::Before | AddBlankLine::Around => {
252 // e.g: target is std::fmt and we can have 116 buf.push(make::tokens::single_newline().into())
253 // use foo::bar
254 // We add a brand new use statement
255 current_use_tree
256 .syntax()
257 .ancestors()
258 .find_map(ast::Use::cast)
259 .map(|it| it.syntax().clone()),
260 true,
261 ),
262 common if common == left.len() && left.len() == right.len() => {
263 // e.g: target is std::fmt and we can have
264 // 1- use std::fmt;
265 // 2- use std::fmt::{ ... }
266 if let Some(list) = tree_list {
267 // In case 2 we need to add self to the nested list
268 // unless it's already there
269 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
270 p.and_then(|it| it.segment())
271 .and_then(|it| it.kind())
272 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
273 .is_some()
274 });
275
276 if has_self {
277 ImportAction::Nothing
278 } else {
279 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
280 }
281 } else {
282 // Case 1
283 ImportAction::Nothing
284 } 117 }
118 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
119 _ => (),
285 } 120 }
286 common if common != left.len() && left.len() == right.len() => { 121
287 // e.g: target is std::fmt and we have 122 if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
288 // use std::io; 123 buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into());
289 // We need to split.
290 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
291 ImportAction::add_nested_import(
292 prev_len + common,
293 path,
294 Some(segments_to_split[0].clone()),
295 false,
296 )
297 } 124 }
298 common if common == right.len() && left.len() > right.len() => { 125 buf.push(use_item.syntax().clone().into());
299 // e.g: target is std::fmt and we can have 126
300 // 1- use std; 127 match add_blank {
301 // 2- use std::{ ... }; 128 AddBlankLine::After | AddBlankLine::Around => {
302 129 buf.push(make::tokens::single_newline().into())
303 // fallback action
304 let mut better_action = ImportAction::add_new_use(
305 current_use_tree
306 .syntax()
307 .ancestors()
308 .find_map(ast::Use::cast)
309 .map(|it| it.syntax().clone()),
310 true,
311 );
312 if let Some(list) = tree_list {
313 // Case 2, check recursively if the path is already imported in the nested list
314 for u in list.use_trees() {
315 let child_action = walk_use_tree_for_best_action(
316 current_path_segments,
317 Some(list.clone()),
318 u,
319 target,
320 );
321 if child_action.is_better(&better_action) {
322 better_action = child_action;
323 if let ImportAction::Nothing = better_action {
324 return better_action;
325 }
326 }
327 }
328 } else {
329 // Case 1, split adding self
330 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
331 } 130 }
332 better_action 131 AddBlankLine::AfterTwice => buf.push(make::tokens::blank_line().into()),
132 _ => (),
333 } 133 }
334 common if common == left.len() && left.len() < right.len() => {
335 // e.g: target is std::fmt and we can have
336 // use std::fmt::Debug;
337 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
338 ImportAction::add_nested_import(
339 prev_len + common,
340 path,
341 Some(segments_to_split[0].clone()),
342 true,
343 )
344 }
345 common if common < left.len() && common < right.len() => {
346 // e.g: target is std::fmt::nested::Debug
347 // use std::fmt::Display
348 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
349 ImportAction::add_nested_import(
350 prev_len + common,
351 path,
352 Some(segments_to_split[0].clone()),
353 false,
354 )
355 }
356 _ => unreachable!(),
357 };
358 134
359 // If we are inside a UseTreeList adding a use statement become adding to the existing 135 buf
360 // tree list.
361 action = match (current_parent_use_tree_list, action.clone()) {
362 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
363 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
364 }
365 (_, _) => action,
366 }; 136 };
367 137
368 // We remove the segments added 138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
369 current_path_segments.truncate(prev_len);
370 action
371} 139}
372 140
373fn best_action_for_target( 141fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
374 container: SyntaxNode, 142 match (vis0, vis1) {
375 anchor: SyntaxNode, 143 (None, None) => true,
376 target: &[SmolStr], 144 // FIXME: Don't use the string representation to check for equality
377) -> ImportAction { 145 // spaces inside of the node would break this comparison
378 let mut storage = Vec::with_capacity(16); // this should be the only allocation 146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
379 let best_action = container 147 _ => false,
380 .children() 148 }
381 .filter_map(ast::Use::cast) 149}
382 .filter(|u| u.visibility().is_none())
383 .filter_map(|it| it.use_tree())
384 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
385 .fold(None, |best, a| match best {
386 Some(best) => Some(ImportAction::better(best, a)),
387 None => Some(a),
388 });
389
390 match best_action {
391 Some(action) => action,
392 None => {
393 // We have no action and no UseItem was found in container so we find
394 // another item and we use it as anchor.
395 // If there are no items above, we choose the target path itself as anchor.
396 // todo: we should include even whitespace blocks as anchor candidates
397 let anchor = container.children().next().or_else(|| Some(anchor));
398 150
399 let add_after_anchor = anchor 151pub(crate) fn try_merge_imports(
400 .clone() 152 lhs: &ast::Use,
401 .and_then(ast::Attr::cast) 153 rhs: &ast::Use,
402 .map(|attr| attr.kind() == ast::AttrKind::Inner) 154 merge_behaviour: MergeBehaviour,
403 .unwrap_or(false); 155) -> Option<ast::Use> {
404 ImportAction::add_new_use(anchor, add_after_anchor) 156 // don't merge imports with different visibilities
405 } 157 if !eq_visibility(lhs.visibility(), rhs.visibility()) {
158 return None;
406 } 159 }
160 let lhs_tree = lhs.use_tree()?;
161 let rhs_tree = rhs.use_tree()?;
162 let merged = try_merge_trees(&lhs_tree, &rhs_tree, merge_behaviour)?;
163 Some(lhs.with_use_tree(merged))
407} 164}
408 165
409fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { 166pub(crate) fn try_merge_trees(
410 match action { 167 lhs: &ast::UseTree,
411 ImportAction::AddNewUse { anchor, add_after_anchor } => { 168 rhs: &ast::UseTree,
412 make_assist_add_new_use(anchor, *add_after_anchor, target, edit) 169 merge: MergeBehaviour,
413 } 170) -> Option<ast::UseTree> {
414 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { 171 let lhs_path = lhs.path()?;
415 // We know that the fist n segments already exists in the use statement we want 172 let rhs_path = rhs.path()?;
416 // to modify, so we want to add only the last target.len() - n segments. 173
417 let segments_to_add = target.split_at(*common_segments).1; 174 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
418 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit) 175 let lhs = lhs.split_prefix(&lhs_prefix);
419 } 176 let rhs = rhs.split_prefix(&rhs_prefix);
420 ImportAction::AddNestedImport { 177 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged)
421 common_segments, 178}
422 path_to_split, 179
423 first_segment_to_split, 180/// Recursively "zips" together lhs and rhs.
424 add_self, 181fn recursive_merge(
425 } => { 182 lhs: &ast::UseTree,
426 let segments_to_add = target.split_at(*common_segments).1; 183 rhs: &ast::UseTree,
427 make_assist_add_nested_import( 184 merge: MergeBehaviour,
428 path_to_split, 185) -> Option<(ast::UseTree, bool)> {
429 first_segment_to_split, 186 let mut use_trees = lhs
430 segments_to_add, 187 .use_tree_list()
431 *add_self, 188 .into_iter()
432 edit, 189 .flat_map(|list| list.use_trees())
433 ) 190 // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this
191 // so early exit the iterator by using Option's Intoiterator impl
192 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() {
193 true => None,
194 false => Some(tree),
195 })
196 .collect::<Option<Vec<_>>>()?;
197 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path()));
198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
199 let rhs_path = rhs_t.path();
200 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) {
201 Ok(idx) => {
202 let lhs_t = &mut use_trees[idx];
203 let lhs_path = lhs_t.path()?;
204 let rhs_path = rhs_path?;
205 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
206 if lhs_prefix == lhs_path && rhs_prefix == rhs_path {
207 let tree_is_self = |tree: ast::UseTree| {
208 tree.path().as_ref().map(path_is_self).unwrap_or(false)
209 };
210 // check if only one of the two trees has a tree list, and whether that then contains `self` or not.
211 // If this is the case we can skip this iteration since the path without the list is already included in the other one via `self`
212 let tree_contains_self = |tree: &ast::UseTree| {
213 tree.use_tree_list()
214 .map(|tree_list| tree_list.use_trees().any(tree_is_self))
215 .unwrap_or(false)
216 };
217 match (tree_contains_self(&lhs_t), tree_contains_self(&rhs_t)) {
218 (true, false) => continue,
219 (false, true) => {
220 *lhs_t = rhs_t;
221 continue;
222 }
223 _ => (),
224 }
225
226 // glob imports arent part of the use-tree lists so we need to special handle them here as well
227 // this special handling is only required for when we merge a module import into a glob import of said module
228 // see the `merge_self_glob` or `merge_mod_into_glob` tests
229 if lhs_t.star_token().is_some() || rhs_t.star_token().is_some() {
230 *lhs_t = make::use_tree(
231 make::path_unqualified(make::path_segment_self()),
232 None,
233 None,
234 false,
235 );
236 use_trees.insert(idx, make::glob_use_tree());
237 continue;
238 }
239 }
240 let lhs = lhs_t.split_prefix(&lhs_prefix);
241 let rhs = rhs_t.split_prefix(&rhs_prefix);
242 let this_has_children = use_trees.len() > 0;
243 match recursive_merge(&lhs, &rhs, merge) {
244 Some((_, has_multiple_children))
245 if merge == MergeBehaviour::Last
246 && this_has_children
247 && has_multiple_children =>
248 {
249 return None
250 }
251 Some((use_tree, _)) => use_trees[idx] = use_tree,
252 None => use_trees.insert(idx, rhs_t),
253 }
254 }
255 Err(_)
256 if merge == MergeBehaviour::Last
257 && use_trees.len() > 0
258 && rhs_t.use_tree_list().is_some() =>
259 {
260 return None
261 }
262 Err(idx) => {
263 use_trees.insert(idx, rhs_t);
264 }
434 } 265 }
435 _ => {}
436 } 266 }
267 let has_multiple_children = use_trees.len() > 1;
268 Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children))
437} 269}
438 270
439fn make_assist_add_new_use( 271/// Traverses both paths until they differ, returning the common prefix of both.
440 anchor: &Option<SyntaxNode>, 272fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
441 after: bool, 273 let mut res = None;
442 target: &[SmolStr], 274 let mut lhs_curr = first_path(&lhs);
443 edit: &mut TextEditBuilder, 275 let mut rhs_curr = first_path(&rhs);
444) { 276 loop {
445 if let Some(anchor) = anchor { 277 match (lhs_curr.segment(), rhs_curr.segment()) {
446 let indent = leading_indent(anchor); 278 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
447 let mut buf = String::new(); 279 _ => break res,
448 if after {
449 buf.push_str("\n");
450 if let Some(spaces) = &indent {
451 buf.push_str(spaces);
452 }
453 } 280 }
454 buf.push_str("use "); 281 res = Some((lhs_curr.clone(), rhs_curr.clone()));
455 fmt_segments_raw(target, &mut buf); 282
456 buf.push_str(";"); 283 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
457 if !after { 284 Some((lhs, rhs)) => {
458 buf.push_str("\n\n"); 285 lhs_curr = lhs;
459 if let Some(spaces) = &indent { 286 rhs_curr = rhs;
460 buf.push_str(&spaces);
461 } 287 }
288 _ => break res,
462 } 289 }
463 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
464 edit.insert(position, buf);
465 } 290 }
466} 291}
467 292
468fn make_assist_add_in_tree_list( 293fn path_is_self(path: &ast::Path) -> bool {
469 tree_list: &ast::UseTreeList, 294 path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none()
470 target: &[SmolStr], 295}
471 add_self: bool, 296
472 edit: &mut TextEditBuilder, 297#[inline]
473) { 298fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
474 let last = tree_list.use_trees().last(); 299 first_path(path).segment()
475 if let Some(last) = last { 300}
476 let mut buf = String::new(); 301
477 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]); 302fn first_path(path: &ast::Path) -> ast::Path {
478 let offset = if let Some(comma) = comma { 303 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
479 comma.text_range().end() 304}
480 } else { 305
481 buf.push_str(","); 306fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
482 last.syntax().text_range().end() 307 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
483 }; 308 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
484 if add_self { 309}
485 buf.push_str(" self") 310
486 } else { 311/// Orders paths in the following way:
487 buf.push_str(" "); 312/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
313// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
314// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
315// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
316fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering {
317 match (path_is_self(a), path_is_self(b)) {
318 (true, true) => Ordering::Equal,
319 (true, false) => Ordering::Less,
320 (false, true) => Ordering::Greater,
321 (false, false) => {
322 let a = segment_iter(a);
323 let b = segment_iter(b);
324 // cmp_by would be useful for us here but that is currently unstable
325 // cmp doesnt work due the lifetimes on text's return type
326 a.zip(b)
327 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
328 .find_map(|(a, b)| match a.text().cmp(b.text()) {
329 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
330 Ordering::Equal => None,
331 })
332 .unwrap_or(Ordering::Equal)
488 } 333 }
489 fmt_segments_raw(target, &mut buf);
490 edit.insert(offset, buf);
491 } else {
492 } 334 }
493} 335}
494 336
495fn make_assist_add_nested_import( 337fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
496 path: &ast::Path, 338 match (a, b) {
497 first_segment_to_split: &Option<ast::PathSegment>, 339 (None, None) => Ordering::Equal,
498 target: &[SmolStr], 340 (None, Some(_)) => Ordering::Less,
499 add_self: bool, 341 (Some(_), None) => Ordering::Greater,
500 edit: &mut TextEditBuilder, 342 (Some(a), Some(b)) => path_cmp(&a, &b),
501) { 343 }
502 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); 344}
503 if let Some(use_tree) = use_tree { 345
504 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split 346/// What type of merges are allowed.
505 { 347#[derive(Copy, Clone, Debug, PartialEq, Eq)]
506 (first_segment_to_split.syntax().text_range().start(), false) 348pub enum MergeBehaviour {
507 } else { 349 /// Merge everything together creating deeply nested imports.
508 (use_tree.syntax().text_range().end(), true) 350 Full,
351 /// Only merge the last import level, doesn't allow import nesting.
352 Last,
353}
354
355#[derive(Eq, PartialEq, PartialOrd, Ord)]
356enum ImportGroup {
357 // the order here defines the order of new group inserts
358 Std,
359 ExternCrate,
360 ThisCrate,
361 ThisModule,
362 SuperModule,
363}
364
365impl ImportGroup {
366 fn new(path: &ast::Path) -> ImportGroup {
367 let default = ImportGroup::ExternCrate;
368
369 let first_segment = match first_segment(path) {
370 Some(it) => it,
371 None => return default,
509 }; 372 };
510 let end = use_tree.syntax().text_range().end();
511 373
512 let mut buf = String::new(); 374 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
513 if add_colon_colon { 375 match kind {
514 buf.push_str("::"); 376 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
377 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
378 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
379 PathSegmentKind::Name(name) => match name.text().as_str() {
380 "std" => ImportGroup::Std,
381 "core" => ImportGroup::Std,
382 // FIXME: can be ThisModule as well
383 _ => ImportGroup::ExternCrate,
384 },
385 PathSegmentKind::Type { .. } => unreachable!(),
515 } 386 }
516 buf.push_str("{");
517 if add_self {
518 buf.push_str("self, ");
519 }
520 fmt_segments_raw(target, &mut buf);
521 if !target.is_empty() {
522 buf.push_str(", ");
523 }
524 edit.insert(start, buf);
525 edit.insert(end, "}".to_string());
526 } 387 }
527} 388}
528 389
529/// If the node is on the beginning of the line, calculate indent. 390#[derive(PartialEq, Eq)]
530fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { 391enum AddBlankLine {
531 for token in prev_tokens(node.first_token()?) { 392 Before,
532 if let Some(ws) = ast::Whitespace::cast(token.clone()) { 393 BeforeTwice,
533 let ws_text = ws.text(); 394 Around,
534 if let Some(pos) = ws_text.rfind('\n') { 395 After,
535 return Some(ws_text[pos + 1..].into()); 396 AfterTwice,
397}
398
399fn find_insert_position(
400 scope: &ImportScope,
401 insert_path: ast::Path,
402) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
403 let group = ImportGroup::new(&insert_path);
404 let path_node_iter = scope
405 .as_syntax_node()
406 .children()
407 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
408 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
409 // Iterator that discards anything thats not in the required grouping
410 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
411 let group_iter = path_node_iter
412 .clone()
413 .skip_while(|(path, _)| ImportGroup::new(path) != group)
414 .take_while(|(path, _)| ImportGroup::new(path) == group);
415
416 let segments = segment_iter(&insert_path);
417 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
418 let mut last = None;
419 // find the element that would come directly after our new import
420 let post_insert =
421 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
422 let check_segments = segment_iter(&path);
423 segments
424 .clone()
425 .zip(check_segments)
426 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
427 .all(|(l, r)| l.text() <= r.text())
428 });
429 match post_insert {
430 // insert our import before that element
431 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
432 // there is no element after our new import, so append it to the end of the group
433 None => match last {
434 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
435 // the group we were looking for actually doesnt exist, so insert
436 None => {
437 // similar concept here to the `last` from above
438 let mut last = None;
439 // find the group that comes after where we want to insert
440 let post_group = path_node_iter
441 .inspect(|(_, node)| last = Some(node.clone()))
442 .find(|(p, _)| ImportGroup::new(p) > group);
443 match post_group {
444 Some((_, node)) => {
445 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
446 }
447 // there is no such group, so append after the last one
448 None => match last {
449 Some(node) => {
450 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
451 }
452 // there are no imports in this file at all
453 None => scope.insert_pos_after_inner_attribute(),
454 },
455 }
536 } 456 }
537 } 457 },
538 if token.text().contains('\n') {
539 break;
540 }
541 } 458 }
542 return None; 459}
543 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 460
544 successors(token.prev_token(), |token| token.prev_token()) 461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 use test_utils::assert_eq_text;
466
467 #[test]
468 fn insert_start() {
469 check_none(
470 "std::bar::AA",
471 r"
472use std::bar::B;
473use std::bar::D;
474use std::bar::F;
475use std::bar::G;",
476 r"
477use std::bar::AA;
478use std::bar::B;
479use std::bar::D;
480use std::bar::F;
481use std::bar::G;",
482 )
483 }
484
485 #[test]
486 fn insert_middle() {
487 check_none(
488 "std::bar::EE",
489 r"
490use std::bar::A;
491use std::bar::D;
492use std::bar::F;
493use std::bar::G;",
494 r"
495use std::bar::A;
496use std::bar::D;
497use std::bar::EE;
498use std::bar::F;
499use std::bar::G;",
500 )
501 }
502
503 #[test]
504 fn insert_end() {
505 check_none(
506 "std::bar::ZZ",
507 r"
508use std::bar::A;
509use std::bar::D;
510use std::bar::F;
511use std::bar::G;",
512 r"
513use std::bar::A;
514use std::bar::D;
515use std::bar::F;
516use std::bar::G;
517use std::bar::ZZ;",
518 )
519 }
520
521 #[test]
522 fn insert_middle_nested() {
523 check_none(
524 "std::bar::EE",
525 r"
526use std::bar::A;
527use std::bar::{D, Z}; // example of weird imports due to user
528use std::bar::F;
529use std::bar::G;",
530 r"
531use std::bar::A;
532use std::bar::EE;
533use std::bar::{D, Z}; // example of weird imports due to user
534use std::bar::F;
535use std::bar::G;",
536 )
537 }
538
539 #[test]
540 fn insert_middle_groups() {
541 check_none(
542 "foo::bar::GG",
543 r"
544use std::bar::A;
545use std::bar::D;
546
547use foo::bar::F;
548use foo::bar::H;",
549 r"
550use std::bar::A;
551use std::bar::D;
552
553use foo::bar::F;
554use foo::bar::GG;
555use foo::bar::H;",
556 )
557 }
558
559 #[test]
560 fn insert_first_matching_group() {
561 check_none(
562 "foo::bar::GG",
563 r"
564use foo::bar::A;
565use foo::bar::D;
566
567use std;
568
569use foo::bar::F;
570use foo::bar::H;",
571 r"
572use foo::bar::A;
573use foo::bar::D;
574use foo::bar::GG;
575
576use std;
577
578use foo::bar::F;
579use foo::bar::H;",
580 )
581 }
582
583 #[test]
584 fn insert_missing_group_std() {
585 check_none(
586 "std::fmt",
587 r"
588use foo::bar::A;
589use foo::bar::D;",
590 r"
591use std::fmt;
592
593use foo::bar::A;
594use foo::bar::D;",
595 )
596 }
597
598 #[test]
599 fn insert_missing_group_self() {
600 check_none(
601 "self::fmt",
602 r"
603use foo::bar::A;
604use foo::bar::D;",
605 r"
606use foo::bar::A;
607use foo::bar::D;
608
609use self::fmt;",
610 )
611 }
612
613 #[test]
614 fn insert_no_imports() {
615 check_full(
616 "foo::bar",
617 "fn main() {}",
618 r"use foo::bar;
619
620fn main() {}",
621 )
622 }
623
624 #[test]
625 fn insert_empty_file() {
626 // empty files will get two trailing newlines
627 // this is due to the test case insert_no_imports above
628 check_full(
629 "foo::bar",
630 "",
631 r"use foo::bar;
632
633",
634 )
635 }
636
637 #[test]
638 fn insert_after_inner_attr() {
639 check_full(
640 "foo::bar",
641 r"#![allow(unused_imports)]",
642 r"#![allow(unused_imports)]
643
644use foo::bar;",
645 )
646 }
647
648 #[test]
649 fn insert_after_inner_attr2() {
650 check_full(
651 "foo::bar",
652 r"#![allow(unused_imports)]
653
654fn main() {}",
655 r"#![allow(unused_imports)]
656
657use foo::bar;
658
659fn main() {}",
660 )
661 }
662
663 #[test]
664 fn merge_groups() {
665 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
666 }
667
668 #[test]
669 fn merge_groups_last() {
670 check_last(
671 "std::io",
672 r"use std::fmt::{Result, Display};",
673 r"use std::fmt::{Result, Display};
674use std::io;",
675 )
676 }
677
678 #[test]
679 fn merge_groups_full() {
680 check_full(
681 "std::io",
682 r"use std::fmt::{Result, Display};",
683 r"use std::{fmt::{Result, Display}, io};",
684 )
685 }
686
687 #[test]
688 fn merge_groups_long_full() {
689 check_full(
690 "std::foo::bar::Baz",
691 r"use std::foo::bar::Qux;",
692 r"use std::foo::bar::{Baz, Qux};",
693 )
694 }
695
696 #[test]
697 fn merge_groups_long_last() {
698 check_last(
699 "std::foo::bar::Baz",
700 r"use std::foo::bar::Qux;",
701 r"use std::foo::bar::{Baz, Qux};",
702 )
703 }
704
705 #[test]
706 fn merge_groups_long_full_list() {
707 check_full(
708 "std::foo::bar::Baz",
709 r"use std::foo::bar::{Qux, Quux};",
710 r"use std::foo::bar::{Baz, Quux, Qux};",
711 )
712 }
713
714 #[test]
715 fn merge_groups_long_last_list() {
716 check_last(
717 "std::foo::bar::Baz",
718 r"use std::foo::bar::{Qux, Quux};",
719 r"use std::foo::bar::{Baz, Quux, Qux};",
720 )
721 }
722
723 #[test]
724 fn merge_groups_long_full_nested() {
725 check_full(
726 "std::foo::bar::Baz",
727 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
728 r"use std::foo::bar::{Baz, Qux, quux::{Fez, Fizz}};",
729 )
730 }
731
732 #[test]
733 fn merge_groups_long_last_nested() {
734 check_last(
735 "std::foo::bar::Baz",
736 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
737 r"use std::foo::bar::Baz;
738use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
739 )
740 }
741
742 #[test]
743 fn merge_groups_full_nested_deep() {
744 check_full(
745 "std::foo::bar::quux::Baz",
746 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
747 r"use std::foo::bar::{Qux, quux::{Baz, Fez, Fizz}};",
748 )
749 }
750
751 #[test]
752 fn merge_groups_skip_pub() {
753 check_full(
754 "std::io",
755 r"pub use std::fmt::{Result, Display};",
756 r"pub use std::fmt::{Result, Display};
757use std::io;",
758 )
759 }
760
761 #[test]
762 fn merge_groups_skip_pub_crate() {
763 check_full(
764 "std::io",
765 r"pub(crate) use std::fmt::{Result, Display};",
766 r"pub(crate) use std::fmt::{Result, Display};
767use std::io;",
768 )
769 }
770
771 #[test]
772 #[ignore] // FIXME: Support this
773 fn split_out_merge() {
774 check_last(
775 "std::fmt::Result",
776 r"use std::{fmt, io};",
777 r"use std::fmt::{self, Result};
778use std::io;",
779 )
780 }
781
782 #[test]
783 fn merge_into_module_import() {
784 check_full(
785 "std::fmt::Result",
786 r"use std::{fmt, io};",
787 r"use std::{fmt::{self, Result}, io};",
788 )
789 }
790
791 #[test]
792 fn merge_groups_self() {
793 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
794 }
795
796 #[test]
797 fn merge_mod_into_glob() {
798 check_full(
799 "token::TokenKind",
800 r"use token::TokenKind::*;",
801 r"use token::TokenKind::{*, self};",
802 )
803 // FIXME: have it emit `use token::TokenKind::{self, *}`?
804 }
805
806 #[test]
807 fn merge_self_glob() {
808 check_full("self", r"use self::*;", r"use self::{*, self};")
809 // FIXME: have it emit `use {self, *}`?
810 }
811
812 #[test]
813 #[ignore] // FIXME: Support this
814 fn merge_partial_path() {
815 check_full(
816 "ast::Foo",
817 r"use syntax::{ast, algo};",
818 r"use syntax::{ast::{self, Foo}, algo};",
819 )
820 }
821
822 #[test]
823 fn merge_glob_nested() {
824 check_full(
825 "foo::bar::quux::Fez",
826 r"use foo::bar::{Baz, quux::*};",
827 r"use foo::bar::{Baz, quux::{self::*, Fez}};",
828 )
829 }
830
831 #[test]
832 fn merge_last_too_long() {
833 check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};");
834 }
835
836 #[test]
837 fn insert_short_before_long() {
838 check_none(
839 "foo::bar",
840 r"use foo::bar::baz::Qux;",
841 r"use foo::bar;
842use foo::bar::baz::Qux;",
843 );
844 }
845
846 #[test]
847 fn merge_last_fail() {
848 check_merge_only_fail(
849 r"use foo::bar::{baz::{Qux, Fez}};",
850 r"use foo::bar::{baaz::{Quux, Feez}};",
851 MergeBehaviour::Last,
852 );
853 }
854
855 #[test]
856 fn merge_last_fail1() {
857 check_merge_only_fail(
858 r"use foo::bar::{baz::{Qux, Fez}};",
859 r"use foo::bar::baaz::{Quux, Feez};",
860 MergeBehaviour::Last,
861 );
862 }
863
864 #[test]
865 fn merge_last_fail2() {
866 check_merge_only_fail(
867 r"use foo::bar::baz::{Qux, Fez};",
868 r"use foo::bar::{baaz::{Quux, Feez}};",
869 MergeBehaviour::Last,
870 );
871 }
872
873 #[test]
874 fn merge_last_fail3() {
875 check_merge_only_fail(
876 r"use foo::bar::baz::{Qux, Fez};",
877 r"use foo::bar::baaz::{Quux, Feez};",
878 MergeBehaviour::Last,
879 );
880 }
881
882 fn check(
883 path: &str,
884 ra_fixture_before: &str,
885 ra_fixture_after: &str,
886 mb: Option<MergeBehaviour>,
887 ) {
888 let file = super::ImportScope::from(
889 ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(),
890 )
891 .unwrap();
892 let path = ast::SourceFile::parse(&format!("use {};", path))
893 .tree()
894 .syntax()
895 .descendants()
896 .find_map(ast::Path::cast)
897 .unwrap();
898
899 let result = insert_use(&file, path, mb).to_string();
900 assert_eq_text!(&result, ra_fixture_after);
901 }
902
903 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
904 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
905 }
906
907 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
908 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
909 }
910
911 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
912 check(path, ra_fixture_before, ra_fixture_after, None)
913 }
914
915 fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehaviour) {
916 let use0 = ast::SourceFile::parse(ra_fixture0)
917 .tree()
918 .syntax()
919 .descendants()
920 .find_map(ast::Use::cast)
921 .unwrap();
922
923 let use1 = ast::SourceFile::parse(ra_fixture1)
924 .tree()
925 .syntax()
926 .descendants()
927 .find_map(ast::Use::cast)
928 .unwrap();
929
930 let result = try_merge_imports(&use0, &use1, mb);
931 assert_eq!(result.map(|u| u.to_string()), None);
545 } 932 }
546} 933}