aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-09-02 14:21:10 +0100
committerLukas Wirth <[email protected]>2020-09-03 17:36:07 +0100
commited37335c012a66f5d028a160221b1ca152a61325 (patch)
treecef02c8c4cc8ad519ecd8f8040a7100d30329da7
parent74e7422b69d35c55ff6fde77258047f0292d36e0 (diff)
Begin refactor of import insertion
-rw-r--r--crates/assists/src/utils/insert_use.rs908
1 files changed, 440 insertions, 468 deletions
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 49096a67c..8ee5e0c9c 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -1,17 +1,13 @@
1//! Handle syntactic aspects of inserting a new `use`. 1use std::iter::{self, successors};
2// FIXME: rewrite according to the plan, outlined in
3// https://github.com/rust-analyzer/rust-analyzer/issues/3301#issuecomment-592931553
4
5use std::iter::successors;
6 2
3use algo::skip_trivia_token;
4use ast::{edit::AstNodeEdit, PathSegmentKind, VisibilityOwner};
7use either::Either; 5use either::Either;
8use syntax::{ 6use syntax::{
9 ast::{self, NameOwner, VisibilityOwner}, 7 algo,
10 AstNode, AstToken, Direction, SmolStr, 8 ast::{self, make, AstNode},
11 SyntaxKind::{PATH, PATH_SEGMENT}, 9 Direction, InsertPosition, SyntaxElement, SyntaxNode, T,
12 SyntaxNode, SyntaxToken, T,
13}; 10};
14use text_edit::TextEditBuilder;
15 11
16use crate::assist_context::AssistContext; 12use crate::assist_context::AssistContext;
17 13
@@ -22,525 +18,501 @@ pub(crate) fn find_insert_use_container(
22) -> Option<Either<ast::ItemList, ast::SourceFile>> { 18) -> Option<Either<ast::ItemList, ast::SourceFile>> {
23 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { 19 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
24 if let Some(module) = ast::Module::cast(n.clone()) { 20 if let Some(module) = ast::Module::cast(n.clone()) {
25 return module.item_list().map(|it| Either::Left(it)); 21 return module.item_list().map(Either::Left);
26 } 22 }
27 Some(Either::Right(ast::SourceFile::cast(n)?)) 23 Some(Either::Right(ast::SourceFile::cast(n)?))
28 }) 24 })
29} 25}
30 26
31/// Creates and inserts a use statement for the given path to import.
32/// The use statement is inserted in the scope most appropriate to the
33/// the cursor position given, additionally merged with the existing use imports.
34pub(crate) fn insert_use_statement( 27pub(crate) fn insert_use_statement(
35 // Ideally the position of the cursor, used to 28 // Ideally the position of the cursor, used to
36 position: &SyntaxNode, 29 position: &SyntaxNode,
37 path_to_import: &str, 30 path_to_import: &str,
38 ctx: &AssistContext, 31 ctx: &crate::assist_context::AssistContext,
39 builder: &mut TextEditBuilder, 32 builder: &mut text_edit::TextEditBuilder,
40) { 33) {
41 let target = path_to_import.split("::").map(SmolStr::new).collect::<Vec<_>>(); 34 insert_use(position.clone(), make::path_from_text(path_to_import), Some(MergeBehaviour::Full));
42 let container = find_insert_use_container(position, ctx); 35}
36
37pub fn insert_use(
38 where_: SyntaxNode,
39 path: ast::Path,
40 merge_behaviour: Option<MergeBehaviour>,
41) -> SyntaxNode {
42 let use_item = make::use_(make::use_tree(path.clone(), None, None, false));
43 // merge into existing imports if possible
44 if let Some(mb) = merge_behaviour {
45 for existing_use in where_.children().filter_map(ast::Use::cast) {
46 if let Some(merged) = try_merge_imports(&existing_use, &use_item, mb) {
47 let to_delete: SyntaxElement = existing_use.syntax().clone().into();
48 let to_delete = to_delete.clone()..=to_delete;
49 let to_insert = iter::once(merged.syntax().clone().into());
50 return algo::replace_children(&where_, to_delete, to_insert);
51 }
52 }
53 }
54
55 // either we weren't allowed to merge or there is no import that fits the merge conditions
56 // so look for the place we have to insert to
57 let (insert_position, add_blank) = find_insert_position(&where_, path);
58
59 let to_insert: Vec<SyntaxElement> = {
60 let mut buf = Vec::new();
61
62 if add_blank == AddBlankLine::Before {
63 buf.push(make::tokens::single_newline().into());
64 }
65
66 buf.push(use_item.syntax().clone().into());
67
68 if add_blank == AddBlankLine::After {
69 buf.push(make::tokens::single_newline().into());
70 } else if add_blank == AddBlankLine::AfterTwice {
71 buf.push(make::tokens::single_newline().into());
72 buf.push(make::tokens::single_newline().into());
73 }
43 74
44 if let Some(container) = container { 75 buf
45 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone()); 76 };
46 let action = best_action_for_target(syntax, position.clone(), &target); 77
47 make_assist(&action, &target, builder); 78 algo::insert_children(&where_, insert_position, to_insert)
79}
80
81fn try_merge_imports(
82 old: &ast::Use,
83 new: &ast::Use,
84 merge_behaviour: MergeBehaviour,
85) -> Option<ast::Use> {
86 // dont merge into re-exports
87 if old.visibility().map(|vis| vis.pub_token()).is_some() {
88 return None;
89 }
90 let old_tree = old.use_tree()?;
91 let new_tree = new.use_tree()?;
92 let merged = try_merge_trees(&old_tree, &new_tree, merge_behaviour)?;
93 Some(old.with_use_tree(merged))
94}
95
96/// Simple function that checks if a UseTreeList is deeper than one level
97fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool {
98 tl.use_trees().any(|use_tree| {
99 use_tree.use_tree_list().is_some() || use_tree.path().and_then(|p| p.qualifier()).is_some()
100 })
101}
102
103pub fn try_merge_trees(
104 old: &ast::UseTree,
105 new: &ast::UseTree,
106 merge_behaviour: MergeBehaviour,
107) -> Option<ast::UseTree> {
108 let lhs_path = old.path()?;
109 let rhs_path = new.path()?;
110
111 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
112 let lhs = old.split_prefix(&lhs_prefix);
113 let rhs = new.split_prefix(&rhs_prefix);
114 let lhs_tl = lhs.use_tree_list()?;
115 let rhs_tl = rhs.use_tree_list()?;
116
117 // if we are only allowed to merge the last level check if the paths are only one level deep
118 // FIXME: This shouldn't work yet i think
119 if merge_behaviour == MergeBehaviour::Last && use_tree_list_is_nested(&lhs_tl)
120 || use_tree_list_is_nested(&rhs_tl)
121 {
122 return None;
48 } 123 }
124
125 let should_insert_comma = lhs_tl
126 .r_curly_token()
127 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
128 .map(|it| it.kind() != T![,])
129 .unwrap_or(true);
130 let mut to_insert: Vec<SyntaxElement> = Vec::new();
131 if should_insert_comma {
132 to_insert.push(make::token(T![,]).into());
133 to_insert.push(make::tokens::single_space().into());
134 }
135 to_insert.extend(
136 rhs_tl
137 .syntax()
138 .children_with_tokens()
139 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
140 );
141 let pos = InsertPosition::Before(lhs_tl.r_curly_token()?.into());
142 let use_tree_list = lhs_tl.insert_children(pos, to_insert);
143 Some(lhs.with_use_tree_list(use_tree_list))
49} 144}
50 145
51fn collect_path_segments_raw( 146/// Traverses both paths until they differ, returning the common prefix of both.
52 segments: &mut Vec<ast::PathSegment>, 147fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
53 mut path: ast::Path, 148 let mut res = None;
54) -> Option<usize> { 149 let mut lhs_curr = first_path(&lhs);
55 let oldlen = segments.len(); 150 let mut rhs_curr = first_path(&rhs);
56 loop { 151 loop {
57 let mut children = path.syntax().children_with_tokens(); 152 match (lhs_curr.segment(), rhs_curr.segment()) {
58 let (first, second, third) = ( 153 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
59 children.next().map(|n| (n.clone(), n.kind())), 154 _ => break,
60 children.next().map(|n| (n.clone(), n.kind())), 155 }
61 children.next().map(|n| (n.clone(), n.kind())), 156 res = Some((lhs_curr.clone(), rhs_curr.clone()));
62 ); 157
63 match (first, second, third) { 158 match lhs_curr.parent_path().zip(rhs_curr.parent_path()) {
64 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => { 159 Some((lhs, rhs)) => {
65 path = ast::Path::cast(subpath.as_node()?.clone())?; 160 lhs_curr = lhs;
66 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?); 161 rhs_curr = rhs;
67 }
68 (Some((segment, PATH_SEGMENT)), _, _) => {
69 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
70 break;
71 } 162 }
72 (_, _, _) => return None, 163 _ => break,
73 } 164 }
74 } 165 }
75 // We need to reverse only the new added segments 166
76 let only_new_segments = segments.split_at_mut(oldlen).1; 167 res
77 only_new_segments.reverse();
78 Some(segments.len() - oldlen)
79} 168}
80 169
81fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) { 170/// What type of merges are allowed.
82 let mut iter = segments.iter(); 171#[derive(Copy, Clone, PartialEq, Eq)]
83 if let Some(s) = iter.next() { 172pub enum MergeBehaviour {
84 buf.push_str(s); 173 /// Merge everything together creating deeply nested imports.
85 } 174 Full,
86 for s in iter { 175 /// Only merge the last import level, doesn't allow import nesting.
87 buf.push_str("::"); 176 Last,
88 buf.push_str(s);
89 }
90} 177}
91 178
92/// Returns the number of common segments. 179#[derive(Eq, PartialEq, PartialOrd, Ord)]
93fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize { 180enum ImportGroup {
94 left.iter().zip(right).take_while(|(l, r)| compare_path_segment(l, r)).count() 181 // the order here defines the order of new group inserts
182 Std,
183 ExternCrate,
184 ThisCrate,
185 ThisModule,
186 SuperModule,
95} 187}
96 188
97fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool { 189impl ImportGroup {
98 if let Some(kb) = b.kind() { 190 fn new(path: &ast::Path) -> ImportGroup {
99 match kb { 191 let default = ImportGroup::ExternCrate;
100 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(), 192
101 ast::PathSegmentKind::SelfKw => a == "self", 193 let first_segment = match first_segment(path) {
102 ast::PathSegmentKind::SuperKw => a == "super", 194 Some(it) => it,
103 ast::PathSegmentKind::CrateKw => a == "crate", 195 None => return default,
104 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports 196 };
197
198 let kind = first_segment.kind().unwrap_or(PathSegmentKind::SelfKw);
199 match kind {
200 PathSegmentKind::SelfKw => ImportGroup::ThisModule,
201 PathSegmentKind::SuperKw => ImportGroup::SuperModule,
202 PathSegmentKind::CrateKw => ImportGroup::ThisCrate,
203 PathSegmentKind::Name(name) => match name.text().as_str() {
204 "std" => ImportGroup::Std,
205 "core" => ImportGroup::Std,
206 // FIXME: can be ThisModule as well
207 _ => ImportGroup::ExternCrate,
208 },
209 PathSegmentKind::Type { .. } => unreachable!(),
105 } 210 }
106 } else {
107 false
108 } 211 }
109} 212}
110 213
111fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool { 214fn first_segment(path: &ast::Path) -> Option<ast::PathSegment> {
112 a == b.text() 215 first_path(path).segment()
113} 216}
114 217
115#[derive(Clone, Debug)] 218fn first_path(path: &ast::Path) -> ast::Path {
116enum ImportAction { 219 successors(Some(path.clone()), ast::Path::qualifier).last().unwrap()
117 Nothing,
118 // Add a brand new use statement.
119 AddNewUse {
120 anchor: Option<SyntaxNode>, // anchor node
121 add_after_anchor: bool,
122 },
123
124 // To split an existing use statement creating a nested import.
125 AddNestedImport {
126 // how may segments matched with the target path
127 common_segments: usize,
128 path_to_split: ast::Path,
129 // the first segment of path_to_split we want to add into the new nested list
130 first_segment_to_split: Option<ast::PathSegment>,
131 // Wether to add 'self' in addition to the target path
132 add_self: bool,
133 },
134 // To add the target path to an existing nested import tree list.
135 AddInTreeList {
136 common_segments: usize,
137 // The UseTreeList where to add the target path
138 tree_list: ast::UseTreeList,
139 add_self: bool,
140 },
141} 220}
142 221
143impl ImportAction { 222fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
144 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self { 223 path.syntax().children().flat_map(ast::PathSegment::cast)
145 ImportAction::AddNewUse { anchor, add_after_anchor } 224}
225
226#[derive(PartialEq, Eq)]
227enum AddBlankLine {
228 Before,
229 After,
230 AfterTwice,
231}
232
233fn find_insert_position(
234 scope: &SyntaxNode,
235 insert_path: ast::Path,
236) -> (InsertPosition<SyntaxElement>, AddBlankLine) {
237 let group = ImportGroup::new(&insert_path);
238 let path_node_iter = scope
239 .children()
240 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
241 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node)));
242 // Iterator that discards anything thats not in the required grouping
243 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
244 let group_iter = path_node_iter
245 .clone()
246 .skip_while(|(path, _)| ImportGroup::new(path) != group)
247 .take_while(|(path, _)| ImportGroup::new(path) == group);
248
249 let segments = segment_iter(&insert_path);
250 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
251 let mut last = None;
252 // find the element that would come directly after our new import
253 let post_insert =
254 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| {
255 let check_segments = segment_iter(&path);
256 segments
257 .clone()
258 .zip(check_segments)
259 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
260 .all(|(l, r)| l.text() <= r.text())
261 });
262 match post_insert {
263 // insert our import before that element
264 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
265 // there is no element after our new import, so append it to the end of the group
266 None => match last {
267 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
268 // the group we were looking for actually doesnt exist, so insert
269 None => {
270 // similar concept here to the `last` from above
271 let mut last = None;
272 // find the group that comes after where we want to insert
273 let post_group = path_node_iter
274 .inspect(|(_, node)| last = Some(node.clone()))
275 .find(|(p, _)| ImportGroup::new(p) > group);
276 match post_group {
277 Some((_, node)) => {
278 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
279 }
280 // there is no such group, so append after the last one
281 None => match last {
282 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
283 // there are no imports in this file at all
284 None => (InsertPosition::First, AddBlankLine::AfterTwice),
285 },
286 }
287 }
288 },
146 } 289 }
290}
147 291
148 fn add_nested_import( 292#[cfg(test)]
149 common_segments: usize, 293mod tests {
150 path_to_split: ast::Path, 294 use super::*;
151 first_segment_to_split: Option<ast::PathSegment>, 295
152 add_self: bool, 296 use test_utils::assert_eq_text;
153 ) -> Self { 297
154 ImportAction::AddNestedImport { 298 #[test]
155 common_segments, 299 fn insert_start() {
156 path_to_split, 300 check_none(
157 first_segment_to_split, 301 "std::bar::A",
158 add_self, 302 r"use std::bar::B;
159 } 303use std::bar::D;
304use std::bar::F;
305use std::bar::G;",
306 r"use std::bar::A;
307use std::bar::B;
308use std::bar::D;
309use std::bar::F;
310use std::bar::G;",
311 )
160 } 312 }
161 313
162 fn add_in_tree_list( 314 #[test]
163 common_segments: usize, 315 fn insert_middle() {
164 tree_list: ast::UseTreeList, 316 check_none(
165 add_self: bool, 317 "std::bar::E",
166 ) -> Self { 318 r"use std::bar::A;
167 ImportAction::AddInTreeList { common_segments, tree_list, add_self } 319use std::bar::D;
320use std::bar::F;
321use std::bar::G;",
322 r"use std::bar::A;
323use std::bar::D;
324use std::bar::E;
325use std::bar::F;
326use std::bar::G;",
327 )
168 } 328 }
169 329
170 fn better(left: ImportAction, right: ImportAction) -> ImportAction { 330 #[test]
171 if left.is_better(&right) { 331 fn insert_end() {
172 left 332 check_none(
173 } else { 333 "std::bar::Z",
174 right 334 r"use std::bar::A;
175 } 335use std::bar::D;
336use std::bar::F;
337use std::bar::G;",
338 r"use std::bar::A;
339use std::bar::D;
340use std::bar::F;
341use std::bar::G;
342use std::bar::Z;",
343 )
176 } 344 }
177 345
178 fn is_better(&self, other: &ImportAction) -> bool { 346 #[test]
179 match (self, other) { 347 fn insert_middle_pnested() {
180 (ImportAction::Nothing, _) => true, 348 check_none(
181 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false, 349 "std::bar::E",
182 ( 350 r"use std::bar::A;
183 ImportAction::AddNestedImport { common_segments: n, .. }, 351use std::bar::{D, Z}; // example of weird imports due to user
184 ImportAction::AddInTreeList { common_segments: m, .. }, 352use std::bar::F;
185 ) 353use std::bar::G;",
186 | ( 354 r"use std::bar::A;
187 ImportAction::AddInTreeList { common_segments: n, .. }, 355use std::bar::E;
188 ImportAction::AddNestedImport { common_segments: m, .. }, 356use std::bar::{D, Z}; // example of weird imports due to user
189 ) 357use std::bar::F;
190 | ( 358use std::bar::G;",
191 ImportAction::AddInTreeList { common_segments: n, .. }, 359 )
192 ImportAction::AddInTreeList { common_segments: m, .. },
193 )
194 | (
195 ImportAction::AddNestedImport { common_segments: n, .. },
196 ImportAction::AddNestedImport { common_segments: m, .. },
197 ) => n > m,
198 (ImportAction::AddInTreeList { .. }, _) => true,
199 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
200 (ImportAction::AddNestedImport { .. }, _) => true,
201 (ImportAction::AddNewUse { .. }, _) => false,
202 }
203 } 360 }
204}
205 361
206// Find out the best ImportAction to import target path against current_use_tree. 362 #[test]
207// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList. 363 fn insert_middle_groups() {
208fn walk_use_tree_for_best_action( 364 check_none(
209 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments 365 "foo::bar::G",
210 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import 366 r"use std::bar::A;
211 current_use_tree: ast::UseTree, // the use tree we are currently examinating 367use std::bar::D;
212 target: &[SmolStr], // the path we want to import 368
213) -> ImportAction { 369use foo::bar::F;
214 // We save the number of segments in the buffer so we can restore the correct segments 370use foo::bar::H;",
215 // before returning. Recursive call will add segments so we need to delete them. 371 r"use std::bar::A;
216 let prev_len = current_path_segments.len(); 372use std::bar::D;
217 373
218 let tree_list = current_use_tree.use_tree_list(); 374use foo::bar::F;
219 let alias = current_use_tree.rename(); 375use foo::bar::G;
220 376use foo::bar::H;",
221 let path = match current_use_tree.path() { 377 )
222 Some(path) => path, 378 }
223 None => {
224 // If the use item don't have a path, it means it's broken (syntax error)
225 return ImportAction::add_new_use(
226 current_use_tree
227 .syntax()
228 .ancestors()
229 .find_map(ast::Use::cast)
230 .map(|it| it.syntax().clone()),
231 true,
232 );
233 }
234 };
235 379
236 // This can happen only if current_use_tree is a direct child of a UseItem 380 #[test]
237 if let Some(name) = alias.and_then(|it| it.name()) { 381 fn insert_first_matching_group() {
238 if compare_path_segment_with_name(&target[0], &name) { 382 check_none(
239 return ImportAction::Nothing; 383 "foo::bar::G",
240 } 384 r"use foo::bar::A;
385use foo::bar::D;
386
387use std;
388
389use foo::bar::F;
390use foo::bar::H;",
391 r"use foo::bar::A;
392use foo::bar::D;
393use foo::bar::G;
394
395use std;
396
397use foo::bar::F;
398use foo::bar::H;",
399 )
241 } 400 }
242 401
243 collect_path_segments_raw(current_path_segments, path.clone()); 402 #[test]
244 403 fn insert_missing_group() {
245 // We compare only the new segments added in the line just above. 404 check_none(
246 // The first prev_len segments were already compared in 'parent' recursive calls. 405 "std::fmt",
247 let left = target.split_at(prev_len).1; 406 r"use foo::bar::A;
248 let right = current_path_segments.split_at(prev_len).1; 407use foo::bar::D;",
249 let common = compare_path_segments(left, &right); 408 r"use std::fmt;
250 let mut action = match common { 409
251 0 => ImportAction::add_new_use( 410use foo::bar::A;
252 // e.g: target is std::fmt and we can have 411use foo::bar::D;",
253 // use foo::bar 412 )
254 // We add a brand new use statement 413 }
255 current_use_tree
256 .syntax()
257 .ancestors()
258 .find_map(ast::Use::cast)
259 .map(|it| it.syntax().clone()),
260 true,
261 ),
262 common if common == left.len() && left.len() == right.len() => {
263 // e.g: target is std::fmt and we can have
264 // 1- use std::fmt;
265 // 2- use std::fmt::{ ... }
266 if let Some(list) = tree_list {
267 // In case 2 we need to add self to the nested list
268 // unless it's already there
269 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
270 p.and_then(|it| it.segment())
271 .and_then(|it| it.kind())
272 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
273 .is_some()
274 });
275
276 if has_self {
277 ImportAction::Nothing
278 } else {
279 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
280 }
281 } else {
282 // Case 1
283 ImportAction::Nothing
284 }
285 }
286 common if common != left.len() && left.len() == right.len() => {
287 // e.g: target is std::fmt and we have
288 // use std::io;
289 // We need to split.
290 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
291 ImportAction::add_nested_import(
292 prev_len + common,
293 path,
294 Some(segments_to_split[0].clone()),
295 false,
296 )
297 }
298 common if common == right.len() && left.len() > right.len() => {
299 // e.g: target is std::fmt and we can have
300 // 1- use std;
301 // 2- use std::{ ... };
302
303 // fallback action
304 let mut better_action = ImportAction::add_new_use(
305 current_use_tree
306 .syntax()
307 .ancestors()
308 .find_map(ast::Use::cast)
309 .map(|it| it.syntax().clone()),
310 true,
311 );
312 if let Some(list) = tree_list {
313 // Case 2, check recursively if the path is already imported in the nested list
314 for u in list.use_trees() {
315 let child_action = walk_use_tree_for_best_action(
316 current_path_segments,
317 Some(list.clone()),
318 u,
319 target,
320 );
321 if child_action.is_better(&better_action) {
322 better_action = child_action;
323 if let ImportAction::Nothing = better_action {
324 return better_action;
325 }
326 }
327 }
328 } else {
329 // Case 1, split adding self
330 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
331 }
332 better_action
333 }
334 common if common == left.len() && left.len() < right.len() => {
335 // e.g: target is std::fmt and we can have
336 // use std::fmt::Debug;
337 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
338 ImportAction::add_nested_import(
339 prev_len + common,
340 path,
341 Some(segments_to_split[0].clone()),
342 true,
343 )
344 }
345 common if common < left.len() && common < right.len() => {
346 // e.g: target is std::fmt::nested::Debug
347 // use std::fmt::Display
348 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
349 ImportAction::add_nested_import(
350 prev_len + common,
351 path,
352 Some(segments_to_split[0].clone()),
353 false,
354 )
355 }
356 _ => unreachable!(),
357 };
358 414
359 // If we are inside a UseTreeList adding a use statement become adding to the existing 415 #[test]
360 // tree list. 416 fn insert_no_imports() {
361 action = match (current_parent_use_tree_list, action.clone()) { 417 check_full(
362 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => { 418 "foo::bar",
363 ImportAction::add_in_tree_list(prev_len, use_tree_list, false) 419 "fn main() {}",
364 } 420 r"use foo::bar;
365 (_, _) => action,
366 };
367 421
368 // We remove the segments added 422fn main() {}",
369 current_path_segments.truncate(prev_len); 423 )
370 action 424 }
371}
372 425
373fn best_action_for_target( 426 #[test]
374 container: SyntaxNode, 427 fn insert_empty_file() {
375 anchor: SyntaxNode, 428 // empty files will get two trailing newlines
376 target: &[SmolStr], 429 // this is due to the test case insert_no_imports above
377) -> ImportAction { 430 check_full(
378 let mut storage = Vec::with_capacity(16); // this should be the only allocation 431 "foo::bar",
379 let best_action = container 432 "",
380 .children() 433 r"use foo::bar;
381 .filter_map(ast::Use::cast) 434
382 .filter(|u| u.visibility().is_none()) 435",
383 .filter_map(|it| it.use_tree()) 436 )
384 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) 437 }
385 .fold(None, |best, a| match best {
386 Some(best) => Some(ImportAction::better(best, a)),
387 None => Some(a),
388 });
389 438
390 match best_action { 439 #[test]
391 Some(action) => action, 440 fn adds_std_group() {
392 None => { 441 check_full(
393 // We have no action and no UseItem was found in container so we find 442 "std::fmt::Debug",
394 // another item and we use it as anchor. 443 r"use stdx;",
395 // If there are no items above, we choose the target path itself as anchor. 444 r"use std::fmt::Debug;
396 // todo: we should include even whitespace blocks as anchor candidates
397 let anchor = container.children().next().or_else(|| Some(anchor));
398 445
399 let add_after_anchor = anchor 446use stdx;",
400 .clone() 447 )
401 .and_then(ast::Attr::cast)
402 .map(|attr| attr.kind() == ast::AttrKind::Inner)
403 .unwrap_or(false);
404 ImportAction::add_new_use(anchor, add_after_anchor)
405 }
406 } 448 }
407}
408 449
409fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) { 450 #[test]
410 match action { 451 fn merges_groups() {
411 ImportAction::AddNewUse { anchor, add_after_anchor } => { 452 check_last("std::io", r"use std::fmt;", r"use std::{fmt, io};")
412 make_assist_add_new_use(anchor, *add_after_anchor, target, edit)
413 }
414 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => {
415 // We know that the fist n segments already exists in the use statement we want
416 // to modify, so we want to add only the last target.len() - n segments.
417 let segments_to_add = target.split_at(*common_segments).1;
418 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
419 }
420 ImportAction::AddNestedImport {
421 common_segments,
422 path_to_split,
423 first_segment_to_split,
424 add_self,
425 } => {
426 let segments_to_add = target.split_at(*common_segments).1;
427 make_assist_add_nested_import(
428 path_to_split,
429 first_segment_to_split,
430 segments_to_add,
431 *add_self,
432 edit,
433 )
434 }
435 _ => {}
436 } 453 }
437}
438 454
439fn make_assist_add_new_use( 455 #[test]
440 anchor: &Option<SyntaxNode>, 456 fn merges_groups_last() {
441 after: bool, 457 check_last(
442 target: &[SmolStr], 458 "std::io",
443 edit: &mut TextEditBuilder, 459 r"use std::fmt::{Result, Display};",
444) { 460 r"use std::fmt::{Result, Display};
445 if let Some(anchor) = anchor { 461use std::io;",
446 let indent = leading_indent(anchor); 462 )
447 let mut buf = String::new();
448 if after {
449 buf.push_str("\n");
450 if let Some(spaces) = &indent {
451 buf.push_str(spaces);
452 }
453 }
454 buf.push_str("use ");
455 fmt_segments_raw(target, &mut buf);
456 buf.push_str(";");
457 if !after {
458 buf.push_str("\n\n");
459 if let Some(spaces) = &indent {
460 buf.push_str(&spaces);
461 }
462 }
463 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
464 edit.insert(position, buf);
465 } 463 }
466}
467 464
468fn make_assist_add_in_tree_list( 465 #[test]
469 tree_list: &ast::UseTreeList, 466 fn merges_groups2() {
470 target: &[SmolStr], 467 check_full(
471 add_self: bool, 468 "std::io",
472 edit: &mut TextEditBuilder, 469 r"use std::fmt::{Result, Display};",
473) { 470 r"use std::{fmt::{Result, Display}, io};",
474 let last = tree_list.use_trees().last(); 471 )
475 if let Some(last) = last {
476 let mut buf = String::new();
477 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
478 let offset = if let Some(comma) = comma {
479 comma.text_range().end()
480 } else {
481 buf.push_str(",");
482 last.syntax().text_range().end()
483 };
484 if add_self {
485 buf.push_str(" self")
486 } else {
487 buf.push_str(" ");
488 }
489 fmt_segments_raw(target, &mut buf);
490 edit.insert(offset, buf);
491 } else {
492 } 472 }
493}
494 473
495fn make_assist_add_nested_import( 474 #[test]
496 path: &ast::Path, 475 fn skip_merges_groups_pub() {
497 first_segment_to_split: &Option<ast::PathSegment>, 476 check_full(
498 target: &[SmolStr], 477 "std::io",
499 add_self: bool, 478 r"pub use std::fmt::{Result, Display};",
500 edit: &mut TextEditBuilder, 479 r"pub use std::fmt::{Result, Display};
501) { 480use std::io;",
502 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast); 481 )
503 if let Some(use_tree) = use_tree { 482 }
504 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
505 {
506 (first_segment_to_split.syntax().text_range().start(), false)
507 } else {
508 (use_tree.syntax().text_range().end(), true)
509 };
510 let end = use_tree.syntax().text_range().end();
511 483
512 let mut buf = String::new(); 484 #[test]
513 if add_colon_colon { 485 fn merges_groups_self() {
514 buf.push_str("::"); 486 check_full("std::fmt::Debug", r"use std::fmt;", r"use std::fmt::{self, Debug};")
515 }
516 buf.push_str("{");
517 if add_self {
518 buf.push_str("self, ");
519 }
520 fmt_segments_raw(target, &mut buf);
521 if !target.is_empty() {
522 buf.push_str(", ");
523 }
524 edit.insert(start, buf);
525 edit.insert(end, "}".to_string());
526 } 487 }
527}
528 488
529/// If the node is on the beginning of the line, calculate indent. 489 fn check(
530fn leading_indent(node: &SyntaxNode) -> Option<SmolStr> { 490 path: &str,
531 for token in prev_tokens(node.first_token()?) { 491 ra_fixture_before: &str,
532 if let Some(ws) = ast::Whitespace::cast(token.clone()) { 492 ra_fixture_after: &str,
533 let ws_text = ws.text(); 493 mb: Option<MergeBehaviour>,
534 if let Some(pos) = ws_text.rfind('\n') { 494 ) {
535 return Some(ws_text[pos + 1..].into()); 495 let file = ast::SourceFile::parse(ra_fixture_before).tree().syntax().clone();
536 } 496 let path = ast::SourceFile::parse(&format!("use {};", path))
537 } 497 .tree()
538 if token.text().contains('\n') { 498 .syntax()
539 break; 499 .descendants()
540 } 500 .find_map(ast::Path::cast)
501 .unwrap();
502
503 let result = insert_use(file, path, mb).to_string();
504 assert_eq_text!(&result, ra_fixture_after);
505 }
506
507 fn check_full(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
508 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Full))
541 } 509 }
542 return None; 510
543 fn prev_tokens(token: SyntaxToken) -> impl Iterator<Item = SyntaxToken> { 511 fn check_last(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
544 successors(token.prev_token(), |token| token.prev_token()) 512 check(path, ra_fixture_before, ra_fixture_after, Some(MergeBehaviour::Last))
513 }
514
515 fn check_none(path: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
516 check(path, ra_fixture_before, ra_fixture_after, None)
545 } 517 }
546} 518}