diff options
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/assist_config.rs | 20 | ||||
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 7 | ||||
-rw-r--r-- | crates/assists/src/handlers/extract_struct_from_enum_variant.rs | 6 | ||||
-rw-r--r-- | crates/assists/src/handlers/merge_imports.rs | 161 | ||||
-rw-r--r-- | crates/assists/src/handlers/remove_dbg.rs | 144 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_qualified_name_with_use.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 3 | ||||
-rw-r--r-- | crates/assists/src/utils/insert_use.rs | 37 |
8 files changed, 249 insertions, 133 deletions
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs index cda2abfb9..adf02edab 100644 --- a/crates/assists/src/assist_config.rs +++ b/crates/assists/src/assist_config.rs | |||
@@ -4,12 +4,13 @@ | |||
4 | //! module, and we use to statically check that we only produce snippet | 4 | //! module, and we use to statically check that we only produce snippet |
5 | //! assists if we are allowed to. | 5 | //! assists if we are allowed to. |
6 | 6 | ||
7 | use crate::AssistKind; | 7 | use crate::{utils::MergeBehaviour, AssistKind}; |
8 | 8 | ||
9 | #[derive(Clone, Debug, PartialEq, Eq)] | 9 | #[derive(Clone, Debug, PartialEq, Eq)] |
10 | pub struct AssistConfig { | 10 | pub struct AssistConfig { |
11 | pub snippet_cap: Option<SnippetCap>, | 11 | pub snippet_cap: Option<SnippetCap>, |
12 | pub allowed: Option<Vec<AssistKind>>, | 12 | pub allowed: Option<Vec<AssistKind>>, |
13 | pub insert_use: InsertUseConfig, | ||
13 | } | 14 | } |
14 | 15 | ||
15 | impl AssistConfig { | 16 | impl AssistConfig { |
@@ -25,6 +26,21 @@ pub struct SnippetCap { | |||
25 | 26 | ||
26 | impl Default for AssistConfig { | 27 | impl Default for AssistConfig { |
27 | fn default() -> Self { | 28 | fn default() -> Self { |
28 | AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None } | 29 | AssistConfig { |
30 | snippet_cap: Some(SnippetCap { _private: () }), | ||
31 | allowed: None, | ||
32 | insert_use: InsertUseConfig::default(), | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | |||
37 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||
38 | pub struct InsertUseConfig { | ||
39 | pub merge: Option<MergeBehaviour>, | ||
40 | } | ||
41 | |||
42 | impl Default for InsertUseConfig { | ||
43 | fn default() -> Self { | ||
44 | InsertUseConfig { merge: Some(MergeBehaviour::Full) } | ||
29 | } | 45 | } |
30 | } | 46 | } |
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 66e819154..b5eb2c722 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -14,10 +14,7 @@ use syntax::{ | |||
14 | SyntaxNode, | 14 | SyntaxNode, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | use crate::{ | 17 | use crate::{utils::insert_use, AssistContext, AssistId, AssistKind, Assists, GroupLabel}; |
18 | utils::{insert_use, MergeBehaviour}, | ||
19 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | ||
20 | }; | ||
21 | 18 | ||
22 | // Assist: auto_import | 19 | // Assist: auto_import |
23 | // | 20 | // |
@@ -60,7 +57,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
60 | let new_syntax = insert_use( | 57 | let new_syntax = insert_use( |
61 | &scope, | 58 | &scope, |
62 | make::path_from_text(&import.to_string()), | 59 | make::path_from_text(&import.to_string()), |
63 | Some(MergeBehaviour::Full), | 60 | ctx.config.insert_use.merge, |
64 | ); | 61 | ); |
65 | builder.replace(syntax.text_range(), new_syntax.to_string()) | 62 | builder.replace(syntax.text_range(), new_syntax.to_string()) |
66 | }, | 63 | }, |
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs index 80c62d8bb..3ea50f375 100644 --- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs | |||
@@ -10,9 +10,7 @@ use syntax::{ | |||
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{ | 12 | use crate::{ |
13 | assist_context::AssistBuilder, | 13 | assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists, |
14 | utils::{insert_use, MergeBehaviour}, | ||
15 | AssistContext, AssistId, AssistKind, Assists, | ||
16 | }; | 14 | }; |
17 | use ast::make; | 15 | use ast::make; |
18 | use insert_use::ImportScope; | 16 | use insert_use::ImportScope; |
@@ -117,7 +115,7 @@ fn insert_import( | |||
117 | let new_syntax = insert_use( | 115 | let new_syntax = insert_use( |
118 | &scope, | 116 | &scope, |
119 | make::path_from_text(&mod_path.to_string()), | 117 | make::path_from_text(&mod_path.to_string()), |
120 | Some(MergeBehaviour::Full), | 118 | ctx.config.insert_use.merge, |
121 | ); | 119 | ); |
122 | // FIXME: this will currently panic as multiple imports will have overlapping text ranges | 120 | // FIXME: this will currently panic as multiple imports will have overlapping text ranges |
123 | builder.replace(syntax.text_range(), new_syntax.to_string()) | 121 | builder.replace(syntax.text_range(), new_syntax.to_string()) |
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 @@ | |||
1 | use std::iter::successors; | ||
2 | |||
3 | use syntax::{ | 1 | use 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 | ||
9 | use crate::{ | 6 | use 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 | ||
70 | fn 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 | |||
103 | fn 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 | |||
126 | fn 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)] |
131 | mod tests { | 70 | mod 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" | ||
135 | pub use std::fmt<|>::Debug; | ||
136 | use std::fmt::Display; | ||
137 | ", | ||
138 | ); | ||
139 | } | ||
140 | |||
141 | #[test] | ||
142 | fn skip_pub_last() { | ||
143 | check_assist_not_applicable( | ||
144 | merge_imports, | ||
145 | r" | ||
146 | use std::fmt<|>::Debug; | ||
147 | pub 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" | ||
157 | pub(crate) use std::fmt<|>::Debug; | ||
158 | pub 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" | ||
168 | pub use std::fmt<|>::Debug; | ||
169 | pub(crate) use std::fmt::Display; | ||
170 | ", | ||
171 | ); | ||
172 | } | ||
173 | |||
174 | #[test] | ||
175 | fn merge_pub() { | ||
176 | check_assist( | ||
177 | merge_imports, | ||
178 | r" | ||
179 | pub use std::fmt<|>::Debug; | ||
180 | pub use std::fmt::Display; | ||
181 | ", | ||
182 | r" | ||
183 | pub use std::fmt::{Debug, Display}; | ||
184 | ", | ||
185 | ) | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn merge_pub_crate() { | ||
190 | check_assist( | ||
191 | merge_imports, | ||
192 | r" | ||
193 | pub(crate) use std::fmt<|>::Debug; | ||
194 | pub(crate) use std::fmt::Display; | ||
195 | ", | ||
196 | r" | ||
197 | pub(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 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, AstNode}, | 2 | ast::{self, AstNode}, |
3 | TextRange, TextSize, T, | 3 | SyntaxElement, TextRange, TextSize, T, |
4 | }; | 4 | }; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
@@ -22,62 +22,108 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
22 | // ``` | 22 | // ``` |
23 | pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 23 | pub(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(¯o_call)?; | ||
25 | 26 | ||
26 | if !is_valid_macrocall(¯o_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(); | 44 | fn 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(¯o_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 |
62 | fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { | 62 | fn 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()) { | 92 | fn 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 | " | ||
210 | fn main() { | ||
211 | let mut a = 1; | ||
212 | while dbg!<|>(a) < 10000 { | ||
213 | a += 1; | ||
214 | } | ||
215 | } | ||
216 | ", | ||
217 | " | ||
218 | fn 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/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs index 85c70d16b..e48407fcc 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -2,7 +2,7 @@ use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRang | |||
2 | use test_utils::mark; | 2 | use test_utils::mark; |
3 | 3 | ||
4 | use crate::{ | 4 | use crate::{ |
5 | utils::{insert_use, ImportScope, MergeBehaviour}, | 5 | utils::{insert_use, ImportScope}, |
6 | AssistContext, AssistId, AssistKind, Assists, | 6 | AssistContext, AssistId, AssistKind, Assists, |
7 | }; | 7 | }; |
8 | use ast::make; | 8 | use ast::make; |
@@ -60,7 +60,7 @@ pub(crate) fn replace_qualified_name_with_use( | |||
60 | let new_syntax = insert_use( | 60 | let new_syntax = insert_use( |
61 | import_scope, | 61 | import_scope, |
62 | make::path_from_text(path_to_import), | 62 | make::path_from_text(path_to_import), |
63 | Some(MergeBehaviour::Full), | 63 | ctx.config.insert_use.merge, |
64 | ); | 64 | ); |
65 | builder.replace(syntax.text_range(), new_syntax.to_string()) | 65 | builder.replace(syntax.text_range(), new_syntax.to_string()) |
66 | } | 66 | } |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 7559ddd63..b0511ceb6 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -16,7 +16,8 @@ use syntax::{ | |||
16 | 16 | ||
17 | use crate::assist_config::SnippetCap; | 17 | use crate::assist_config::SnippetCap; |
18 | 18 | ||
19 | pub(crate) use insert_use::{insert_use, ImportScope, MergeBehaviour}; | 19 | pub use insert_use::MergeBehaviour; |
20 | pub(crate) use insert_use::{insert_use, ImportScope}; | ||
20 | 21 | ||
21 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { | 22 | pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr { |
22 | extract_trivial_expression(&block) | 23 | extract_trivial_expression(&block) |
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index 8a4c8520d..6d110aaaf 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 | ||
141 | fn try_merge_imports( | 141 | fn 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 | |||
151 | pub(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 |
164 | pub fn try_merge_trees( | 174 | pub(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, |
@@ -226,7 +236,7 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa | |||
226 | } | 236 | } |
227 | 237 | ||
228 | /// What type of merges are allowed. | 238 | /// What type of merges are allowed. |
229 | #[derive(Copy, Clone, PartialEq, Eq)] | 239 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
230 | pub enum MergeBehaviour { | 240 | pub enum MergeBehaviour { |
231 | /// Merge everything together creating deeply nested imports. | 241 | /// Merge everything together creating deeply nested imports. |
232 | Full, | 242 | Full, |
@@ -278,7 +288,8 @@ fn first_path(path: &ast::Path) -> ast::Path { | |||
278 | } | 288 | } |
279 | 289 | ||
280 | fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Clone { | 290 | fn 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; |
688 | use foo::bar;", | 699 | use 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; | ||
709 | use foo::bar::baz::Qux;", | ||
689 | ); | 710 | ); |
690 | } | 711 | } |
691 | 712 | ||