aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/expand_glob_import.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/expand_glob_import.rs')
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs253
1 files changed, 219 insertions, 34 deletions
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index 0288d1c77..ff9c80d49 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -1,10 +1,12 @@
1use either::Either; 1use either::Either;
2use std::iter::successors;
3
2use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; 4use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
3use ide_db::{ 5use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass}, 6 defs::{classify_name_ref, Definition, NameRefClass},
5 RootDatabase, 7 RootDatabase,
6}; 8};
7use syntax::{ast, AstNode, SyntaxToken, T}; 9use syntax::{algo, ast, AstNode, SourceFile, SyntaxNode, SyntaxToken, T};
8 10
9use crate::{ 11use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists}, 12 assist_context::{AssistBuilder, AssistContext, Assists},
@@ -48,27 +50,39 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
48 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); 50 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
49 51
50 let defs_in_mod = find_defs_in_mod(ctx, scope, module)?; 52 let defs_in_mod = find_defs_in_mod(ctx, scope, module)?;
51 let name_refs_in_source_file = 53 let names_to_import = find_names_to_import(ctx, source_file, defs_in_mod);
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);
54 54
55 let target = parent.syntax(); 55 let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
56 acc.add( 56 acc.add(
57 AssistId("expand_glob_import", AssistKind::RefactorRewrite), 57 AssistId("expand_glob_import", AssistKind::RefactorRewrite),
58 "Expand glob import", 58 "Expand glob import",
59 target.text_range(), 59 target.text_range(),
60 |builder| { 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
66fn find_parent_and_path(star: &SyntaxToken) -> Option<(ast::UseTree, ast::Path)> { 66fn find_parent_and_path(
67 star.ancestors().find_map(|n| { 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 let use_tree = ast::UseTree::cast(n)?; 82 let use_tree = ast::UseTree::cast(n)?;
69 let path = use_tree.path()?; 83 let path = use_tree.path()?;
70 Some((use_tree, path)) 84 Some((use_tree, path))
71 }) 85 }
72} 86}
73 87
74#[derive(PartialEq)] 88#[derive(PartialEq)]
@@ -105,14 +119,36 @@ fn find_defs_in_mod(
105 Some(defs) 119 Some(defs)
106} 120}
107 121
108fn find_used_names( 122fn find_names_to_import(
109 ctx: &AssistContext, 123 ctx: &AssistContext,
124 source_file: &SourceFile,
110 defs_in_mod: Vec<Def>, 125 defs_in_mod: Vec<Def>,
111 name_refs_in_source_file: Vec<ast::NameRef>,
112) -> Vec<Name> { 126) -> Vec<Name> {
113 let defs_in_source_file = name_refs_in_source_file 127 let (name_refs_in_use_item, name_refs_in_source) = source_file
114 .iter() 128 .syntax()
115 .filter_map(|r| classify_name_ref(&ctx.sema, r)) 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 .filter_map(|rc| match rc { 152 .filter_map(|rc| match rc {
117 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), 153 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
118 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), 154 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
@@ -141,28 +177,62 @@ fn find_used_names(
141 177
142fn replace_ast( 178fn replace_ast(
143 builder: &mut AssistBuilder, 179 builder: &mut AssistBuilder,
144 parent: ast::UseTree, 180 parent: Either<ast::UseTree, ast::UseTreeList>,
145 path: ast::Path, 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() { 184 let existing_use_trees = match parent.clone() {
149 [name] => ast::make::use_tree( 185 Either::Left(_) => vec![],
150 ast::make::path_from_text(&format!("{}::{}", path, name)), 186 Either::Right(u) => u.use_trees().filter(|n|
151 None, 187 // filter out star
152 None, 188 n.star_token().is_none()
153 false, 189 ).collect(),
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 ),
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#[cfg(test)] 238#[cfg(test)]
@@ -236,7 +306,46 @@ mod foo {
236 pub fn f() {} 306 pub fn f() {}
237} 307}
238 308
239use foo::{Baz, Bar, f}; 309use foo::{f, Baz, Bar};
310
311fn 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"
323mod foo {
324 pub struct Bar;
325 pub struct Baz;
326 pub struct Qux;
327
328 pub fn f() {}
329}
330
331use foo::Bar;
332use foo::{*<|>, f};
333
334fn qux(bar: Bar, baz: Baz) {
335 f();
336}
337",
338 r"
339mod foo {
340 pub struct Bar;
341 pub struct Baz;
342 pub struct Qux;
343
344 pub fn f() {}
345}
346
347use foo::Bar;
348use foo::{f, Baz};
240 349
241fn qux(bar: Bar, baz: Baz) { 350fn qux(bar: Bar, baz: Baz) {
242 f(); 351 f();
@@ -286,7 +395,7 @@ mod foo {
286 } 395 }
287} 396}
288 397
289use foo::{bar::{Baz, Bar, f}, baz::*}; 398use foo::{bar::{f, Baz, Bar}, baz::*};
290 399
291fn qux(bar: Bar, baz: Baz) { 400fn qux(bar: Bar, baz: Baz) {
292 f(); 401 f();
@@ -494,6 +603,82 @@ fn qux(bar: Bar, baz: Baz) {
494} 603}
495", 604",
496 ); 605 );
606
607 check_assist(
608 expand_glob_import,
609 r"
610mod 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
633use foo::{
634 bar::{*, f},
635 baz::{g, qux::{q::j, *<|>}}
636};
637
638fn qux(bar: Bar, baz: Baz) {
639 f();
640 g();
641 h();
642 j();
643}
644",
645 r"
646mod 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
669use foo::{
670 bar::{*, f},
671 baz::{g, qux::{q::j, h}}
672};
673
674fn qux(bar: Bar, baz: Baz) {
675 f();
676 g();
677 h();
678 j();
679}
680",
681 );
497 } 682 }
498 683
499 #[test] 684 #[test]