1 use std::iter::successors;
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make},
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
9 use crate::{Assist, AssistCtx, AssistId};
11 // Assist: merge_imports
13 // Merges two imports with a common prefix.
16 // use std::<|>fmt::Formatter;
21 // use std::{fmt::Formatter, io};
23 pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> {
24 let tree: ast::UseTree = ctx.find_node_at_offset()?;
25 let mut rewriter = SyntaxRewriter::default();
26 let mut offset = ctx.frange.range.start();
28 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) {
29 let (merged, to_delete) = next_prev()
30 .filter_map(|dir| neighbor(&use_item, dir))
31 .filter_map(|it| Some((it.clone(), it.use_tree()?)))
32 .find_map(|(use_item, use_tree)| {
33 Some((try_merge_trees(&tree, &use_tree)?, use_item))
36 rewriter.replace_ast(&tree, &merged);
37 rewriter += to_delete.remove();
39 if to_delete.syntax().text_range().end() < offset {
40 offset -= to_delete.syntax().text_range().len();
43 let (merged, to_delete) = next_prev()
44 .filter_map(|dir| neighbor(&tree, dir))
45 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
47 rewriter.replace_ast(&tree, &merged);
48 rewriter += to_delete.remove();
50 if to_delete.syntax().text_range().end() < offset {
51 offset -= to_delete.syntax().text_range().len();
55 ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| {
56 edit.rewrite(rewriter);
57 // FIXME: we only need because our diff is imprecise
58 edit.set_cursor(offset);
62 fn next_prev() -> impl Iterator<Item = Direction> {
63 [Direction::Next, Direction::Prev].iter().copied()
66 fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
67 let lhs_path = old.path()?;
68 let rhs_path = new.path()?;
70 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
72 let lhs = old.split_prefix(&lhs_prefix);
73 let rhs = new.split_prefix(&rhs_prefix);
75 let should_insert_comma = lhs
78 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
79 .map(|it| it.kind() != T![,])
82 let mut to_insert: Vec<SyntaxElement> = Vec::new();
83 if should_insert_comma {
84 to_insert.push(make::token(T![,]).into());
85 to_insert.push(make::tokens::single_space().into());
90 .children_with_tokens()
91 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
93 let use_tree_list = lhs.use_tree_list()?;
94 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
95 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
96 Some(lhs.with_use_tree_list(use_tree_list))
99 fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
101 let mut lhs_curr = first_path(&lhs);
102 let mut rhs_curr = first_path(&rhs);
104 match (lhs_curr.segment(), rhs_curr.segment()) {
105 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
108 res = Some((lhs_curr.clone(), rhs_curr.clone()));
110 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
111 (Some(lhs), Some(rhs)) => {
122 fn first_path(path: &ast::Path) -> ast::Path {
123 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
128 use crate::tests::check_assist;
133 fn test_merge_first() {
137 use std::fmt<|>::Debug;
138 use std::fmt::Display;
141 use std::fmt<|>::{Debug, Display};
147 fn test_merge_second() {
152 use std::fmt<|>::Display;
155 use std::fmt:<|>:{Display, Debug};
161 fn test_merge_nested() {
165 use std::{fmt<|>::Debug, fmt::Display};
168 use std::{fmt<|>::{Debug, Display}};
174 use std::{fmt::Debug, fmt<|>::Display};
177 use std::{fmt::<|>{Display, Debug}};
183 fn test_merge_single_wildcard_diff_prefixes() {
191 use std<|>::{cell::*, str};
197 fn test_merge_both_wildcard_diff_prefixes() {
205 use std<|>::{cell::*, str::*};
211 fn removes_just_enough_whitespace() {
221 use foo<|>::{bar, baz};
229 fn works_with_trailing_comma() {
261 fn test_double_comma() {