diff options
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/handlers/auto_import.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/handlers/change_visibility.rs | 18 | ||||
-rw-r--r-- | crates/assists/src/handlers/fill_match_arms.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/handlers/fix_visibility.rs | 6 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_from_impl_for_enum.rs | 2 | ||||
-rw-r--r-- | crates/assists/src/handlers/qualify_path.rs | 1048 | ||||
-rw-r--r-- | crates/assists/src/handlers/replace_string_with_char.rs | 141 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 4 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 36 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 25 | ||||
-rw-r--r-- | crates/assists/src/utils/import_assets.rs | 40 |
11 files changed, 1283 insertions, 43 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index 4a7059c83..e49e641b3 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -6,7 +6,7 @@ use crate::{ | |||
6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | // Feature: Import Insertion | 9 | // Feature: Auto Import |
10 | // | 10 | // |
11 | // Using the `auto-import` assist it is possible to insert missing imports for unresolved items. | 11 | // Using the `auto-import` assist it is possible to insert missing imports for unresolved items. |
12 | // When inserting an import it will do so in a structured manner by keeping imports grouped, | 12 | // When inserting an import it will do so in a structured manner by keeping imports grouped, |
@@ -100,7 +100,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
100 | let group = import_group_message(import_assets.import_candidate()); | 100 | let group = import_group_message(import_assets.import_candidate()); |
101 | let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; | 101 | let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; |
102 | let syntax = scope.as_syntax_node(); | 102 | let syntax = scope.as_syntax_node(); |
103 | for import in proposed_imports { | 103 | for (import, _) in proposed_imports { |
104 | acc.add_group( | 104 | acc.add_group( |
105 | &group, | 105 | &group, |
106 | AssistId("auto_import", AssistKind::QuickFix), | 106 | AssistId("auto_import", AssistKind::QuickFix), |
diff --git a/crates/assists/src/handlers/change_visibility.rs b/crates/assists/src/handlers/change_visibility.rs index 32dc05378..22d7c95d9 100644 --- a/crates/assists/src/handlers/change_visibility.rs +++ b/crates/assists/src/handlers/change_visibility.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use syntax::{ | 1 | use syntax::{ |
2 | ast::{self, NameOwner, VisibilityOwner}, | 2 | ast::{self, NameOwner, VisibilityOwner}, |
3 | AstNode, | 3 | AstNode, |
4 | SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY}, | 4 | SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, VISIBILITY}, |
5 | T, | 5 | T, |
6 | }; | 6 | }; |
7 | use test_utils::mark; | 7 | use test_utils::mark; |
@@ -30,13 +30,20 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | |||
30 | let item_keyword = ctx.token_at_offset().find(|leaf| { | 30 | let item_keyword = ctx.token_at_offset().find(|leaf| { |
31 | matches!( | 31 | matches!( |
32 | leaf.kind(), | 32 | leaf.kind(), |
33 | T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] | 33 | T![const] |
34 | | T![static] | ||
35 | | T![fn] | ||
36 | | T![mod] | ||
37 | | T![struct] | ||
38 | | T![enum] | ||
39 | | T![trait] | ||
40 | | T![type] | ||
34 | ) | 41 | ) |
35 | }); | 42 | }); |
36 | 43 | ||
37 | let (offset, target) = if let Some(keyword) = item_keyword { | 44 | let (offset, target) = if let Some(keyword) = item_keyword { |
38 | let parent = keyword.parent(); | 45 | let parent = keyword.parent(); |
39 | let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT]; | 46 | let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT]; |
40 | // Parent is not a definition, can't add visibility | 47 | // Parent is not a definition, can't add visibility |
41 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | 48 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { |
42 | return None; | 49 | return None; |
@@ -160,6 +167,11 @@ mod tests { | |||
160 | } | 167 | } |
161 | 168 | ||
162 | #[test] | 169 | #[test] |
170 | fn change_visibility_type_alias() { | ||
171 | check_assist(change_visibility, "<|>type T = ();", "pub(crate) type T = ();"); | ||
172 | } | ||
173 | |||
174 | #[test] | ||
163 | fn change_visibility_handles_comment_attrs() { | 175 | fn change_visibility_handles_comment_attrs() { |
164 | check_assist( | 176 | check_assist( |
165 | change_visibility, | 177 | change_visibility, |
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs index 676f5ad92..eda45f5b3 100644 --- a/crates/assists/src/handlers/fill_match_arms.rs +++ b/crates/assists/src/handlers/fill_match_arms.rs | |||
@@ -59,7 +59,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) |
60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
61 | .collect::<Vec<_>>(); | 61 | .collect::<Vec<_>>(); |
62 | if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() { | 62 | if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { |
63 | // Match `Some` variant first. | 63 | // Match `Some` variant first. |
64 | mark::hit!(option_order); | 64 | mark::hit!(option_order); |
65 | variants.reverse() | 65 | variants.reverse() |
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs index d505e9444..66f74150c 100644 --- a/crates/assists/src/handlers/fix_visibility.rs +++ b/crates/assists/src/handlers/fix_visibility.rs | |||
@@ -1,9 +1,11 @@ | |||
1 | use base_db::FileId; | 1 | use base_db::FileId; |
2 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; | 2 | use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; |
3 | use syntax::{ast, AstNode, TextRange, TextSize}; | 3 | use syntax::{ |
4 | ast::{self, VisibilityOwner}, | ||
5 | AstNode, TextRange, TextSize, | ||
6 | }; | ||
4 | 7 | ||
5 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; |
6 | use ast::VisibilityOwner; | ||
7 | 9 | ||
8 | // FIXME: this really should be a fix for diagnostic, rather than an assist. | 10 | // FIXME: this really should be a fix for diagnostic, rather than an assist. |
9 | 11 | ||
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs index 7f04b9572..674e5a175 100644 --- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs +++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs | |||
@@ -75,7 +75,7 @@ fn existing_from_impl( | |||
75 | let enum_ = variant.parent_enum(sema.db); | 75 | let enum_ = variant.parent_enum(sema.db); |
76 | let krate = enum_.module(sema.db).krate(); | 76 | let krate = enum_.module(sema.db).krate(); |
77 | 77 | ||
78 | let from_trait = FamousDefs(sema, krate).core_convert_From()?; | 78 | let from_trait = FamousDefs(sema, Some(krate)).core_convert_From()?; |
79 | 79 | ||
80 | let enum_type = enum_.ty(sema.db); | 80 | let enum_type = enum_.ty(sema.db); |
81 | 81 | ||
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs new file mode 100644 index 000000000..f436bdbbf --- /dev/null +++ b/crates/assists/src/handlers/qualify_path.rs | |||
@@ -0,0 +1,1048 @@ | |||
1 | use std::iter; | ||
2 | |||
3 | use hir::AsName; | ||
4 | use ide_db::RootDatabase; | ||
5 | use syntax::{ | ||
6 | ast, | ||
7 | ast::{make, ArgListOwner}, | ||
8 | AstNode, | ||
9 | }; | ||
10 | use test_utils::mark; | ||
11 | |||
12 | use crate::{ | ||
13 | assist_context::{AssistContext, Assists}, | ||
14 | utils::import_assets::{ImportAssets, ImportCandidate}, | ||
15 | utils::mod_path_to_ast, | ||
16 | AssistId, AssistKind, GroupLabel, | ||
17 | }; | ||
18 | |||
19 | // Assist: qualify_path | ||
20 | // | ||
21 | // If the name is unresolved, provides all possible qualified paths for it. | ||
22 | // | ||
23 | // ``` | ||
24 | // fn main() { | ||
25 | // let map = HashMap<|>::new(); | ||
26 | // } | ||
27 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | ||
28 | // ``` | ||
29 | // -> | ||
30 | // ``` | ||
31 | // fn main() { | ||
32 | // let map = std::collections::HashMap::new(); | ||
33 | // } | ||
34 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | ||
35 | // ``` | ||
36 | pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
37 | let import_assets = | ||
38 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { | ||
39 | ImportAssets::for_regular_path(path_under_caret, &ctx.sema) | ||
40 | } else if let Some(method_under_caret) = | ||
41 | ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>() | ||
42 | { | ||
43 | ImportAssets::for_method_call(method_under_caret, &ctx.sema) | ||
44 | } else { | ||
45 | None | ||
46 | }?; | ||
47 | let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema); | ||
48 | if proposed_imports.is_empty() { | ||
49 | return None; | ||
50 | } | ||
51 | |||
52 | let candidate = import_assets.import_candidate(); | ||
53 | let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; | ||
54 | |||
55 | let qualify_candidate = match candidate { | ||
56 | ImportCandidate::QualifierStart(_) => { | ||
57 | mark::hit!(qualify_path_qualifier_start); | ||
58 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; | ||
59 | let segment = path.segment()?; | ||
60 | QualifyCandidate::QualifierStart(segment) | ||
61 | } | ||
62 | ImportCandidate::UnqualifiedName(_) => { | ||
63 | mark::hit!(qualify_path_unqualified_name); | ||
64 | QualifyCandidate::UnqualifiedName | ||
65 | } | ||
66 | ImportCandidate::TraitAssocItem(_) => { | ||
67 | mark::hit!(qualify_path_trait_assoc_item); | ||
68 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; | ||
69 | let (qualifier, segment) = (path.qualifier()?, path.segment()?); | ||
70 | QualifyCandidate::TraitAssocItem(qualifier, segment) | ||
71 | } | ||
72 | ImportCandidate::TraitMethod(_) => { | ||
73 | mark::hit!(qualify_path_trait_method); | ||
74 | let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?; | ||
75 | QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr) | ||
76 | } | ||
77 | }; | ||
78 | |||
79 | let group_label = group_label(candidate); | ||
80 | for (import, item) in proposed_imports { | ||
81 | acc.add_group( | ||
82 | &group_label, | ||
83 | AssistId("qualify_path", AssistKind::QuickFix), | ||
84 | label(candidate, &import), | ||
85 | range, | ||
86 | |builder| { | ||
87 | qualify_candidate.qualify( | ||
88 | |replace_with: String| builder.replace(range, replace_with), | ||
89 | import, | ||
90 | item, | ||
91 | ) | ||
92 | }, | ||
93 | ); | ||
94 | } | ||
95 | Some(()) | ||
96 | } | ||
97 | |||
98 | enum QualifyCandidate<'db> { | ||
99 | QualifierStart(ast::PathSegment), | ||
100 | UnqualifiedName, | ||
101 | TraitAssocItem(ast::Path, ast::PathSegment), | ||
102 | TraitMethod(&'db RootDatabase, ast::MethodCallExpr), | ||
103 | } | ||
104 | |||
105 | impl QualifyCandidate<'_> { | ||
106 | fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { | ||
107 | match self { | ||
108 | QualifyCandidate::QualifierStart(segment) => { | ||
109 | let import = mod_path_to_ast(&import); | ||
110 | replacer(format!("{}::{}", import, segment)); | ||
111 | } | ||
112 | QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()), | ||
113 | QualifyCandidate::TraitAssocItem(qualifier, segment) => { | ||
114 | let import = mod_path_to_ast(&import); | ||
115 | replacer(format!("<{} as {}>::{}", qualifier, import, segment)); | ||
116 | } | ||
117 | &QualifyCandidate::TraitMethod(db, ref mcall_expr) => { | ||
118 | Self::qualify_trait_method(db, mcall_expr, replacer, import, item); | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | |||
123 | fn qualify_trait_method( | ||
124 | db: &RootDatabase, | ||
125 | mcall_expr: &ast::MethodCallExpr, | ||
126 | mut replacer: impl FnMut(String), | ||
127 | import: hir::ModPath, | ||
128 | item: hir::ItemInNs, | ||
129 | ) -> Option<()> { | ||
130 | let receiver = mcall_expr.receiver()?; | ||
131 | let trait_method_name = mcall_expr.name_ref()?; | ||
132 | let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); | ||
133 | let trait_ = item_as_trait(item)?; | ||
134 | let method = find_trait_method(db, trait_, &trait_method_name)?; | ||
135 | if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { | ||
136 | let import = mod_path_to_ast(&import); | ||
137 | let receiver = match self_access { | ||
138 | hir::Access::Shared => make::expr_ref(receiver, false), | ||
139 | hir::Access::Exclusive => make::expr_ref(receiver, true), | ||
140 | hir::Access::Owned => receiver, | ||
141 | }; | ||
142 | replacer(format!( | ||
143 | "{}::{}{}", | ||
144 | import, | ||
145 | trait_method_name, | ||
146 | match arg_list.clone() { | ||
147 | Some(args) => make::arg_list(iter::once(receiver).chain(args)), | ||
148 | None => make::arg_list(iter::once(receiver)), | ||
149 | } | ||
150 | )); | ||
151 | } | ||
152 | Some(()) | ||
153 | } | ||
154 | } | ||
155 | |||
156 | fn find_trait_method( | ||
157 | db: &RootDatabase, | ||
158 | trait_: hir::Trait, | ||
159 | trait_method_name: &ast::NameRef, | ||
160 | ) -> Option<hir::Function> { | ||
161 | if let Some(hir::AssocItem::Function(method)) = | ||
162 | trait_.items(db).into_iter().find(|item: &hir::AssocItem| { | ||
163 | item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false) | ||
164 | }) | ||
165 | { | ||
166 | Some(method) | ||
167 | } else { | ||
168 | None | ||
169 | } | ||
170 | } | ||
171 | |||
172 | fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> { | ||
173 | if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) { | ||
174 | Some(trait_) | ||
175 | } else { | ||
176 | None | ||
177 | } | ||
178 | } | ||
179 | |||
180 | fn group_label(candidate: &ImportCandidate) -> GroupLabel { | ||
181 | let name = match candidate { | ||
182 | ImportCandidate::UnqualifiedName(it) | ImportCandidate::QualifierStart(it) => &it.name, | ||
183 | ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name, | ||
184 | }; | ||
185 | GroupLabel(format!("Qualify {}", name)) | ||
186 | } | ||
187 | |||
188 | fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String { | ||
189 | match candidate { | ||
190 | ImportCandidate::UnqualifiedName(_) => format!("Qualify as `{}`", &import), | ||
191 | ImportCandidate::QualifierStart(_) => format!("Qualify with `{}`", &import), | ||
192 | ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import), | ||
193 | ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import), | ||
194 | } | ||
195 | } | ||
196 | |||
197 | #[cfg(test)] | ||
198 | mod tests { | ||
199 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
200 | |||
201 | use super::*; | ||
202 | |||
203 | #[test] | ||
204 | fn applicable_when_found_an_import_partial() { | ||
205 | mark::check!(qualify_path_unqualified_name); | ||
206 | check_assist( | ||
207 | qualify_path, | ||
208 | r" | ||
209 | mod std { | ||
210 | pub mod fmt { | ||
211 | pub struct Formatter; | ||
212 | } | ||
213 | } | ||
214 | |||
215 | use std::fmt; | ||
216 | |||
217 | <|>Formatter | ||
218 | ", | ||
219 | r" | ||
220 | mod std { | ||
221 | pub mod fmt { | ||
222 | pub struct Formatter; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | use std::fmt; | ||
227 | |||
228 | fmt::Formatter | ||
229 | ", | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn applicable_when_found_an_import() { | ||
235 | check_assist( | ||
236 | qualify_path, | ||
237 | r" | ||
238 | <|>PubStruct | ||
239 | |||
240 | pub mod PubMod { | ||
241 | pub struct PubStruct; | ||
242 | } | ||
243 | ", | ||
244 | r" | ||
245 | PubMod::PubStruct | ||
246 | |||
247 | pub mod PubMod { | ||
248 | pub struct PubStruct; | ||
249 | } | ||
250 | ", | ||
251 | ); | ||
252 | } | ||
253 | |||
254 | #[test] | ||
255 | fn applicable_in_macros() { | ||
256 | check_assist( | ||
257 | qualify_path, | ||
258 | r" | ||
259 | macro_rules! foo { | ||
260 | ($i:ident) => { fn foo(a: $i) {} } | ||
261 | } | ||
262 | foo!(Pub<|>Struct); | ||
263 | |||
264 | pub mod PubMod { | ||
265 | pub struct PubStruct; | ||
266 | } | ||
267 | ", | ||
268 | r" | ||
269 | macro_rules! foo { | ||
270 | ($i:ident) => { fn foo(a: $i) {} } | ||
271 | } | ||
272 | foo!(PubMod::PubStruct); | ||
273 | |||
274 | pub mod PubMod { | ||
275 | pub struct PubStruct; | ||
276 | } | ||
277 | ", | ||
278 | ); | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn applicable_when_found_multiple_imports() { | ||
283 | check_assist( | ||
284 | qualify_path, | ||
285 | r" | ||
286 | PubSt<|>ruct | ||
287 | |||
288 | pub mod PubMod1 { | ||
289 | pub struct PubStruct; | ||
290 | } | ||
291 | pub mod PubMod2 { | ||
292 | pub struct PubStruct; | ||
293 | } | ||
294 | pub mod PubMod3 { | ||
295 | pub struct PubStruct; | ||
296 | } | ||
297 | ", | ||
298 | r" | ||
299 | PubMod3::PubStruct | ||
300 | |||
301 | pub mod PubMod1 { | ||
302 | pub struct PubStruct; | ||
303 | } | ||
304 | pub mod PubMod2 { | ||
305 | pub struct PubStruct; | ||
306 | } | ||
307 | pub mod PubMod3 { | ||
308 | pub struct PubStruct; | ||
309 | } | ||
310 | ", | ||
311 | ); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn not_applicable_for_already_imported_types() { | ||
316 | check_assist_not_applicable( | ||
317 | qualify_path, | ||
318 | r" | ||
319 | use PubMod::PubStruct; | ||
320 | |||
321 | PubStruct<|> | ||
322 | |||
323 | pub mod PubMod { | ||
324 | pub struct PubStruct; | ||
325 | } | ||
326 | ", | ||
327 | ); | ||
328 | } | ||
329 | |||
330 | #[test] | ||
331 | fn not_applicable_for_types_with_private_paths() { | ||
332 | check_assist_not_applicable( | ||
333 | qualify_path, | ||
334 | r" | ||
335 | PrivateStruct<|> | ||
336 | |||
337 | pub mod PubMod { | ||
338 | struct PrivateStruct; | ||
339 | } | ||
340 | ", | ||
341 | ); | ||
342 | } | ||
343 | |||
344 | #[test] | ||
345 | fn not_applicable_when_no_imports_found() { | ||
346 | check_assist_not_applicable( | ||
347 | qualify_path, | ||
348 | " | ||
349 | PubStruct<|>", | ||
350 | ); | ||
351 | } | ||
352 | |||
353 | #[test] | ||
354 | fn not_applicable_in_import_statements() { | ||
355 | check_assist_not_applicable( | ||
356 | qualify_path, | ||
357 | r" | ||
358 | use PubStruct<|>; | ||
359 | |||
360 | pub mod PubMod { | ||
361 | pub struct PubStruct; | ||
362 | }", | ||
363 | ); | ||
364 | } | ||
365 | |||
366 | #[test] | ||
367 | fn qualify_function() { | ||
368 | check_assist( | ||
369 | qualify_path, | ||
370 | r" | ||
371 | test_function<|> | ||
372 | |||
373 | pub mod PubMod { | ||
374 | pub fn test_function() {}; | ||
375 | } | ||
376 | ", | ||
377 | r" | ||
378 | PubMod::test_function | ||
379 | |||
380 | pub mod PubMod { | ||
381 | pub fn test_function() {}; | ||
382 | } | ||
383 | ", | ||
384 | ); | ||
385 | } | ||
386 | |||
387 | #[test] | ||
388 | fn qualify_macro() { | ||
389 | check_assist( | ||
390 | qualify_path, | ||
391 | r" | ||
392 | //- /lib.rs crate:crate_with_macro | ||
393 | #[macro_export] | ||
394 | macro_rules! foo { | ||
395 | () => () | ||
396 | } | ||
397 | |||
398 | //- /main.rs crate:main deps:crate_with_macro | ||
399 | fn main() { | ||
400 | foo<|> | ||
401 | } | ||
402 | ", | ||
403 | r" | ||
404 | fn main() { | ||
405 | crate_with_macro::foo | ||
406 | } | ||
407 | ", | ||
408 | ); | ||
409 | } | ||
410 | |||
411 | #[test] | ||
412 | fn qualify_path_target() { | ||
413 | check_assist_target( | ||
414 | qualify_path, | ||
415 | r" | ||
416 | struct AssistInfo { | ||
417 | group_label: Option<<|>GroupLabel>, | ||
418 | } | ||
419 | |||
420 | mod m { pub struct GroupLabel; } | ||
421 | ", | ||
422 | "GroupLabel", | ||
423 | ) | ||
424 | } | ||
425 | |||
426 | #[test] | ||
427 | fn not_applicable_when_path_start_is_imported() { | ||
428 | check_assist_not_applicable( | ||
429 | qualify_path, | ||
430 | r" | ||
431 | pub mod mod1 { | ||
432 | pub mod mod2 { | ||
433 | pub mod mod3 { | ||
434 | pub struct TestStruct; | ||
435 | } | ||
436 | } | ||
437 | } | ||
438 | |||
439 | use mod1::mod2; | ||
440 | fn main() { | ||
441 | mod2::mod3::TestStruct<|> | ||
442 | } | ||
443 | ", | ||
444 | ); | ||
445 | } | ||
446 | |||
447 | #[test] | ||
448 | fn not_applicable_for_imported_function() { | ||
449 | check_assist_not_applicable( | ||
450 | qualify_path, | ||
451 | r" | ||
452 | pub mod test_mod { | ||
453 | pub fn test_function() {} | ||
454 | } | ||
455 | |||
456 | use test_mod::test_function; | ||
457 | fn main() { | ||
458 | test_function<|> | ||
459 | } | ||
460 | ", | ||
461 | ); | ||
462 | } | ||
463 | |||
464 | #[test] | ||
465 | fn associated_struct_function() { | ||
466 | check_assist( | ||
467 | qualify_path, | ||
468 | r" | ||
469 | mod test_mod { | ||
470 | pub struct TestStruct {} | ||
471 | impl TestStruct { | ||
472 | pub fn test_function() {} | ||
473 | } | ||
474 | } | ||
475 | |||
476 | fn main() { | ||
477 | TestStruct::test_function<|> | ||
478 | } | ||
479 | ", | ||
480 | r" | ||
481 | mod test_mod { | ||
482 | pub struct TestStruct {} | ||
483 | impl TestStruct { | ||
484 | pub fn test_function() {} | ||
485 | } | ||
486 | } | ||
487 | |||
488 | fn main() { | ||
489 | test_mod::TestStruct::test_function | ||
490 | } | ||
491 | ", | ||
492 | ); | ||
493 | } | ||
494 | |||
495 | #[test] | ||
496 | fn associated_struct_const() { | ||
497 | mark::check!(qualify_path_qualifier_start); | ||
498 | check_assist( | ||
499 | qualify_path, | ||
500 | r" | ||
501 | mod test_mod { | ||
502 | pub struct TestStruct {} | ||
503 | impl TestStruct { | ||
504 | const TEST_CONST: u8 = 42; | ||
505 | } | ||
506 | } | ||
507 | |||
508 | fn main() { | ||
509 | TestStruct::TEST_CONST<|> | ||
510 | } | ||
511 | ", | ||
512 | r" | ||
513 | mod test_mod { | ||
514 | pub struct TestStruct {} | ||
515 | impl TestStruct { | ||
516 | const TEST_CONST: u8 = 42; | ||
517 | } | ||
518 | } | ||
519 | |||
520 | fn main() { | ||
521 | test_mod::TestStruct::TEST_CONST | ||
522 | } | ||
523 | ", | ||
524 | ); | ||
525 | } | ||
526 | |||
527 | #[test] | ||
528 | fn associated_trait_function() { | ||
529 | check_assist( | ||
530 | qualify_path, | ||
531 | r" | ||
532 | mod test_mod { | ||
533 | pub trait TestTrait { | ||
534 | fn test_function(); | ||
535 | } | ||
536 | pub struct TestStruct {} | ||
537 | impl TestTrait for TestStruct { | ||
538 | fn test_function() {} | ||
539 | } | ||
540 | } | ||
541 | |||
542 | fn main() { | ||
543 | test_mod::TestStruct::test_function<|> | ||
544 | } | ||
545 | ", | ||
546 | r" | ||
547 | mod test_mod { | ||
548 | pub trait TestTrait { | ||
549 | fn test_function(); | ||
550 | } | ||
551 | pub struct TestStruct {} | ||
552 | impl TestTrait for TestStruct { | ||
553 | fn test_function() {} | ||
554 | } | ||
555 | } | ||
556 | |||
557 | fn main() { | ||
558 | <test_mod::TestStruct as test_mod::TestTrait>::test_function | ||
559 | } | ||
560 | ", | ||
561 | ); | ||
562 | } | ||
563 | |||
564 | #[test] | ||
565 | fn not_applicable_for_imported_trait_for_function() { | ||
566 | check_assist_not_applicable( | ||
567 | qualify_path, | ||
568 | r" | ||
569 | mod test_mod { | ||
570 | pub trait TestTrait { | ||
571 | fn test_function(); | ||
572 | } | ||
573 | pub trait TestTrait2 { | ||
574 | fn test_function(); | ||
575 | } | ||
576 | pub enum TestEnum { | ||
577 | One, | ||
578 | Two, | ||
579 | } | ||
580 | impl TestTrait2 for TestEnum { | ||
581 | fn test_function() {} | ||
582 | } | ||
583 | impl TestTrait for TestEnum { | ||
584 | fn test_function() {} | ||
585 | } | ||
586 | } | ||
587 | |||
588 | use test_mod::TestTrait2; | ||
589 | fn main() { | ||
590 | test_mod::TestEnum::test_function<|>; | ||
591 | } | ||
592 | ", | ||
593 | ) | ||
594 | } | ||
595 | |||
596 | #[test] | ||
597 | fn associated_trait_const() { | ||
598 | mark::check!(qualify_path_trait_assoc_item); | ||
599 | check_assist( | ||
600 | qualify_path, | ||
601 | r" | ||
602 | mod test_mod { | ||
603 | pub trait TestTrait { | ||
604 | const TEST_CONST: u8; | ||
605 | } | ||
606 | pub struct TestStruct {} | ||
607 | impl TestTrait for TestStruct { | ||
608 | const TEST_CONST: u8 = 42; | ||
609 | } | ||
610 | } | ||
611 | |||
612 | fn main() { | ||
613 | test_mod::TestStruct::TEST_CONST<|> | ||
614 | } | ||
615 | ", | ||
616 | r" | ||
617 | mod test_mod { | ||
618 | pub trait TestTrait { | ||
619 | const TEST_CONST: u8; | ||
620 | } | ||
621 | pub struct TestStruct {} | ||
622 | impl TestTrait for TestStruct { | ||
623 | const TEST_CONST: u8 = 42; | ||
624 | } | ||
625 | } | ||
626 | |||
627 | fn main() { | ||
628 | <test_mod::TestStruct as test_mod::TestTrait>::TEST_CONST | ||
629 | } | ||
630 | ", | ||
631 | ); | ||
632 | } | ||
633 | |||
634 | #[test] | ||
635 | fn not_applicable_for_imported_trait_for_const() { | ||
636 | check_assist_not_applicable( | ||
637 | qualify_path, | ||
638 | r" | ||
639 | mod test_mod { | ||
640 | pub trait TestTrait { | ||
641 | const TEST_CONST: u8; | ||
642 | } | ||
643 | pub trait TestTrait2 { | ||
644 | const TEST_CONST: f64; | ||
645 | } | ||
646 | pub enum TestEnum { | ||
647 | One, | ||
648 | Two, | ||
649 | } | ||
650 | impl TestTrait2 for TestEnum { | ||
651 | const TEST_CONST: f64 = 42.0; | ||
652 | } | ||
653 | impl TestTrait for TestEnum { | ||
654 | const TEST_CONST: u8 = 42; | ||
655 | } | ||
656 | } | ||
657 | |||
658 | use test_mod::TestTrait2; | ||
659 | fn main() { | ||
660 | test_mod::TestEnum::TEST_CONST<|>; | ||
661 | } | ||
662 | ", | ||
663 | ) | ||
664 | } | ||
665 | |||
666 | #[test] | ||
667 | fn trait_method() { | ||
668 | mark::check!(qualify_path_trait_method); | ||
669 | check_assist( | ||
670 | qualify_path, | ||
671 | r" | ||
672 | mod test_mod { | ||
673 | pub trait TestTrait { | ||
674 | fn test_method(&self); | ||
675 | } | ||
676 | pub struct TestStruct {} | ||
677 | impl TestTrait for TestStruct { | ||
678 | fn test_method(&self) {} | ||
679 | } | ||
680 | } | ||
681 | |||
682 | fn main() { | ||
683 | let test_struct = test_mod::TestStruct {}; | ||
684 | test_struct.test_meth<|>od() | ||
685 | } | ||
686 | ", | ||
687 | r" | ||
688 | mod test_mod { | ||
689 | pub trait TestTrait { | ||
690 | fn test_method(&self); | ||
691 | } | ||
692 | pub struct TestStruct {} | ||
693 | impl TestTrait for TestStruct { | ||
694 | fn test_method(&self) {} | ||
695 | } | ||
696 | } | ||
697 | |||
698 | fn main() { | ||
699 | let test_struct = test_mod::TestStruct {}; | ||
700 | test_mod::TestTrait::test_method(&test_struct) | ||
701 | } | ||
702 | ", | ||
703 | ); | ||
704 | } | ||
705 | |||
706 | #[test] | ||
707 | fn trait_method_multi_params() { | ||
708 | check_assist( | ||
709 | qualify_path, | ||
710 | r" | ||
711 | mod test_mod { | ||
712 | pub trait TestTrait { | ||
713 | fn test_method(&self, test: i32); | ||
714 | } | ||
715 | pub struct TestStruct {} | ||
716 | impl TestTrait for TestStruct { | ||
717 | fn test_method(&self, test: i32) {} | ||
718 | } | ||
719 | } | ||
720 | |||
721 | fn main() { | ||
722 | let test_struct = test_mod::TestStruct {}; | ||
723 | test_struct.test_meth<|>od(42) | ||
724 | } | ||
725 | ", | ||
726 | r" | ||
727 | mod test_mod { | ||
728 | pub trait TestTrait { | ||
729 | fn test_method(&self, test: i32); | ||
730 | } | ||
731 | pub struct TestStruct {} | ||
732 | impl TestTrait for TestStruct { | ||
733 | fn test_method(&self, test: i32) {} | ||
734 | } | ||
735 | } | ||
736 | |||
737 | fn main() { | ||
738 | let test_struct = test_mod::TestStruct {}; | ||
739 | test_mod::TestTrait::test_method(&test_struct, 42) | ||
740 | } | ||
741 | ", | ||
742 | ); | ||
743 | } | ||
744 | |||
745 | #[test] | ||
746 | fn trait_method_consume() { | ||
747 | check_assist( | ||
748 | qualify_path, | ||
749 | r" | ||
750 | mod test_mod { | ||
751 | pub trait TestTrait { | ||
752 | fn test_method(self); | ||
753 | } | ||
754 | pub struct TestStruct {} | ||
755 | impl TestTrait for TestStruct { | ||
756 | fn test_method(self) {} | ||
757 | } | ||
758 | } | ||
759 | |||
760 | fn main() { | ||
761 | let test_struct = test_mod::TestStruct {}; | ||
762 | test_struct.test_meth<|>od() | ||
763 | } | ||
764 | ", | ||
765 | r" | ||
766 | mod test_mod { | ||
767 | pub trait TestTrait { | ||
768 | fn test_method(self); | ||
769 | } | ||
770 | pub struct TestStruct {} | ||
771 | impl TestTrait for TestStruct { | ||
772 | fn test_method(self) {} | ||
773 | } | ||
774 | } | ||
775 | |||
776 | fn main() { | ||
777 | let test_struct = test_mod::TestStruct {}; | ||
778 | test_mod::TestTrait::test_method(test_struct) | ||
779 | } | ||
780 | ", | ||
781 | ); | ||
782 | } | ||
783 | |||
784 | #[test] | ||
785 | fn trait_method_cross_crate() { | ||
786 | check_assist( | ||
787 | qualify_path, | ||
788 | r" | ||
789 | //- /main.rs crate:main deps:dep | ||
790 | fn main() { | ||
791 | let test_struct = dep::test_mod::TestStruct {}; | ||
792 | test_struct.test_meth<|>od() | ||
793 | } | ||
794 | //- /dep.rs crate:dep | ||
795 | pub mod test_mod { | ||
796 | pub trait TestTrait { | ||
797 | fn test_method(&self); | ||
798 | } | ||
799 | pub struct TestStruct {} | ||
800 | impl TestTrait for TestStruct { | ||
801 | fn test_method(&self) {} | ||
802 | } | ||
803 | } | ||
804 | ", | ||
805 | r" | ||
806 | fn main() { | ||
807 | let test_struct = dep::test_mod::TestStruct {}; | ||
808 | dep::test_mod::TestTrait::test_method(&test_struct) | ||
809 | } | ||
810 | ", | ||
811 | ); | ||
812 | } | ||
813 | |||
814 | #[test] | ||
815 | fn assoc_fn_cross_crate() { | ||
816 | check_assist( | ||
817 | qualify_path, | ||
818 | r" | ||
819 | //- /main.rs crate:main deps:dep | ||
820 | fn main() { | ||
821 | dep::test_mod::TestStruct::test_func<|>tion | ||
822 | } | ||
823 | //- /dep.rs crate:dep | ||
824 | pub mod test_mod { | ||
825 | pub trait TestTrait { | ||
826 | fn test_function(); | ||
827 | } | ||
828 | pub struct TestStruct {} | ||
829 | impl TestTrait for TestStruct { | ||
830 | fn test_function() {} | ||
831 | } | ||
832 | } | ||
833 | ", | ||
834 | r" | ||
835 | fn main() { | ||
836 | <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::test_function | ||
837 | } | ||
838 | ", | ||
839 | ); | ||
840 | } | ||
841 | |||
842 | #[test] | ||
843 | fn assoc_const_cross_crate() { | ||
844 | check_assist( | ||
845 | qualify_path, | ||
846 | r" | ||
847 | //- /main.rs crate:main deps:dep | ||
848 | fn main() { | ||
849 | dep::test_mod::TestStruct::CONST<|> | ||
850 | } | ||
851 | //- /dep.rs crate:dep | ||
852 | pub mod test_mod { | ||
853 | pub trait TestTrait { | ||
854 | const CONST: bool; | ||
855 | } | ||
856 | pub struct TestStruct {} | ||
857 | impl TestTrait for TestStruct { | ||
858 | const CONST: bool = true; | ||
859 | } | ||
860 | } | ||
861 | ", | ||
862 | r" | ||
863 | fn main() { | ||
864 | <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::CONST | ||
865 | } | ||
866 | ", | ||
867 | ); | ||
868 | } | ||
869 | |||
870 | #[test] | ||
871 | fn assoc_fn_as_method_cross_crate() { | ||
872 | check_assist_not_applicable( | ||
873 | qualify_path, | ||
874 | r" | ||
875 | //- /main.rs crate:main deps:dep | ||
876 | fn main() { | ||
877 | let test_struct = dep::test_mod::TestStruct {}; | ||
878 | test_struct.test_func<|>tion() | ||
879 | } | ||
880 | //- /dep.rs crate:dep | ||
881 | pub mod test_mod { | ||
882 | pub trait TestTrait { | ||
883 | fn test_function(); | ||
884 | } | ||
885 | pub struct TestStruct {} | ||
886 | impl TestTrait for TestStruct { | ||
887 | fn test_function() {} | ||
888 | } | ||
889 | } | ||
890 | ", | ||
891 | ); | ||
892 | } | ||
893 | |||
894 | #[test] | ||
895 | fn private_trait_cross_crate() { | ||
896 | check_assist_not_applicable( | ||
897 | qualify_path, | ||
898 | r" | ||
899 | //- /main.rs crate:main deps:dep | ||
900 | fn main() { | ||
901 | let test_struct = dep::test_mod::TestStruct {}; | ||
902 | test_struct.test_meth<|>od() | ||
903 | } | ||
904 | //- /dep.rs crate:dep | ||
905 | pub mod test_mod { | ||
906 | trait TestTrait { | ||
907 | fn test_method(&self); | ||
908 | } | ||
909 | pub struct TestStruct {} | ||
910 | impl TestTrait for TestStruct { | ||
911 | fn test_method(&self) {} | ||
912 | } | ||
913 | } | ||
914 | ", | ||
915 | ); | ||
916 | } | ||
917 | |||
918 | #[test] | ||
919 | fn not_applicable_for_imported_trait_for_method() { | ||
920 | check_assist_not_applicable( | ||
921 | qualify_path, | ||
922 | r" | ||
923 | mod test_mod { | ||
924 | pub trait TestTrait { | ||
925 | fn test_method(&self); | ||
926 | } | ||
927 | pub trait TestTrait2 { | ||
928 | fn test_method(&self); | ||
929 | } | ||
930 | pub enum TestEnum { | ||
931 | One, | ||
932 | Two, | ||
933 | } | ||
934 | impl TestTrait2 for TestEnum { | ||
935 | fn test_method(&self) {} | ||
936 | } | ||
937 | impl TestTrait for TestEnum { | ||
938 | fn test_method(&self) {} | ||
939 | } | ||
940 | } | ||
941 | |||
942 | use test_mod::TestTrait2; | ||
943 | fn main() { | ||
944 | let one = test_mod::TestEnum::One; | ||
945 | one.test<|>_method(); | ||
946 | } | ||
947 | ", | ||
948 | ) | ||
949 | } | ||
950 | |||
951 | #[test] | ||
952 | fn dep_import() { | ||
953 | check_assist( | ||
954 | qualify_path, | ||
955 | r" | ||
956 | //- /lib.rs crate:dep | ||
957 | pub struct Struct; | ||
958 | |||
959 | //- /main.rs crate:main deps:dep | ||
960 | fn main() { | ||
961 | Struct<|> | ||
962 | } | ||
963 | ", | ||
964 | r" | ||
965 | fn main() { | ||
966 | dep::Struct | ||
967 | } | ||
968 | ", | ||
969 | ); | ||
970 | } | ||
971 | |||
972 | #[test] | ||
973 | fn whole_segment() { | ||
974 | // Tests that only imports whose last segment matches the identifier get suggested. | ||
975 | check_assist( | ||
976 | qualify_path, | ||
977 | r" | ||
978 | //- /lib.rs crate:dep | ||
979 | pub mod fmt { | ||
980 | pub trait Display {} | ||
981 | } | ||
982 | |||
983 | pub fn panic_fmt() {} | ||
984 | |||
985 | //- /main.rs crate:main deps:dep | ||
986 | struct S; | ||
987 | |||
988 | impl f<|>mt::Display for S {} | ||
989 | ", | ||
990 | r" | ||
991 | struct S; | ||
992 | |||
993 | impl dep::fmt::Display for S {} | ||
994 | ", | ||
995 | ); | ||
996 | } | ||
997 | |||
998 | #[test] | ||
999 | fn macro_generated() { | ||
1000 | // Tests that macro-generated items are suggested from external crates. | ||
1001 | check_assist( | ||
1002 | qualify_path, | ||
1003 | r" | ||
1004 | //- /lib.rs crate:dep | ||
1005 | macro_rules! mac { | ||
1006 | () => { | ||
1007 | pub struct Cheese; | ||
1008 | }; | ||
1009 | } | ||
1010 | |||
1011 | mac!(); | ||
1012 | |||
1013 | //- /main.rs crate:main deps:dep | ||
1014 | fn main() { | ||
1015 | Cheese<|>; | ||
1016 | } | ||
1017 | ", | ||
1018 | r" | ||
1019 | fn main() { | ||
1020 | dep::Cheese; | ||
1021 | } | ||
1022 | ", | ||
1023 | ); | ||
1024 | } | ||
1025 | |||
1026 | #[test] | ||
1027 | fn casing() { | ||
1028 | // Tests that differently cased names don't interfere and we only suggest the matching one. | ||
1029 | check_assist( | ||
1030 | qualify_path, | ||
1031 | r" | ||
1032 | //- /lib.rs crate:dep | ||
1033 | pub struct FMT; | ||
1034 | pub struct fmt; | ||
1035 | |||
1036 | //- /main.rs crate:main deps:dep | ||
1037 | fn main() { | ||
1038 | FMT<|>; | ||
1039 | } | ||
1040 | ", | ||
1041 | r" | ||
1042 | fn main() { | ||
1043 | dep::FMT; | ||
1044 | } | ||
1045 | ", | ||
1046 | ); | ||
1047 | } | ||
1048 | } | ||
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs new file mode 100644 index 000000000..4ca87a8ec --- /dev/null +++ b/crates/assists/src/handlers/replace_string_with_char.rs | |||
@@ -0,0 +1,141 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, HasStringValue}, | ||
3 | AstToken, | ||
4 | SyntaxKind::STRING, | ||
5 | }; | ||
6 | |||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
8 | |||
9 | // Assist: replace_string_with_char | ||
10 | // | ||
11 | // Replace string with char. | ||
12 | // | ||
13 | // ``` | ||
14 | // fn main() { | ||
15 | // find("{<|>"); | ||
16 | // } | ||
17 | // ``` | ||
18 | // -> | ||
19 | // ``` | ||
20 | // fn main() { | ||
21 | // find('{'); | ||
22 | // } | ||
23 | // ``` | ||
24 | pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
25 | let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; | ||
26 | let value = token.value()?; | ||
27 | let target = token.syntax().text_range(); | ||
28 | |||
29 | if value.chars().take(2).count() != 1 { | ||
30 | return None; | ||
31 | } | ||
32 | |||
33 | acc.add( | ||
34 | AssistId("replace_string_with_char", AssistKind::RefactorRewrite), | ||
35 | "Replace string with char", | ||
36 | target, | ||
37 | |edit| { | ||
38 | edit.replace(token.syntax().text_range(), format!("'{}'", value)); | ||
39 | }, | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | #[cfg(test)] | ||
44 | mod tests { | ||
45 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
46 | |||
47 | use super::*; | ||
48 | |||
49 | #[test] | ||
50 | fn replace_string_with_char_target() { | ||
51 | check_assist_target( | ||
52 | replace_string_with_char, | ||
53 | r#" | ||
54 | fn f() { | ||
55 | let s = "<|>c"; | ||
56 | } | ||
57 | "#, | ||
58 | r#""c""#, | ||
59 | ); | ||
60 | } | ||
61 | |||
62 | #[test] | ||
63 | fn replace_string_with_char_assist() { | ||
64 | check_assist( | ||
65 | replace_string_with_char, | ||
66 | r#" | ||
67 | fn f() { | ||
68 | let s = "<|>c"; | ||
69 | } | ||
70 | "#, | ||
71 | r##" | ||
72 | fn f() { | ||
73 | let s = 'c'; | ||
74 | } | ||
75 | "##, | ||
76 | ) | ||
77 | } | ||
78 | |||
79 | #[test] | ||
80 | fn replace_string_with_char_assist_with_emoji() { | ||
81 | check_assist( | ||
82 | replace_string_with_char, | ||
83 | r#" | ||
84 | fn f() { | ||
85 | let s = "<|>😀"; | ||
86 | } | ||
87 | "#, | ||
88 | r##" | ||
89 | fn f() { | ||
90 | let s = '😀'; | ||
91 | } | ||
92 | "##, | ||
93 | ) | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn replace_string_with_char_assist_not_applicable() { | ||
98 | check_assist_not_applicable( | ||
99 | replace_string_with_char, | ||
100 | r#" | ||
101 | fn f() { | ||
102 | let s = "<|>test"; | ||
103 | } | ||
104 | "#, | ||
105 | ) | ||
106 | } | ||
107 | |||
108 | #[test] | ||
109 | fn replace_string_with_char_works_inside_macros() { | ||
110 | check_assist( | ||
111 | replace_string_with_char, | ||
112 | r#" | ||
113 | fn f() { | ||
114 | format!(<|>"x", 92) | ||
115 | } | ||
116 | "#, | ||
117 | r##" | ||
118 | fn f() { | ||
119 | format!('x', 92) | ||
120 | } | ||
121 | "##, | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn replace_string_with_char_works_func_args() { | ||
127 | check_assist( | ||
128 | replace_string_with_char, | ||
129 | r#" | ||
130 | fn f() { | ||
131 | find(<|>"x"); | ||
132 | } | ||
133 | "#, | ||
134 | r##" | ||
135 | fn f() { | ||
136 | find('x'); | ||
137 | } | ||
138 | "##, | ||
139 | ) | ||
140 | } | ||
141 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index a2bec818c..8a664f654 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -150,6 +150,7 @@ mod handlers { | |||
150 | mod merge_match_arms; | 150 | mod merge_match_arms; |
151 | mod move_bounds; | 151 | mod move_bounds; |
152 | mod move_guard; | 152 | mod move_guard; |
153 | mod qualify_path; | ||
153 | mod raw_string; | 154 | mod raw_string; |
154 | mod remove_dbg; | 155 | mod remove_dbg; |
155 | mod remove_mut; | 156 | mod remove_mut; |
@@ -159,6 +160,7 @@ mod handlers { | |||
159 | mod replace_impl_trait_with_generic; | 160 | mod replace_impl_trait_with_generic; |
160 | mod replace_let_with_if_let; | 161 | mod replace_let_with_if_let; |
161 | mod replace_qualified_name_with_use; | 162 | mod replace_qualified_name_with_use; |
163 | mod replace_string_with_char; | ||
162 | mod replace_unwrap_with_match; | 164 | mod replace_unwrap_with_match; |
163 | mod split_import; | 165 | mod split_import; |
164 | mod unwrap_block; | 166 | mod unwrap_block; |
@@ -196,6 +198,7 @@ mod handlers { | |||
196 | move_bounds::move_bounds_to_where_clause, | 198 | move_bounds::move_bounds_to_where_clause, |
197 | move_guard::move_arm_cond_to_match_guard, | 199 | move_guard::move_arm_cond_to_match_guard, |
198 | move_guard::move_guard_to_arm_body, | 200 | move_guard::move_guard_to_arm_body, |
201 | qualify_path::qualify_path, | ||
199 | raw_string::add_hash, | 202 | raw_string::add_hash, |
200 | raw_string::make_raw_string, | 203 | raw_string::make_raw_string, |
201 | raw_string::make_usual_string, | 204 | raw_string::make_usual_string, |
@@ -208,6 +211,7 @@ mod handlers { | |||
208 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, | 211 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, |
209 | replace_let_with_if_let::replace_let_with_if_let, | 212 | replace_let_with_if_let::replace_let_with_if_let, |
210 | replace_qualified_name_with_use::replace_qualified_name_with_use, | 213 | replace_qualified_name_with_use::replace_qualified_name_with_use, |
214 | replace_string_with_char::replace_string_with_char, | ||
211 | replace_unwrap_with_match::replace_unwrap_with_match, | 215 | replace_unwrap_with_match::replace_unwrap_with_match, |
212 | split_import::split_import, | 216 | split_import::split_import, |
213 | unwrap_block::unwrap_block, | 217 | unwrap_block::unwrap_block, |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 41f536574..acbf5b652 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -713,6 +713,25 @@ fn handle(action: Action) { | |||
713 | } | 713 | } |
714 | 714 | ||
715 | #[test] | 715 | #[test] |
716 | fn doctest_qualify_path() { | ||
717 | check_doc_test( | ||
718 | "qualify_path", | ||
719 | r#####" | ||
720 | fn main() { | ||
721 | let map = HashMap<|>::new(); | ||
722 | } | ||
723 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
724 | "#####, | ||
725 | r#####" | ||
726 | fn main() { | ||
727 | let map = std::collections::HashMap::new(); | ||
728 | } | ||
729 | pub mod std { pub mod collections { pub struct HashMap { } } } | ||
730 | "#####, | ||
731 | ) | ||
732 | } | ||
733 | |||
734 | #[test] | ||
716 | fn doctest_remove_dbg() { | 735 | fn doctest_remove_dbg() { |
717 | check_doc_test( | 736 | check_doc_test( |
718 | "remove_dbg", | 737 | "remove_dbg", |
@@ -882,6 +901,23 @@ fn process(map: HashMap<String, String>) {} | |||
882 | } | 901 | } |
883 | 902 | ||
884 | #[test] | 903 | #[test] |
904 | fn doctest_replace_string_with_char() { | ||
905 | check_doc_test( | ||
906 | "replace_string_with_char", | ||
907 | r#####" | ||
908 | fn main() { | ||
909 | find("{<|>"); | ||
910 | } | ||
911 | "#####, | ||
912 | r#####" | ||
913 | fn main() { | ||
914 | find('{'); | ||
915 | } | ||
916 | "#####, | ||
917 | ) | ||
918 | } | ||
919 | |||
920 | #[test] | ||
885 | fn doctest_replace_unwrap_with_match() { | 921 | fn doctest_replace_unwrap_with_match() { |
886 | check_doc_test( | 922 | check_doc_test( |
887 | "replace_unwrap_with_match", | 923 | "replace_unwrap_with_match", |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index b37b0d2b6..1a6b48b45 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -275,7 +275,7 @@ impl TryEnum { | |||
275 | /// somewhat similar to the known paths infra inside hir, but it different; We | 275 | /// somewhat similar to the known paths infra inside hir, but it different; We |
276 | /// want to make sure that IDE specific paths don't become interesting inside | 276 | /// want to make sure that IDE specific paths don't become interesting inside |
277 | /// the compiler itself as well. | 277 | /// the compiler itself as well. |
278 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Crate); | 278 | pub struct FamousDefs<'a, 'b>(pub &'a Semantics<'b, RootDatabase>, pub Option<Crate>); |
279 | 279 | ||
280 | #[allow(non_snake_case)] | 280 | #[allow(non_snake_case)] |
281 | impl FamousDefs<'_, '_> { | 281 | impl FamousDefs<'_, '_> { |
@@ -362,6 +362,10 @@ pub mod prelude { | |||
362 | pub use prelude::*; | 362 | pub use prelude::*; |
363 | "#; | 363 | "#; |
364 | 364 | ||
365 | pub fn core(&self) -> Option<Crate> { | ||
366 | self.find_crate("core") | ||
367 | } | ||
368 | |||
365 | pub(crate) fn core_convert_From(&self) -> Option<Trait> { | 369 | pub(crate) fn core_convert_From(&self) -> Option<Trait> { |
366 | self.find_trait("core:convert:From") | 370 | self.find_trait("core:convert:From") |
367 | } | 371 | } |
@@ -399,21 +403,20 @@ pub use prelude::*; | |||
399 | } | 403 | } |
400 | } | 404 | } |
401 | 405 | ||
406 | fn find_crate(&self, name: &str) -> Option<Crate> { | ||
407 | let krate = self.1?; | ||
408 | let db = self.0.db; | ||
409 | let res = | ||
410 | krate.dependencies(db).into_iter().find(|dep| dep.name.to_string() == name)?.krate; | ||
411 | Some(res) | ||
412 | } | ||
413 | |||
402 | fn find_def(&self, path: &str) -> Option<ScopeDef> { | 414 | fn find_def(&self, path: &str) -> Option<ScopeDef> { |
403 | let db = self.0.db; | 415 | let db = self.0.db; |
404 | let mut path = path.split(':'); | 416 | let mut path = path.split(':'); |
405 | let trait_ = path.next_back()?; | 417 | let trait_ = path.next_back()?; |
406 | let std_crate = path.next()?; | 418 | let std_crate = path.next()?; |
407 | let std_crate = if self | 419 | let std_crate = self.find_crate(std_crate)?; |
408 | .1 | ||
409 | .declaration_name(db) | ||
410 | .map(|name| name.to_string() == std_crate) | ||
411 | .unwrap_or(false) | ||
412 | { | ||
413 | self.1 | ||
414 | } else { | ||
415 | self.1.dependencies(db).into_iter().find(|dep| dep.name.to_string() == std_crate)?.krate | ||
416 | }; | ||
417 | let mut module = std_crate.root_module(db); | 420 | let mut module = std_crate.root_module(db); |
418 | for segment in path { | 421 | for segment in path { |
419 | module = module.children(db).find_map(|child| { | 422 | module = module.children(db).find_map(|child| { |
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs index 601f51098..23db3a74b 100644 --- a/crates/assists/src/utils/import_assets.rs +++ b/crates/assists/src/utils/import_assets.rs | |||
@@ -1,6 +1,4 @@ | |||
1 | //! Look up accessible paths for items. | 1 | //! Look up accessible paths for items. |
2 | use std::collections::BTreeSet; | ||
3 | |||
4 | use either::Either; | 2 | use either::Either; |
5 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; | 3 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; |
6 | use ide_db::{imports_locator, RootDatabase}; | 4 | use ide_db::{imports_locator, RootDatabase}; |
@@ -29,12 +27,12 @@ pub(crate) enum ImportCandidate { | |||
29 | #[derive(Debug)] | 27 | #[derive(Debug)] |
30 | pub(crate) struct TraitImportCandidate { | 28 | pub(crate) struct TraitImportCandidate { |
31 | pub ty: hir::Type, | 29 | pub ty: hir::Type, |
32 | pub name: String, | 30 | pub name: ast::NameRef, |
33 | } | 31 | } |
34 | 32 | ||
35 | #[derive(Debug)] | 33 | #[derive(Debug)] |
36 | pub(crate) struct PathImportCandidate { | 34 | pub(crate) struct PathImportCandidate { |
37 | pub name: String, | 35 | pub name: ast::NameRef, |
38 | } | 36 | } |
39 | 37 | ||
40 | #[derive(Debug)] | 38 | #[derive(Debug)] |
@@ -86,9 +84,9 @@ impl ImportAssets { | |||
86 | fn get_search_query(&self) -> &str { | 84 | fn get_search_query(&self) -> &str { |
87 | match &self.import_candidate { | 85 | match &self.import_candidate { |
88 | ImportCandidate::UnqualifiedName(candidate) | 86 | ImportCandidate::UnqualifiedName(candidate) |
89 | | ImportCandidate::QualifierStart(candidate) => &candidate.name, | 87 | | ImportCandidate::QualifierStart(candidate) => candidate.name.text(), |
90 | ImportCandidate::TraitAssocItem(candidate) | 88 | ImportCandidate::TraitAssocItem(candidate) |
91 | | ImportCandidate::TraitMethod(candidate) => &candidate.name, | 89 | | ImportCandidate::TraitMethod(candidate) => candidate.name.text(), |
92 | } | 90 | } |
93 | } | 91 | } |
94 | 92 | ||
@@ -96,7 +94,7 @@ impl ImportAssets { | |||
96 | &self, | 94 | &self, |
97 | sema: &Semantics<RootDatabase>, | 95 | sema: &Semantics<RootDatabase>, |
98 | config: &InsertUseConfig, | 96 | config: &InsertUseConfig, |
99 | ) -> BTreeSet<hir::ModPath> { | 97 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
100 | let _p = profile::span("import_assists::search_for_imports"); | 98 | let _p = profile::span("import_assists::search_for_imports"); |
101 | self.search_for(sema, Some(config.prefix_kind)) | 99 | self.search_for(sema, Some(config.prefix_kind)) |
102 | } | 100 | } |
@@ -106,7 +104,7 @@ impl ImportAssets { | |||
106 | pub(crate) fn search_for_relative_paths( | 104 | pub(crate) fn search_for_relative_paths( |
107 | &self, | 105 | &self, |
108 | sema: &Semantics<RootDatabase>, | 106 | sema: &Semantics<RootDatabase>, |
109 | ) -> BTreeSet<hir::ModPath> { | 107 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
110 | let _p = profile::span("import_assists::search_for_relative_paths"); | 108 | let _p = profile::span("import_assists::search_for_relative_paths"); |
111 | self.search_for(sema, None) | 109 | self.search_for(sema, None) |
112 | } | 110 | } |
@@ -115,7 +113,7 @@ impl ImportAssets { | |||
115 | &self, | 113 | &self, |
116 | sema: &Semantics<RootDatabase>, | 114 | sema: &Semantics<RootDatabase>, |
117 | prefixed: Option<hir::PrefixKind>, | 115 | prefixed: Option<hir::PrefixKind>, |
118 | ) -> BTreeSet<hir::ModPath> { | 116 | ) -> Vec<(hir::ModPath, hir::ItemInNs)> { |
119 | let db = sema.db; | 117 | let db = sema.db; |
120 | let mut trait_candidates = FxHashSet::default(); | 118 | let mut trait_candidates = FxHashSet::default(); |
121 | let current_crate = self.module_with_name_to_import.krate(); | 119 | let current_crate = self.module_with_name_to_import.krate(); |
@@ -181,7 +179,7 @@ impl ImportAssets { | |||
181 | } | 179 | } |
182 | }; | 180 | }; |
183 | 181 | ||
184 | imports_locator::find_imports(sema, current_crate, &self.get_search_query()) | 182 | let mut res = imports_locator::find_imports(sema, current_crate, &self.get_search_query()) |
185 | .into_iter() | 183 | .into_iter() |
186 | .filter_map(filter) | 184 | .filter_map(filter) |
187 | .filter_map(|candidate| { | 185 | .filter_map(|candidate| { |
@@ -191,10 +189,13 @@ impl ImportAssets { | |||
191 | } else { | 189 | } else { |
192 | self.module_with_name_to_import.find_use_path(db, item) | 190 | self.module_with_name_to_import.find_use_path(db, item) |
193 | } | 191 | } |
192 | .map(|path| (path, item)) | ||
194 | }) | 193 | }) |
195 | .filter(|use_path| !use_path.segments.is_empty()) | 194 | .filter(|(use_path, _)| !use_path.segments.is_empty()) |
196 | .take(20) | 195 | .take(20) |
197 | .collect::<BTreeSet<_>>() | 196 | .collect::<Vec<_>>(); |
197 | res.sort_by_key(|(path, _)| path.clone()); | ||
198 | res | ||
198 | } | 199 | } |
199 | 200 | ||
200 | fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { | 201 | fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { |
@@ -215,7 +216,7 @@ impl ImportCandidate { | |||
215 | Some(_) => None, | 216 | Some(_) => None, |
216 | None => Some(Self::TraitMethod(TraitImportCandidate { | 217 | None => Some(Self::TraitMethod(TraitImportCandidate { |
217 | ty: sema.type_of_expr(&method_call.receiver()?)?, | 218 | ty: sema.type_of_expr(&method_call.receiver()?)?, |
218 | name: method_call.name_ref()?.syntax().to_string(), | 219 | name: method_call.name_ref()?, |
219 | })), | 220 | })), |
220 | } | 221 | } |
221 | } | 222 | } |
@@ -243,24 +244,17 @@ impl ImportCandidate { | |||
243 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { | 244 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { |
244 | ImportCandidate::TraitAssocItem(TraitImportCandidate { | 245 | ImportCandidate::TraitAssocItem(TraitImportCandidate { |
245 | ty: assoc_item_path.ty(sema.db), | 246 | ty: assoc_item_path.ty(sema.db), |
246 | name: segment.syntax().to_string(), | 247 | name: segment.name_ref()?, |
247 | }) | 248 | }) |
248 | } | 249 | } |
249 | _ => return None, | 250 | _ => return None, |
250 | } | 251 | } |
251 | } else { | 252 | } else { |
252 | ImportCandidate::QualifierStart(PathImportCandidate { | 253 | ImportCandidate::QualifierStart(PathImportCandidate { name: qualifier_start }) |
253 | name: qualifier_start.syntax().to_string(), | ||
254 | }) | ||
255 | } | 254 | } |
256 | } else { | 255 | } else { |
257 | ImportCandidate::UnqualifiedName(PathImportCandidate { | 256 | ImportCandidate::UnqualifiedName(PathImportCandidate { |
258 | name: segment | 257 | name: segment.syntax().descendants().find_map(ast::NameRef::cast)?, |
259 | .syntax() | ||
260 | .descendants() | ||
261 | .find_map(ast::NameRef::cast)? | ||
262 | .syntax() | ||
263 | .to_string(), | ||
264 | }) | 258 | }) |
265 | }; | 259 | }; |
266 | Some(candidate) | 260 | Some(candidate) |