diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ide_assists/src/handlers/auto_import.rs | 17 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/merge_imports.rs | 14 | ||||
-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 | 21 | ||||
-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-- | crates/stdx/src/lib.rs | 28 | ||||
-rw-r--r-- | crates/syntax/src/ast/node_ext.rs | 23 |
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" | ||
220 | pub(in this::path) use std::fmt$0::Debug; | ||
221 | pub(in this::path) use std::fmt::Display; | ||
222 | ", | ||
223 | r" | ||
224 | pub(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; | |||
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..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. |
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,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 | ||
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 | (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 | ||
302 | fn eq_attrs( | 300 | pub 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 | ||
311 | fn path_is_self(path: &ast::Path) -> bool { | 314 | fn 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}; | |||
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/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 | ||
145 | pub fn iter_eq_by<I, I2, F>(this: I2, other: I, mut eq: F) -> bool | ||
146 | where | ||
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)] |
144 | mod tests { | 172 | mod 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 | ||
613 | impl ast::LifetimeParam { | 636 | impl ast::LifetimeParam { |