aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs61
-rw-r--r--crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs2
-rw-r--r--crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs13
-rw-r--r--crates/ide_assists/src/tests.rs1
-rw-r--r--crates/ide_completion/src/item.rs2
-rw-r--r--crates/ide_completion/src/tests.rs1
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs78
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs72
-rw-r--r--crates/rust-analyzer/src/config.rs4
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs1
-rw-r--r--crates/syntax/src/ast/node_ext.rs17
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
14 files changed, 216 insertions, 48 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index d4748ef3a..8df8b060d 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -103,8 +103,9 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
103 let scope = match scope.clone() { 103 let scope = match scope.clone() {
104 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)), 104 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
105 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)), 105 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
106 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
106 }; 107 };
107 insert_use(&scope, mod_path_to_ast(&import.import_path), ctx.config.insert_use); 108 insert_use(&scope, mod_path_to_ast(&import.import_path), &ctx.config.insert_use);
108 }, 109 },
109 ); 110 );
110 } 111 }
@@ -994,4 +995,62 @@ const _: () = {
994"#, 995"#,
995 ); 996 );
996 } 997 }
998
999 #[test]
1000 fn respects_cfg_attr() {
1001 check_assist(
1002 auto_import,
1003 r#"
1004mod bar {
1005 pub struct Bar;
1006}
1007
1008#[cfg(test)]
1009fn foo() {
1010 Bar$0
1011}
1012"#,
1013 r#"
1014mod bar {
1015 pub struct Bar;
1016}
1017
1018#[cfg(test)]
1019fn foo() {
1020use bar::Bar;
1021
1022 Bar
1023}
1024"#,
1025 );
1026 }
1027
1028 #[test]
1029 fn respects_cfg_attr2() {
1030 check_assist(
1031 auto_import,
1032 r#"
1033mod bar {
1034 pub struct Bar;
1035}
1036
1037#[cfg(test)]
1038const FOO: Bar = {
1039 Bar$0
1040}
1041"#,
1042 r#"
1043mod bar {
1044 pub struct Bar;
1045}
1046
1047#[cfg(test)]
1048const FOO: Bar = {
1049use bar::Bar;
1050
1051 Bar
1052}
1053"#,
1054 );
1055 }
997} 1056}
diff --git a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
index 6c6ff16c2..430710448 100644
--- a/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ide_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -236,7 +236,7 @@ fn apply_references(
236 import: Option<(ImportScope, hir::ModPath)>, 236 import: Option<(ImportScope, hir::ModPath)>,
237) { 237) {
238 if let Some((scope, path)) = import { 238 if let Some((scope, path)) = import {
239 insert_use(&scope, mod_path_to_ast(&path), insert_use_cfg); 239 insert_use(&scope, mod_path_to_ast(&path), &insert_use_cfg);
240 } 240 }
241 // deep clone to prevent cycle 241 // deep clone to prevent cycle
242 let path = make::path_from_segments(iter::once(segment.clone_subtree()), false); 242 let path = make::path_from_segments(iter::once(segment.clone_subtree()), false);
diff --git a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
index 39f5eb4ff..26778ee74 100644
--- a/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -32,7 +32,6 @@ pub(crate) fn replace_qualified_name_with_use(
32 32
33 let target = path.syntax().text_range(); 33 let target = path.syntax().text_range();
34 let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?; 34 let scope = ImportScope::find_insert_use_container_with_macros(path.syntax(), &ctx.sema)?;
35 let syntax = scope.as_syntax_node();
36 acc.add( 35 acc.add(
37 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite), 36 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
38 "Replace qualified path with use", 37 "Replace qualified path with use",
@@ -40,11 +39,13 @@ pub(crate) fn replace_qualified_name_with_use(
40 |builder| { 39 |builder| {
41 // Now that we've brought the name into scope, re-qualify all paths that could be 40 // Now that we've brought the name into scope, re-qualify all paths that could be
42 // affected (that is, all paths inside the node we added the `use` to). 41 // affected (that is, all paths inside the node we added the `use` to).
43 let syntax = builder.make_syntax_mut(syntax.clone()); 42 let scope = match scope {
44 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) { 43 ImportScope::File(it) => ImportScope::File(builder.make_mut(it)),
45 shorten_paths(&syntax, &path.clone_for_update()); 44 ImportScope::Module(it) => ImportScope::Module(builder.make_mut(it)),
46 insert_use(import_scope, path, ctx.config.insert_use); 45 ImportScope::Block(it) => ImportScope::Block(builder.make_mut(it)),
47 } 46 };
47 shorten_paths(scope.as_syntax_node(), &path.clone_for_update());
48 insert_use(&scope, path, &ctx.config.insert_use);
48 }, 49 },
49 ) 50 )
50} 51}
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 4e96ff1ec..841537c77 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -28,6 +28,7 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
28 prefix_kind: hir::PrefixKind::Plain, 28 prefix_kind: hir::PrefixKind::Plain,
29 enforce_granularity: true, 29 enforce_granularity: true,
30 group: true, 30 group: true,
31 skip_glob_imports: true,
31 }, 32 },
32}; 33};
33 34
diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs
index 99edb9499..ae63d132e 100644
--- a/crates/ide_completion/src/item.rs
+++ b/crates/ide_completion/src/item.rs
@@ -378,7 +378,7 @@ impl ImportEdit {
378 let _p = profile::span("ImportEdit::to_text_edit"); 378 let _p = profile::span("ImportEdit::to_text_edit");
379 379
380 let new_ast = self.scope.clone_for_update(); 380 let new_ast = self.scope.clone_for_update();
381 insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), cfg); 381 insert_use::insert_use(&new_ast, mod_path_to_ast(&self.import.import_path), &cfg);
382 let mut import_insert = TextEdit::builder(); 382 let mut import_insert = TextEdit::builder();
383 algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node()) 383 algo::diff(self.scope.as_syntax_node(), new_ast.as_syntax_node())
384 .into_text_edit(&mut import_insert); 384 .into_text_edit(&mut import_insert);
diff --git a/crates/ide_completion/src/tests.rs b/crates/ide_completion/src/tests.rs
index 1ea6017ce..211c89c40 100644
--- a/crates/ide_completion/src/tests.rs
+++ b/crates/ide_completion/src/tests.rs
@@ -36,6 +36,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
36 prefix_kind: PrefixKind::Plain, 36 prefix_kind: PrefixKind::Plain,
37 enforce_granularity: true, 37 enforce_granularity: true,
38 group: true, 38 group: true,
39 skip_glob_imports: true,
39 }, 40 },
40}; 41};
41 42
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index 10bbafe77..e6b4832e7 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -5,7 +5,7 @@ use hir::Semantics;
5use syntax::{ 5use syntax::{
6 algo, 6 algo,
7 ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner}, 7 ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner},
8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, 8 match_ast, ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
9}; 9};
10 10
11use crate::{ 11use crate::{
@@ -36,22 +36,39 @@ pub struct InsertUseConfig {
36 pub enforce_granularity: bool, 36 pub enforce_granularity: bool,
37 pub prefix_kind: PrefixKind, 37 pub prefix_kind: PrefixKind,
38 pub group: bool, 38 pub group: bool,
39 pub skip_glob_imports: bool,
39} 40}
40 41
41#[derive(Debug, Clone)] 42#[derive(Debug, Clone)]
42pub enum ImportScope { 43pub enum ImportScope {
43 File(ast::SourceFile), 44 File(ast::SourceFile),
44 Module(ast::ItemList), 45 Module(ast::ItemList),
46 Block(ast::BlockExpr),
45} 47}
46 48
47impl ImportScope { 49impl ImportScope {
48 pub fn from(syntax: SyntaxNode) -> Option<Self> { 50 fn from(syntax: SyntaxNode) -> Option<Self> {
49 if let Some(module) = ast::Module::cast(syntax.clone()) { 51 fn contains_cfg_attr(attrs: &dyn AttrsOwner) -> bool {
50 module.item_list().map(ImportScope::Module) 52 attrs
51 } else if let this @ Some(_) = ast::SourceFile::cast(syntax.clone()) { 53 .attrs()
52 this.map(ImportScope::File) 54 .any(|attr| attr.as_simple_call().map_or(false, |(ident, _)| ident == "cfg"))
53 } else { 55 }
54 ast::ItemList::cast(syntax).map(ImportScope::Module) 56 match_ast! {
57 match syntax {
58 ast::Module(module) => module.item_list().map(ImportScope::Module),
59 ast::SourceFile(file) => Some(ImportScope::File(file)),
60 ast::Fn(func) => contains_cfg_attr(&func).then(|| func.body().map(ImportScope::Block)).flatten(),
61 ast::Const(konst) => contains_cfg_attr(&konst).then(|| match konst.body()? {
62 ast::Expr::BlockExpr(block) => Some(block),
63 _ => None,
64 }).flatten().map(ImportScope::Block),
65 ast::Static(statik) => contains_cfg_attr(&statik).then(|| match statik.body()? {
66 ast::Expr::BlockExpr(block) => Some(block),
67 _ => None,
68 }).flatten().map(ImportScope::Block),
69 _ => None,
70
71 }
55 } 72 }
56 } 73 }
57 74
@@ -72,6 +89,7 @@ impl ImportScope {
72 match self { 89 match self {
73 ImportScope::File(file) => file.syntax(), 90 ImportScope::File(file) => file.syntax(),
74 ImportScope::Module(item_list) => item_list.syntax(), 91 ImportScope::Module(item_list) => item_list.syntax(),
92 ImportScope::Block(block) => block.syntax(),
75 } 93 }
76 } 94 }
77 95
@@ -79,6 +97,7 @@ impl ImportScope {
79 match self { 97 match self {
80 ImportScope::File(file) => ImportScope::File(file.clone_for_update()), 98 ImportScope::File(file) => ImportScope::File(file.clone_for_update()),
81 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), 99 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
100 ImportScope::Block(block) => ImportScope::Block(block.clone_for_update()),
82 } 101 }
83 } 102 }
84 103
@@ -95,6 +114,7 @@ impl ImportScope {
95 let mut use_stmts = match self { 114 let mut use_stmts = match self {
96 ImportScope::File(f) => f.items(), 115 ImportScope::File(f) => f.items(),
97 ImportScope::Module(m) => m.items(), 116 ImportScope::Module(m) => m.items(),
117 ImportScope::Block(b) => b.items(),
98 } 118 }
99 .filter_map(use_stmt); 119 .filter_map(use_stmt);
100 let mut res = ImportGranularityGuess::Unknown; 120 let mut res = ImportGranularityGuess::Unknown;
@@ -153,7 +173,7 @@ enum ImportGranularityGuess {
153} 173}
154 174
155/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 175/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
156pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { 176pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: &InsertUseConfig) {
157 let _p = profile::span("insert_use"); 177 let _p = profile::span("insert_use");
158 let mut mb = match cfg.granularity { 178 let mut mb = match cfg.granularity {
159 ImportGranularity::Crate => Some(MergeBehavior::Crate), 179 ImportGranularity::Crate => Some(MergeBehavior::Crate),
@@ -175,7 +195,10 @@ pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig
175 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); 195 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
176 // merge into existing imports if possible 196 // merge into existing imports if possible
177 if let Some(mb) = mb { 197 if let Some(mb) = mb {
178 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 198 let filter = |it: &_| !(cfg.skip_glob_imports && ast::Use::is_simple_glob(it));
199 for existing_use in
200 scope.as_syntax_node().children().filter_map(ast::Use::cast).filter(filter)
201 {
179 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 202 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
180 ted::replace(existing_use.syntax(), merged.syntax()); 203 ted::replace(existing_use.syntax(), merged.syntax());
181 return; 204 return;
@@ -315,28 +338,29 @@ fn insert_use_(
315 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline()); 338 ted::insert(ted::Position::after(last_inner_element), make::tokens::single_newline());
316 return; 339 return;
317 } 340 }
318 match scope { 341 let l_curly = match scope {
319 ImportScope::File(_) => { 342 ImportScope::File(_) => {
320 cov_mark::hit!(insert_group_empty_file); 343 cov_mark::hit!(insert_group_empty_file);
321 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line()); 344 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
322 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()) 345 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
346 return;
323 } 347 }
348 // don't insert the imports before the item list/block expr's opening curly brace
349 ImportScope::Module(item_list) => item_list.l_curly_token(),
324 // don't insert the imports before the item list's opening curly brace 350 // don't insert the imports before the item list's opening curly brace
325 ImportScope::Module(item_list) => match item_list.l_curly_token() { 351 ImportScope::Block(block) => block.l_curly_token(),
326 Some(b) => { 352 };
327 cov_mark::hit!(insert_group_empty_module); 353 match l_curly {
328 ted::insert(ted::Position::after(&b), make::tokens::single_newline()); 354 Some(b) => {
329 ted::insert(ted::Position::after(&b), use_item.syntax()); 355 cov_mark::hit!(insert_group_empty_module);
330 } 356 ted::insert(ted::Position::after(&b), make::tokens::single_newline());
331 None => { 357 ted::insert(ted::Position::after(&b), use_item.syntax());
332 // This should never happens, broken module syntax node 358 }
333 ted::insert( 359 None => {
334 ted::Position::first_child_of(scope_syntax), 360 // This should never happens, broken module syntax node
335 make::tokens::blank_line(), 361 ted::insert(ted::Position::first_child_of(scope_syntax), make::tokens::blank_line());
336 ); 362 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax());
337 ted::insert(ted::Position::first_child_of(scope_syntax), use_item.syntax()); 363 }
338 }
339 },
340 } 364 }
341} 365}
342 366
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 5a88ec742..263edcdc9 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -4,6 +4,23 @@ use hir::PrefixKind;
4use test_utils::assert_eq_text; 4use test_utils::assert_eq_text;
5 5
6#[test] 6#[test]
7fn insert_skips_lone_glob_imports() {
8 check(
9 "use foo::baz::A",
10 r"
11use foo::bar::*;
12",
13 r"
14use foo::bar::*;
15use foo::baz::A;
16",
17 ImportGranularity::Crate,
18 false,
19 false,
20 );
21}
22
23#[test]
7fn insert_not_group() { 24fn insert_not_group() {
8 cov_mark::check!(insert_no_grouping_last); 25 cov_mark::check!(insert_no_grouping_last);
9 check( 26 check(
@@ -534,17 +551,37 @@ fn merge_groups_self() {
534 551
535#[test] 552#[test]
536fn merge_mod_into_glob() { 553fn merge_mod_into_glob() {
537 check_crate( 554 check_with_config(
538 "token::TokenKind", 555 "token::TokenKind",
539 r"use token::TokenKind::*;", 556 r"use token::TokenKind::*;",
540 r"use token::TokenKind::{*, self};", 557 r"use token::TokenKind::{*, self};",
558 false,
559 &InsertUseConfig {
560 granularity: ImportGranularity::Crate,
561 enforce_granularity: true,
562 prefix_kind: PrefixKind::Plain,
563 group: false,
564 skip_glob_imports: false,
565 },
541 ) 566 )
542 // FIXME: have it emit `use token::TokenKind::{self, *}`? 567 // FIXME: have it emit `use token::TokenKind::{self, *}`?
543} 568}
544 569
545#[test] 570#[test]
546fn merge_self_glob() { 571fn merge_self_glob() {
547 check_crate("self", r"use self::*;", r"use self::{*, self};") 572 check_with_config(
573 "self",
574 r"use self::*;",
575 r"use self::{*, self};",
576 false,
577 &InsertUseConfig {
578 granularity: ImportGranularity::Crate,
579 enforce_granularity: true,
580 prefix_kind: PrefixKind::Plain,
581 group: false,
582 skip_glob_imports: false,
583 },
584 )
548 // FIXME: have it emit `use {self, *}`? 585 // FIXME: have it emit `use {self, *}`?
549} 586}
550 587
@@ -757,13 +794,12 @@ use foo::bar::qux;
757 ); 794 );
758} 795}
759 796
760fn check( 797fn check_with_config(
761 path: &str, 798 path: &str,
762 ra_fixture_before: &str, 799 ra_fixture_before: &str,
763 ra_fixture_after: &str, 800 ra_fixture_after: &str,
764 granularity: ImportGranularity,
765 module: bool, 801 module: bool,
766 group: bool, 802 config: &InsertUseConfig,
767) { 803) {
768 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone(); 804 let mut syntax = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
769 if module { 805 if module {
@@ -777,18 +813,32 @@ fn check(
777 .find_map(ast::Path::cast) 813 .find_map(ast::Path::cast)
778 .unwrap(); 814 .unwrap();
779 815
780 insert_use( 816 insert_use(&file, path, config);
781 &file, 817 let result = file.as_syntax_node().to_string();
818 assert_eq_text!(ra_fixture_after, &result);
819}
820
821fn check(
822 path: &str,
823 ra_fixture_before: &str,
824 ra_fixture_after: &str,
825 granularity: ImportGranularity,
826 module: bool,
827 group: bool,
828) {
829 check_with_config(
782 path, 830 path,
783 InsertUseConfig { 831 ra_fixture_before,
832 ra_fixture_after,
833 module,
834 &InsertUseConfig {
784 granularity, 835 granularity,
785 enforce_granularity: true, 836 enforce_granularity: true,
786 prefix_kind: PrefixKind::Plain, 837 prefix_kind: PrefixKind::Plain,
787 group, 838 group,
839 skip_glob_imports: true,
788 }, 840 },
789 ); 841 )
790 let result = file.as_syntax_node().to_string();
791 assert_eq_text!(ra_fixture_after, &result);
792} 842}
793 843
794fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 844fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index c4757a1cb..16c295639 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -44,6 +44,9 @@ config_data! {
44 assist_importPrefix: ImportPrefixDef = "\"plain\"", 44 assist_importPrefix: ImportPrefixDef = "\"plain\"",
45 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines. 45 /// Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
46 assist_importGroup: bool = "true", 46 assist_importGroup: bool = "true",
47 /// Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
48 assist_allowMergingIntoGlobImports: bool = "true",
49
47 /// Show function name and docs in parameter hints. 50 /// Show function name and docs in parameter hints.
48 callInfo_full: bool = "true", 51 callInfo_full: bool = "true",
49 52
@@ -672,6 +675,7 @@ impl Config {
672 ImportPrefixDef::BySelf => PrefixKind::BySelf, 675 ImportPrefixDef::BySelf => PrefixKind::BySelf,
673 }, 676 },
674 group: self.data.assist_importGroup, 677 group: self.data.assist_importGroup,
678 skip_glob_imports: !self.data.assist_allowMergingIntoGlobImports,
675 } 679 }
676 } 680 }
677 pub fn completion(&self) -> CompletionConfig { 681 pub fn completion(&self) -> CompletionConfig {
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 8ddeb59f7..f8afc930a 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -143,6 +143,7 @@ fn integrated_completion_benchmark() {
143 prefix_kind: hir::PrefixKind::ByCrate, 143 prefix_kind: hir::PrefixKind::ByCrate,
144 enforce_granularity: true, 144 enforce_granularity: true,
145 group: true, 145 group: true,
146 skip_glob_imports: true,
146 }, 147 },
147 }; 148 };
148 let position = 149 let position =
@@ -178,6 +179,7 @@ fn integrated_completion_benchmark() {
178 prefix_kind: hir::PrefixKind::ByCrate, 179 prefix_kind: hir::PrefixKind::ByCrate,
179 enforce_granularity: true, 180 enforce_granularity: true,
180 group: true, 181 group: true,
182 skip_glob_imports: true,
181 }, 183 },
182 }; 184 };
183 let position = 185 let position =
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index e53cd3c7b..310d8c6d2 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1187,6 +1187,7 @@ mod tests {
1187 prefix_kind: PrefixKind::Plain, 1187 prefix_kind: PrefixKind::Plain,
1188 enforce_granularity: true, 1188 enforce_granularity: true,
1189 group: true, 1189 group: true,
1190 skip_glob_imports: true,
1190 }, 1191 },
1191 }, 1192 },
1192 ide_db::base_db::FilePosition { file_id, offset }, 1193 ide_db::base_db::FilePosition { file_id, offset },
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index b057e6624..e33e5bb03 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -8,7 +8,7 @@ use parser::SyntaxKind;
8use rowan::{GreenNodeData, GreenTokenData}; 8use rowan::{GreenNodeData, GreenTokenData};
9 9
10use crate::{ 10use crate::{
11 ast::{self, support, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode}, 11 ast::{self, support, AstChildren, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode},
12 NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T, 12 NodeOrToken, SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
13}; 13};
14 14
@@ -45,6 +45,12 @@ fn text_of_first_token(node: &SyntaxNode) -> TokenText<'_> {
45 } 45 }
46} 46}
47 47
48impl ast::BlockExpr {
49 pub fn items(&self) -> AstChildren<ast::Item> {
50 support::children(self.syntax())
51 }
52}
53
48#[derive(Debug, PartialEq, Eq, Clone)] 54#[derive(Debug, PartialEq, Eq, Clone)]
49pub enum Macro { 55pub enum Macro {
50 MacroRules(ast::MacroRules), 56 MacroRules(ast::MacroRules),
@@ -281,6 +287,15 @@ impl ast::Path {
281 successors(self.qualifier(), |p| p.qualifier()) 287 successors(self.qualifier(), |p| p.qualifier())
282 } 288 }
283} 289}
290
291impl ast::Use {
292 pub fn is_simple_glob(&self) -> bool {
293 self.use_tree()
294 .map(|use_tree| use_tree.use_tree_list().is_none() && use_tree.star_token().is_some())
295 .unwrap_or(false)
296 }
297}
298
284impl ast::UseTree { 299impl ast::UseTree {
285 pub fn is_simple_path(&self) -> bool { 300 pub fn is_simple_path(&self) -> bool {
286 self.use_tree_list().is_none() && self.star_token().is_none() 301 self.use_tree_list().is_none() && self.star_token().is_none()
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 34a91486b..18ea77266 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -18,6 +18,11 @@ The path structure for newly inserted paths to use.
18-- 18--
19Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines. 19Group inserted imports by the [following order](https://rust-analyzer.github.io/manual.html#auto-import). Groups are separated by newlines.
20-- 20--
21[[rust-analyzer.assist.allowMergingIntoGlobImports]]rust-analyzer.assist.allowMergingIntoGlobImports (default: `true`)::
22+
23--
24Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.
25--
21[[rust-analyzer.callInfo.full]]rust-analyzer.callInfo.full (default: `true`):: 26[[rust-analyzer.callInfo.full]]rust-analyzer.callInfo.full (default: `true`)::
22+ 27+
23-- 28--
diff --git a/editors/code/package.json b/editors/code/package.json
index 1fac700be..c077bd2c0 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -432,6 +432,11 @@
432 "default": true, 432 "default": true,
433 "type": "boolean" 433 "type": "boolean"
434 }, 434 },
435 "rust-analyzer.assist.allowMergingIntoGlobImports": {
436 "markdownDescription": "Whether to allow import insertion to merge new imports into single path glob imports like `use std::fmt::*;`.",
437 "default": true,
438 "type": "boolean"
439 },
435 "rust-analyzer.callInfo.full": { 440 "rust-analyzer.callInfo.full": {
436 "markdownDescription": "Show function name and docs in parameter hints.", 441 "markdownDescription": "Show function name and docs in parameter hints.",
437 "default": true, 442 "default": true,