aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/merge_imports.rs161
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs144
-rw-r--r--crates/assists/src/utils/insert_use.rs35
3 files changed, 222 insertions, 118 deletions
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index 35b884206..0bd679260 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -1,14 +1,14 @@
1use std::iter::successors;
2
3use syntax::{ 1use syntax::{
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter}, 2 algo::{neighbor, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make}, 3 ast, AstNode,
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7}; 4};
8 5
9use crate::{ 6use crate::{
10 assist_context::{AssistContext, Assists}, 7 assist_context::{AssistContext, Assists},
11 utils::next_prev, 8 utils::{
9 insert_use::{try_merge_imports, try_merge_trees},
10 next_prev, MergeBehaviour,
11 },
12 AssistId, AssistKind, 12 AssistId, AssistKind,
13}; 13};
14 14
@@ -30,23 +30,22 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
30 let mut offset = ctx.offset(); 30 let mut offset = ctx.offset();
31 31
32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) { 32 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
33 let (merged, to_delete) = next_prev() 33 let (merged, to_delete) =
34 .filter_map(|dir| neighbor(&use_item, dir)) 34 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
35 .filter_map(|it| Some((it.clone(), it.use_tree()?))) 35 try_merge_imports(&use_item, &use_item2, MergeBehaviour::Full).zip(Some(use_item2))
36 .find_map(|(use_item, use_tree)| {
37 Some((try_merge_trees(&tree, &use_tree)?, use_item))
38 })?; 36 })?;
39 37
40 rewriter.replace_ast(&tree, &merged); 38 rewriter.replace_ast(&use_item, &merged);
41 rewriter += to_delete.remove(); 39 rewriter += to_delete.remove();
42 40
43 if to_delete.syntax().text_range().end() < offset { 41 if to_delete.syntax().text_range().end() < offset {
44 offset -= to_delete.syntax().text_range().len(); 42 offset -= to_delete.syntax().text_range().len();
45 } 43 }
46 } else { 44 } else {
47 let (merged, to_delete) = next_prev() 45 let (merged, to_delete) =
48 .filter_map(|dir| neighbor(&tree, dir)) 46 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
49 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; 47 try_merge_trees(&tree, &use_tree, MergeBehaviour::Full).zip(Some(use_tree))
48 })?;
50 49
51 rewriter.replace_ast(&tree, &merged); 50 rewriter.replace_ast(&tree, &merged);
52 rewriter += to_delete.remove(); 51 rewriter += to_delete.remove();
@@ -67,66 +66,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
67 ) 66 )
68} 67}
69 68
70fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
71 let lhs_path = old.path()?;
72 let rhs_path = new.path()?;
73
74 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
75
76 let lhs = old.split_prefix(&lhs_prefix);
77 let rhs = new.split_prefix(&rhs_prefix);
78
79 let should_insert_comma = lhs
80 .use_tree_list()?
81 .r_curly_token()
82 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
83 .map(|it| it.kind() != T![,])
84 .unwrap_or(true);
85
86 let mut to_insert: Vec<SyntaxElement> = Vec::new();
87 if should_insert_comma {
88 to_insert.push(make::token(T![,]).into());
89 to_insert.push(make::tokens::single_space().into());
90 }
91 to_insert.extend(
92 rhs.use_tree_list()?
93 .syntax()
94 .children_with_tokens()
95 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
96 );
97 let use_tree_list = lhs.use_tree_list()?;
98 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
99 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
100 Some(lhs.with_use_tree_list(use_tree_list))
101}
102
103fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
104 let mut res = None;
105 let mut lhs_curr = first_path(&lhs);
106 let mut rhs_curr = first_path(&rhs);
107 loop {
108 match (lhs_curr.segment(), rhs_curr.segment()) {
109 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
110 _ => break,
111 }
112 res = Some((lhs_curr.clone(), rhs_curr.clone()));
113
114 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
115 (Some(lhs), Some(rhs)) => {
116 lhs_curr = lhs;
117 rhs_curr = rhs;
118 }
119 _ => break,
120 }
121 }
122
123 res
124}
125
126fn first_path(path: &ast::Path) -> ast::Path {
127 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
128}
129
130#[cfg(test)] 69#[cfg(test)]
131mod tests { 70mod tests {
132 use crate::tests::{check_assist, check_assist_not_applicable}; 71 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -189,6 +128,78 @@ use std::{fmt::{Display, self}};
189 } 128 }
190 129
191 #[test] 130 #[test]
131 fn skip_pub1() {
132 check_assist_not_applicable(
133 merge_imports,
134 r"
135pub use std::fmt<|>::Debug;
136use std::fmt::Display;
137",
138 );
139 }
140
141 #[test]
142 fn skip_pub_last() {
143 check_assist_not_applicable(
144 merge_imports,
145 r"
146use std::fmt<|>::Debug;
147pub use std::fmt::Display;
148",
149 );
150 }
151
152 #[test]
153 fn skip_pub_crate_pub() {
154 check_assist_not_applicable(
155 merge_imports,
156 r"
157pub(crate) use std::fmt<|>::Debug;
158pub use std::fmt::Display;
159",
160 );
161 }
162
163 #[test]
164 fn skip_pub_pub_crate() {
165 check_assist_not_applicable(
166 merge_imports,
167 r"
168pub use std::fmt<|>::Debug;
169pub(crate) use std::fmt::Display;
170",
171 );
172 }
173
174 #[test]
175 fn merge_pub() {
176 check_assist(
177 merge_imports,
178 r"
179pub use std::fmt<|>::Debug;
180pub use std::fmt::Display;
181",
182 r"
183pub use std::fmt::{Debug, Display};
184",
185 )
186 }
187
188 #[test]
189 fn merge_pub_crate() {
190 check_assist(
191 merge_imports,
192 r"
193pub(crate) use std::fmt<|>::Debug;
194pub(crate) use std::fmt::Display;
195",
196 r"
197pub(crate) use std::fmt::{Debug, Display};
198",
199 )
200 }
201
202 #[test]
192 fn test_merge_nested() { 203 fn test_merge_nested() {
193 check_assist( 204 check_assist(
194 merge_imports, 205 merge_imports,
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index 4e252edf0..a8ab2aecc 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -1,6 +1,6 @@
1use syntax::{ 1use syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 TextRange, TextSize, T, 3 SyntaxElement, TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -22,62 +22,108 @@ use crate::{AssistContext, AssistId, AssistKind, Assists};
22// ``` 22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; 24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25 let new_contents = adjusted_macro_contents(&macro_call)?;
25 26
26 if !is_valid_macrocall(&macro_call, "dbg")? { 27 let macro_text_range = macro_call.syntax().text_range();
27 return None;
28 }
29
30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31
32 let macro_end = if macro_call.semicolon_token().is_some() { 28 let macro_end = if macro_call.semicolon_token().is_some() {
33 macro_call.syntax().text_range().end() - TextSize::of(';') 29 macro_text_range.end() - TextSize::of(';')
34 } else { 30 } else {
35 macro_call.syntax().text_range().end() 31 macro_text_range.end()
36 }; 32 };
37 33
38 // macro_range determines what will be deleted and replaced with macro_content 34 acc.add(
39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end); 35 AssistId("remove_dbg", AssistKind::Refactor),
40 let paste_instead_of_dbg = { 36 "Remove dbg!()",
41 let text = macro_call.token_tree()?.syntax().text(); 37 macro_text_range,
42 38 |builder| {
43 // leafiness determines if we should include the parenthesis or not 39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
44 let slice_index: TextRange = if is_leaf { 40 },
45 // leaf means - we can extract the contents of the dbg! in text 41 )
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')')) 42}
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
52 };
53 43
54 let target = macro_call.syntax().text_range(); 44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| { 45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
56 builder.replace(macro_range, paste_instead_of_dbg); 46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 let is_leaf = macro_call.syntax().next_sibling().is_none();
53 Some(if !is_leaf && needs_parentheses_around_macro_contents(contents) {
54 format!("({})", macro_text_in_brackets)
55 } else {
56 macro_text_in_brackets.to_string()
57 }) 57 })
58} 58}
59 59
60/// Verifies that the given macro_call actually matches the given name 60/// Verifies that the given macro_call actually matches the given name
61/// and contains proper ending tokens 61/// and contains proper ending tokens, then returns the contents between the ending tokens
62fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { 62fn get_valid_macrocall_contents(
63 macro_call: &ast::MacroCall,
64 macro_name: &str,
65) -> Option<Vec<SyntaxElement>> {
63 let path = macro_call.path()?; 66 let path = macro_call.path()?;
64 let name_ref = path.segment()?.name_ref()?; 67 let name_ref = path.segment()?.name_ref()?;
65 68
66 // Make sure it is actually a dbg-macro call, dbg followed by ! 69 // Make sure it is actually a dbg-macro call, dbg followed by !
67 let excl = path.syntax().next_sibling_or_token()?; 70 let excl = path.syntax().next_sibling_or_token()?;
68
69 if name_ref.text() != macro_name || excl.kind() != T![!] { 71 if name_ref.text() != macro_name || excl.kind() != T![!] {
70 return None; 72 return None;
71 } 73 }
72 74
73 let node = macro_call.token_tree()?.syntax().clone(); 75 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
74 let first_child = node.first_child_or_token()?; 76 let first_child = children_with_tokens.next()?;
75 let last_child = node.last_child_or_token()?; 77 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
78 let last_child = contents_between_brackets.pop()?;
79
80 if contents_between_brackets.is_empty() {
81 None
82 } else {
83 match (first_child.kind(), last_child.kind()) {
84 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
85 Some(contents_between_brackets)
86 }
87 _ => None,
88 }
89 }
90}
76 91
77 match (first_child.kind(), last_child.kind()) { 92fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
78 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), 93 if macro_contents.len() < 2 {
79 _ => Some(false), 94 return false;
80 } 95 }
96 let mut unpaired_brackets_in_contents = Vec::new();
97 for element in macro_contents {
98 match element.kind() {
99 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
100 T![')'] => {
101 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
102 {
103 return true;
104 }
105 }
106 T![']'] => {
107 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
108 {
109 return true;
110 }
111 }
112 T!['}'] => {
113 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
114 {
115 return true;
116 }
117 }
118 symbol_kind => {
119 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
120 if symbol_not_in_bracket && symbol_kind.is_punct() {
121 return true;
122 }
123 }
124 }
125 }
126 !unpaired_brackets_in_contents.is_empty()
81} 127}
82 128
83#[cfg(test)] 129#[cfg(test)]
@@ -157,12 +203,38 @@ fn foo(n: usize) {
157 } 203 }
158 204
159 #[test] 205 #[test]
206 fn remove_dbg_from_non_leaf_simple_expression() {
207 check_assist(
208 remove_dbg,
209 "
210fn main() {
211 let mut a = 1;
212 while dbg!<|>(a) < 10000 {
213 a += 1;
214 }
215}
216",
217 "
218fn main() {
219 let mut a = 1;
220 while a < 10000 {
221 a += 1;
222 }
223}
224",
225 );
226 }
227
228 #[test]
160 fn test_remove_dbg_keep_expression() { 229 fn test_remove_dbg_keep_expression() {
161 check_assist( 230 check_assist(
162 remove_dbg, 231 remove_dbg,
163 r#"let res = <|>dbg!(a + b).foo();"#, 232 r#"let res = <|>dbg!(a + b).foo();"#,
164 r#"let res = (a + b).foo();"#, 233 r#"let res = (a + b).foo();"#,
165 ); 234 );
235
236 check_assist(remove_dbg, r#"let res = <|>dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
237 check_assist(remove_dbg, r#"let res = <|>dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
166 } 238 }
167 239
168 #[test] 240 #[test]
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 8a4c8520d..98553b2e0 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -138,13 +138,23 @@ pub(crate) fn insert_use(
138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert) 138 algo::insert_children(scope.as_syntax_node(), insert_position, to_insert)
139} 139}
140 140
141fn try_merge_imports( 141fn eq_visibility(vis0: Option<ast::Visibility>, vis1: Option<ast::Visibility>) -> bool {
142 match (vis0, vis1) {
143 (None, None) => true,
144 // FIXME: Don't use the string representation to check for equality
145 // spaces inside of the node would break this comparison
146 (Some(vis0), Some(vis1)) => vis0.to_string() == vis1.to_string(),
147 _ => false,
148 }
149}
150
151pub(crate) fn try_merge_imports(
142 old: &ast::Use, 152 old: &ast::Use,
143 new: &ast::Use, 153 new: &ast::Use,
144 merge_behaviour: MergeBehaviour, 154 merge_behaviour: MergeBehaviour,
145) -> Option<ast::Use> { 155) -> Option<ast::Use> {
146 // don't merge into re-exports 156 // don't merge imports with different visibilities
147 if old.visibility().and_then(|vis| vis.pub_token()).is_some() { 157 if !eq_visibility(old.visibility(), new.visibility()) {
148 return None; 158 return None;
149 } 159 }
150 let old_tree = old.use_tree()?; 160 let old_tree = old.use_tree()?;
@@ -161,7 +171,7 @@ fn use_tree_list_is_nested(tl: &ast::UseTreeList) -> bool {
161} 171}
162 172
163// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion 173// FIXME: currently this merely prepends the new tree into old, ideally it would insert the items in a sorted fashion
164pub fn try_merge_trees( 174pub(crate) fn try_merge_trees(
165 old: &ast::UseTree, 175 old: &ast::UseTree,
166 new: &ast::UseTree, 176 new: &ast::UseTree,
167 merge_behaviour: MergeBehaviour, 177 merge_behaviour: MergeBehaviour,
@@ -278,7 +288,8 @@ fn first_path(path: &ast::Path) -> ast::Path {
278} 288}
279 289
280fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { 290fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone {
281 path.syntax().children().flat_map(ast::PathSegment::cast) 291 // cant make use of SyntaxNode::siblings, because the returned Iterator is not clone
292 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
282} 293}
283 294
284#[derive(PartialEq, Eq)] 295#[derive(PartialEq, Eq)]
@@ -684,8 +695,18 @@ use std::io;",
684 check_last( 695 check_last(
685 "foo::bar", 696 "foo::bar",
686 r"use foo::bar::baz::Qux;", 697 r"use foo::bar::baz::Qux;",
687 r"use foo::bar::baz::Qux; 698 r"use foo::bar;
688use foo::bar;", 699use foo::bar::baz::Qux;",
700 );
701 }
702
703 #[test]
704 fn insert_short_before_long() {
705 check_none(
706 "foo::bar",
707 r"use foo::bar::baz::Qux;",
708 r"use foo::bar;
709use foo::bar::baz::Qux;",
689 ); 710 );
690 } 711 }
691 712