aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-09-04 14:28:05 +0100
committerGitHub <[email protected]>2020-09-04 14:28:05 +0100
commit9dfa69a44ac5054b5c970fdd13ade4d50f4c097d (patch)
tree58b1f9069d022c985ef57a74212fcefd03a877c3 /crates
parent8a21b2c96acb351baa81422f6633462b5db2298a (diff)
parent82f61e6629f709d7f347fd801ef5c31f476ff29e (diff)
Merge #5935
5935: Rewrite import insertion r=matklad a=Veykril This is my attempt at refactoring the import insertion #3947. I hope what I created here is somewhat in line with what was requested, it wouldn't surprise me . `common_prefix` is a copy from `merge_imports.rs` so those should be unified somewhere, `try_merge_trees` is also copied from there but slighly modified to take the `MergeBehaviour` enum into account. `MergeBehaviour` should in the end become a configuration option, and the order if `ImportGroup` probably as well? I'm not too familiar with the assist stuff and the like which is why I dont know what i have to do with `insert_use_statement` and `find_insert_use_container` for now. I will most likely add more test cases in the end as well as I currently only tried to hit every path in `find_insert_position`. Some of the merge tests also fail atm due to them not sorting what they insert. There is also this test case I'm not sure if we want to support it. I would assume we want to? https://github.com/rust-analyzer/rust-analyzer/pull/5935/files#diff-6923916dd8bdd2f1ab4b984adacd265fR540-R547 The entire module was rewritten so looking at the the file itself is probably better than looking at the diff. Regarding the sub issues of #3947: - #3301: This is fixed with the rewrite, what this implementation does is that it scans through the first occurence of groupings and picks the appropriate one out. This means the user can actually rearrange the groupings on a per file basis to their liking. If a group isnt being found it is inserted according to the `ImportGroup` variant order(Would be nice if this was configurable I imagine). - #3831: This should be fixed with the introduced `MergeBehaviour` enum and it's `Last` variant. - #3946: This should also be [fixed](https://github.com/rust-analyzer/rust-analyzer/pull/5935/files#diff-6923916dd8bdd2f1ab4b984adacd265fR87) - #5795: This is fixed in the sense that the grouping search picks the first group that is of the same kind as the import that is being added. So if there is a random import in the middle of the program it should only be considered if there is no group of the same kind in the file already present. - the last point in the list I havent checked yet, tho I got the feeling that it's not gonna be too simple as that will require knowledge of whether in this example `ast` is a crate or the module that is already imported. Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/assists/src/handlers/auto_import.rs20
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs33
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs68
-rw-r--r--crates/assists/src/utils.rs2
-rw-r--r--crates/assists/src/utils/insert_use.rs1144
-rw-r--r--crates/syntax/src/ast/make.rs12
6 files changed, 744 insertions, 535 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index c4770f336..66e819154 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,11 +1,13 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use ast::make;
3use either::Either; 4use either::Either;
4use hir::{ 5use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 6 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type, 7 Type,
7}; 8};
8use ide_db::{imports_locator, RootDatabase}; 9use ide_db::{imports_locator, RootDatabase};
10use insert_use::ImportScope;
9use rustc_hash::FxHashSet; 11use rustc_hash::FxHashSet;
10use syntax::{ 12use syntax::{
11 ast::{self, AstNode}, 13 ast::{self, AstNode},
@@ -13,7 +15,8 @@ use syntax::{
13}; 15};
14 16
15use crate::{ 17use crate::{
16 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel, 18 utils::{insert_use, MergeBehaviour},
19 AssistContext, AssistId, AssistKind, Assists, GroupLabel,
17}; 20};
18 21
19// Assist: auto_import 22// Assist: auto_import
@@ -44,6 +47,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
44 47
45 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; 48 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
46 let group = auto_import_assets.get_import_group_message(); 49 let group = auto_import_assets.get_import_group_message();
50 let scope =
51 ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?;
52 let syntax = scope.as_syntax_node();
47 for import in proposed_imports { 53 for import in proposed_imports {
48 acc.add_group( 54 acc.add_group(
49 &group, 55 &group,
@@ -51,12 +57,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
51 format!("Import `{}`", &import), 57 format!("Import `{}`", &import),
52 range, 58 range,
53 |builder| { 59 |builder| {
54 insert_use_statement( 60 let new_syntax = insert_use(
55 &auto_import_assets.syntax_under_caret, 61 &scope,
56 &import.to_string(), 62 make::path_from_text(&import.to_string()),
57 ctx, 63 Some(MergeBehaviour::Full),
58 builder.text_edit_builder(),
59 ); 64 );
65 builder.replace(syntax.text_range(), new_syntax.to_string())
60 }, 66 },
61 ); 67 );
62 } 68 }
@@ -358,7 +364,7 @@ mod tests {
358 } 364 }
359 ", 365 ",
360 r" 366 r"
361 use PubMod::{PubStruct2, PubStruct1}; 367 use PubMod::{PubStruct1, PubStruct2};
362 368
363 struct Test { 369 struct Test {
364 test: PubStruct2<u8>, 370 test: PubStruct2<u8>,
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 8ac20210a..80c62d8bb 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,9 +10,12 @@ use syntax::{
10}; 10};
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, 13 assist_context::AssistBuilder,
14 AssistKind, Assists, 14 utils::{insert_use, MergeBehaviour},
15 AssistContext, AssistId, AssistKind, Assists,
15}; 16};
17use ast::make;
18use insert_use::ImportScope;
16 19
17// Assist: extract_struct_from_enum_variant 20// Assist: extract_struct_from_enum_variant
18// 21//
@@ -94,6 +97,7 @@ fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVari
94 .any(|(name, _)| name.to_string() == variant_name.to_string()) 97 .any(|(name, _)| name.to_string() == variant_name.to_string())
95} 98}
96 99
100#[allow(dead_code)]
97fn insert_import( 101fn insert_import(
98 ctx: &AssistContext, 102 ctx: &AssistContext,
99 builder: &mut AssistBuilder, 103 builder: &mut AssistBuilder,
@@ -107,12 +111,16 @@ fn insert_import(
107 if let Some(mut mod_path) = mod_path { 111 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop(); 112 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone()); 113 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement( 114 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
111 path.syntax(), 115 let syntax = scope.as_syntax_node();
112 &mod_path.to_string(), 116
113 ctx, 117 let new_syntax = insert_use(
114 builder.text_edit_builder(), 118 &scope,
119 make::path_from_text(&mod_path.to_string()),
120 Some(MergeBehaviour::Full),
115 ); 121 );
122 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
123 builder.replace(syntax.text_range(), new_syntax.to_string())
116 } 124 }
117 Some(()) 125 Some(())
118} 126}
@@ -167,9 +175,9 @@ fn update_reference(
167 builder: &mut AssistBuilder, 175 builder: &mut AssistBuilder,
168 reference: Reference, 176 reference: Reference,
169 source_file: &SourceFile, 177 source_file: &SourceFile,
170 enum_module_def: &ModuleDef, 178 _enum_module_def: &ModuleDef,
171 variant_hir_name: &Name, 179 _variant_hir_name: &Name,
172 visited_modules_set: &mut FxHashSet<Module>, 180 _visited_modules_set: &mut FxHashSet<Module>,
173) -> Option<()> { 181) -> Option<()> {
174 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>( 182 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
175 source_file.syntax(), 183 source_file.syntax(),
@@ -178,13 +186,14 @@ fn update_reference(
178 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 186 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
179 let list = call.arg_list()?; 187 let list = call.arg_list()?;
180 let segment = path_expr.path()?.segment()?; 188 let segment = path_expr.path()?.segment()?;
181 let module = ctx.sema.scope(&path_expr.syntax()).module()?; 189 let _module = ctx.sema.scope(&path_expr.syntax()).module()?;
182 let list_range = list.syntax().text_range(); 190 let list_range = list.syntax().text_range();
183 let inside_list_range = TextRange::new( 191 let inside_list_range = TextRange::new(
184 list_range.start().checked_add(TextSize::from(1))?, 192 list_range.start().checked_add(TextSize::from(1))?,
185 list_range.end().checked_sub(TextSize::from(1))?, 193 list_range.end().checked_sub(TextSize::from(1))?,
186 ); 194 );
187 builder.edit_file(reference.file_range.file_id); 195 builder.edit_file(reference.file_range.file_id);
196 /* FIXME: this most likely requires AST-based editing, see `insert_import`
188 if !visited_modules_set.contains(&module) { 197 if !visited_modules_set.contains(&module) {
189 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) 198 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
190 .is_some() 199 .is_some()
@@ -192,6 +201,7 @@ fn update_reference(
192 visited_modules_set.insert(module); 201 visited_modules_set.insert(module);
193 } 202 }
194 } 203 }
204 */
195 builder.replace(inside_list_range, format!("{}{}", segment, list)); 205 builder.replace(inside_list_range, format!("{}{}", segment, list));
196 Some(()) 206 Some(())
197} 207}
@@ -250,6 +260,7 @@ pub enum A { One(One) }"#,
250 } 260 }
251 261
252 #[test] 262 #[test]
263 #[ignore] // FIXME: this currently panics if `insert_import` is used
253 fn test_extract_struct_with_complex_imports() { 264 fn test_extract_struct_with_complex_imports() {
254 check_assist( 265 check_assist(
255 extract_struct_from_enum_variant, 266 extract_struct_from_enum_variant,
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index 470e5f8ff..85c70d16b 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -2,9 +2,10 @@ use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRang
2use test_utils::mark; 2use test_utils::mark;
3 3
4use crate::{ 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement}, 5 utils::{insert_use, ImportScope, MergeBehaviour},
6 AssistContext, AssistId, AssistKind, Assists, 6 AssistContext, AssistId, AssistKind, Assists,
7}; 7};
8use ast::make;
8 9
9// Assist: replace_qualified_name_with_use 10// Assist: replace_qualified_name_with_use
10// 11//
@@ -32,7 +33,7 @@ pub(crate) fn replace_qualified_name_with_use(
32 mark::hit!(dont_import_trivial_paths); 33 mark::hit!(dont_import_trivial_paths);
33 return None; 34 return None;
34 } 35 }
35 let path_to_import = path.to_string().clone(); 36 let path_to_import = path.to_string();
36 let path_to_import = match path.segment()?.generic_arg_list() { 37 let path_to_import = match path.segment()?.generic_arg_list() {
37 Some(generic_args) => { 38 Some(generic_args) => {
38 let generic_args_start = 39 let generic_args_start =
@@ -43,28 +44,26 @@ pub(crate) fn replace_qualified_name_with_use(
43 }; 44 };
44 45
45 let target = path.syntax().text_range(); 46 let target = path.syntax().text_range();
47 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
48 let syntax = scope.as_syntax_node();
46 acc.add( 49 acc.add(
47 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 50 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
48 "Replace qualified path with use", 51 "Replace qualified path with use",
49 target, 52 target,
50 |builder| { 53 |builder| {
51 let container = match find_insert_use_container(path.syntax(), ctx) {
52 Some(c) => c,
53 None => return,
54 };
55 insert_use_statement(
56 path.syntax(),
57 &path_to_import.to_string(),
58 ctx,
59 builder.text_edit_builder(),
60 );
61
62 // Now that we've brought the name into scope, re-qualify all paths that could be 54 // Now that we've brought the name into scope, re-qualify all paths that could be
63 // affected (that is, all paths inside the node we added the `use` to). 55 // affected (that is, all paths inside the node we added the `use` to).
64 let mut rewriter = SyntaxRewriter::default(); 56 let mut rewriter = SyntaxRewriter::default();
65 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); 57 shorten_paths(&mut rewriter, syntax.clone(), path);
66 shorten_paths(&mut rewriter, syntax, path); 58 let rewritten_syntax = rewriter.rewrite(&syntax);
67 builder.rewrite(rewriter); 59 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
60 let new_syntax = insert_use(
61 import_scope,
62 make::path_from_text(path_to_import),
63 Some(MergeBehaviour::Full),
64 );
65 builder.replace(syntax.text_range(), new_syntax.to_string())
66 }
68 }, 67 },
69 ) 68 )
70} 69}
@@ -220,9 +219,10 @@ impl std::fmt::Debug<|> for Foo {
220} 219}
221 ", 220 ",
222 r" 221 r"
223use stdx;
224use std::fmt::Debug; 222use std::fmt::Debug;
225 223
224use stdx;
225
226impl Debug for Foo { 226impl Debug for Foo {
227} 227}
228 ", 228 ",
@@ -274,7 +274,7 @@ impl std::io<|> for Foo {
274} 274}
275 ", 275 ",
276 r" 276 r"
277use std::{io, fmt}; 277use std::{fmt, io};
278 278
279impl io for Foo { 279impl io for Foo {
280} 280}
@@ -293,7 +293,7 @@ impl std::fmt::Debug<|> for Foo {
293} 293}
294 ", 294 ",
295 r" 295 r"
296use std::fmt::{self, Debug, }; 296use std::fmt::{self, Debug};
297 297
298impl Debug for Foo { 298impl Debug for Foo {
299} 299}
@@ -312,7 +312,7 @@ impl std::fmt<|> for Foo {
312} 312}
313 ", 313 ",
314 r" 314 r"
315use std::fmt::{self, Debug}; 315use std::fmt::{Debug, self};
316 316
317impl fmt for Foo { 317impl fmt for Foo {
318} 318}
@@ -330,8 +330,9 @@ use std::fmt::{Debug, nested::{Display}};
330impl std::fmt::nested<|> for Foo { 330impl std::fmt::nested<|> for Foo {
331} 331}
332", 332",
333 // FIXME(veykril): should be nested::{self, Display} here
333 r" 334 r"
334use std::fmt::{Debug, nested::{Display, self}}; 335use std::fmt::{Debug, nested::{Display}, nested};
335 336
336impl nested for Foo { 337impl nested for Foo {
337} 338}
@@ -349,8 +350,9 @@ use std::fmt::{Debug, nested::{self, Display}};
349impl std::fmt::nested<|> for Foo { 350impl std::fmt::nested<|> for Foo {
350} 351}
351", 352",
353 // FIXME(veykril): nested is duplicated now
352 r" 354 r"
353use std::fmt::{Debug, nested::{self, Display}}; 355use std::fmt::{Debug, nested::{self, Display}, nested};
354 356
355impl nested for Foo { 357impl nested for Foo {
356} 358}
@@ -369,7 +371,7 @@ impl std::fmt::nested::Debug<|> for Foo {
369} 371}
370", 372",
371 r" 373 r"
372use std::fmt::{Debug, nested::{Display, Debug}}; 374use std::fmt::{Debug, nested::{Display}, nested::Debug};
373 375
374impl Debug for Foo { 376impl Debug for Foo {
375} 377}
@@ -388,7 +390,7 @@ impl std::fmt::nested::Display<|> for Foo {
388} 390}
389", 391",
390 r" 392 r"
391use std::fmt::{nested::Display, Debug}; 393use std::fmt::{Debug, nested::Display};
392 394
393impl Display for Foo { 395impl Display for Foo {
394} 396}
@@ -407,7 +409,7 @@ impl std::fmt::Display<|> for Foo {
407} 409}
408", 410",
409 r" 411 r"
410use std::fmt::{Display, nested::Debug}; 412use std::fmt::{nested::Debug, Display};
411 413
412impl Display for Foo { 414impl Display for Foo {
413} 415}
@@ -427,11 +429,12 @@ use crate::{
427 429
428fn foo() { crate::ty::lower<|>::trait_env() } 430fn foo() { crate::ty::lower<|>::trait_env() }
429", 431",
432 // FIXME(veykril): formatting broke here
430 r" 433 r"
431use crate::{ 434use crate::{
432 ty::{Substs, Ty, lower}, 435 ty::{Substs, Ty},
433 AssocItem, 436 AssocItem,
434}; 437ty::lower};
435 438
436fn foo() { lower::trait_env() } 439fn foo() { lower::trait_env() }
437", 440",
@@ -451,6 +454,8 @@ impl foo::Debug<|> for Foo {
451 r" 454 r"
452use std::fmt as foo; 455use std::fmt as foo;
453 456
457use foo::Debug;
458
454impl Debug for Foo { 459impl Debug for Foo {
455} 460}
456", 461",
@@ -515,6 +520,7 @@ fn main() {
515 ", 520 ",
516 r" 521 r"
517#![allow(dead_code)] 522#![allow(dead_code)]
523
518use std::fmt::Debug; 524use std::fmt::Debug;
519 525
520fn main() { 526fn main() {
@@ -627,7 +633,7 @@ fn main() {
627} 633}
628 ", 634 ",
629 r" 635 r"
630use std::fmt::{self, Display}; 636use std::fmt::{Display, self};
631 637
632fn main() { 638fn main() {
633 fmt; 639 fmt;
@@ -647,9 +653,8 @@ impl std::io<|> for Foo {
647} 653}
648 ", 654 ",
649 r" 655 r"
650use std::io;
651
652pub use std::fmt; 656pub use std::fmt;
657use std::io;
653 658
654impl io for Foo { 659impl io for Foo {
655} 660}
@@ -668,9 +673,8 @@ impl std::io<|> for Foo {
668} 673}
669 ", 674 ",
670 r" 675 r"
671use std::io;
672
673pub(crate) use std::fmt; 676pub(crate) use std::fmt;
677use std::io;
674 678
675impl io for Foo { 679impl io for Foo {
676} 680}
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index daa7b64f7..7559ddd63 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -16,7 +16,7 @@ use syntax::{
16 16
17use crate::assist_config::SnippetCap; 17use crate::assist_config::SnippetCap;
18 18
19pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; 19pub(crate) use insert_use::{insert_use, ImportScope, MergeBehaviour};
20 20
21pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { 21pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
22 extract_trivial_expression(&block) 22 extract_trivial_expression(&block)
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 49096a67c..8a4c8520d 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,546 +1,724 @@
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::iter::{self, successors};
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4 3
5use std::iter::successors; 4use algo::skip_trivia_token;
6 5use ast::{
7use either::Either; 6 edit::{AstNodeEdit, IndentLevel},
7 PathSegmentKind, VisibilityOwner,
8};
8use syntax::{ 9use syntax::{
9 ast::{self, NameOwner, VisibilityOwner}, 10 algo,
10 AstNode, AstToken, Direction, SmolStr, 11 ast::{self, make, AstNode},
11 SyntaxKind::{PATH, PATH_SEGMENT}, 12 Direction, InsertPosition, SyntaxElement, SyntaxNode, T,
12 SyntaxNode, SyntaxToken, T,
13}; 13};
14use text_edit::TextEditBuilder; 14use test_utils::mark;
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 15
31/// Creates and inserts a use statement for the given path to import. 16#[derive(Debug)]
32/// The use statement is inserted in the scope most appropriate to the 17pub enum ImportScope {
33/// the cursor position given, additionally merged with the existing use imports. 18 File(ast::SourceFile),
34pub(crate) fn insert_use_statement( 19 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} 20}
50 21
51fn collect_path_segments_raw( 22impl ImportScope {
52 segments: &mut Vec<ast::PathSegment>, 23 pub fn from(syntax: SyntaxNode) -> Option<Self> {
53 mut path: ast::Path, 24 if let Some(module) = ast::Module::cast(syntax.clone()) {
54) -> Option<usize> { 25 module.item_list().map(ImportScope::Module)
55 let oldlen = segments.len(); 26 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) {
56 loop { 27 this.map(ImportScope::File)
57 let mut children = path.syntax().children_with_tokens(); 28 } else {
58 let (first, second, third) = ( 29 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 } 30 }
74 } 31 }
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 32
81fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 33 /// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
82 let mut iter = segments.iter(); 34 pub(crate) fn find_insert_use_container(
83 if let Some(s) = iter.next() { 35 position: &SyntaxNode,
84 buf.push_str(s); 36 ctx: &crate::assist_context::AssistContext,
85 } 37 ) -> Option<Self> {
86 for s in iter { 38 ctx.sema.ancestors_with_macros(position.clone()).find_map(Self::from)
87 buf.push_str("::");
88 buf.push_str(s);
89 } 39 }
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 40
97fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 41 pub(crate) fn as_syntax_node(&self) -> &SyntaxNode {
98 if let Some(kb) = b.kind() { 42 match self {
99 match kb { 43 ImportScope::File(file) => file.syntax(),
100 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), 44 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 } 45 }
106 } else {
107 false
108 } 46 }
109}
110
111fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
112 a == b.text()
113}
114
115#[derive(Clone, Debug)]
116enum ImportAction {
117 Nothing,
118 // Add a brand new use statement.
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 47
143impl ImportAction { 48 fn indent_level(&self) -> IndentLevel {
144 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self { 49 match self {
145 ImportAction::AddNewUse { anchor, add_after_anchor } 50 ImportScope::File(file) => file.indent_level(),
146 } 51 ImportScope::Module(item_list) => item_list.indent_level() + 1,
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 } 52 }
160 } 53 }
161 54
162 fn add_in_tree_list( 55 fn first_insert_pos(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
163 common_segments: usize, 56 match self {
164 tree_list: ast::UseTreeList, 57 ImportScope::File(_) => (InsertPosition::First, AddBlankLine::AfterTwice),
165 add_self: bool, 58 // don't insert the imports before the item list's opening curly brace
166 ) -> Self { 59 ImportScope::Module(item_list) => item_list
167 ImportAction::AddInTreeList { common_segments, tree_list, add_self } 60 .l_curly_token()
168 } 61 .map(|b| (InsertPosition::After(b.into()), AddBlankLine::Around))
169 62 .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 } 63 }
176 } 64 }
177 65
178 fn is_better(&self, other: &ImportAction) -> bool { 66 fn insert_pos_after_inner_attribute(&self) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
179 match (self, other) { 67 // check if the scope has inner attributes, we dont want to insert in front of them
180 (ImportAction::Nothing, _) => true, 68 match self
181 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, 69 .as_syntax_node()
182 ( 70 .children()
183 ImportAction::AddNestedImport { common_segments: n, .. }, 71 // no flat_map here cause we want to short circuit the iterator
184 ImportAction::AddInTreeList { common_segments: m, .. }, 72 .map(ast::Attr::cast)
185 ) 73 .take_while(|attr| {
186 | ( 74 attr.as_ref().map(|attr| attr.kind() == ast::AttrKind::Inner).unwrap_or(false)
187 ImportAction::AddInTreeList { common_segments: n, .. }, 75 })
188 ImportAction::AddNestedImport { common_segments: m, .. }, 76 .last()
189 ) 77 .flatten()
190 | ( 78 {
191 ImportAction::AddInTreeList { common_segments: n, .. }, 79 Some(attr) => {
192 ImportAction::AddInTreeList { common_segments: m, .. }, 80 (InsertPosition::After(attr.syntax().clone().into()), AddBlankLine::BeforeTwice)
193 ) 81 }
194 | ( 82 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 } 83 }
203 } 84 }
204} 85}
205 86
206// Find out the best ImportAction to import target path against current_use_tree. 87/// 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. 88pub(crate) fn insert_use(
208fn walk_use_tree_for_best_action( 89 scope: &ImportScope,
209 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments 90 path: ast::Path,
210 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import 91 merge: Option<MergeBehaviour>,
211 current_use_tree: ast::UseTree, // the use tree we are currently examinating 92) -> SyntaxNode {
212 target: &[SmolStr], // the path we want to import 93 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
213) -> ImportAction { 94 // merge into existing imports if possible
214 // We save the number of segments in the buffer so we can restore the correct segments 95 if let Some(mb) = merge {
215 // before returning. Recursive call will add segments so we need to delete them. 96 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
216 let prev_len = current_path_segments.len(); 97 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
217 98 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
218 let tree_list = current_use_tree.use_tree_list(); 99 let to_delete = to_delete.clone()..=to_delete;
219 let alias = current_use_tree.rename(); 100 let to_insert = iter::once(merged.syntax().clone().into());
220 101 return algo::replace_children(scope.as_syntax_node(), to_delete, to_insert);
221 let path = match current_use_tree.path() { 102 }
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 } 103 }
241 } 104 }
242 105
243 collect_path_segments_raw(current_path_segments, path.clone()); 106 // either we weren't allowed to merge or there is no import that fits the merge conditions
244 107 // so look for the place we have to insert to
245 // We compare only the new segments added in the line just above. 108 let (insert_position, add_blank) = find_insert_position(scope, path);
246 // The first prev_len segments were already compared in 'parent' recursive calls. 109
247 let left = target.split_at(prev_len).1; 110 let to_insert: Vec<SyntaxElement> = {
248 let right = current_path_segments.split_at(prev_len).1; 111 let mut buf = Vec::new();
249 let common = compare_path_segments(left, &right); 112
250 let mut action = match common { 113 match add_blank {
251 0 => ImportAction::add_new_use( 114 AddBlankLine::Before | AddBlankLine::Around => {
252 // e.g: target is std::fmt and we can have 115 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 } 116 }
117 AddBlankLine::BeforeTwice => buf.push(make::tokens::blank_line().into()),
118 _ => (),
285 } 119 }
286 common if common != left.len() && left.len() == right.len() => { 120
287 // e.g: target is std::fmt and we have 121 if let ident_level @ 1..=usize::MAX = scope.indent_level().0 as usize {
288 // use std::io; 122 // FIXME: this alone doesnt properly re-align all cases
289 // We need to split. 123 buf.push(make::tokens::whitespace(&" ".repeat(4 * ident_level)).into());
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()),
333 } 132 _ => (),
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 } 133 }
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 try_merge_imports(
374 container: SyntaxNode, 142 old: &ast::Use,
375 anchor: SyntaxNode, 143 new: &ast::Use,
376 target: &[SmolStr], 144 merge_behaviour: MergeBehaviour,
377) -> ImportAction { 145) -> Option<ast::Use> {
378 let mut storage = Vec::with_capacity(16); // this should be the only allocation 146 // don't merge into re-exports
379 let best_action = container 147 if old.visibility().and_then(|vis| vis.pub_token()).is_some() {
380 .children() 148 return None;
381 .filter_map(ast::Use::cast) 149 }
382 .filter(|u| u.visibility().is_none()) 150 let old_tree = old.use_tree()?;
383 .filter_map(|it| it.use_tree()) 151 let new_tree = new.use_tree()?;
384 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) 152 let merged = try_merge_trees(&old_tree, &new_tree, merge_behaviour)?;
385 .fold(None, |best, a| match best { 153 Some(old.with_use_tree(merged))
386 Some(best) => Some(ImportAction::better(best, a)), 154}
387 None => Some(a),
388 });
389 155
390 match best_action { 156/// Simple function that checks if a UseTreeList is deeper than one level
391 Some(action) => action, 157fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool {
392 None => { 158 tl.use_trees().any(|use_tree| {
393 // We have no action and no UseItem was found in container so we find 159 use_tree.use_tree_list().is_some() || use_tree.path().and_then(|p| p.qualifier()).is_some()
394 // another item and we use it as anchor. 160 })
395 // If there are no items above, we choose the target path itself as anchor. 161}
396 // todo: we should include even whitespace blocks as anchor candidates
397 let anchor = container.children().next().or_else(|| Some(anchor));
398 162
399 let add_after_anchor = anchor 163// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion
400 .clone() 164pub fn try_merge_trees(
401 .and_then(ast::Attr::cast) 165 old: &ast::UseTree,
402 .map(|attr| attr.kind() == ast::AttrKind::Inner) 166 new: &ast::UseTree,
403 .unwrap_or(false); 167 merge_behaviour: MergeBehaviour,
404 ImportAction::add_new_use(anchor, add_after_anchor) 168) -> Option<ast::UseTree> {
405 } 169 let lhs_path = old.path()?;
170 let rhs_path = new.path()?;
171
172 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
173 let lhs = old.split_prefix(&lhs_prefix);
174 let rhs = new.split_prefix(&rhs_prefix);
175 let lhs_tl = lhs.use_tree_list()?;
176 let rhs_tl = rhs.use_tree_list()?;
177
178 // if we are only allowed to merge the last level check if the split off paths are only one level deep
179 if merge_behaviour == MergeBehaviour::Last
180 && (use_tree_list_is_nested(&lhs_tl) || use_tree_list_is_nested(&rhs_tl))
181 {
182 mark::hit!(test_last_merge_too_long);
183 return None;
406 } 184 }
407}
408 185
409fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { 186 let should_insert_comma = lhs_tl
410 match action { 187 .r_curly_token()
411 ImportAction::AddNewUse { anchor, add_after_anchor } => { 188 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
412 make_assist_add_new_use(anchor, *add_after_anchor, target, edit) 189 .map(|it| it.kind())
413 } 190 != Some(T![,]);
414 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => { 191 let mut to_insert: Vec<SyntaxElement> = Vec::new();
415 // We know that the fist n segments already exists in the use statement we want 192 if should_insert_comma {
416 // to modify, so we want to add only the last target.len() - n segments. 193 to_insert.push(make::token(T![,]).into());
417 let segments_to_add = target.split_at(*common_segments).1; 194 to_insert.push(make::tokens::single_space().into());
418 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
419 }
420 ImportAction::AddNestedImport {
421 common_segments,
422 path_to_split,
423 first_segment_to_split,
424 add_self,
425 } => {
426 let segments_to_add = target.split_at(*common_segments).1;
427 make_assist_add_nested_import(
428 path_to_split,
429 first_segment_to_split,
430 segments_to_add,
431 *add_self,
432 edit,
433 )
434 }
435 _ => {}
436 } 195 }
196 to_insert.extend(
197 rhs_tl.syntax().children_with_tokens().filter(|it| !matches!(it.kind(), T!['{'] | T!['}'])),
198 );
199 let pos = InsertPosition::Before(lhs_tl.r_curly_token()?.into());
200 let use_tree_list = lhs_tl.insert_children(pos, to_insert);
201 Some(lhs.with_use_tree_list(use_tree_list))
437} 202}
438 203
439fn make_assist_add_new_use( 204/// Traverses both paths until they differ, returning the common prefix of both.
440 anchor: &Option<SyntaxNode>, 205fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
441 after: bool, 206 let mut res = None;
442 target: &[SmolStr], 207 let mut lhs_curr = first_path(&lhs);
443 edit: &mut TextEditBuilder, 208 let mut rhs_curr = first_path(&rhs);
444) { 209 loop {
445 if let Some(anchor) = anchor { 210 match (lhs_curr.segment(), rhs_curr.segment()) {
446 let indent = leading_indent(anchor); 211 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
447 let mut buf = String::new(); 212 _ => break,
448 if after {
449 buf.push_str("\n");
450 if let Some(spaces) = &indent {
451 buf.push_str(spaces);
452 }
453 } 213 }
454 buf.push_str("use "); 214 res = Some((lhs_curr.clone(), rhs_curr.clone()));
455 fmt_segments_raw(target, &mut buf); 215
456 buf.push_str(";"); 216 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
457 if !after { 217 Some((lhs, rhs)) => {
458 buf.push_str("\n\n"); 218 lhs_curr = lhs;
459 if let Some(spaces) = &indent { 219 rhs_curr = rhs;
460 buf.push_str(&spaces);
461 } 220 }
221 _ => break,
462 } 222 }
463 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
464 edit.insert(position, buf);
465 } 223 }
224
225 res
466} 226}
467 227
468fn make_assist_add_in_tree_list( 228/// What type of merges are allowed.
469 tree_list: &ast::UseTreeList, 229#[derive(Copy, Clone, PartialEq, Eq)]
470 target: &[SmolStr], 230pub enum MergeBehaviour {
471 add_self: bool, 231 /// Merge everything together creating deeply nested imports.
472 edit: &mut TextEditBuilder, 232 Full,
473) { 233 /// Only merge the last import level, doesn't allow import nesting.
474 let last = tree_list.use_trees().last(); 234 Last,
475 if let Some(last) = last {
476 let mut buf = String::new();
477 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
478 let offset = if let Some(comma) = comma {
479 comma.text_range().end()
480 } else {
481 buf.push_str(",");
482 last.syntax().text_range().end()
483 };
484 if add_self {
485 buf.push_str(" self")
486 } else {
487 buf.push_str(" ");
488 }
489 fmt_segments_raw(target, &mut buf);
490 edit.insert(offset, buf);
491 } else {
492 }
493} 235}
494 236
495fn make_assist_add_nested_import( 237#[derive(Eq, PartialEq, PartialOrd, Ord)]
496 path: &ast::Path, 238enum ImportGroup {
497 first_segment_to_split: &Option<ast::PathSegment>, 239 // the order here defines the order of new group inserts
498 target: &[SmolStr], 240 Std,
499 add_self: bool, 241 ExternCrate,
500 edit: &mut TextEditBuilder, 242 ThisCrate,
501) { 243 ThisModule,
502 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); 244 SuperModule,
503 if let Some(use_tree) = use_tree { 245}
504 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split 246
505 { 247impl ImportGroup {
506 (first_segment_to_split.syntax().text_range().start(), false) 248 fn new(path: &ast::Path) -> ImportGroup {
507 } else { 249 let default = ImportGroup::ExternCrate;
508 (use_tree.syntax().text_range().end(), true) 250
251 let first_segment = match first_segment(path) {
252 Some(it) => it,
253 None => return default,
509 }; 254 };
510 let end = use_tree.syntax().text_range().end();
511 255
512 let mut buf = String::new(); 256 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
513 if add_colon_colon { 257 match kind {
514 buf.push_str("::"); 258 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
515 } 259 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
516 buf.push_str("{"); 260 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
517 if add_self { 261 PathSegmentKind::Name(name) => match name.text().as_str() {
518 buf.push_str("self, "); 262 "std" => ImportGroup::Std,
519 } 263 "core" => ImportGroup::Std,
520 fmt_segments_raw(target, &mut buf); 264 // FIXME: can be ThisModule as well
521 if !target.is_empty() { 265 _ => ImportGroup::ExternCrate,
522 buf.push_str(", "); 266 },
267 PathSegmentKind::Type { .. } => unreachable!(),
523 } 268 }
524 edit.insert(start, buf);
525 edit.insert(end, "}".to_string());
526 } 269 }
527} 270}
528 271
529/// If the node is on the beginning of the line, calculate indent. 272fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
530fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { 273 first_path(path).segment()
531 for token in prev_tokens(node.first_token()?) { 274}
532 if let Some(ws) = ast::Whitespace::cast(token.clone()) { 275
533 let ws_text = ws.text(); 276fn first_path(path: &ast::Path) -> ast::Path {
534 if let Some(pos) = ws_text.rfind('\n') { 277 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
535 return Some(ws_text[pos + 1..].into()); 278}
279
280fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
281 path.syntax().children().flat_map(ast::PathSegment::cast)
282}
283
284#[derive(PartialEq, Eq)]
285enum AddBlankLine {
286 Before,
287 BeforeTwice,
288 Around,
289 After,
290 AfterTwice,
291}
292
293fn find_insert_position(
294 scope: &ImportScope,
295 insert_path: ast::Path,
296) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
297 let group = ImportGroup::new(&insert_path);
298 let path_node_iter = scope
299 .as_syntax_node()
300 .children()
301 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
302 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
303 // Iterator that discards anything thats not in the required grouping
304 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
305 let group_iter = path_node_iter
306 .clone()
307 .skip_while(|(path, _)| ImportGroup::new(path) != group)
308 .take_while(|(path, _)| ImportGroup::new(path) == group);
309
310 let segments = segment_iter(&insert_path);
311 // 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
312 let mut last = None;
313 // find the element that would come directly after our new import
314 let post_insert =
315 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
316 let check_segments = segment_iter(&path);
317 segments
318 .clone()
319 .zip(check_segments)
320 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
321 .all(|(l, r)| l.text() <= r.text())
322 });
323 match post_insert {
324 // insert our import before that element
325 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
326 // there is no element after our new import, so append it to the end of the group
327 None => match last {
328 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
329 // the group we were looking for actually doesnt exist, so insert
330 None => {
331 // similar concept here to the `last` from above
332 let mut last = None;
333 // find the group that comes after where we want to insert
334 let post_group = path_node_iter
335 .inspect(|(_, node)| last = Some(node.clone()))
336 .find(|(p, _)| ImportGroup::new(p) > group);
337 match post_group {
338 Some((_, node)) => {
339 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
340 }
341 // there is no such group, so append after the last one
342 None => match last {
343 Some(node) => {
344 (InsertPosition::After(node.into()), AddBlankLine::BeforeTwice)
345 }
346 // there are no imports in this file at all
347 None => scope.insert_pos_after_inner_attribute(),
348 },
349 }
536 } 350 }
537 } 351 },
538 if token.text().contains('\n') { 352 }
539 break; 353}
540 } 354
355#[cfg(test)]
356mod tests {
357 use super::*;
358
359 use test_utils::assert_eq_text;
360
361 #[test]
362 fn insert_start() {
363 check_none(
364 "std::bar::AA",
365 r"
366use std::bar::B;
367use std::bar::D;
368use std::bar::F;
369use std::bar::G;",
370 r"
371use std::bar::AA;
372use std::bar::B;
373use std::bar::D;
374use std::bar::F;
375use std::bar::G;",
376 )
377 }
378
379 #[test]
380 fn insert_middle() {
381 check_none(
382 "std::bar::EE",
383 r"
384use std::bar::A;
385use std::bar::D;
386use std::bar::F;
387use std::bar::G;",
388 r"
389use std::bar::A;
390use std::bar::D;
391use std::bar::EE;
392use std::bar::F;
393use std::bar::G;",
394 )
395 }
396
397 #[test]
398 fn insert_end() {
399 check_none(
400 "std::bar::ZZ",
401 r"
402use std::bar::A;
403use std::bar::D;
404use std::bar::F;
405use std::bar::G;",
406 r"
407use std::bar::A;
408use std::bar::D;
409use std::bar::F;
410use std::bar::G;
411use std::bar::ZZ;",
412 )
413 }
414
415 #[test]
416 fn insert_middle_nested() {
417 check_none(
418 "std::bar::EE",
419 r"
420use std::bar::A;
421use std::bar::{D, Z}; // example of weird imports due to user
422use std::bar::F;
423use std::bar::G;",
424 r"
425use std::bar::A;
426use std::bar::EE;
427use std::bar::{D, Z}; // example of weird imports due to user
428use std::bar::F;
429use std::bar::G;",
430 )
431 }
432
433 #[test]
434 fn insert_middle_groups() {
435 check_none(
436 "foo::bar::GG",
437 r"
438use std::bar::A;
439use std::bar::D;
440
441use foo::bar::F;
442use foo::bar::H;",
443 r"
444use std::bar::A;
445use std::bar::D;
446
447use foo::bar::F;
448use foo::bar::GG;
449use foo::bar::H;",
450 )
451 }
452
453 #[test]
454 fn insert_first_matching_group() {
455 check_none(
456 "foo::bar::GG",
457 r"
458use foo::bar::A;
459use foo::bar::D;
460
461use std;
462
463use foo::bar::F;
464use foo::bar::H;",
465 r"
466use foo::bar::A;
467use foo::bar::D;
468use foo::bar::GG;
469
470use std;
471
472use foo::bar::F;
473use foo::bar::H;",
474 )
475 }
476
477 #[test]
478 fn insert_missing_group_std() {
479 check_none(
480 "std::fmt",
481 r"
482use foo::bar::A;
483use foo::bar::D;",
484 r"
485use std::fmt;
486
487use foo::bar::A;
488use foo::bar::D;",
489 )
490 }
491
492 #[test]
493 fn insert_missing_group_self() {
494 check_none(
495 "self::fmt",
496 r"
497use foo::bar::A;
498use foo::bar::D;",
499 r"
500use foo::bar::A;
501use foo::bar::D;
502
503use self::fmt;",
504 )
505 }
506
507 #[test]
508 fn insert_no_imports() {
509 check_full(
510 "foo::bar",
511 "fn main() {}",
512 r"use foo::bar;
513
514fn main() {}",
515 )
516 }
517
518 #[test]
519 fn insert_empty_file() {
520 // empty files will get two trailing newlines
521 // this is due to the test case insert_no_imports above
522 check_full(
523 "foo::bar",
524 "",
525 r"use foo::bar;
526
527",
528 )
529 }
530
531 #[test]
532 fn insert_after_inner_attr() {
533 check_full(
534 "foo::bar",
535 r"#![allow(unused_imports)]",
536 r"#![allow(unused_imports)]
537
538use foo::bar;",
539 )
541 } 540 }
542 return None; 541
543 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 542 #[test]
544 successors(token.prev_token(), |token| token.prev_token()) 543 fn insert_after_inner_attr2() {
544 check_full(
545 "foo::bar",
546 r"#![allow(unused_imports)]
547
548fn main() {}",
549 r"#![allow(unused_imports)]
550
551use foo::bar;
552
553fn main() {}",
554 )
555 }
556
557 #[test]
558 fn merge_groups() {
559 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
560 }
561
562 #[test]
563 fn merge_groups_last() {
564 check_last(
565 "std::io",
566 r"use std::fmt::{Result, Display};",
567 r"use std::fmt::{Result, Display};
568use std::io;",
569 )
570 }
571
572 #[test]
573 fn merge_groups_full() {
574 check_full(
575 "std::io",
576 r"use std::fmt::{Result, Display};",
577 r"use std::{fmt::{Result, Display}, io};",
578 )
579 }
580
581 #[test]
582 fn merge_groups_long_full() {
583 check_full(
584 "std::foo::bar::Baz",
585 r"use std::foo::bar::Qux;",
586 r"use std::foo::bar::{Qux, Baz};",
587 )
588 }
589
590 #[test]
591 fn merge_groups_long_last() {
592 check_last(
593 "std::foo::bar::Baz",
594 r"use std::foo::bar::Qux;",
595 r"use std::foo::bar::{Qux, Baz};",
596 )
597 }
598
599 #[test]
600 fn merge_groups_long_full_list() {
601 check_full(
602 "std::foo::bar::Baz",
603 r"use std::foo::bar::{Qux, Quux};",
604 r"use std::foo::bar::{Qux, Quux, Baz};",
605 )
606 }
607
608 #[test]
609 fn merge_groups_long_last_list() {
610 check_last(
611 "std::foo::bar::Baz",
612 r"use std::foo::bar::{Qux, Quux};",
613 r"use std::foo::bar::{Qux, Quux, Baz};",
614 )
615 }
616
617 #[test]
618 fn merge_groups_long_full_nested() {
619 check_full(
620 "std::foo::bar::Baz",
621 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
622 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}, Baz};",
623 )
624 }
625
626 #[test]
627 fn merge_groups_long_last_nested() {
628 check_last(
629 "std::foo::bar::Baz",
630 r"use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
631 r"use std::foo::bar::Baz;
632use std::foo::bar::{Qux, quux::{Fez, Fizz}};",
633 )
634 }
635
636 #[test]
637 fn merge_groups_skip_pub() {
638 check_full(
639 "std::io",
640 r"pub use std::fmt::{Result, Display};",
641 r"pub use std::fmt::{Result, Display};
642use std::io;",
643 )
644 }
645
646 #[test]
647 fn merge_groups_skip_pub_crate() {
648 check_full(
649 "std::io",
650 r"pub(crate) use std::fmt::{Result, Display};",
651 r"pub(crate) use std::fmt::{Result, Display};
652use std::io;",
653 )
654 }
655
656 #[test]
657 #[ignore] // FIXME: Support this
658 fn split_out_merge() {
659 check_last(
660 "std::fmt::Result",
661 r"use std::{fmt, io};",
662 r"use std::{self, fmt::Result};
663use std::io;",
664 )
665 }
666
667 #[test]
668 fn merge_groups_self() {
669 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
670 }
671
672 #[test]
673 fn merge_self_glob() {
674 check_full(
675 "token::TokenKind",
676 r"use token::TokenKind::*;",
677 r"use token::TokenKind::{self::*, self};",
678 )
679 }
680
681 #[test]
682 fn merge_last_too_long() {
683 mark::check!(test_last_merge_too_long);
684 check_last(
685 "foo::bar",
686 r"use foo::bar::baz::Qux;",
687 r"use foo::bar::baz::Qux;
688use foo::bar;",
689 );
690 }
691
692 fn check(
693 path: &str,
694 ra_fixture_before: &str,
695 ra_fixture_after: &str,
696 mb: Option<MergeBehaviour>,
697 ) {
698 let file = super::ImportScope::from(
699 ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(),
700 )
701 .unwrap();
702 let path = ast::SourceFile::parse(&format!("use {};", path))
703 .tree()
704 .syntax()
705 .descendants()
706 .find_map(ast::Path::cast)
707 .unwrap();
708
709 let result = insert_use(&file, path, mb).to_string();
710 assert_eq_text!(&result, ra_fixture_after);
711 }
712
713 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
714 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
715 }
716
717 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
718 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
719 }
720
721 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
722 check(path, ra_fixture_before, ra_fixture_after, None)
545 } 723 }
546} 724}
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index c2c938ad1..33f1ad7b3 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -339,7 +339,7 @@ pub mod tokens {
339 use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken}; 339 use crate::{ast, AstNode, Parse, SourceFile, SyntaxKind::*, SyntaxToken};
340 340
341 pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = 341 pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> =
342 Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true)\n;")); 342 Lazy::new(|| SourceFile::parse("const C: <()>::Item = (1 != 1, 2 == 2, !true)\n;\n\n"));
343 343
344 pub fn single_space() -> SyntaxToken { 344 pub fn single_space() -> SyntaxToken {
345 SOURCE_FILE 345 SOURCE_FILE
@@ -379,6 +379,16 @@ pub mod tokens {
379 .unwrap() 379 .unwrap()
380 } 380 }
381 381
382 pub fn blank_line() -> SyntaxToken {
383 SOURCE_FILE
384 .tree()
385 .syntax()
386 .descendants_with_tokens()
387 .filter_map(|it| it.into_token())
388 .find(|it| it.kind() == WHITESPACE && it.text().as_str() == "\n\n")
389 .unwrap()
390 }
391
382 pub struct WsBuilder(SourceFile); 392 pub struct WsBuilder(SourceFile);
383 393
384 impl WsBuilder { 394 impl WsBuilder {