aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ide_assists/src/handlers/auto_import.rs17
-rw-r--r--crates/ide_assists/src/handlers/merge_imports.rs14
-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.rs21
-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--crates/stdx/src/lib.rs28
-rw-r--r--crates/syntax/src/ast/node_ext.rs23
12 files changed, 373 insertions, 54 deletions
diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs
index dda5a6631..d4748ef3a 100644
--- a/crates/ide_assists/src/handlers/auto_import.rs
+++ b/crates/ide_assists/src/handlers/auto_import.rs
@@ -33,20 +33,19 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
33// use super::AssistContext; 33// use super::AssistContext;
34// ``` 34// ```
35// 35//
36// .Merge Behavior 36// .Import Granularity
37// 37//
38// It is possible to configure how use-trees are merged with the `importMergeBehavior` setting. 38// It is possible to configure how use-trees are merged with the `importGranularity` setting.
39// It has the following configurations: 39// It has the following configurations:
40// 40//
41// - `full`: This setting will cause auto-import to always completely merge use-trees that share the 41// - `crate`: Merge imports from the same crate into a single use statement. This kind of
42// same path prefix while also merging inner trees that share the same path-prefix. This kind of
43// nesting is only supported in Rust versions later than 1.24. 42// nesting is only supported in Rust versions later than 1.24.
44// - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree 43// - `module`: Merge imports from the same module into a single use statement.
45// will only contain a nesting of single segment paths at the very end. 44// - `item`: Don't merge imports at all, creating one import per item.
46// - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple 45// - `preserve`: Do not change the granularity of any imports. For auto-import this has the same
47// paths. 46// effect as `item`.
48// 47//
49// In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehavior`. 48// In `VS Code` the configuration for this is `rust-analyzer.assist.importGranularity`.
50// 49//
51// .Import Prefix 50// .Import Prefix
52// 51//
diff --git a/crates/ide_assists/src/handlers/merge_imports.rs b/crates/ide_assists/src/handlers/merge_imports.rs
index 31854840c..fc117bd9a 100644
--- a/crates/ide_assists/src/handlers/merge_imports.rs
+++ b/crates/ide_assists/src/handlers/merge_imports.rs
@@ -213,6 +213,20 @@ pub(crate) use std::fmt::{Debug, Display};
213 } 213 }
214 214
215 #[test] 215 #[test]
216 fn merge_pub_in_path_crate() {
217 check_assist(
218 merge_imports,
219 r"
220pub(in this::path) use std::fmt$0::Debug;
221pub(in this::path) use std::fmt::Display;
222",
223 r"
224pub(in this::path) use std::fmt::{Debug, Display};
225",
226 )
227 }
228
229 #[test]
216 fn test_merge_nested() { 230 fn test_merge_nested() {
217 check_assist( 231 check_assist(
218 merge_imports, 232 merge_imports,
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..0dbabb44f 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,23 +289,26 @@ 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 (Some(vis0), Some(vis1)) => vis0.is_eq_to(&vis1),
296 // spaces inside of the node would break this comparison
297 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
298 _ => false, 296 _ => false,
299 } 297 }
300} 298}
301 299
302fn eq_attrs( 300pub fn eq_attrs(
303 attrs0: impl Iterator<Item = ast::Attr>, 301 attrs0: impl Iterator<Item = ast::Attr>,
304 attrs1: impl Iterator<Item = ast::Attr>, 302 attrs1: impl Iterator<Item = ast::Attr>,
305) -> bool { 303) -> bool {
306 let attrs0 = attrs0.map(|attr| attr.to_string()); 304 // FIXME order of attributes should not matter
307 let attrs1 = attrs1.map(|attr| attr.to_string()); 305 let attrs0 = attrs0
308 attrs0.eq(attrs1) 306 .flat_map(|attr| attr.syntax().descendants_with_tokens())
307 .flat_map(|it| it.into_token());
308 let attrs1 = attrs1
309 .flat_map(|attr| attr.syntax().descendants_with_tokens())
310 .flat_map(|it| it.into_token());
311 stdx::iter_eq_by(attrs0, attrs1, |tok, tok2| tok.text() == tok2.text())
309} 312}
310 313
311fn path_is_self(path: &ast::Path) -> bool { 314fn path_is_self(path: &ast::Path) -> 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/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 340fcacfa..18d5fadb9 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -140,6 +140,34 @@ impl JodChild {
140 } 140 }
141} 141}
142 142
143// feature: iter_order_by
144// Iterator::eq_by
145pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool
146where
147 I: IntoIterator,
148 I2: IntoIterator,
149 F: FnMut(I2::Item, I::Item) -> bool,
150{
151 let mut other = other.into_iter();
152 let mut this = this.into_iter();
153
154 loop {
155 let x = match this.next() {
156 None => return other.next().is_none(),
157 Some(val) => val,
158 };
159
160 let y = match other.next() {
161 None => return false,
162 Some(val) => val,
163 };
164
165 if !eq(x, y) {
166 return false;
167 }
168 }
169}
170
143#[cfg(test)] 171#[cfg(test)]
144mod tests { 172mod tests {
145 use super::*; 173 use super::*;
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index bef49238f..df8f98b5b 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -608,6 +608,29 @@ impl ast::Visibility {
608 None => VisibilityKind::Pub, 608 None => VisibilityKind::Pub,
609 } 609 }
610 } 610 }
611
612 pub fn is_eq_to(&self, other: &Self) -> bool {
613 match (self.kind(), other.kind()) {
614 (VisibilityKind::In(this), VisibilityKind::In(other)) => {
615 stdx::iter_eq_by(this.segments(), other.segments(), |lhs, rhs| {
616 lhs.kind().zip(rhs.kind()).map_or(false, |it| match it {
617 (PathSegmentKind::CrateKw, PathSegmentKind::CrateKw)
618 | (PathSegmentKind::SelfKw, PathSegmentKind::SelfKw)
619 | (PathSegmentKind::SuperKw, PathSegmentKind::SuperKw) => true,
620 (PathSegmentKind::Name(lhs), PathSegmentKind::Name(rhs)) => {
621 lhs.text() == rhs.text()
622 }
623 _ => false,
624 })
625 })
626 }
627 (VisibilityKind::PubSelf, VisibilityKind::PubSelf)
628 | (VisibilityKind::PubSuper, VisibilityKind::PubSuper)
629 | (VisibilityKind::PubCrate, VisibilityKind::PubCrate)
630 | (VisibilityKind::Pub, VisibilityKind::Pub) => true,
631 _ => false,
632 }
633 }
611} 634}
612 635
613impl ast::LifetimeParam { 636impl ast::LifetimeParam {