diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-05-20 09:27:16 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-05-20 09:27:16 +0100 |
commit | 8bb37737c9e8b7a390ae29d5fb0daaeeb495d2b5 (patch) | |
tree | 0f8d45124abe86151e1954c1766534116e174209 | |
parent | 764241e38e46316b6370977e8b51e841e93e84b9 (diff) | |
parent | 2bf720900f94e36969af44ff8ac52470faf9af4b (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.rs | 8 | ||||
-rw-r--r-- | crates/ide_completion/src/test_utils.rs | 8 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 105 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/insert_use/tests.rs | 141 | ||||
-rw-r--r-- | crates/ide_db/src/helpers/merge_imports.rs | 6 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 46 | ||||
-rw-r--r-- | crates/rust-analyzer/src/integrated_benchmarks.rs | 11 | ||||
-rw-r--r-- | crates/rust-analyzer/src/to_proto.rs | 5 | ||||
-rw-r--r-- | docs/user/generated_config.adoc | 9 | ||||
-rw-r--r-- | editors/code/package.json | 21 |
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; | |||
4 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use ide_db::{ | 5 | use 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 @@ | |||
3 | use hir::{PrefixKind, Semantics}; | 3 | use hir::{PrefixKind, Semantics}; |
4 | use ide_db::{ | 4 | use 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 | }; |
9 | use itertools::Itertools; | 12 | use 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; | |||
4 | use hir::Semantics; | 4 | use hir::Semantics; |
5 | use syntax::{ | 5 | use 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 | ||
11 | use crate::{ | 11 | use 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 | ||
16 | pub use hir::PrefixKind; | 18 | pub use hir::PrefixKind; |
17 | 19 | ||
20 | /// How imports should be grouped into use statements. | ||
21 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
22 | pub 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)] |
19 | pub struct InsertUseConfig { | 34 | pub 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)] | ||
143 | enum 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. |
71 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { | 152 | pub 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; | |||
21 | use self::bar::A; | 21 | use self::bar::A; |
22 | use super::bar::A; | 22 | use super::bar::A; |
23 | use external_crate2::bar::A;", | 23 | use 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] | ||
635 | fn guess_empty() { | ||
636 | check_guess("", ImportGranularityGuess::Unknown); | ||
637 | } | ||
638 | |||
639 | #[test] | ||
640 | fn 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] | ||
647 | fn guess_unknown() { | ||
648 | check_guess( | ||
649 | r" | ||
650 | use foo::bar::baz; | ||
651 | use oof::rab::xuq; | ||
652 | ", | ||
653 | ImportGranularityGuess::Unknown, | ||
654 | ); | ||
655 | } | ||
656 | |||
657 | #[test] | ||
658 | fn guess_item() { | ||
659 | check_guess( | ||
660 | r" | ||
661 | use foo::bar::baz; | ||
662 | use foo::bar::qux; | ||
663 | ", | ||
664 | ImportGranularityGuess::Item, | ||
665 | ); | ||
666 | } | ||
667 | |||
668 | #[test] | ||
669 | fn guess_module() { | ||
670 | check_guess( | ||
671 | r" | ||
672 | use foo::bar::baz; | ||
673 | use 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" | ||
680 | use foo::bar::baz; | ||
681 | use foo::{baz::{qux, quux}, bar}; | ||
682 | ", | ||
683 | ImportGranularityGuess::Module, | ||
684 | ); | ||
685 | } | ||
686 | |||
687 | #[test] | ||
688 | fn guess_crate_or_module() { | ||
689 | check_guess( | ||
690 | r" | ||
691 | use foo::bar::baz; | ||
692 | use oof::bar::{qux, quux}; | ||
693 | ", | ||
694 | ImportGranularityGuess::CrateOrModule, | ||
695 | ); | ||
696 | } | ||
697 | |||
698 | #[test] | ||
699 | fn guess_crate() { | ||
700 | check_guess( | ||
701 | r" | ||
702 | use frob::bar::baz; | ||
703 | use foo::{baz::{qux, quux}, bar}; | ||
704 | ", | ||
705 | ImportGranularityGuess::Crate, | ||
706 | ); | ||
707 | } | ||
708 | |||
709 | #[test] | ||
710 | fn guess_skips_differing_vis() { | ||
711 | check_guess( | ||
712 | r" | ||
713 | use foo::bar::baz; | ||
714 | pub use foo::bar::qux; | ||
715 | ", | ||
716 | ImportGranularityGuess::Unknown, | ||
717 | ); | ||
718 | } | ||
719 | |||
720 | #[test] | ||
721 | fn guess_skips_differing_attrs() { | ||
722 | check_guess( | ||
723 | r" | ||
724 | pub use foo::bar::baz; | ||
725 | #[doc(hidden)] | ||
726 | pub use foo::bar::qux; | ||
727 | ", | ||
728 | ImportGranularityGuess::Unknown, | ||
729 | ); | ||
730 | } | ||
731 | |||
732 | #[test] | ||
733 | fn guess_grouping_matters() { | ||
734 | check_guess( | ||
735 | r" | ||
736 | use foo::bar::baz; | ||
737 | use oof::bar::baz; | ||
738 | use foo::bar::qux; | ||
739 | ", | ||
740 | ImportGranularityGuess::Unknown, | ||
741 | ); | ||
742 | } | ||
743 | |||
634 | fn check( | 744 | fn 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 | ||
659 | fn check_crate(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 778 | fn 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 | ||
663 | fn check_module(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 782 | fn 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 | ||
667 | fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | 786 | fn 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 | ||
671 | fn check_merge_only_fail(ra_fixture0: &str, ra_fixture1: &str, mb: MergeBehavior) { | 790 | fn 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 | |||
809 | fn 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. |
184 | fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> { | 184 | pub 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 | ||
292 | fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool { | 292 | pub 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 | ||
302 | fn eq_attrs( | 302 | pub 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}; | |||
12 | use flycheck::FlycheckConfig; | 12 | use flycheck::FlycheckConfig; |
13 | use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; | 13 | use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig}; |
14 | use ide_db::helpers::{ | 14 | use ide_db::helpers::{ |
15 | insert_use::{InsertUseConfig, PrefixKind}, | 15 | insert_use::{ImportGranularity, InsertUseConfig, PrefixKind}, |
16 | merge_imports::MergeBehavior, | ||
17 | SnippetCap, | 16 | SnippetCap, |
18 | }; | 17 | }; |
19 | use lsp_types::{ClientCapabilities, MarkupKind}; | 18 | use lsp_types::{ClientCapabilities, MarkupKind}; |
@@ -35,15 +34,18 @@ use crate::{ | |||
35 | // be specified directly in `package.json`. | 34 | // be specified directly in `package.json`. |
36 | config_data! { | 35 | config_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")] |
751 | enum MergeBehaviorDef { | 755 | enum 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 @@ | |||
13 | use std::{convert::TryFrom, sync::Arc}; | 13 | use std::{convert::TryFrom, sync::Arc}; |
14 | 14 | ||
15 | use ide::{Change, CompletionConfig, FilePosition, TextSize}; | 15 | use ide::{Change, CompletionConfig, FilePosition, TextSize}; |
16 | use ide_db::helpers::{insert_use::InsertUseConfig, merge_imports::MergeBehavior, SnippetCap}; | 16 | use ide_db::helpers::{ |
17 | insert_use::{ImportGranularity, InsertUseConfig}, | ||
18 | SnippetCap, | ||
19 | }; | ||
17 | use test_utils::project_root; | 20 | use test_utils::project_root; |
18 | use vfs::{AbsPathBuf, VfsPath}; | 21 | use 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 | -- |
4 | The strategy to use when inserting new imports or merging imports. | 4 | How imports should be grouped into use statements. |
5 | -- | ||
6 | [[rust-analyzer.assist.importEnforceGranularity]]rust-analyzer.assist.importEnforceGranularity (default: `false`):: | ||
7 | + | ||
8 | -- | ||
9 | 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. | ||
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", |