|
1 | 1 | use either::Either;
|
| 2 | +use std::iter::successors; |
| 3 | + |
2 | 4 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
|
3 | 5 | use ide_db::{
|
4 | 6 | defs::{classify_name_ref, Definition, NameRefClass},
|
5 | 7 | RootDatabase,
|
6 | 8 | };
|
7 |
| -use syntax::{ast, AstNode, SyntaxToken, T}; |
| 9 | +use syntax::{algo, ast, AstNode, SourceFile, SyntaxNode, SyntaxToken, T}; |
8 | 10 |
|
9 | 11 | use crate::{
|
10 | 12 | assist_context::{AssistBuilder, AssistContext, Assists},
|
@@ -48,27 +50,39 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
|
48 | 50 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
|
49 | 51 |
|
50 | 52 | let defs_in_mod = find_defs_in_mod(ctx, scope, module)?;
|
51 |
| - let name_refs_in_source_file = |
52 |
| - source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); |
53 |
| - let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); |
| 53 | + let names_to_import = find_names_to_import(ctx, source_file, defs_in_mod); |
54 | 54 |
|
55 |
| - let target = parent.syntax(); |
| 55 | + let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone()); |
56 | 56 | acc.add(
|
57 | 57 | AssistId("expand_glob_import", AssistKind::RefactorRewrite),
|
58 | 58 | "Expand glob import",
|
59 | 59 | target.text_range(),
|
60 | 60 | |builder| {
|
61 |
| - replace_ast(builder, parent, mod_path, used_names); |
| 61 | + replace_ast(builder, parent, mod_path, names_to_import); |
62 | 62 | },
|
63 | 63 | )
|
64 | 64 | }
|
65 | 65 |
|
66 |
| -fn find_parent_and_path(star: &SyntaxToken) -> Option<(ast::UseTree, ast::Path)> { |
67 |
| - star.ancestors().find_map(|n| { |
| 66 | +fn find_parent_and_path( |
| 67 | + star: &SyntaxToken, |
| 68 | +) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> { |
| 69 | + return star.ancestors().find_map(|n| { |
| 70 | + find_use_tree_list(n.clone()) |
| 71 | + .and_then(|(u, p)| Some((Either::Right(u), p))) |
| 72 | + .or_else(|| find_use_tree(n).and_then(|(u, p)| Some((Either::Left(u), p)))) |
| 73 | + }); |
| 74 | + |
| 75 | + fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> { |
| 76 | + let use_tree_list = ast::UseTreeList::cast(n)?; |
| 77 | + let path = use_tree_list.parent_use_tree().path()?; |
| 78 | + Some((use_tree_list, path)) |
| 79 | + } |
| 80 | + |
| 81 | + fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> { |
68 | 82 | let use_tree = ast::UseTree::cast(n)?;
|
69 | 83 | let path = use_tree.path()?;
|
70 | 84 | Some((use_tree, path))
|
71 |
| - }) |
| 85 | + } |
72 | 86 | }
|
73 | 87 |
|
74 | 88 | #[derive(PartialEq)]
|
@@ -105,14 +119,36 @@ fn find_defs_in_mod(
|
105 | 119 | Some(defs)
|
106 | 120 | }
|
107 | 121 |
|
108 |
| -fn find_used_names( |
| 122 | +fn find_names_to_import( |
109 | 123 | ctx: &AssistContext,
|
| 124 | + source_file: &SourceFile, |
110 | 125 | defs_in_mod: Vec<Def>,
|
111 |
| - name_refs_in_source_file: Vec<ast::NameRef>, |
112 | 126 | ) -> Vec<Name> {
|
113 |
| - let defs_in_source_file = name_refs_in_source_file |
114 |
| - .iter() |
115 |
| - .filter_map(|r| classify_name_ref(&ctx.sema, r)) |
| 127 | + let (name_refs_in_use_item, name_refs_in_source) = source_file |
| 128 | + .syntax() |
| 129 | + .descendants() |
| 130 | + .filter_map(|n| { |
| 131 | + let name_ref = ast::NameRef::cast(n.clone())?; |
| 132 | + let name_ref_class = classify_name_ref(&ctx.sema, &name_ref)?; |
| 133 | + let is_in_use_item = |
| 134 | + successors(n.parent(), |n| n.parent()).find_map(ast::Use::cast).is_some(); |
| 135 | + Some((name_ref_class, is_in_use_item)) |
| 136 | + }) |
| 137 | + .partition::<Vec<_>, _>(|&(_, is_in_use_item)| is_in_use_item); |
| 138 | + |
| 139 | + let name_refs_to_import: Vec<NameRefClass> = name_refs_in_source |
| 140 | + .into_iter() |
| 141 | + .filter_map(|(r, _)| { |
| 142 | + if name_refs_in_use_item.contains(&(r.clone(), true)) { |
| 143 | + // already imported |
| 144 | + return None; |
| 145 | + } |
| 146 | + Some(r) |
| 147 | + }) |
| 148 | + .collect(); |
| 149 | + |
| 150 | + let defs_in_source_file = name_refs_to_import |
| 151 | + .into_iter() |
116 | 152 | .filter_map(|rc| match rc {
|
117 | 153 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
|
118 | 154 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
|
@@ -141,28 +177,62 @@ fn find_used_names(
|
141 | 177 |
|
142 | 178 | fn replace_ast(
|
143 | 179 | builder: &mut AssistBuilder,
|
144 |
| - parent: ast::UseTree, |
| 180 | + parent: Either<ast::UseTree, ast::UseTreeList>, |
145 | 181 | path: ast::Path,
|
146 |
| - used_names: Vec<Name>, |
| 182 | + names_to_import: Vec<Name>, |
147 | 183 | ) {
|
148 |
| - let replacement = match used_names.as_slice() { |
149 |
| - [name] => ast::make::use_tree( |
150 |
| - ast::make::path_from_text(&format!("{}::{}", path, name)), |
151 |
| - None, |
152 |
| - None, |
153 |
| - false, |
154 |
| - ), |
155 |
| - names => ast::make::use_tree( |
156 |
| - path, |
157 |
| - Some(ast::make::use_tree_list(names.iter().map(|n| { |
158 |
| - ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) |
159 |
| - }))), |
160 |
| - None, |
161 |
| - false, |
162 |
| - ), |
| 184 | + let existing_use_trees = match parent.clone() { |
| 185 | + Either::Left(_) => vec![], |
| 186 | + Either::Right(u) => u.use_trees().filter(|n| |
| 187 | + // filter out star |
| 188 | + n.star_token().is_none() |
| 189 | + ).collect(), |
163 | 190 | };
|
164 | 191 |
|
165 |
| - builder.replace_ast(parent, replacement); |
| 192 | + let new_use_trees: Vec<ast::UseTree> = names_to_import |
| 193 | + .iter() |
| 194 | + .map(|n| ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)) |
| 195 | + .collect(); |
| 196 | + |
| 197 | + let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat(); |
| 198 | + |
| 199 | + match use_trees.as_slice() { |
| 200 | + [name] => { |
| 201 | + if let Some(end_path) = name.path() { |
| 202 | + let replacement = ast::make::use_tree( |
| 203 | + ast::make::path_from_text(&format!("{}::{}", path, end_path)), |
| 204 | + None, |
| 205 | + None, |
| 206 | + false, |
| 207 | + ); |
| 208 | + |
| 209 | + algo::diff( |
| 210 | + &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()), |
| 211 | + replacement.syntax(), |
| 212 | + ) |
| 213 | + .into_text_edit(builder.text_edit_builder()); |
| 214 | + } |
| 215 | + } |
| 216 | + names => { |
| 217 | + let replacement = match parent { |
| 218 | + Either::Left(_) => ast::make::use_tree( |
| 219 | + path, |
| 220 | + Some(ast::make::use_tree_list(names.to_owned())), |
| 221 | + None, |
| 222 | + false, |
| 223 | + ) |
| 224 | + .syntax() |
| 225 | + .clone(), |
| 226 | + Either::Right(_) => ast::make::use_tree_list(names.to_owned()).syntax().clone(), |
| 227 | + }; |
| 228 | + |
| 229 | + algo::diff( |
| 230 | + &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()), |
| 231 | + &replacement, |
| 232 | + ) |
| 233 | + .into_text_edit(builder.text_edit_builder()); |
| 234 | + } |
| 235 | + }; |
166 | 236 | }
|
167 | 237 |
|
168 | 238 | #[cfg(test)]
|
@@ -236,7 +306,46 @@ mod foo {
|
236 | 306 | pub fn f() {}
|
237 | 307 | }
|
238 | 308 |
|
239 |
| -use foo::{Baz, Bar, f}; |
| 309 | +use foo::{f, Baz, Bar}; |
| 310 | +
|
| 311 | +fn qux(bar: Bar, baz: Baz) { |
| 312 | + f(); |
| 313 | +} |
| 314 | +", |
| 315 | + ) |
| 316 | + } |
| 317 | + |
| 318 | + #[test] |
| 319 | + fn expanding_glob_import_with_existing_uses_in_same_module() { |
| 320 | + check_assist( |
| 321 | + expand_glob_import, |
| 322 | + r" |
| 323 | +mod foo { |
| 324 | + pub struct Bar; |
| 325 | + pub struct Baz; |
| 326 | + pub struct Qux; |
| 327 | +
|
| 328 | + pub fn f() {} |
| 329 | +} |
| 330 | +
|
| 331 | +use foo::Bar; |
| 332 | +use foo::{*<|>, f}; |
| 333 | +
|
| 334 | +fn qux(bar: Bar, baz: Baz) { |
| 335 | + f(); |
| 336 | +} |
| 337 | +", |
| 338 | + r" |
| 339 | +mod foo { |
| 340 | + pub struct Bar; |
| 341 | + pub struct Baz; |
| 342 | + pub struct Qux; |
| 343 | +
|
| 344 | + pub fn f() {} |
| 345 | +} |
| 346 | +
|
| 347 | +use foo::Bar; |
| 348 | +use foo::{f, Baz}; |
240 | 349 |
|
241 | 350 | fn qux(bar: Bar, baz: Baz) {
|
242 | 351 | f();
|
@@ -286,7 +395,7 @@ mod foo {
|
286 | 395 | }
|
287 | 396 | }
|
288 | 397 |
|
289 |
| -use foo::{bar::{Baz, Bar, f}, baz::*}; |
| 398 | +use foo::{bar::{f, Baz, Bar}, baz::*}; |
290 | 399 |
|
291 | 400 | fn qux(bar: Bar, baz: Baz) {
|
292 | 401 | f();
|
@@ -486,6 +595,82 @@ use foo::{
|
486 | 595 | baz::{g, qux::{h, q::j}}
|
487 | 596 | };
|
488 | 597 |
|
| 598 | +fn qux(bar: Bar, baz: Baz) { |
| 599 | + f(); |
| 600 | + g(); |
| 601 | + h(); |
| 602 | + j(); |
| 603 | +} |
| 604 | +", |
| 605 | + ); |
| 606 | + |
| 607 | + check_assist( |
| 608 | + expand_glob_import, |
| 609 | + r" |
| 610 | +mod foo { |
| 611 | + pub mod bar { |
| 612 | + pub struct Bar; |
| 613 | + pub struct Baz; |
| 614 | + pub struct Qux; |
| 615 | +
|
| 616 | + pub fn f() {} |
| 617 | + } |
| 618 | +
|
| 619 | + pub mod baz { |
| 620 | + pub fn g() {} |
| 621 | +
|
| 622 | + pub mod qux { |
| 623 | + pub fn h() {} |
| 624 | + pub fn m() {} |
| 625 | +
|
| 626 | + pub mod q { |
| 627 | + pub fn j() {} |
| 628 | + } |
| 629 | + } |
| 630 | + } |
| 631 | +} |
| 632 | +
|
| 633 | +use foo::{ |
| 634 | + bar::{*, f}, |
| 635 | + baz::{g, qux::{q::j, *<|>}} |
| 636 | +}; |
| 637 | +
|
| 638 | +fn qux(bar: Bar, baz: Baz) { |
| 639 | + f(); |
| 640 | + g(); |
| 641 | + h(); |
| 642 | + j(); |
| 643 | +} |
| 644 | +", |
| 645 | + r" |
| 646 | +mod foo { |
| 647 | + pub mod bar { |
| 648 | + pub struct Bar; |
| 649 | + pub struct Baz; |
| 650 | + pub struct Qux; |
| 651 | +
|
| 652 | + pub fn f() {} |
| 653 | + } |
| 654 | +
|
| 655 | + pub mod baz { |
| 656 | + pub fn g() {} |
| 657 | +
|
| 658 | + pub mod qux { |
| 659 | + pub fn h() {} |
| 660 | + pub fn m() {} |
| 661 | +
|
| 662 | + pub mod q { |
| 663 | + pub fn j() {} |
| 664 | + } |
| 665 | + } |
| 666 | + } |
| 667 | +} |
| 668 | +
|
| 669 | +use foo::{ |
| 670 | + bar::{*, f}, |
| 671 | + baz::{g, qux::{q::j, h}} |
| 672 | +}; |
| 673 | +
|
489 | 674 | fn qux(bar: Bar, baz: Baz) {
|
490 | 675 | f();
|
491 | 676 | g();
|
|
0 commit comments