aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers')
-rw-r--r--crates/assists/src/handlers/auto_import.rs4
-rw-r--r--crates/assists/src/handlers/change_visibility.rs18
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs2
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs6
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs2
-rw-r--r--crates/assists/src/handlers/generate_impl.rs42
-rw-r--r--crates/assists/src/handlers/qualify_path.rs1048
-rw-r--r--crates/assists/src/handlers/replace_string_with_char.rs141
8 files changed, 1252 insertions, 11 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 @@
1use syntax::{ 1use 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};
7use test_utils::mark; 7use 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 @@
1use base_db::FileId; 1use base_db::FileId;
2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; 2use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
3use syntax::{ast, AstNode, TextRange, TextSize}; 3use syntax::{
4 ast::{self, VisibilityOwner},
5 AstNode, TextRange, TextSize,
6};
4 7
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists}; 8use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use 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/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs
index 9989109b5..114974465 100644
--- a/crates/assists/src/handlers/generate_impl.rs
+++ b/crates/assists/src/handlers/generate_impl.rs
@@ -1,6 +1,6 @@
1use itertools::Itertools; 1use itertools::Itertools;
2use stdx::format_to; 2use stdx::format_to;
3use syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner}; 3use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner};
4 4
5use crate::{AssistContext, AssistId, AssistKind, Assists}; 5use crate::{AssistContext, AssistId, AssistKind, Assists};
6 6
@@ -27,6 +27,7 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?; 27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
28 let name = nominal.name()?; 28 let name = nominal.name()?;
29 let target = nominal.syntax().text_range(); 29 let target = nominal.syntax().text_range();
30
30 acc.add( 31 acc.add(
31 AssistId("generate_impl", AssistKind::Generate), 32 AssistId("generate_impl", AssistKind::Generate),
32 format!("Generate impl for `{}`", name), 33 format!("Generate impl for `{}`", name),
@@ -35,7 +36,15 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()
35 let type_params = nominal.generic_param_list(); 36 let type_params = nominal.generic_param_list();
36 let start_offset = nominal.syntax().text_range().end(); 37 let start_offset = nominal.syntax().text_range().end();
37 let mut buf = String::new(); 38 let mut buf = String::new();
38 buf.push_str("\n\nimpl"); 39 buf.push_str("\n\n");
40 nominal
41 .attrs()
42 .filter(|attr| {
43 attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)
44 })
45 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
46
47 buf.push_str("impl");
39 if let Some(type_params) = &type_params { 48 if let Some(type_params) = &type_params {
40 format_to!(buf, "{}", type_params.syntax()); 49 format_to!(buf, "{}", type_params.syntax());
41 } 50 }
@@ -91,6 +100,35 @@ mod tests {
91 "struct Foo<'a, T: Foo<'a>> {<|>}", 100 "struct Foo<'a, T: Foo<'a>> {<|>}",
92 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", 101 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
93 ); 102 );
103 check_assist(
104 generate_impl,
105 r#"
106 #[cfg(feature = "foo")]
107 struct Foo<'a, T: Foo<'a>> {<|>}"#,
108 r#"
109 #[cfg(feature = "foo")]
110 struct Foo<'a, T: Foo<'a>> {}
111
112 #[cfg(feature = "foo")]
113 impl<'a, T: Foo<'a>> Foo<'a, T> {
114 $0
115 }"#,
116 );
117
118 check_assist(
119 generate_impl,
120 r#"
121 #[cfg(not(feature = "foo"))]
122 struct Foo<'a, T: Foo<'a>> {<|>}"#,
123 r#"
124 #[cfg(not(feature = "foo"))]
125 struct Foo<'a, T: Foo<'a>> {}
126
127 #[cfg(not(feature = "foo"))]
128 impl<'a, T: Foo<'a>> Foo<'a, T> {
129 $0
130 }"#,
131 );
94 } 132 }
95 133
96 #[test] 134 #[test]
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 @@
1use std::iter;
2
3use hir::AsName;
4use ide_db::RootDatabase;
5use syntax::{
6 ast,
7 ast::{make, ArgListOwner},
8 AstNode,
9};
10use test_utils::mark;
11
12use 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// ```
36pub(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
98enum QualifyCandidate<'db> {
99 QualifierStart(ast::PathSegment),
100 UnqualifiedName,
101 TraitAssocItem(ast::Path, ast::PathSegment),
102 TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
103}
104
105impl 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
156fn 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
172fn 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
180fn 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
188fn 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)]
198mod 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]
394macro_rules! foo {
395 () => ()
396}
397
398//- /main.rs crate:main deps:crate_with_macro
399fn main() {
400 foo<|>
401}
402",
403 r"
404fn 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
957pub struct Struct;
958
959//- /main.rs crate:main deps:dep
960fn main() {
961 Struct<|>
962}
963",
964 r"
965fn 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
979pub mod fmt {
980 pub trait Display {}
981}
982
983pub fn panic_fmt() {}
984
985//- /main.rs crate:main deps:dep
986struct S;
987
988impl f<|>mt::Display for S {}
989",
990 r"
991struct S;
992
993impl 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
1005macro_rules! mac {
1006 () => {
1007 pub struct Cheese;
1008 };
1009}
1010
1011mac!();
1012
1013//- /main.rs crate:main deps:dep
1014fn main() {
1015 Cheese<|>;
1016}
1017",
1018 r"
1019fn 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
1033pub struct FMT;
1034pub struct fmt;
1035
1036//- /main.rs crate:main deps:dep
1037fn main() {
1038 FMT<|>;
1039}
1040",
1041 r"
1042fn 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 @@
1use syntax::{
2 ast::{self, HasStringValue},
3 AstToken,
4 SyntaxKind::STRING,
5};
6
7use 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// ```
24pub(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)]
44mod 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}