diff options
Diffstat (limited to 'crates/ide_db/src/helpers/insert_use.rs')
-rw-r--r-- | crates/ide_db/src/helpers/insert_use.rs | 85 |
1 files changed, 67 insertions, 18 deletions
diff --git a/crates/ide_db/src/helpers/insert_use.rs b/crates/ide_db/src/helpers/insert_use.rs index 1fdd110cc..e7ae69302 100644 --- a/crates/ide_db/src/helpers/insert_use.rs +++ b/crates/ide_db/src/helpers/insert_use.rs | |||
@@ -4,12 +4,14 @@ 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, ModuleItemOwner, PathSegmentKind}, | 7 | ast::{self, make, AstNode, 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_visibility, try_merge_imports, use_tree_path_cmp, MergeBehavior, | ||
14 | }, | ||
13 | RootDatabase, | 15 | RootDatabase, |
14 | }; | 16 | }; |
15 | 17 | ||
@@ -18,8 +20,6 @@ pub use hir::PrefixKind; | |||
18 | /// How imports should be grouped into use statements. | 20 | /// How imports should be grouped into use statements. |
19 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 21 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
20 | pub enum ImportGranularity { | 22 | pub enum ImportGranularity { |
21 | /// Try to guess the granularity of imports on a per module basis by observing the existing imports. | ||
22 | Guess, | ||
23 | /// Do not change the granularity of any imports and preserve the original structure written by the developer. | 23 | /// Do not change the granularity of any imports and preserve the original structure written by the developer. |
24 | Preserve, | 24 | Preserve, |
25 | /// Merge imports from the same crate into a single use statement. | 25 | /// Merge imports from the same crate into a single use statement. |
@@ -33,6 +33,7 @@ pub enum ImportGranularity { | |||
33 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | 33 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
34 | pub struct InsertUseConfig { | 34 | pub struct InsertUseConfig { |
35 | pub granularity: ImportGranularity, | 35 | pub granularity: ImportGranularity, |
36 | pub enforce_granularity: bool, | ||
36 | pub prefix_kind: PrefixKind, | 37 | pub prefix_kind: PrefixKind, |
37 | pub group: bool, | 38 | pub group: bool, |
38 | } | 39 | } |
@@ -81,40 +82,88 @@ impl ImportScope { | |||
81 | } | 82 | } |
82 | } | 83 | } |
83 | 84 | ||
84 | fn guess_merge_behavior_from_scope(&self) -> Option<MergeBehavior> { | 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. | ||
85 | let use_stmt = |item| match item { | 88 | let use_stmt = |item| match item { |
86 | ast::Item::Use(use_) => use_.use_tree(), | 89 | ast::Item::Use(use_) => { |
90 | let use_tree = use_.use_tree()?; | ||
91 | Some((use_tree, use_.visibility())) | ||
92 | } | ||
87 | _ => None, | 93 | _ => None, |
88 | }; | 94 | }; |
89 | let use_stmts = match self { | 95 | let mut use_stmts = match self { |
90 | ImportScope::File(f) => f.items(), | 96 | ImportScope::File(f) => f.items(), |
91 | ImportScope::Module(m) => m.items(), | 97 | ImportScope::Module(m) => m.items(), |
92 | } | 98 | } |
93 | .filter_map(use_stmt); | 99 | .filter_map(use_stmt); |
94 | let mut res = None; | 100 | let mut res = ImportGranularityGuess::Unknown; |
95 | for tree in use_stmts { | 101 | let (mut prev, mut prev_vis) = match use_stmts.next() { |
96 | if let Some(list) = tree.use_tree_list() { | 102 | Some(it) => it, |
97 | if list.use_trees().any(|tree| tree.use_tree_list().is_some()) { | 103 | None => return res, |
98 | // double nested tree list, can only be a crate style import at this point | 104 | }; |
99 | return Some(MergeBehavior::Crate); | 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; | ||
100 | } | 113 | } |
101 | // has to be at least a module style based import, might be crate style tho so look further | ||
102 | res = Some(MergeBehavior::Module); | ||
103 | } | 114 | } |
115 | |||
116 | let (curr, curr_vis) = match use_stmts.next() { | ||
117 | Some(it) => it, | ||
118 | None => break res, | ||
119 | }; | ||
120 | if eq_visibility(prev_vis, curr_vis.clone()) { | ||
121 | if let Some((prev_path, curr_path)) = prev.path().zip(curr.path()) { | ||
122 | if let Some(_) = common_prefix(&prev_path, &curr_path) { | ||
123 | if prev.use_tree_list().is_none() && curr.use_tree_list().is_none() { | ||
124 | // Same prefix but no use tree lists so this has to be of item style. | ||
125 | break ImportGranularityGuess::Item; // this overwrites CrateOrModule, technically the file doesn't adhere to anything here. | ||
126 | } else { | ||
127 | // Same prefix with item tree lists, has to be module style as it | ||
128 | // can't be crate style since the trees wouldn't share a prefix then. | ||
129 | break ImportGranularityGuess::Module; | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | prev = curr; | ||
135 | prev_vis = curr_vis; | ||
104 | } | 136 | } |
105 | res | ||
106 | } | 137 | } |
107 | } | 138 | } |
108 | 139 | ||
140 | #[derive(PartialEq, PartialOrd, Debug, Clone, Copy)] | ||
141 | enum ImportGranularityGuess { | ||
142 | Unknown, | ||
143 | Item, | ||
144 | Module, | ||
145 | Crate, | ||
146 | CrateOrModule, | ||
147 | } | ||
148 | |||
109 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. | 149 | /// Insert an import path into the given file/node. A `merge` value of none indicates that no import merging is allowed to occur. |
110 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { | 150 | pub fn insert_use<'a>(scope: &ImportScope, path: ast::Path, cfg: InsertUseConfig) { |
111 | let _p = profile::span("insert_use"); | 151 | let _p = profile::span("insert_use"); |
112 | let mb = match cfg.granularity { | 152 | let mut mb = match cfg.granularity { |
113 | ImportGranularity::Guess => scope.guess_merge_behavior_from_scope(), | ||
114 | ImportGranularity::Crate => Some(MergeBehavior::Crate), | 153 | ImportGranularity::Crate => Some(MergeBehavior::Crate), |
115 | ImportGranularity::Module => Some(MergeBehavior::Module), | 154 | ImportGranularity::Module => Some(MergeBehavior::Module), |
116 | ImportGranularity::Item | ImportGranularity::Preserve => None, | 155 | ImportGranularity::Item | ImportGranularity::Preserve => None, |
117 | }; | 156 | }; |
157 | if !cfg.enforce_granularity { | ||
158 | let file_granularity = scope.guess_granularity_from_scope(); | ||
159 | mb = match file_granularity { | ||
160 | ImportGranularityGuess::Unknown => mb, | ||
161 | ImportGranularityGuess::Item => None, | ||
162 | ImportGranularityGuess::Module => Some(MergeBehavior::Module), | ||
163 | ImportGranularityGuess::Crate => Some(MergeBehavior::Crate), | ||
164 | ImportGranularityGuess::CrateOrModule => mb.or(Some(MergeBehavior::Crate)), | ||
165 | }; | ||
166 | } | ||
118 | 167 | ||
119 | let use_item = | 168 | let use_item = |
120 | make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); | 169 | make::use_(None, make::use_tree(path.clone(), None, None, false)).clone_for_update(); |