aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-20 09:27:16 +0100
committerGitHub <[email protected]>2021-05-20 09:27:16 +0100
commit8bb37737c9e8b7a390ae29d5fb0daaeeb495d2b5 (patch)
tree0f8d45124abe86151e1954c1766534116e174209
parent764241e38e46316b6370977e8b51e841e93e84b9 (diff)
parent2bf720900f94e36969af44ff8ac52470faf9af4b (diff)
Merge #8873
8873: Implement import-granularity guessing r=matklad a=Veykril This renames our `MergeBehavior` to `ImportGranularity` as rustfmt has it as the purpose of them are basically the same. `ImportGranularity::Preserve` currently has no specific purpose for us as we don't have an organize imports assist yet, so it currently acts the same as `ImportGranularity::Item`. We now try to guess the import style on a per file basis and fall back to the user granularity setting if the file has no specific style yet or where it is ambiguous. This can be turned off by setting `import.enforceGranularity` to `true`. Closes https://github.com/rust-analyzer/rust-analyzer/issues/8870 Co-authored-by: Lukas Tobias Wirth <[email protected]>
-rw-r--r--crates/ide_assists/src/tests.rs8
-rw-r--r--crates/ide_completion/src/test_utils.rs8
-rw-r--r--crates/ide_db/src/helpers/insert_use.rs105
-rw-r--r--crates/ide_db/src/helpers/insert_use/tests.rs141
-rw-r--r--crates/ide_db/src/helpers/merge_imports.rs6
-rw-r--r--crates/rust-analyzer/src/config.rs46
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs11
-rw-r--r--crates/rust-analyzer/src/to_proto.rs5
-rw-r--r--docs/user/generated_config.adoc9
-rw-r--r--editors/code/package.json21
10 files changed, 312 insertions, 48 deletions
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs
index 0d3969c36..1739302bf 100644
--- a/crates/ide_assists/src/tests.rs
+++ b/crates/ide_assists/src/tests.rs
@@ -4,7 +4,10 @@ use expect_test::expect;
4use hir::Semantics; 4use hir::Semantics;
5use ide_db::{ 5use ide_db::{
6 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}, 6 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
7 helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap}, 7 helpers::{
8 insert_use::{ImportGranularity, InsertUseConfig},
9 SnippetCap,
10 },
8 source_change::FileSystemEdit, 11 source_change::FileSystemEdit,
9 RootDatabase, 12 RootDatabase,
10}; 13};
@@ -21,8 +24,9 @@ pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
21 snippet_cap: SnippetCap::new(true), 24 snippet_cap: SnippetCap::new(true),
22 allowed: None, 25 allowed: None,
23 insert_use: InsertUseConfig { 26 insert_use: InsertUseConfig {
24 merge: Some(MergeBehavior::Crate), 27 granularity: ImportGranularity::Crate,
25 prefix_kind: hir::PrefixKind::Plain, 28 prefix_kind: hir::PrefixKind::Plain,
29 enforce_granularity: true,
26 group: true, 30 group: true,
27 }, 31 },
28}; 32};
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index 939fb2d47..37be575e5 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -3,7 +3,10 @@
3use hir::{PrefixKind, Semantics}; 3use hir::{PrefixKind, Semantics};
4use ide_db::{ 4use ide_db::{
5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition}, 5 base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
6 helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap}, 6 helpers::{
7 insert_use::{ImportGranularity, InsertUseConfig},
8 SnippetCap,
9 },
7 RootDatabase, 10 RootDatabase,
8}; 11};
9use itertools::Itertools; 12use itertools::Itertools;
@@ -20,8 +23,9 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
20 add_call_argument_snippets: true, 23 add_call_argument_snippets: true,
21 snippet_cap: SnippetCap::new(true), 24 snippet_cap: SnippetCap::new(true),
22 insert_use: InsertUseConfig { 25 insert_use: InsertUseConfig {
23 merge: Some(MergeBehavior::Crate), 26 granularity: ImportGranularity::Crate,
24 prefix_kind: PrefixKind::Plain, 27 prefix_kind: PrefixKind::Plain,
28 enforce_granularity: true,
25 group: true, 29 group: true,
26 }, 30 },
27}; 31};
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs
index 55cdc4da3..aa61c5bcb 100644
--- a/crates/ide_db/src/helpers/insert_use.rs
+++ b/crates/ide_db/src/helpers/insert_use.rs
@@ -4,20 +4,36 @@ use std::cmp::Ordering;
4use hir::Semantics; 4use hir::Semantics;
5use syntax::{ 5use syntax::{
6 algo, 6 algo,
7 ast::{self, make, AstNode, PathSegmentKind}, 7 ast::{self, make, AstNode, AttrsOwner, ModuleItemOwner, PathSegmentKind, VisibilityOwner},
8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken, 8 ted, AstToken, Direction, NodeOrToken, SyntaxNode, SyntaxToken,
9}; 9};
10 10
11use crate::{ 11use crate::{
12 helpers::merge_imports::{try_merge_imports, use_tree_path_cmp, MergeBehavior}, 12 helpers::merge_imports::{
13 common_prefix, eq_attrs, eq_visibility, try_merge_imports, use_tree_path_cmp, MergeBehavior,
14 },
13 RootDatabase, 15 RootDatabase,
14}; 16};
15 17
16pub use hir::PrefixKind; 18pub use hir::PrefixKind;
17 19
20/// How imports should be grouped into use statements.
21#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22pub enum ImportGranularity {
23 /// Do not change the granularity of any imports and preserve the original structure written by the developer.
24 Preserve,
25 /// Merge imports from the same crate into a single use statement.
26 Crate,
27 /// Merge imports from the same module into a single use statement.
28 Module,
29 /// Flatten imports so that each has its own use statement.
30 Item,
31}
32
18#[derive(Clone, Copy, Debug, PartialEq, Eq)] 33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct InsertUseConfig { 34pub struct InsertUseConfig {
20 pub merge: Option<MergeBehavior>, 35 pub granularity: ImportGranularity,
36 pub enforce_granularity: bool,
21 pub prefix_kind: PrefixKind, 37 pub prefix_kind: PrefixKind,
22 pub group: bool, 38 pub group: bool,
23} 39}
@@ -65,15 +81,96 @@ impl ImportScope {
65 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()), 81 ImportScope::Module(item_list) => ImportScope::Module(item_list.clone_for_update()),
66 } 82 }
67 } 83 }
84
85 fn guess_granularity_from_scope(&self) -> ImportGranularityGuess {
86 // The idea is simple, just check each import as well as the import and its precedent together for
87 // whether they fulfill a granularity criteria.
88 let use_stmt = |item| match item {
89 ast::Item::Use(use_) => {
90 let use_tree = use_.use_tree()?;
91 Some((use_tree, use_.visibility(), use_.attrs()))
92 }
93 _ => None,
94 };
95 let mut use_stmts = match self {
96 ImportScope::File(f) => f.items(),
97 ImportScope::Module(m) => m.items(),
98 }
99 .filter_map(use_stmt);
100 let mut res = ImportGranularityGuess::Unknown;
101 let (mut prev, mut prev_vis, mut prev_attrs) = match use_stmts.next() {
102 Some(it) => it,
103 None => return res,
104 };
105 loop {
106 if let Some(use_tree_list) = prev.use_tree_list() {
107 if use_tree_list.use_trees().any(|tree| tree.use_tree_list().is_some()) {
108 // Nested tree lists can only occur in crate style, or with no proper style being enforced in the file.
109 break ImportGranularityGuess::Crate;
110 } else {
111 // Could still be crate-style so continue looking.
112 res = ImportGranularityGuess::CrateOrModule;
113 }
114 }
115
116 let (curr, curr_vis, curr_attrs) = match use_stmts.next() {
117 Some(it) => it,
118 None => break res,
119 };
120 if eq_visibility(prev_vis, curr_vis.clone()) && eq_attrs(prev_attrs, curr_attrs.clone())
121 {
122 if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) {
123 if let Some(_) = common_prefix(&prev_path, &curr_path) {
124 if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() {
125 // Same prefix but no use tree lists so this has to be of item style.
126 break ImportGranularityGuess::Item; // this overwrites CrateOrModule, technically the file doesn't adhere to anything here.
127 } else {
128 // Same prefix with item tree lists, has to be module style as it
129 // can't be crate style since the trees wouldn't share a prefix then.
130 break ImportGranularityGuess::Module;
131 }
132 }
133 }
134 }
135 prev = curr;
136 prev_vis = curr_vis;
137 prev_attrs = curr_attrs;
138 }
139 }
140}
141
142#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
143enum ImportGranularityGuess {
144 Unknown,
145 Item,
146 Module,
147 Crate,
148 CrateOrModule,
68} 149}
69 150
70/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. 151/// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur.
71pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { 152pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) {
72 let _p = profile::span("insert_use"); 153 let _p = profile::span("insert_use");
154 let mut mb = match cfg.granularity {
155 ImportGranularity::Crate => Some(MergeBehavior::Crate),
156 ImportGranularity::Module => Some(MergeBehavior::Module),
157 ImportGranularity::Item | ImportGranularity::Preserve => None,
158 };
159 if !cfg.enforce_granularity {
160 let file_granularity = scope.guess_granularity_from_scope();
161 mb = match file_granularity {
162 ImportGranularityGuess::Unknown => mb,
163 ImportGranularityGuess::Item => None,
164 ImportGranularityGuess::Module => Some(MergeBehavior::Module),
165 ImportGranularityGuess::Crate => Some(MergeBehavior::Crate),
166 ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)),
167 };
168 }
169
73 let use_item = 170 let use_item =
74 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); 171 make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update();
75 // merge into existing imports if possible 172 // merge into existing imports if possible
76 if let Some(mb) = cfg.merge { 173 if let Some(mb) = mb {
77 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) { 174 for existing_use in scope.as_syntax_node().children().filter_map(ast::Use::cast) {
78 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) { 175 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
79 ted::replace(existing_use.syntax(), merged.syntax()); 176 ted::replace(existing_use.syntax(), merged.syntax());
diff --git a/crates/ide_db/src/helpers/insert_use/tests.rs b/crates/ide_db/src/helpers/insert_use/tests.rs
index 248227d29..78a2a87b3 100644
--- a/crates/ide_db/src/helpers/insert_use/tests.rs
+++ b/crates/ide_db/src/helpers/insert_use/tests.rs
@@ -21,7 +21,7 @@ use crate::bar::A;
21use self::bar::A; 21use self::bar::A;
22use super::bar::A; 22use super::bar::A;
23use external_crate2::bar::A;", 23use external_crate2::bar::A;",
24 None, 24 ImportGranularity::Item,
25 false, 25 false,
26 false, 26 false,
27 ); 27 );
@@ -36,7 +36,7 @@ fn insert_not_group_empty() {
36 r"use external_crate2::bar::A; 36 r"use external_crate2::bar::A;
37 37
38", 38",
39 None, 39 ImportGranularity::Item,
40 false, 40 false,
41 false, 41 false,
42 ); 42 );
@@ -281,7 +281,7 @@ fn insert_empty_module() {
281 r"{ 281 r"{
282 use foo::bar; 282 use foo::bar;
283}", 283}",
284 None, 284 ImportGranularity::Item,
285 true, 285 true,
286 true, 286 true,
287 ) 287 )
@@ -631,11 +631,121 @@ fn merge_last_fail3() {
631 ); 631 );
632} 632}
633 633
634#[test]
635fn guess_empty() {
636 check_guess("", ImportGranularityGuess::Unknown);
637}
638
639#[test]
640fn guess_single() {
641 check_guess(r"use foo::{baz::{qux, quux}, bar};", ImportGranularityGuess::Crate);
642 check_guess(r"use foo::bar;", ImportGranularityGuess::Unknown);
643 check_guess(r"use foo::bar::{baz, qux};", ImportGranularityGuess::CrateOrModule);
644}
645
646#[test]
647fn guess_unknown() {
648 check_guess(
649 r"
650use foo::bar::baz;
651use oof::rab::xuq;
652",
653 ImportGranularityGuess::Unknown,
654 );
655}
656
657#[test]
658fn guess_item() {
659 check_guess(
660 r"
661use foo::bar::baz;
662use foo::bar::qux;
663",
664 ImportGranularityGuess::Item,
665 );
666}
667
668#[test]
669fn guess_module() {
670 check_guess(
671 r"
672use foo::bar::baz;
673use foo::bar::{qux, quux};
674",
675 ImportGranularityGuess::Module,
676 );
677 // this is a rather odd case, technically this file isn't following any style properly.
678 check_guess(
679 r"
680use foo::bar::baz;
681use foo::{baz::{qux, quux}, bar};
682",
683 ImportGranularityGuess::Module,
684 );
685}
686
687#[test]
688fn guess_crate_or_module() {
689 check_guess(
690 r"
691use foo::bar::baz;
692use oof::bar::{qux, quux};
693",
694 ImportGranularityGuess::CrateOrModule,
695 );
696}
697
698#[test]
699fn guess_crate() {
700 check_guess(
701 r"
702use frob::bar::baz;
703use foo::{baz::{qux, quux}, bar};
704",
705 ImportGranularityGuess::Crate,
706 );
707}
708
709#[test]
710fn guess_skips_differing_vis() {
711 check_guess(
712 r"
713use foo::bar::baz;
714pub use foo::bar::qux;
715",
716 ImportGranularityGuess::Unknown,
717 );
718}
719
720#[test]
721fn guess_skips_differing_attrs() {
722 check_guess(
723 r"
724pub use foo::bar::baz;
725#[doc(hidden)]
726pub use foo::bar::qux;
727",
728 ImportGranularityGuess::Unknown,
729 );
730}
731
732#[test]
733fn guess_grouping_matters() {
734 check_guess(
735 r"
736use foo::bar::baz;
737use oof::bar::baz;
738use foo::bar::qux;
739",
740 ImportGranularityGuess::Unknown,
741 );
742}
743
634fn check( 744fn check(
635 path: &str, 745 path: &str,
636 ra_fixture_before: &str, 746 ra_fixture_before: &str,
637 ra_fixture_after: &str, 747 ra_fixture_after: &str,
638 mb: Option<MergeBehavior>, 748 granularity: ImportGranularity,
639 module: bool, 749 module: bool,
640 group: bool, 750 group: bool,
641) { 751) {
@@ -651,21 +761,30 @@ fn check(
651 .find_map(ast::Path::cast) 761 .find_map(ast::Path::cast)
652 .unwrap(); 762 .unwrap();
653 763
654 insert_use(&file, path, InsertUseConfig { merge: mb, prefix_kind: PrefixKind::Plain, group }); 764 insert_use(
765 &file,
766 path,
767 InsertUseConfig {
768 granularity,
769 enforce_granularity: true,
770 prefix_kind: PrefixKind::Plain,
771 group,
772 },
773 );
655 let result = file.as_syntax_node().to_string(); 774 let result = file.as_syntax_node().to_string();
656 assert_eq_text!(ra_fixture_after, &result); 775 assert_eq_text!(ra_fixture_after, &result);
657} 776}
658 777
659fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 778fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
660 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Crate), false, true) 779 check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Crate, false, true)
661} 780}
662 781
663fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 782fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
664 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehavior::Module), false, true) 783 check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Module, false, true)
665} 784}
666 785
667fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 786fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
668 check(path, ra_fixture_before, ra_fixture_after, None, false, true) 787 check(path, ra_fixture_before, ra_fixture_after, ImportGranularity::Item, false, true)
669} 788}
670 789
671fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { 790fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) {
@@ -686,3 +805,9 @@ fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior
686 let result = try_merge_imports(&use0, &use1, mb); 805 let result = try_merge_imports(&use0, &use1, mb);
687 assert_eq!(result.map(|u| u.to_string()), None); 806 assert_eq!(result.map(|u| u.to_string()), None);
688} 807}
808
809fn check_guess(ra_fixture: &str, expected: ImportGranularityGuess) {
810 let syntax = ast::SourceFile::parse(ra_fixture).tree().syntax().clone();
811 let file = super::ImportScope::from(syntax).unwrap();
812 assert_eq!(file.guess_granularity_from_scope(), expected);
813}
diff --git a/crates/ide_db/src/helpers/merge_imports.rs b/crates/ide_db/src/helpers/merge_imports.rs
index 8fb40e837..697e8bcff 100644
--- a/crates/ide_db/src/helpers/merge_imports.rs
+++ b/crates/ide_db/src/helpers/merge_imports.rs
@@ -181,7 +181,7 @@ fn recursive_merge(
181} 181}
182 182
183/// Traverses both paths until they differ, returning the common prefix of both. 183/// Traverses both paths until they differ, returning the common prefix of both.
184fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> { 184pub fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
185 let mut res = None; 185 let mut res = None;
186 let mut lhs_curr = lhs.first_qualifier_or_self(); 186 let mut lhs_curr = lhs.first_qualifier_or_self();
187 let mut rhs_curr = rhs.first_qualifier_or_self(); 187 let mut rhs_curr = rhs.first_qualifier_or_self();
@@ -289,7 +289,7 @@ fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
289 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text)) 289 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
290} 290}
291 291
292fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { 292pub fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
293 match (vis0, vis1) { 293 match (vis0, vis1) {
294 (None, None) => true, 294 (None, None) => true,
295 // FIXME: Don't use the string representation to check for equality 295 // FIXME: Don't use the string representation to check for equality
@@ -299,7 +299,7 @@ fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -
299 } 299 }
300} 300}
301 301
302fn eq_attrs( 302pub fn eq_attrs(
303 attrs0: impl Iterator<Item = ast::Attr>, 303 attrs0: impl Iterator<Item = ast::Attr>,
304 attrs1: impl Iterator<Item = ast::Attr>, 304 attrs1: impl Iterator<Item = ast::Attr>,
305) -> bool { 305) -> bool {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 339014fd3..b700d025f 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -12,8 +12,7 @@ use std::{ffi::OsString, iter, path::PathBuf};
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; 13use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
14use ide_db::helpers::{ 14use ide_db::helpers::{
15 insert_use::{InsertUseConfig, PrefixKind}, 15 insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
16 merge_imports::MergeBehavior,
17 SnippetCap, 16 SnippetCap,
18}; 17};
19use lsp_types::{ClientCapabilities, MarkupKind}; 18use lsp_types::{ClientCapabilities, MarkupKind};
@@ -35,15 +34,18 @@ use crate::{
35// be specified directly in `package.json`. 34// be specified directly in `package.json`.
36config_data! { 35config_data! {
37 struct ConfigData { 36 struct ConfigData {
38 /// The strategy to use when inserting new imports or merging imports. 37 /// How imports should be grouped into use statements.
38 assist_importGranularity |
39 assist_importMergeBehavior | 39 assist_importMergeBehavior |
40 assist_importMergeBehaviour: MergeBehaviorDef = "\"crate\"", 40 assist_importMergeBehaviour: ImportGranularityDef = "\"crate\"",
41 /// Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
42 assist_importEnforceGranularity: bool = "false",
41 /// The path structure for newly inserted paths to use. 43 /// The path structure for newly inserted paths to use.
42 assist_importPrefix: ImportPrefixDef = "\"plain\"", 44 assist_importPrefix: ImportPrefixDef = "\"plain\"",
43 /// 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.
44 assist_importGroup: bool = "true", 46 assist_importGroup: bool = "true",
45 /// Show function name and docs in parameter hints. 47 /// Show function name and docs in parameter hints.
46 callInfo_full: bool = "true", 48 callInfo_full: bool = "true",
47 49
48 /// Automatically refresh project info via `cargo metadata` on 50 /// Automatically refresh project info via `cargo metadata` on
49 /// `Cargo.toml` changes. 51 /// `Cargo.toml` changes.
@@ -624,11 +626,13 @@ impl Config {
624 } 626 }
625 fn insert_use_config(&self) -> InsertUseConfig { 627 fn insert_use_config(&self) -> InsertUseConfig {
626 InsertUseConfig { 628 InsertUseConfig {
627 merge: match self.data.assist_importMergeBehavior { 629 granularity: match self.data.assist_importGranularity {
628 MergeBehaviorDef::None => None, 630 ImportGranularityDef::Preserve => ImportGranularity::Preserve,
629 MergeBehaviorDef::Crate => Some(MergeBehavior::Crate), 631 ImportGranularityDef::Item => ImportGranularity::Item,
630 MergeBehaviorDef::Module => Some(MergeBehavior::Module), 632 ImportGranularityDef::Crate => ImportGranularity::Crate,
633 ImportGranularityDef::Module => ImportGranularity::Module,
631 }, 634 },
635 enforce_granularity: self.data.assist_importEnforceGranularity,
632 prefix_kind: match self.data.assist_importPrefix { 636 prefix_kind: match self.data.assist_importPrefix {
633 ImportPrefixDef::Plain => PrefixKind::Plain, 637 ImportPrefixDef::Plain => PrefixKind::Plain,
634 ImportPrefixDef::ByCrate => PrefixKind::ByCrate, 638 ImportPrefixDef::ByCrate => PrefixKind::ByCrate,
@@ -748,8 +752,10 @@ enum ManifestOrProjectJson {
748 752
749#[derive(Deserialize, Debug, Clone)] 753#[derive(Deserialize, Debug, Clone)]
750#[serde(rename_all = "snake_case")] 754#[serde(rename_all = "snake_case")]
751enum MergeBehaviorDef { 755enum ImportGranularityDef {
752 None, 756 Preserve,
757 #[serde(alias = "none")]
758 Item,
753 #[serde(alias = "full")] 759 #[serde(alias = "full")]
754 Crate, 760 Crate,
755 #[serde(alias = "last")] 761 #[serde(alias = "last")]
@@ -782,7 +788,7 @@ macro_rules! _config_data {
782 (struct $name:ident { 788 (struct $name:ident {
783 $( 789 $(
784 $(#[doc=$doc:literal])* 790 $(#[doc=$doc:literal])*
785 $field:ident $(| $alias:ident)?: $ty:ty = $default:expr, 791 $field:ident $(| $alias:ident)*: $ty:ty = $default:expr,
786 )* 792 )*
787 }) => { 793 }) => {
788 #[allow(non_snake_case)] 794 #[allow(non_snake_case)]
@@ -794,7 +800,7 @@ macro_rules! _config_data {
794 $field: get_field( 800 $field: get_field(
795 &mut json, 801 &mut json,
796 stringify!($field), 802 stringify!($field),
797 None$(.or(Some(stringify!($alias))))?, 803 None$(.or(Some(stringify!($alias))))*,
798 $default, 804 $default,
799 ), 805 ),
800 )*} 806 )*}
@@ -931,6 +937,16 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
931 "Merge imports from the same module into a single `use` statement." 937 "Merge imports from the same module into a single `use` statement."
932 ], 938 ],
933 }, 939 },
940 "ImportGranularityDef" => set! {
941 "type": "string",
942 "enum": ["preserve", "crate", "module", "item"],
943 "enumDescriptions": [
944 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
945 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
946 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
947 "Flatten imports so that each has its own use statement."
948 ],
949 },
934 "ImportPrefixDef" => set! { 950 "ImportPrefixDef" => set! {
935 "type": "string", 951 "type": "string",
936 "enum": [ 952 "enum": [
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index ba2790acb..781073fe5 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -13,7 +13,10 @@
13use std::{convert::TryFrom, sync::Arc}; 13use std::{convert::TryFrom, sync::Arc};
14 14
15use ide::{Change, CompletionConfig, FilePosition, TextSize}; 15use ide::{Change, CompletionConfig, FilePosition, TextSize};
16use ide_db::helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap}; 16use ide_db::helpers::{
17 insert_use::{ImportGranularity, InsertUseConfig},
18 SnippetCap,
19};
17use test_utils::project_root; 20use test_utils::project_root;
18use vfs::{AbsPathBuf, VfsPath}; 21use vfs::{AbsPathBuf, VfsPath};
19 22
@@ -133,8 +136,9 @@ fn integrated_completion_benchmark() {
133 add_call_argument_snippets: true, 136 add_call_argument_snippets: true,
134 snippet_cap: SnippetCap::new(true), 137 snippet_cap: SnippetCap::new(true),
135 insert_use: InsertUseConfig { 138 insert_use: InsertUseConfig {
136 merge: Some(MergeBehavior::Crate), 139 granularity: ImportGranularity::Crate,
137 prefix_kind: hir::PrefixKind::ByCrate, 140 prefix_kind: hir::PrefixKind::ByCrate,
141 enforce_granularity: true,
138 group: true, 142 group: true,
139 }, 143 },
140 }; 144 };
@@ -166,8 +170,9 @@ fn integrated_completion_benchmark() {
166 add_call_argument_snippets: true, 170 add_call_argument_snippets: true,
167 snippet_cap: SnippetCap::new(true), 171 snippet_cap: SnippetCap::new(true),
168 insert_use: InsertUseConfig { 172 insert_use: InsertUseConfig {
169 merge: Some(MergeBehavior::Crate), 173 granularity: ImportGranularity::Crate,
170 prefix_kind: hir::PrefixKind::ByCrate, 174 prefix_kind: hir::PrefixKind::ByCrate,
175 enforce_granularity: true,
171 group: true, 176 group: true,
172 }, 177 },
173 }; 178 };
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 9dec46c78..ef9e0aee9 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -1145,7 +1145,7 @@ mod tests {
1145 1145
1146 use ide::Analysis; 1146 use ide::Analysis;
1147 use ide_db::helpers::{ 1147 use ide_db::helpers::{
1148 insert_use::{InsertUseConfig, PrefixKind}, 1148 insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
1149 SnippetCap, 1149 SnippetCap,
1150 }; 1150 };
1151 1151
@@ -1177,8 +1177,9 @@ mod tests {
1177 add_call_argument_snippets: true, 1177 add_call_argument_snippets: true,
1178 snippet_cap: SnippetCap::new(true), 1178 snippet_cap: SnippetCap::new(true),
1179 insert_use: InsertUseConfig { 1179 insert_use: InsertUseConfig {
1180 merge: None, 1180 granularity: ImportGranularity::Item,
1181 prefix_kind: PrefixKind::Plain, 1181 prefix_kind: PrefixKind::Plain,
1182 enforce_granularity: true,
1182 group: true, 1183 group: true,
1183 }, 1184 },
1184 }, 1185 },
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index b32411887..c02bab7cc 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -1,7 +1,12 @@
1[[rust-analyzer.assist.importMergeBehavior]]rust-analyzer.assist.importMergeBehavior (default: `"crate"`):: 1[[rust-analyzer.assist.importGranularity]]rust-analyzer.assist.importGranularity (default: `"crate"`)::
2+ 2+
3-- 3--
4The strategy to use when inserting new imports or merging imports. 4How imports should be grouped into use statements.
5--
6[[rust-analyzer.assist.importEnforceGranularity]]rust-analyzer.assist.importEnforceGranularity (default: `false`)::
7+
8--
9Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.
5-- 10--
6[[rust-analyzer.assist.importPrefix]]rust-analyzer.assist.importPrefix (default: `"plain"`):: 11[[rust-analyzer.assist.importPrefix]]rust-analyzer.assist.importPrefix (default: `"plain"`)::
7+ 12+
diff --git a/editors/code/package.json b/editors/code/package.json
index 99223c4e8..1743b374c 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -385,21 +385,28 @@
385 "markdownDescription": "Optional settings passed to the debug engine. Example: `{ \"lldb\": { \"terminal\":\"external\"} }`" 385 "markdownDescription": "Optional settings passed to the debug engine. Example: `{ \"lldb\": { \"terminal\":\"external\"} }`"
386 }, 386 },
387 "$generated-start": false, 387 "$generated-start": false,
388 "rust-analyzer.assist.importMergeBehavior": { 388 "rust-analyzer.assist.importGranularity": {
389 "markdownDescription": "The strategy to use when inserting new imports or merging imports.", 389 "markdownDescription": "How imports should be grouped into use statements.",
390 "default": "crate", 390 "default": "crate",
391 "type": "string", 391 "type": "string",
392 "enum": [ 392 "enum": [
393 "none", 393 "preserve",
394 "crate", 394 "crate",
395 "module" 395 "module",
396 "item"
396 ], 397 ],
397 "enumDescriptions": [ 398 "enumDescriptions": [
398 "Do not merge imports at all.", 399 "Do not change the granularity of any imports and preserve the original structure written by the developer.",
399 "Merge imports from the same crate into a single `use` statement.", 400 "Merge imports from the same crate into a single use statement. Conversely, imports from different crates are split into separate statements.",
400 "Merge imports from the same module into a single `use` statement." 401 "Merge imports from the same module into a single use statement. Conversely, imports from different modules are split into separate statements.",
402 "Flatten imports so that each has its own use statement."
401 ] 403 ]
402 }, 404 },
405 "rust-analyzer.assist.importEnforceGranularity": {
406 "markdownDescription": "Whether to enforce the import granularity setting for all files. If set to false rust-analyzer will try to keep import styles consistent per file.",
407 "default": false,
408 "type": "boolean"
409 },
403 "rust-analyzer.assist.importPrefix": { 410 "rust-analyzer.assist.importPrefix": {
404 "markdownDescription": "The path structure for newly inserted paths to use.", 411 "markdownDescription": "The path structure for newly inserted paths to use.",
405 "default": "plain", 412 "default": "plain",