aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists')
-rw-r--r--crates/ra_assists/src/assists/add_derive.rs105
-rw-r--r--crates/ra_assists/src/assists/add_explicit_type.rs86
-rw-r--r--crates/ra_assists/src/assists/add_impl.rs77
-rw-r--r--crates/ra_assists/src/assists/add_missing_impl_members.rs340
-rw-r--r--crates/ra_assists/src/assists/auto_import.rs939
-rw-r--r--crates/ra_assists/src/assists/change_visibility.rs159
-rw-r--r--crates/ra_assists/src/assists/fill_match_arms.rs229
-rw-r--r--crates/ra_assists/src/assists/flip_binexpr.rs141
-rw-r--r--crates/ra_assists/src/assists/flip_comma.rs68
-rw-r--r--crates/ra_assists/src/assists/inline_local_variable.rs636
-rw-r--r--crates/ra_assists/src/assists/introduce_variable.rs516
-rw-r--r--crates/ra_assists/src/assists/merge_match_arms.rs188
-rw-r--r--crates/ra_assists/src/assists/move_bounds.rs135
-rw-r--r--crates/ra_assists/src/assists/move_guard.rs261
-rw-r--r--crates/ra_assists/src/assists/raw_string.rs370
-rw-r--r--crates/ra_assists/src/assists/remove_dbg.rs137
-rw-r--r--crates/ra_assists/src/assists/replace_if_let_with_match.rs102
-rw-r--r--crates/ra_assists/src/assists/split_import.rs61
18 files changed, 4550 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_derive.rs b/crates/ra_assists/src/assists/add_derive.rs
new file mode 100644
index 000000000..9c88644df
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_derive.rs
@@ -0,0 +1,105 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode, AttrsOwner},
4 SyntaxKind::{COMMENT, WHITESPACE},
5 TextUnit,
6};
7
8use crate::{Assist, AssistCtx, AssistId};
9
10pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
12 let node_start = derive_insertion_offset(&nominal)?;
13 ctx.add_action(AssistId("add_derive"), "add `#[derive]`", |edit| {
14 let derive_attr = nominal
15 .attrs()
16 .filter_map(|x| x.as_call())
17 .filter(|(name, _arg)| name == "derive")
18 .map(|(_name, arg)| arg)
19 .next();
20 let offset = match derive_attr {
21 None => {
22 edit.insert(node_start, "#[derive()]\n");
23 node_start + TextUnit::of_str("#[derive(")
24 }
25 Some(tt) => tt.syntax().text_range().end() - TextUnit::of_char(')'),
26 };
27 edit.target(nominal.syntax().text_range());
28 edit.set_cursor(offset)
29 });
30
31 ctx.build()
32}
33
34// Insert `derive` after doc comments.
35fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> {
36 let non_ws_child = nominal
37 .syntax()
38 .children_with_tokens()
39 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
40 Some(non_ws_child.text_range().start())
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::helpers::{check_assist, check_assist_target};
47
48 #[test]
49 fn add_derive_new() {
50 check_assist(
51 add_derive,
52 "struct Foo { a: i32, <|>}",
53 "#[derive(<|>)]\nstruct Foo { a: i32, }",
54 );
55 check_assist(
56 add_derive,
57 "struct Foo { <|> a: i32, }",
58 "#[derive(<|>)]\nstruct Foo { a: i32, }",
59 );
60 }
61
62 #[test]
63 fn add_derive_existing() {
64 check_assist(
65 add_derive,
66 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
67 "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
68 );
69 }
70
71 #[test]
72 fn add_derive_new_with_doc_comment() {
73 check_assist(
74 add_derive,
75 "
76/// `Foo` is a pretty important struct.
77/// It does stuff.
78struct Foo { a: i32<|>, }
79 ",
80 "
81/// `Foo` is a pretty important struct.
82/// It does stuff.
83#[derive(<|>)]
84struct Foo { a: i32, }
85 ",
86 );
87 }
88
89 #[test]
90 fn add_derive_target() {
91 check_assist_target(
92 add_derive,
93 "
94struct SomeThingIrrelevant;
95/// `Foo` is a pretty important struct.
96/// It does stuff.
97struct Foo { a: i32<|>, }
98struct EvenMoreIrrelevant;
99 ",
100 "/// `Foo` is a pretty important struct.
101/// It does stuff.
102struct Foo { a: i32, }",
103 );
104 }
105}
diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs
new file mode 100644
index 000000000..78f0f7f28
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_explicit_type.rs
@@ -0,0 +1,86 @@
1use hir::{db::HirDatabase, HirDisplay, Ty};
2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner},
4 T,
5};
6
7use crate::{Assist, AssistCtx, AssistId};
8
9/// Add explicit type assist.
10pub(crate) fn add_explicit_type(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let stmt = ctx.node_at_offset::<LetStmt>()?;
12 let expr = stmt.initializer()?;
13 let pat = stmt.pat()?;
14 // Must be a binding
15 let pat = match pat {
16 ast::Pat::BindPat(bind_pat) => bind_pat,
17 _ => return None,
18 };
19 let pat_range = pat.syntax().text_range();
20 // The binding must have a name
21 let name = pat.name()?;
22 let name_range = name.syntax().text_range();
23 // Assist not applicable if the type has already been specified
24 if stmt.syntax().children_with_tokens().any(|child| child.kind() == T![:]) {
25 return None;
26 }
27 // Infer type
28 let db = ctx.db;
29 let analyzer = hir::SourceAnalyzer::new(db, ctx.frange.file_id, stmt.syntax(), None);
30 let ty = analyzer.type_of(db, &expr)?;
31 // Assist not applicable if the type is unknown
32 if is_unknown(&ty) {
33 return None;
34 }
35
36 ctx.add_action(AssistId("add_explicit_type"), "add explicit type", |edit| {
37 edit.target(pat_range);
38 edit.insert(name_range.end(), format!(": {}", ty.display(db)));
39 });
40 ctx.build()
41}
42
43/// Returns true if any type parameter is unknown
44fn is_unknown(ty: &Ty) -> bool {
45 match ty {
46 Ty::Unknown => true,
47 Ty::Apply(a_ty) => a_ty.parameters.iter().any(is_unknown),
48 _ => false,
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
57
58 #[test]
59 fn add_explicit_type_target() {
60 check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a");
61 }
62
63 #[test]
64 fn add_explicit_type_works_for_simple_expr() {
65 check_assist(
66 add_explicit_type,
67 "fn f() { let a<|> = 1; }",
68 "fn f() { let a<|>: i32 = 1; }",
69 );
70 }
71
72 #[test]
73 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
74 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
75 }
76
77 #[test]
78 fn add_explicit_type_not_applicable_if_ty_already_specified() {
79 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
80 }
81
82 #[test]
83 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
84 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
85 }
86}
diff --git a/crates/ra_assists/src/assists/add_impl.rs b/crates/ra_assists/src/assists/add_impl.rs
new file mode 100644
index 000000000..4b61f4031
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_impl.rs
@@ -0,0 +1,77 @@
1use format_buf::format;
2use hir::db::HirDatabase;
3use join_to_string::join;
4use ra_syntax::{
5 ast::{self, AstNode, NameOwner, TypeParamsOwner},
6 TextUnit,
7};
8
9use crate::{Assist, AssistCtx, AssistId};
10
11pub(crate) fn add_impl(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
13 let name = nominal.name()?;
14 ctx.add_action(AssistId("add_impl"), "add impl", |edit| {
15 edit.target(nominal.syntax().text_range());
16 let type_params = nominal.type_param_list();
17 let start_offset = nominal.syntax().text_range().end();
18 let mut buf = String::new();
19 buf.push_str("\n\nimpl");
20 if let Some(type_params) = &type_params {
21 format!(buf, "{}", type_params.syntax());
22 }
23 buf.push_str(" ");
24 buf.push_str(name.text().as_str());
25 if let Some(type_params) = type_params {
26 let lifetime_params = type_params
27 .lifetime_params()
28 .filter_map(|it| it.lifetime_token())
29 .map(|it| it.text().clone());
30 let type_params =
31 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
32 join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf);
33 }
34 buf.push_str(" {\n");
35 edit.set_cursor(start_offset + TextUnit::of_str(&buf));
36 buf.push_str("\n}");
37 edit.insert(start_offset, buf);
38 });
39
40 ctx.build()
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46 use crate::helpers::{check_assist, check_assist_target};
47
48 #[test]
49 fn test_add_impl() {
50 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n<|>\n}\n");
51 check_assist(
52 add_impl,
53 "struct Foo<T: Clone> {<|>}",
54 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n<|>\n}",
55 );
56 check_assist(
57 add_impl,
58 "struct Foo<'a, T: Foo<'a>> {<|>}",
59 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n<|>\n}",
60 );
61 }
62
63 #[test]
64 fn add_impl_target() {
65 check_assist_target(
66 add_impl,
67 "
68struct SomeThingIrrelevant;
69/// Has a lifetime parameter
70struct Foo<'a, T: Foo<'a>> {<|>}
71struct EvenMoreIrrelevant;
72",
73 "/// Has a lifetime parameter
74struct Foo<'a, T: Foo<'a>> {}",
75 );
76 }
77}
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs
new file mode 100644
index 000000000..cbeb7054f
--- /dev/null
+++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs
@@ -0,0 +1,340 @@
1use hir::{db::HirDatabase, HasSource};
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner},
4 SmolStr,
5};
6
7use crate::{
8 ast_editor::{AstBuilder, AstEditor},
9 Assist, AssistCtx, AssistId,
10};
11
12#[derive(PartialEq)]
13enum AddMissingImplMembersMode {
14 DefaultMethodsOnly,
15 NoDefaultMethods,
16}
17
18pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
19 add_missing_impl_members_inner(
20 ctx,
21 AddMissingImplMembersMode::NoDefaultMethods,
22 "add_impl_missing_members",
23 "add missing impl members",
24 )
25}
26
27pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
28 add_missing_impl_members_inner(
29 ctx,
30 AddMissingImplMembersMode::DefaultMethodsOnly,
31 "add_impl_default_members",
32 "add impl default members",
33 )
34}
35
36fn add_missing_impl_members_inner(
37 mut ctx: AssistCtx<impl HirDatabase>,
38 mode: AddMissingImplMembersMode,
39 assist_id: &'static str,
40 label: &'static str,
41) -> Option<Assist> {
42 let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?;
43 let impl_item_list = impl_node.item_list()?;
44
45 let trait_def = {
46 let file_id = ctx.frange.file_id;
47 let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None);
48
49 resolve_target_trait_def(ctx.db, &analyzer, &impl_node)?
50 };
51
52 let def_name = |item: &ast::ImplItem| -> Option<SmolStr> {
53 match item {
54 ast::ImplItem::FnDef(def) => def.name(),
55 ast::ImplItem::TypeAliasDef(def) => def.name(),
56 ast::ImplItem::ConstDef(def) => def.name(),
57 }
58 .map(|it| it.text().clone())
59 };
60
61 let trait_items = trait_def.item_list()?.impl_items();
62 let impl_items = impl_item_list.impl_items().collect::<Vec<_>>();
63
64 let missing_items: Vec<_> = trait_items
65 .filter(|t| def_name(t).is_some())
66 .filter(|t| match t {
67 ast::ImplItem::FnDef(def) => match mode {
68 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
69 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
70 },
71 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
72 })
73 .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t)))
74 .collect();
75 if missing_items.is_empty() {
76 return None;
77 }
78
79 ctx.add_action(AssistId(assist_id), label, |edit| {
80 let n_existing_items = impl_item_list.impl_items().count();
81 let items = missing_items.into_iter().map(|it| match it {
82 ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()),
83 _ => strip_docstring(it),
84 });
85 let mut ast_editor = AstEditor::new(impl_item_list);
86
87 ast_editor.append_items(items);
88
89 let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap();
90 let cursor_position = first_new_item.syntax().text_range().start();
91 ast_editor.into_text_edit(edit.text_edit_builder());
92
93 edit.set_cursor(cursor_position);
94 });
95
96 ctx.build()
97}
98
99fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem {
100 let mut ast_editor = AstEditor::new(item);
101 ast_editor.strip_attrs_and_docs();
102 ast_editor.ast().to_owned()
103}
104
105fn add_body(fn_def: ast::FnDef) -> ast::FnDef {
106 let mut ast_editor = AstEditor::new(fn_def.clone());
107 if fn_def.body().is_none() {
108 ast_editor.set_body(&AstBuilder::<ast::Block>::single_expr(
109 &AstBuilder::<ast::Expr>::unimplemented(),
110 ));
111 }
112 ast_editor.ast().to_owned()
113}
114
115/// Given an `ast::ImplBlock`, resolves the target trait (the one being
116/// implemented) to a `ast::TraitDef`.
117fn resolve_target_trait_def(
118 db: &impl HirDatabase,
119 analyzer: &hir::SourceAnalyzer,
120 impl_block: &ast::ImplBlock,
121) -> Option<ast::TraitDef> {
122 let ast_path = impl_block
123 .target_trait()
124 .map(|it| it.syntax().clone())
125 .and_then(ast::PathType::cast)?
126 .path()?;
127
128 match analyzer.resolve_path(db, &ast_path) {
129 Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast),
130 _ => None,
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::helpers::{check_assist, check_assist_not_applicable};
138
139 #[test]
140 fn test_add_missing_impl_members() {
141 check_assist(
142 add_missing_impl_members,
143 "
144trait Foo {
145 type Output;
146
147 const CONST: usize = 42;
148
149 fn foo(&self);
150 fn bar(&self);
151 fn baz(&self);
152}
153
154struct S;
155
156impl Foo for S {
157 fn bar(&self) {}
158<|>
159}",
160 "
161trait Foo {
162 type Output;
163
164 const CONST: usize = 42;
165
166 fn foo(&self);
167 fn bar(&self);
168 fn baz(&self);
169}
170
171struct S;
172
173impl Foo for S {
174 fn bar(&self) {}
175 <|>type Output;
176 const CONST: usize = 42;
177 fn foo(&self) { unimplemented!() }
178 fn baz(&self) { unimplemented!() }
179
180}",
181 );
182 }
183
184 #[test]
185 fn test_copied_overriden_members() {
186 check_assist(
187 add_missing_impl_members,
188 "
189trait Foo {
190 fn foo(&self);
191 fn bar(&self) -> bool { true }
192 fn baz(&self) -> u32 { 42 }
193}
194
195struct S;
196
197impl Foo for S {
198 fn bar(&self) {}
199<|>
200}",
201 "
202trait Foo {
203 fn foo(&self);
204 fn bar(&self) -> bool { true }
205 fn baz(&self) -> u32 { 42 }
206}
207
208struct S;
209
210impl Foo for S {
211 fn bar(&self) {}
212 <|>fn foo(&self) { unimplemented!() }
213
214}",
215 );
216 }
217
218 #[test]
219 fn test_empty_impl_block() {
220 check_assist(
221 add_missing_impl_members,
222 "
223trait Foo { fn foo(&self); }
224struct S;
225impl Foo for S { <|> }",
226 "
227trait Foo { fn foo(&self); }
228struct S;
229impl Foo for S {
230 <|>fn foo(&self) { unimplemented!() }
231}",
232 );
233 }
234
235 #[test]
236 fn test_cursor_after_empty_impl_block() {
237 check_assist(
238 add_missing_impl_members,
239 "
240trait Foo { fn foo(&self); }
241struct S;
242impl Foo for S {}<|>",
243 "
244trait Foo { fn foo(&self); }
245struct S;
246impl Foo for S {
247 <|>fn foo(&self) { unimplemented!() }
248}",
249 )
250 }
251
252 #[test]
253 fn test_empty_trait() {
254 check_assist_not_applicable(
255 add_missing_impl_members,
256 "
257trait Foo;
258struct S;
259impl Foo for S { <|> }",
260 )
261 }
262
263 #[test]
264 fn test_ignore_unnamed_trait_members_and_default_methods() {
265 check_assist_not_applicable(
266 add_missing_impl_members,
267 "
268trait Foo {
269 fn (arg: u32);
270 fn valid(some: u32) -> bool { false }
271}
272struct S;
273impl Foo for S { <|> }",
274 )
275 }
276
277 #[test]
278 fn test_with_docstring_and_attrs() {
279 check_assist(
280 add_missing_impl_members,
281 r#"
282#[doc(alias = "test alias")]
283trait Foo {
284 /// doc string
285 type Output;
286
287 #[must_use]
288 fn foo(&self);
289}
290struct S;
291impl Foo for S {}<|>"#,
292 r#"
293#[doc(alias = "test alias")]
294trait Foo {
295 /// doc string
296 type Output;
297
298 #[must_use]
299 fn foo(&self);
300}
301struct S;
302impl Foo for S {
303 <|>type Output;
304 fn foo(&self) { unimplemented!() }
305}"#,
306 )
307 }
308
309 #[test]
310 fn test_default_methods() {
311 check_assist(
312 add_missing_default_members,
313 "
314trait Foo {
315 type Output;
316
317 const CONST: usize = 42;
318
319 fn valid(some: u32) -> bool { false }
320 fn foo(some: u32) -> bool;
321}
322struct S;
323impl Foo for S { <|> }",
324 "
325trait Foo {
326 type Output;
327
328 const CONST: usize = 42;
329
330 fn valid(some: u32) -> bool { false }
331 fn foo(some: u32) -> bool;
332}
333struct S;
334impl Foo for S {
335 <|>fn valid(some: u32) -> bool { false }
336}",
337 )
338 }
339
340}
diff --git a/crates/ra_assists/src/assists/auto_import.rs b/crates/ra_assists/src/assists/auto_import.rs
new file mode 100644
index 000000000..5aae98546
--- /dev/null
+++ b/crates/ra_assists/src/assists/auto_import.rs
@@ -0,0 +1,939 @@
1use hir::{self, db::HirDatabase};
2use ra_text_edit::TextEditBuilder;
3
4use crate::{
5 assist_ctx::{Assist, AssistCtx},
6 AssistId,
7};
8use ra_syntax::{
9 ast::{self, NameOwner},
10 AstNode, Direction, SmolStr,
11 SyntaxKind::{PATH, PATH_SEGMENT},
12 SyntaxNode, TextRange, T,
13};
14
15fn collect_path_segments_raw(
16 segments: &mut Vec<ast::PathSegment>,
17 mut path: ast::Path,
18) -> Option<usize> {
19 let oldlen = segments.len();
20 loop {
21 let mut children = path.syntax().children_with_tokens();
22 let (first, second, third) = (
23 children.next().map(|n| (n.clone(), n.kind())),
24 children.next().map(|n| (n.clone(), n.kind())),
25 children.next().map(|n| (n.clone(), n.kind())),
26 );
27 match (first, second, third) {
28 (Some((subpath, PATH)), Some((_, T![::])), Some((segment, PATH_SEGMENT))) => {
29 path = ast::Path::cast(subpath.as_node()?.clone())?;
30 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
31 }
32 (Some((segment, PATH_SEGMENT)), _, _) => {
33 segments.push(ast::PathSegment::cast(segment.as_node()?.clone())?);
34 break;
35 }
36 (_, _, _) => return None,
37 }
38 }
39 // We need to reverse only the new added segments
40 let only_new_segments = segments.split_at_mut(oldlen).1;
41 only_new_segments.reverse();
42 Some(segments.len() - oldlen)
43}
44
45fn fmt_segments(segments: &[SmolStr]) -> String {
46 let mut buf = String::new();
47 fmt_segments_raw(segments, &mut buf);
48 buf
49}
50
51fn fmt_segments_raw(segments: &[SmolStr], buf: &mut String) {
52 let mut iter = segments.iter();
53 if let Some(s) = iter.next() {
54 buf.push_str(s);
55 }
56 for s in iter {
57 buf.push_str("::");
58 buf.push_str(s);
59 }
60}
61
62// Returns the numeber of common segments.
63fn compare_path_segments(left: &[SmolStr], right: &[ast::PathSegment]) -> usize {
64 left.iter().zip(right).filter(|(l, r)| compare_path_segment(l, r)).count()
65}
66
67fn compare_path_segment(a: &SmolStr, b: &ast::PathSegment) -> bool {
68 if let Some(kb) = b.kind() {
69 match kb {
70 ast::PathSegmentKind::Name(nameref_b) => a == nameref_b.text(),
71 ast::PathSegmentKind::SelfKw => a == "self",
72 ast::PathSegmentKind::SuperKw => a == "super",
73 ast::PathSegmentKind::CrateKw => a == "crate",
74 ast::PathSegmentKind::Type { .. } => false, // not allowed in imports
75 }
76 } else {
77 false
78 }
79}
80
81fn compare_path_segment_with_name(a: &SmolStr, b: &ast::Name) -> bool {
82 a == b.text()
83}
84
85#[derive(Clone)]
86enum ImportAction {
87 Nothing,
88 // Add a brand new use statement.
89 AddNewUse {
90 anchor: Option<SyntaxNode>, // anchor node
91 add_after_anchor: bool,
92 },
93
94 // To split an existing use statement creating a nested import.
95 AddNestedImport {
96 // how may segments matched with the target path
97 common_segments: usize,
98 path_to_split: ast::Path,
99 // the first segment of path_to_split we want to add into the new nested list
100 first_segment_to_split: Option<ast::PathSegment>,
101 // Wether to add 'self' in addition to the target path
102 add_self: bool,
103 },
104 // To add the target path to an existing nested import tree list.
105 AddInTreeList {
106 common_segments: usize,
107 // The UseTreeList where to add the target path
108 tree_list: ast::UseTreeList,
109 add_self: bool,
110 },
111}
112
113impl ImportAction {
114 fn add_new_use(anchor: Option<SyntaxNode>, add_after_anchor: bool) -> Self {
115 ImportAction::AddNewUse { anchor, add_after_anchor }
116 }
117
118 fn add_nested_import(
119 common_segments: usize,
120 path_to_split: ast::Path,
121 first_segment_to_split: Option<ast::PathSegment>,
122 add_self: bool,
123 ) -> Self {
124 ImportAction::AddNestedImport {
125 common_segments,
126 path_to_split,
127 first_segment_to_split,
128 add_self,
129 }
130 }
131
132 fn add_in_tree_list(
133 common_segments: usize,
134 tree_list: ast::UseTreeList,
135 add_self: bool,
136 ) -> Self {
137 ImportAction::AddInTreeList { common_segments, tree_list, add_self }
138 }
139
140 fn better(left: ImportAction, right: ImportAction) -> ImportAction {
141 if left.is_better(&right) {
142 left
143 } else {
144 right
145 }
146 }
147
148 fn is_better(&self, other: &ImportAction) -> bool {
149 match (self, other) {
150 (ImportAction::Nothing, _) => true,
151 (ImportAction::AddInTreeList { .. }, ImportAction::Nothing) => false,
152 (
153 ImportAction::AddNestedImport { common_segments: n, .. },
154 ImportAction::AddInTreeList { common_segments: m, .. },
155 ) => n > m,
156 (
157 ImportAction::AddInTreeList { common_segments: n, .. },
158 ImportAction::AddNestedImport { common_segments: m, .. },
159 ) => n > m,
160 (ImportAction::AddInTreeList { .. }, _) => true,
161 (ImportAction::AddNestedImport { .. }, ImportAction::Nothing) => false,
162 (ImportAction::AddNestedImport { .. }, _) => true,
163 (ImportAction::AddNewUse { .. }, _) => false,
164 }
165 }
166}
167
168// Find out the best ImportAction to import target path against current_use_tree.
169// If current_use_tree has a nested import the function gets called recursively on every UseTree inside a UseTreeList.
170fn walk_use_tree_for_best_action(
171 current_path_segments: &mut Vec<ast::PathSegment>, // buffer containing path segments
172 current_parent_use_tree_list: Option<ast::UseTreeList>, // will be Some value if we are in a nested import
173 current_use_tree: ast::UseTree, // the use tree we are currently examinating
174 target: &[SmolStr], // the path we want to import
175) -> ImportAction {
176 // We save the number of segments in the buffer so we can restore the correct segments
177 // before returning. Recursive call will add segments so we need to delete them.
178 let prev_len = current_path_segments.len();
179
180 let tree_list = current_use_tree.use_tree_list();
181 let alias = current_use_tree.alias();
182
183 let path = match current_use_tree.path() {
184 Some(path) => path,
185 None => {
186 // If the use item don't have a path, it means it's broken (syntax error)
187 return ImportAction::add_new_use(
188 current_use_tree
189 .syntax()
190 .ancestors()
191 .find_map(ast::UseItem::cast)
192 .map(|it| it.syntax().clone()),
193 true,
194 );
195 }
196 };
197
198 // This can happen only if current_use_tree is a direct child of a UseItem
199 if let Some(name) = alias.and_then(|it| it.name()) {
200 if compare_path_segment_with_name(&target[0], &name) {
201 return ImportAction::Nothing;
202 }
203 }
204
205 collect_path_segments_raw(current_path_segments, path.clone());
206
207 // We compare only the new segments added in the line just above.
208 // The first prev_len segments were already compared in 'parent' recursive calls.
209 let left = target.split_at(prev_len).1;
210 let right = current_path_segments.split_at(prev_len).1;
211 let common = compare_path_segments(left, &right);
212 let mut action = match common {
213 0 => ImportAction::add_new_use(
214 // e.g: target is std::fmt and we can have
215 // use foo::bar
216 // We add a brand new use statement
217 current_use_tree
218 .syntax()
219 .ancestors()
220 .find_map(ast::UseItem::cast)
221 .map(|it| it.syntax().clone()),
222 true,
223 ),
224 common if common == left.len() && left.len() == right.len() => {
225 // e.g: target is std::fmt and we can have
226 // 1- use std::fmt;
227 // 2- use std::fmt:{ ... }
228 if let Some(list) = tree_list {
229 // In case 2 we need to add self to the nested list
230 // unless it's already there
231 let has_self = list.use_trees().map(|it| it.path()).any(|p| {
232 p.and_then(|it| it.segment())
233 .and_then(|it| it.kind())
234 .filter(|k| *k == ast::PathSegmentKind::SelfKw)
235 .is_some()
236 });
237
238 if has_self {
239 ImportAction::Nothing
240 } else {
241 ImportAction::add_in_tree_list(current_path_segments.len(), list, true)
242 }
243 } else {
244 // Case 1
245 ImportAction::Nothing
246 }
247 }
248 common if common != left.len() && left.len() == right.len() => {
249 // e.g: target is std::fmt and we have
250 // use std::io;
251 // We need to split.
252 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
253 ImportAction::add_nested_import(
254 prev_len + common,
255 path,
256 Some(segments_to_split[0].clone()),
257 false,
258 )
259 }
260 common if common == right.len() && left.len() > right.len() => {
261 // e.g: target is std::fmt and we can have
262 // 1- use std;
263 // 2- use std::{ ... };
264
265 // fallback action
266 let mut better_action = ImportAction::add_new_use(
267 current_use_tree
268 .syntax()
269 .ancestors()
270 .find_map(ast::UseItem::cast)
271 .map(|it| it.syntax().clone()),
272 true,
273 );
274 if let Some(list) = tree_list {
275 // Case 2, check recursively if the path is already imported in the nested list
276 for u in list.use_trees() {
277 let child_action = walk_use_tree_for_best_action(
278 current_path_segments,
279 Some(list.clone()),
280 u,
281 target,
282 );
283 if child_action.is_better(&better_action) {
284 better_action = child_action;
285 if let ImportAction::Nothing = better_action {
286 return better_action;
287 }
288 }
289 }
290 } else {
291 // Case 1, split adding self
292 better_action = ImportAction::add_nested_import(prev_len + common, path, None, true)
293 }
294 better_action
295 }
296 common if common == left.len() && left.len() < right.len() => {
297 // e.g: target is std::fmt and we can have
298 // use std::fmt::Debug;
299 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
300 ImportAction::add_nested_import(
301 prev_len + common,
302 path,
303 Some(segments_to_split[0].clone()),
304 true,
305 )
306 }
307 common if common < left.len() && common < right.len() => {
308 // e.g: target is std::fmt::nested::Debug
309 // use std::fmt::Display
310 let segments_to_split = current_path_segments.split_at(prev_len + common).1;
311 ImportAction::add_nested_import(
312 prev_len + common,
313 path,
314 Some(segments_to_split[0].clone()),
315 false,
316 )
317 }
318 _ => unreachable!(),
319 };
320
321 // If we are inside a UseTreeList adding a use statement become adding to the existing
322 // tree list.
323 action = match (current_parent_use_tree_list, action.clone()) {
324 (Some(use_tree_list), ImportAction::AddNewUse { .. }) => {
325 ImportAction::add_in_tree_list(prev_len, use_tree_list, false)
326 }
327 (_, _) => action,
328 };
329
330 // We remove the segments added
331 current_path_segments.truncate(prev_len);
332 action
333}
334
335fn best_action_for_target(
336 container: SyntaxNode,
337 anchor: SyntaxNode,
338 target: &[SmolStr],
339) -> ImportAction {
340 let mut storage = Vec::with_capacity(16); // this should be the only allocation
341 let best_action = container
342 .children()
343 .filter_map(ast::UseItem::cast)
344 .filter_map(|it| it.use_tree())
345 .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target))
346 .fold(None, |best, a| match best {
347 Some(best) => Some(ImportAction::better(best, a)),
348 None => Some(a),
349 });
350
351 match best_action {
352 Some(action) => action,
353 None => {
354 // We have no action and no UseItem was found in container so we find
355 // another item and we use it as anchor.
356 // If there are no items above, we choose the target path itself as anchor.
357 // todo: we should include even whitespace blocks as anchor candidates
358 let anchor = container
359 .children()
360 .find(|n| n.text_range().start() < anchor.text_range().start())
361 .or_else(|| Some(anchor));
362
363 ImportAction::add_new_use(anchor, false)
364 }
365 }
366}
367
368fn make_assist(action: &ImportAction, target: &[SmolStr], edit: &mut TextEditBuilder) {
369 match action {
370 ImportAction::AddNewUse { anchor, add_after_anchor } => {
371 make_assist_add_new_use(anchor, *add_after_anchor, target, edit)
372 }
373 ImportAction::AddInTreeList { common_segments, tree_list, add_self } => {
374 // We know that the fist n segments already exists in the use statement we want
375 // to modify, so we want to add only the last target.len() - n segments.
376 let segments_to_add = target.split_at(*common_segments).1;
377 make_assist_add_in_tree_list(tree_list, segments_to_add, *add_self, edit)
378 }
379 ImportAction::AddNestedImport {
380 common_segments,
381 path_to_split,
382 first_segment_to_split,
383 add_self,
384 } => {
385 let segments_to_add = target.split_at(*common_segments).1;
386 make_assist_add_nested_import(
387 path_to_split,
388 first_segment_to_split,
389 segments_to_add,
390 *add_self,
391 edit,
392 )
393 }
394 _ => {}
395 }
396}
397
398fn make_assist_add_new_use(
399 anchor: &Option<SyntaxNode>,
400 after: bool,
401 target: &[SmolStr],
402 edit: &mut TextEditBuilder,
403) {
404 if let Some(anchor) = anchor {
405 let indent = ra_fmt::leading_indent(anchor);
406 let mut buf = String::new();
407 if after {
408 buf.push_str("\n");
409 if let Some(spaces) = &indent {
410 buf.push_str(spaces);
411 }
412 }
413 buf.push_str("use ");
414 fmt_segments_raw(target, &mut buf);
415 buf.push_str(";");
416 if !after {
417 buf.push_str("\n\n");
418 if let Some(spaces) = &indent {
419 buf.push_str(&spaces);
420 }
421 }
422 let position = if after { anchor.text_range().end() } else { anchor.text_range().start() };
423 edit.insert(position, buf);
424 }
425}
426
427fn make_assist_add_in_tree_list(
428 tree_list: &ast::UseTreeList,
429 target: &[SmolStr],
430 add_self: bool,
431 edit: &mut TextEditBuilder,
432) {
433 let last = tree_list.use_trees().last();
434 if let Some(last) = last {
435 let mut buf = String::new();
436 let comma = last.syntax().siblings(Direction::Next).find(|n| n.kind() == T![,]);
437 let offset = if let Some(comma) = comma {
438 comma.text_range().end()
439 } else {
440 buf.push_str(",");
441 last.syntax().text_range().end()
442 };
443 if add_self {
444 buf.push_str(" self")
445 } else {
446 buf.push_str(" ");
447 }
448 fmt_segments_raw(target, &mut buf);
449 edit.insert(offset, buf);
450 } else {
451
452 }
453}
454
455fn make_assist_add_nested_import(
456 path: &ast::Path,
457 first_segment_to_split: &Option<ast::PathSegment>,
458 target: &[SmolStr],
459 add_self: bool,
460 edit: &mut TextEditBuilder,
461) {
462 let use_tree = path.syntax().ancestors().find_map(ast::UseTree::cast);
463 if let Some(use_tree) = use_tree {
464 let (start, add_colon_colon) = if let Some(first_segment_to_split) = first_segment_to_split
465 {
466 (first_segment_to_split.syntax().text_range().start(), false)
467 } else {
468 (use_tree.syntax().text_range().end(), true)
469 };
470 let end = use_tree.syntax().text_range().end();
471
472 let mut buf = String::new();
473 if add_colon_colon {
474 buf.push_str("::");
475 }
476 buf.push_str("{ ");
477 if add_self {
478 buf.push_str("self, ");
479 }
480 fmt_segments_raw(target, &mut buf);
481 if !target.is_empty() {
482 buf.push_str(", ");
483 }
484 edit.insert(start, buf);
485 edit.insert(end, "}".to_string());
486 }
487}
488
489fn apply_auto_import(
490 container: &SyntaxNode,
491 path: &ast::Path,
492 target: &[SmolStr],
493 edit: &mut TextEditBuilder,
494) {
495 let action = best_action_for_target(container.clone(), path.syntax().clone(), target);
496 make_assist(&action, target, edit);
497 if let Some(last) = path.segment() {
498 // Here we are assuming the assist will provide a correct use statement
499 // so we can delete the path qualifier
500 edit.delete(TextRange::from_to(
501 path.syntax().text_range().start(),
502 last.syntax().text_range().start(),
503 ));
504 }
505}
506
507pub fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
508 let mut ps = Vec::<SmolStr>::with_capacity(10);
509 match path.kind {
510 hir::PathKind::Abs => ps.push("".into()),
511 hir::PathKind::Crate => ps.push("crate".into()),
512 hir::PathKind::Plain => {}
513 hir::PathKind::Self_ => ps.push("self".into()),
514 hir::PathKind::Super => ps.push("super".into()),
515 hir::PathKind::Type(_) => return None,
516 }
517 for s in path.segments.iter() {
518 ps.push(s.name.to_string().into());
519 }
520 Some(ps)
521}
522
523// This function produces sequence of text edits into edit
524// to import the target path in the most appropriate scope given
525// the cursor position
526pub fn auto_import_text_edit(
527 // Ideally the position of the cursor, used to
528 position: &SyntaxNode,
529 // The statement to use as anchor (last resort)
530 anchor: &SyntaxNode,
531 // The path to import as a sequence of strings
532 target: &[SmolStr],
533 edit: &mut TextEditBuilder,
534) {
535 let container = position.ancestors().find_map(|n| {
536 if let Some(module) = ast::Module::cast(n.clone()) {
537 return module.item_list().map(|it| it.syntax().clone());
538 }
539 ast::SourceFile::cast(n).map(|it| it.syntax().clone())
540 });
541
542 if let Some(container) = container {
543 let action = best_action_for_target(container, anchor.clone(), target);
544 make_assist(&action, target, edit);
545 }
546}
547
548pub(crate) fn auto_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
549 let path: ast::Path = ctx.node_at_offset()?;
550 // We don't want to mess with use statements
551 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
552 return None;
553 }
554
555 let hir_path = hir::Path::from_ast(path.clone())?;
556 let segments = collect_hir_path_segments(&hir_path)?;
557 if segments.len() < 2 {
558 return None;
559 }
560
561 if let Some(module) = path.syntax().ancestors().find_map(ast::Module::cast) {
562 if let (Some(item_list), Some(name)) = (module.item_list(), module.name()) {
563 ctx.add_action(
564 AssistId("auto_import"),
565 format!("import {} in mod {}", fmt_segments(&segments), name.text()),
566 |edit| {
567 apply_auto_import(
568 item_list.syntax(),
569 &path,
570 &segments,
571 edit.text_edit_builder(),
572 );
573 },
574 );
575 }
576 } else {
577 let current_file = path.syntax().ancestors().find_map(ast::SourceFile::cast)?;
578 ctx.add_action(
579 AssistId("auto_import"),
580 format!("import {} in the current file", fmt_segments(&segments)),
581 |edit| {
582 apply_auto_import(
583 current_file.syntax(),
584 &path,
585 &segments,
586 edit.text_edit_builder(),
587 );
588 },
589 );
590 }
591
592 ctx.build()
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use crate::helpers::{check_assist, check_assist_not_applicable};
599
600 #[test]
601 fn test_auto_import_add_use_no_anchor() {
602 check_assist(
603 auto_import,
604 "
605std::fmt::Debug<|>
606 ",
607 "
608use std::fmt::Debug;
609
610Debug<|>
611 ",
612 );
613 }
614 #[test]
615 fn test_auto_import_add_use_no_anchor_with_item_below() {
616 check_assist(
617 auto_import,
618 "
619std::fmt::Debug<|>
620
621fn main() {
622}
623 ",
624 "
625use std::fmt::Debug;
626
627Debug<|>
628
629fn main() {
630}
631 ",
632 );
633 }
634
635 #[test]
636 fn test_auto_import_add_use_no_anchor_with_item_above() {
637 check_assist(
638 auto_import,
639 "
640fn main() {
641}
642
643std::fmt::Debug<|>
644 ",
645 "
646use std::fmt::Debug;
647
648fn main() {
649}
650
651Debug<|>
652 ",
653 );
654 }
655
656 #[test]
657 fn test_auto_import_add_use_no_anchor_2seg() {
658 check_assist(
659 auto_import,
660 "
661std::fmt<|>::Debug
662 ",
663 "
664use std::fmt;
665
666fmt<|>::Debug
667 ",
668 );
669 }
670
671 #[test]
672 fn test_auto_import_add_use() {
673 check_assist(
674 auto_import,
675 "
676use stdx;
677
678impl std::fmt::Debug<|> for Foo {
679}
680 ",
681 "
682use stdx;
683use std::fmt::Debug;
684
685impl Debug<|> for Foo {
686}
687 ",
688 );
689 }
690
691 #[test]
692 fn test_auto_import_file_use_other_anchor() {
693 check_assist(
694 auto_import,
695 "
696impl std::fmt::Debug<|> for Foo {
697}
698 ",
699 "
700use std::fmt::Debug;
701
702impl Debug<|> for Foo {
703}
704 ",
705 );
706 }
707
708 #[test]
709 fn test_auto_import_add_use_other_anchor_indent() {
710 check_assist(
711 auto_import,
712 "
713 impl std::fmt::Debug<|> for Foo {
714 }
715 ",
716 "
717 use std::fmt::Debug;
718
719 impl Debug<|> for Foo {
720 }
721 ",
722 );
723 }
724
725 #[test]
726 fn test_auto_import_split_different() {
727 check_assist(
728 auto_import,
729 "
730use std::fmt;
731
732impl std::io<|> for Foo {
733}
734 ",
735 "
736use std::{ io, fmt};
737
738impl io<|> for Foo {
739}
740 ",
741 );
742 }
743
744 #[test]
745 fn test_auto_import_split_self_for_use() {
746 check_assist(
747 auto_import,
748 "
749use std::fmt;
750
751impl std::fmt::Debug<|> for Foo {
752}
753 ",
754 "
755use std::fmt::{ self, Debug, };
756
757impl Debug<|> for Foo {
758}
759 ",
760 );
761 }
762
763 #[test]
764 fn test_auto_import_split_self_for_target() {
765 check_assist(
766 auto_import,
767 "
768use std::fmt::Debug;
769
770impl std::fmt<|> for Foo {
771}
772 ",
773 "
774use std::fmt::{ self, Debug};
775
776impl fmt<|> for Foo {
777}
778 ",
779 );
780 }
781
782 #[test]
783 fn test_auto_import_add_to_nested_self_nested() {
784 check_assist(
785 auto_import,
786 "
787use std::fmt::{Debug, nested::{Display}};
788
789impl std::fmt::nested<|> for Foo {
790}
791",
792 "
793use std::fmt::{Debug, nested::{Display, self}};
794
795impl nested<|> for Foo {
796}
797",
798 );
799 }
800
801 #[test]
802 fn test_auto_import_add_to_nested_self_already_included() {
803 check_assist(
804 auto_import,
805 "
806use std::fmt::{Debug, nested::{self, Display}};
807
808impl std::fmt::nested<|> for Foo {
809}
810",
811 "
812use std::fmt::{Debug, nested::{self, Display}};
813
814impl nested<|> for Foo {
815}
816",
817 );
818 }
819
820 #[test]
821 fn test_auto_import_add_to_nested_nested() {
822 check_assist(
823 auto_import,
824 "
825use std::fmt::{Debug, nested::{Display}};
826
827impl std::fmt::nested::Debug<|> for Foo {
828}
829",
830 "
831use std::fmt::{Debug, nested::{Display, Debug}};
832
833impl Debug<|> for Foo {
834}
835",
836 );
837 }
838
839 #[test]
840 fn test_auto_import_split_common_target_longer() {
841 check_assist(
842 auto_import,
843 "
844use std::fmt::Debug;
845
846impl std::fmt::nested::Display<|> for Foo {
847}
848",
849 "
850use std::fmt::{ nested::Display, Debug};
851
852impl Display<|> for Foo {
853}
854",
855 );
856 }
857
858 #[test]
859 fn test_auto_import_split_common_use_longer() {
860 check_assist(
861 auto_import,
862 "
863use std::fmt::nested::Debug;
864
865impl std::fmt::Display<|> for Foo {
866}
867",
868 "
869use std::fmt::{ Display, nested::Debug};
870
871impl Display<|> for Foo {
872}
873",
874 );
875 }
876
877 #[test]
878 fn test_auto_import_alias() {
879 check_assist(
880 auto_import,
881 "
882use std::fmt as foo;
883
884impl foo::Debug<|> for Foo {
885}
886",
887 "
888use std::fmt as foo;
889
890impl Debug<|> for Foo {
891}
892",
893 );
894 }
895
896 #[test]
897 fn test_auto_import_not_applicable_one_segment() {
898 check_assist_not_applicable(
899 auto_import,
900 "
901impl foo<|> for Foo {
902}
903",
904 );
905 }
906
907 #[test]
908 fn test_auto_import_not_applicable_in_use() {
909 check_assist_not_applicable(
910 auto_import,
911 "
912use std::fmt<|>;
913",
914 );
915 }
916
917 #[test]
918 fn test_auto_import_add_use_no_anchor_in_mod_mod() {
919 check_assist(
920 auto_import,
921 "
922mod foo {
923 mod bar {
924 std::fmt::Debug<|>
925 }
926}
927 ",
928 "
929mod foo {
930 mod bar {
931 use std::fmt::Debug;
932
933 Debug<|>
934 }
935}
936 ",
937 );
938 }
939}
diff --git a/crates/ra_assists/src/assists/change_visibility.rs b/crates/ra_assists/src/assists/change_visibility.rs
new file mode 100644
index 000000000..60c74debc
--- /dev/null
+++ b/crates/ra_assists/src/assists/change_visibility.rs
@@ -0,0 +1,159 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, NameOwner, VisibilityOwner},
4 AstNode,
5 SyntaxKind::{
6 ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
7 WHITESPACE,
8 },
9 SyntaxNode, TextUnit, T,
10};
11
12use crate::{Assist, AssistCtx, AssistId};
13
14pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
15 if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() {
16 return change_vis(ctx, vis);
17 }
18 add_vis(ctx)
19}
20
21fn add_vis(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
22 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
23 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
24 _ => false,
25 });
26
27 let (offset, target) = if let Some(keyword) = item_keyword {
28 let parent = keyword.parent();
29 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
30 // Parent is not a definition, can't add visibility
31 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
32 return None;
33 }
34 // Already have visibility, do nothing
35 if parent.children().any(|child| child.kind() == VISIBILITY) {
36 return None;
37 }
38 (vis_offset(&parent), keyword.text_range())
39 } else {
40 let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?;
41 let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?;
42 if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some()
43 {
44 return None;
45 }
46 (vis_offset(field.syntax()), ident.text_range())
47 };
48
49 ctx.add_action(AssistId("change_visibility"), "make pub(crate)", |edit| {
50 edit.target(target);
51 edit.insert(offset, "pub(crate) ");
52 edit.set_cursor(offset);
53 });
54
55 ctx.build()
56}
57
58fn vis_offset(node: &SyntaxNode) -> TextUnit {
59 node.children_with_tokens()
60 .skip_while(|it| match it.kind() {
61 WHITESPACE | COMMENT | ATTR => true,
62 _ => false,
63 })
64 .next()
65 .map(|it| it.text_range().start())
66 .unwrap_or_else(|| node.text_range().start())
67}
68
69fn change_vis(mut ctx: AssistCtx<impl HirDatabase>, vis: ast::Visibility) -> Option<Assist> {
70 if vis.syntax().text() == "pub" {
71 ctx.add_action(AssistId("change_visibility"), "change to pub(crate)", |edit| {
72 edit.target(vis.syntax().text_range());
73 edit.replace(vis.syntax().text_range(), "pub(crate)");
74 edit.set_cursor(vis.syntax().text_range().start())
75 });
76
77 return ctx.build();
78 }
79 if vis.syntax().text() == "pub(crate)" {
80 ctx.add_action(AssistId("change_visibility"), "change to pub", |edit| {
81 edit.target(vis.syntax().text_range());
82 edit.replace(vis.syntax().text_range(), "pub");
83 edit.set_cursor(vis.syntax().text_range().start());
84 });
85
86 return ctx.build();
87 }
88 None
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::helpers::{check_assist, check_assist_target};
95
96 #[test]
97 fn change_visibility_adds_pub_crate_to_items() {
98 check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}");
99 check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}");
100 check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}");
101 check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}");
102 check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}");
103 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}");
104 check_assist(
105 change_visibility,
106 "unsafe f<|>n foo() {}",
107 "<|>pub(crate) unsafe fn foo() {}",
108 );
109 }
110
111 #[test]
112 fn change_visibility_works_with_struct_fields() {
113 check_assist(
114 change_visibility,
115 "struct S { <|>field: u32 }",
116 "struct S { <|>pub(crate) field: u32 }",
117 )
118 }
119
120 #[test]
121 fn change_visibility_pub_to_pub_crate() {
122 check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}")
123 }
124
125 #[test]
126 fn change_visibility_pub_crate_to_pub() {
127 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}")
128 }
129
130 #[test]
131 fn change_visibility_handles_comment_attrs() {
132 check_assist(
133 change_visibility,
134 "
135 /// docs
136
137 // comments
138
139 #[derive(Debug)]
140 <|>struct Foo;
141 ",
142 "
143 /// docs
144
145 // comments
146
147 #[derive(Debug)]
148 <|>pub(crate) struct Foo;
149 ",
150 )
151 }
152
153 #[test]
154 fn change_visibility_target() {
155 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
156 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
157 check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
158 }
159}
diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs
new file mode 100644
index 000000000..f59062bb9
--- /dev/null
+++ b/crates/ra_assists/src/assists/fill_match_arms.rs
@@ -0,0 +1,229 @@
1use std::iter;
2
3use hir::{db::HirDatabase, Adt, HasSource};
4use ra_syntax::ast::{self, AstNode, NameOwner};
5
6use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId};
7
8pub(crate) fn fill_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?;
10 let match_arm_list = match_expr.match_arm_list()?;
11
12 // We already have some match arms, so we don't provide any assists.
13 // Unless if there is only one trivial match arm possibly created
14 // by match postfix complete. Trivial match arm is the catch all arm.
15 let mut existing_arms = match_arm_list.arms();
16 if let Some(arm) = existing_arms.next() {
17 if !is_trivial(&arm) || existing_arms.next().is_some() {
18 return None;
19 }
20 };
21
22 let expr = match_expr.expr()?;
23 let enum_def = {
24 let file_id = ctx.frange.file_id;
25 let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, expr.syntax(), None);
26 resolve_enum_def(ctx.db, &analyzer, &expr)?
27 };
28 let variant_list = enum_def.variant_list()?;
29
30 ctx.add_action(AssistId("fill_match_arms"), "fill match arms", |edit| {
31 let variants = variant_list.variants();
32 let arms = variants.filter_map(build_pat).map(|pat| {
33 AstBuilder::<ast::MatchArm>::from_pieces(
34 iter::once(pat),
35 &AstBuilder::<ast::Expr>::unit(),
36 )
37 });
38 let new_arm_list = AstBuilder::<ast::MatchArmList>::from_arms(arms);
39
40 edit.target(match_expr.syntax().text_range());
41 edit.set_cursor(expr.syntax().text_range().start());
42 edit.replace_node_and_indent(match_arm_list.syntax(), new_arm_list.syntax().text());
43 });
44
45 ctx.build()
46}
47
48fn is_trivial(arm: &ast::MatchArm) -> bool {
49 arm.pats().any(|pat| match pat {
50 ast::Pat::PlaceholderPat(..) => true,
51 _ => false,
52 })
53}
54
55fn resolve_enum_def(
56 db: &impl HirDatabase,
57 analyzer: &hir::SourceAnalyzer,
58 expr: &ast::Expr,
59) -> Option<ast::EnumDef> {
60 let expr_ty = analyzer.type_of(db, &expr)?;
61
62 analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() {
63 Some((Adt::Enum(e), _)) => Some(e.source(db).ast),
64 _ => None,
65 })
66}
67
68fn build_pat(var: ast::EnumVariant) -> Option<ast::Pat> {
69 let path = &AstBuilder::<ast::Path>::from_pieces(var.parent_enum().name()?, var.name()?);
70
71 let pat: ast::Pat = match var.kind() {
72 ast::StructKind::Tuple(field_list) => {
73 let pats = iter::repeat(AstBuilder::<ast::PlaceholderPat>::placeholder().into())
74 .take(field_list.fields().count());
75 AstBuilder::<ast::TupleStructPat>::from_pieces(path, pats).into()
76 }
77 ast::StructKind::Named(field_list) => {
78 let pats = field_list
79 .fields()
80 .map(|f| AstBuilder::<ast::BindPat>::from_name(&f.name().unwrap()).into());
81 AstBuilder::<ast::RecordPat>::from_pieces(path, pats).into()
82 }
83 ast::StructKind::Unit => AstBuilder::<ast::PathPat>::from_path(path).into(),
84 };
85
86 Some(pat)
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::helpers::{check_assist, check_assist_target};
92
93 use super::fill_match_arms;
94
95 #[test]
96 fn fill_match_arms_empty_body() {
97 check_assist(
98 fill_match_arms,
99 r#"
100 enum A {
101 As,
102 Bs,
103 Cs(String),
104 Ds(String, String),
105 Es{ x: usize, y: usize }
106 }
107
108 fn main() {
109 let a = A::As;
110 match a<|> {}
111 }
112 "#,
113 r#"
114 enum A {
115 As,
116 Bs,
117 Cs(String),
118 Ds(String, String),
119 Es{ x: usize, y: usize }
120 }
121
122 fn main() {
123 let a = A::As;
124 match <|>a {
125 A::As => (),
126 A::Bs => (),
127 A::Cs(_) => (),
128 A::Ds(_, _) => (),
129 A::Es{ x, y } => (),
130 }
131 }
132 "#,
133 );
134 }
135
136 #[test]
137 fn test_fill_match_arm_refs() {
138 check_assist(
139 fill_match_arms,
140 r#"
141 enum A {
142 As,
143 }
144
145 fn foo(a: &A) {
146 match a<|> {
147 }
148 }
149 "#,
150 r#"
151 enum A {
152 As,
153 }
154
155 fn foo(a: &A) {
156 match <|>a {
157 A::As => (),
158 }
159 }
160 "#,
161 );
162
163 check_assist(
164 fill_match_arms,
165 r#"
166 enum A {
167 Es{ x: usize, y: usize }
168 }
169
170 fn foo(a: &mut A) {
171 match a<|> {
172 }
173 }
174 "#,
175 r#"
176 enum A {
177 Es{ x: usize, y: usize }
178 }
179
180 fn foo(a: &mut A) {
181 match <|>a {
182 A::Es{ x, y } => (),
183 }
184 }
185 "#,
186 );
187 }
188
189 #[test]
190 fn fill_match_arms_target() {
191 check_assist_target(
192 fill_match_arms,
193 r#"
194 enum E { X, Y }
195
196 fn main() {
197 match E::X<|> {}
198 }
199 "#,
200 "match E::X {}",
201 );
202 }
203
204 #[test]
205 fn fill_match_arms_trivial_arm() {
206 check_assist(
207 fill_match_arms,
208 r#"
209 enum E { X, Y }
210
211 fn main() {
212 match E::X {
213 <|>_ => {},
214 }
215 }
216 "#,
217 r#"
218 enum E { X, Y }
219
220 fn main() {
221 match <|>E::X {
222 E::X => (),
223 E::Y => (),
224 }
225 }
226 "#,
227 );
228 }
229}
diff --git a/crates/ra_assists/src/assists/flip_binexpr.rs b/crates/ra_assists/src/assists/flip_binexpr.rs
new file mode 100644
index 000000000..b55b36a8e
--- /dev/null
+++ b/crates/ra_assists/src/assists/flip_binexpr.rs
@@ -0,0 +1,141 @@
1use hir::db::HirDatabase;
2use ra_syntax::ast::{AstNode, BinExpr, BinOp};
3
4use crate::{Assist, AssistCtx, AssistId};
5
6/// Flip binary expression assist.
7pub(crate) fn flip_binexpr(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
8 let expr = ctx.node_at_offset::<BinExpr>()?;
9 let lhs = expr.lhs()?.syntax().clone();
10 let rhs = expr.rhs()?.syntax().clone();
11 let op_range = expr.op_token()?.text_range();
12 // The assist should be applied only if the cursor is on the operator
13 let cursor_in_range = ctx.frange.range.is_subrange(&op_range);
14 if !cursor_in_range {
15 return None;
16 }
17 let action: FlipAction = expr.op_kind()?.into();
18 // The assist should not be applied for certain operators
19 if let FlipAction::DontFlip = action {
20 return None;
21 }
22
23 ctx.add_action(AssistId("flip_binexpr"), "flip binary expression", |edit| {
24 edit.target(op_range);
25 if let FlipAction::FlipAndReplaceOp(new_op) = action {
26 edit.replace(op_range, new_op);
27 }
28 edit.replace(lhs.text_range(), rhs.text());
29 edit.replace(rhs.text_range(), lhs.text());
30 });
31
32 ctx.build()
33}
34
35enum FlipAction {
36 // Flip the expression
37 Flip,
38 // Flip the expression and replace the operator with this string
39 FlipAndReplaceOp(&'static str),
40 // Do not flip the expression
41 DontFlip,
42}
43
44impl From<BinOp> for FlipAction {
45 fn from(op_kind: BinOp) -> Self {
46 match op_kind {
47 BinOp::Assignment => FlipAction::DontFlip,
48 BinOp::AddAssign => FlipAction::DontFlip,
49 BinOp::DivAssign => FlipAction::DontFlip,
50 BinOp::MulAssign => FlipAction::DontFlip,
51 BinOp::RemAssign => FlipAction::DontFlip,
52 BinOp::ShrAssign => FlipAction::DontFlip,
53 BinOp::ShlAssign => FlipAction::DontFlip,
54 BinOp::SubAssign => FlipAction::DontFlip,
55 BinOp::BitOrAssign => FlipAction::DontFlip,
56 BinOp::BitAndAssign => FlipAction::DontFlip,
57 BinOp::BitXorAssign => FlipAction::DontFlip,
58 BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
59 BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
60 BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
61 BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
62 _ => FlipAction::Flip,
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
72
73 #[test]
74 fn flip_binexpr_target_is_the_op() {
75 check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==")
76 }
77
78 #[test]
79 fn flip_binexpr_not_applicable_for_assignment() {
80 check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }")
81 }
82
83 #[test]
84 fn flip_binexpr_works_for_eq() {
85 check_assist(
86 flip_binexpr,
87 "fn f() { let res = 1 ==<|> 2; }",
88 "fn f() { let res = 2 ==<|> 1; }",
89 )
90 }
91
92 #[test]
93 fn flip_binexpr_works_for_gt() {
94 check_assist(
95 flip_binexpr,
96 "fn f() { let res = 1 ><|> 2; }",
97 "fn f() { let res = 2 <<|> 1; }",
98 )
99 }
100
101 #[test]
102 fn flip_binexpr_works_for_lteq() {
103 check_assist(
104 flip_binexpr,
105 "fn f() { let res = 1 <=<|> 2; }",
106 "fn f() { let res = 2 >=<|> 1; }",
107 )
108 }
109
110 #[test]
111 fn flip_binexpr_works_for_complex_expr() {
112 check_assist(
113 flip_binexpr,
114 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
115 "fn f() { let res = (2 + 2) ==<|> (1 + 1); }",
116 )
117 }
118
119 #[test]
120 fn flip_binexpr_works_inside_match() {
121 check_assist(
122 flip_binexpr,
123 r#"
124 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
125 match other.downcast_ref::<Self>() {
126 None => false,
127 Some(it) => it ==<|> self,
128 }
129 }
130 "#,
131 r#"
132 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
133 match other.downcast_ref::<Self>() {
134 None => false,
135 Some(it) => self ==<|> it,
136 }
137 }
138 "#,
139 )
140 }
141}
diff --git a/crates/ra_assists/src/assists/flip_comma.rs b/crates/ra_assists/src/assists/flip_comma.rs
new file mode 100644
index 000000000..5ee7561bc
--- /dev/null
+++ b/crates/ra_assists/src/assists/flip_comma.rs
@@ -0,0 +1,68 @@
1use hir::db::HirDatabase;
2use ra_syntax::{algo::non_trivia_sibling, Direction, T};
3
4use crate::{Assist, AssistCtx, AssistId};
5
6pub(crate) fn flip_comma(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
7 let comma = ctx.token_at_offset().find(|leaf| leaf.kind() == T![,])?;
8 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
9 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
10
11 // Don't apply a "flip" in case of a last comma
12 // that typically comes before punctuation
13 if next.kind().is_punct() {
14 return None;
15 }
16
17 ctx.add_action(AssistId("flip_comma"), "flip comma", |edit| {
18 edit.target(comma.text_range());
19 edit.replace(prev.text_range(), next.to_string());
20 edit.replace(next.text_range(), prev.to_string());
21 });
22
23 ctx.build()
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29
30 use crate::helpers::{check_assist, check_assist_target};
31
32 #[test]
33 fn flip_comma_works_for_function_parameters() {
34 check_assist(
35 flip_comma,
36 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
37 "fn foo(y: Result<(), ()>,<|> x: i32) {}",
38 )
39 }
40
41 #[test]
42 fn flip_comma_target() {
43 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
44 }
45
46 #[test]
47 #[should_panic]
48 fn flip_comma_before_punct() {
49 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
50 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
51 // declaration body.
52 check_assist_target(
53 flip_comma,
54 "pub enum Test { \
55 A,<|> \
56 }",
57 ",",
58 );
59
60 check_assist_target(
61 flip_comma,
62 "pub struct Test { \
63 foo: usize,<|> \
64 }",
65 ",",
66 );
67 }
68}
diff --git a/crates/ra_assists/src/assists/inline_local_variable.rs b/crates/ra_assists/src/assists/inline_local_variable.rs
new file mode 100644
index 000000000..eedb29199
--- /dev/null
+++ b/crates/ra_assists/src/assists/inline_local_variable.rs
@@ -0,0 +1,636 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode, AstToken},
4 TextRange,
5};
6
7use crate::assist_ctx::AssistBuilder;
8use crate::{Assist, AssistCtx, AssistId};
9
10pub(crate) fn inline_local_varialbe(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let let_stmt = ctx.node_at_offset::<ast::LetStmt>()?;
12 let bind_pat = match let_stmt.pat()? {
13 ast::Pat::BindPat(pat) => pat,
14 _ => return None,
15 };
16 if bind_pat.is_mutable() {
17 return None;
18 }
19 let initializer_expr = let_stmt.initializer()?;
20 let delete_range = if let Some(whitespace) = let_stmt
21 .syntax()
22 .next_sibling_or_token()
23 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
24 {
25 TextRange::from_to(
26 let_stmt.syntax().text_range().start(),
27 whitespace.syntax().text_range().end(),
28 )
29 } else {
30 let_stmt.syntax().text_range()
31 };
32 let analyzer = hir::SourceAnalyzer::new(ctx.db, ctx.frange.file_id, bind_pat.syntax(), None);
33 let refs = analyzer.find_all_refs(&bind_pat);
34
35 let mut wrap_in_parens = vec![true; refs.len()];
36
37 for (i, desc) in refs.iter().enumerate() {
38 let usage_node = ctx
39 .covering_node_for_range(desc.range)
40 .ancestors()
41 .find_map(|node| ast::PathExpr::cast(node))?;
42 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
43 let usage_parent = match usage_parent_option {
44 Some(u) => u,
45 None => {
46 wrap_in_parens[i] = false;
47 continue;
48 }
49 };
50
51 wrap_in_parens[i] = match (&initializer_expr, usage_parent) {
52 (ast::Expr::CallExpr(_), _)
53 | (ast::Expr::IndexExpr(_), _)
54 | (ast::Expr::MethodCallExpr(_), _)
55 | (ast::Expr::FieldExpr(_), _)
56 | (ast::Expr::TryExpr(_), _)
57 | (ast::Expr::RefExpr(_), _)
58 | (ast::Expr::Literal(_), _)
59 | (ast::Expr::TupleExpr(_), _)
60 | (ast::Expr::ArrayExpr(_), _)
61 | (ast::Expr::ParenExpr(_), _)
62 | (ast::Expr::PathExpr(_), _)
63 | (ast::Expr::BlockExpr(_), _)
64 | (_, ast::Expr::CallExpr(_))
65 | (_, ast::Expr::TupleExpr(_))
66 | (_, ast::Expr::ArrayExpr(_))
67 | (_, ast::Expr::ParenExpr(_))
68 | (_, ast::Expr::ForExpr(_))
69 | (_, ast::Expr::WhileExpr(_))
70 | (_, ast::Expr::BreakExpr(_))
71 | (_, ast::Expr::ReturnExpr(_))
72 | (_, ast::Expr::MatchExpr(_)) => false,
73 _ => true,
74 };
75 }
76
77 let init_str = initializer_expr.syntax().text().to_string();
78 let init_in_paren = format!("({})", &init_str);
79
80 ctx.add_action(
81 AssistId("inline_local_variable"),
82 "inline local variable",
83 move |edit: &mut AssistBuilder| {
84 edit.delete(delete_range);
85 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
86 if should_wrap {
87 edit.replace(desc.range, init_in_paren.clone())
88 } else {
89 edit.replace(desc.range, init_str.clone())
90 }
91 }
92 edit.set_cursor(delete_range.start())
93 },
94 );
95
96 ctx.build()
97}
98
99#[cfg(test)]
100mod tests {
101 use crate::helpers::{check_assist, check_assist_not_applicable};
102
103 use super::*;
104
105 #[test]
106 fn test_inline_let_bind_literal_expr() {
107 check_assist(
108 inline_local_varialbe,
109 "
110fn bar(a: usize) {}
111fn foo() {
112 let a<|> = 1;
113 a + 1;
114 if a > 10 {
115 }
116
117 while a > 10 {
118
119 }
120 let b = a * 10;
121 bar(a);
122}",
123 "
124fn bar(a: usize) {}
125fn foo() {
126 <|>1 + 1;
127 if 1 > 10 {
128 }
129
130 while 1 > 10 {
131
132 }
133 let b = 1 * 10;
134 bar(1);
135}",
136 );
137 }
138
139 #[test]
140 fn test_inline_let_bind_bin_expr() {
141 check_assist(
142 inline_local_varialbe,
143 "
144fn bar(a: usize) {}
145fn foo() {
146 let a<|> = 1 + 1;
147 a + 1;
148 if a > 10 {
149 }
150
151 while a > 10 {
152
153 }
154 let b = a * 10;
155 bar(a);
156}",
157 "
158fn bar(a: usize) {}
159fn foo() {
160 <|>(1 + 1) + 1;
161 if (1 + 1) > 10 {
162 }
163
164 while (1 + 1) > 10 {
165
166 }
167 let b = (1 + 1) * 10;
168 bar(1 + 1);
169}",
170 );
171 }
172
173 #[test]
174 fn test_inline_let_bind_function_call_expr() {
175 check_assist(
176 inline_local_varialbe,
177 "
178fn bar(a: usize) {}
179fn foo() {
180 let a<|> = bar(1);
181 a + 1;
182 if a > 10 {
183 }
184
185 while a > 10 {
186
187 }
188 let b = a * 10;
189 bar(a);
190}",
191 "
192fn bar(a: usize) {}
193fn foo() {
194 <|>bar(1) + 1;
195 if bar(1) > 10 {
196 }
197
198 while bar(1) > 10 {
199
200 }
201 let b = bar(1) * 10;
202 bar(bar(1));
203}",
204 );
205 }
206
207 #[test]
208 fn test_inline_let_bind_cast_expr() {
209 check_assist(
210 inline_local_varialbe,
211 "
212fn bar(a: usize): usize { a }
213fn foo() {
214 let a<|> = bar(1) as u64;
215 a + 1;
216 if a > 10 {
217 }
218
219 while a > 10 {
220
221 }
222 let b = a * 10;
223 bar(a);
224}",
225 "
226fn bar(a: usize): usize { a }
227fn foo() {
228 <|>(bar(1) as u64) + 1;
229 if (bar(1) as u64) > 10 {
230 }
231
232 while (bar(1) as u64) > 10 {
233
234 }
235 let b = (bar(1) as u64) * 10;
236 bar(bar(1) as u64);
237}",
238 );
239 }
240
241 #[test]
242 fn test_inline_let_bind_block_expr() {
243 check_assist(
244 inline_local_varialbe,
245 "
246fn foo() {
247 let a<|> = { 10 + 1 };
248 a + 1;
249 if a > 10 {
250 }
251
252 while a > 10 {
253
254 }
255 let b = a * 10;
256 bar(a);
257}",
258 "
259fn foo() {
260 <|>{ 10 + 1 } + 1;
261 if { 10 + 1 } > 10 {
262 }
263
264 while { 10 + 1 } > 10 {
265
266 }
267 let b = { 10 + 1 } * 10;
268 bar({ 10 + 1 });
269}",
270 );
271 }
272
273 #[test]
274 fn test_inline_let_bind_paren_expr() {
275 check_assist(
276 inline_local_varialbe,
277 "
278fn foo() {
279 let a<|> = ( 10 + 1 );
280 a + 1;
281 if a > 10 {
282 }
283
284 while a > 10 {
285
286 }
287 let b = a * 10;
288 bar(a);
289}",
290 "
291fn foo() {
292 <|>( 10 + 1 ) + 1;
293 if ( 10 + 1 ) > 10 {
294 }
295
296 while ( 10 + 1 ) > 10 {
297
298 }
299 let b = ( 10 + 1 ) * 10;
300 bar(( 10 + 1 ));
301}",
302 );
303 }
304
305 #[test]
306 fn test_not_inline_mut_variable() {
307 check_assist_not_applicable(
308 inline_local_varialbe,
309 "
310fn foo() {
311 let mut a<|> = 1 + 1;
312 a + 1;
313}",
314 );
315 }
316
317 #[test]
318 fn test_call_expr() {
319 check_assist(
320 inline_local_varialbe,
321 "
322fn foo() {
323 let a<|> = bar(10 + 1);
324 let b = a * 10;
325 let c = a as usize;
326}",
327 "
328fn foo() {
329 <|>let b = bar(10 + 1) * 10;
330 let c = bar(10 + 1) as usize;
331}",
332 );
333 }
334
335 #[test]
336 fn test_index_expr() {
337 check_assist(
338 inline_local_varialbe,
339 "
340fn foo() {
341 let x = vec![1, 2, 3];
342 let a<|> = x[0];
343 let b = a * 10;
344 let c = a as usize;
345}",
346 "
347fn foo() {
348 let x = vec![1, 2, 3];
349 <|>let b = x[0] * 10;
350 let c = x[0] as usize;
351}",
352 );
353 }
354
355 #[test]
356 fn test_method_call_expr() {
357 check_assist(
358 inline_local_varialbe,
359 "
360fn foo() {
361 let bar = vec![1];
362 let a<|> = bar.len();
363 let b = a * 10;
364 let c = a as usize;
365}",
366 "
367fn foo() {
368 let bar = vec![1];
369 <|>let b = bar.len() * 10;
370 let c = bar.len() as usize;
371}",
372 );
373 }
374
375 #[test]
376 fn test_field_expr() {
377 check_assist(
378 inline_local_varialbe,
379 "
380struct Bar {
381 foo: usize
382}
383
384fn foo() {
385 let bar = Bar { foo: 1 };
386 let a<|> = bar.foo;
387 let b = a * 10;
388 let c = a as usize;
389}",
390 "
391struct Bar {
392 foo: usize
393}
394
395fn foo() {
396 let bar = Bar { foo: 1 };
397 <|>let b = bar.foo * 10;
398 let c = bar.foo as usize;
399}",
400 );
401 }
402
403 #[test]
404 fn test_try_expr() {
405 check_assist(
406 inline_local_varialbe,
407 "
408fn foo() -> Option<usize> {
409 let bar = Some(1);
410 let a<|> = bar?;
411 let b = a * 10;
412 let c = a as usize;
413 None
414}",
415 "
416fn foo() -> Option<usize> {
417 let bar = Some(1);
418 <|>let b = bar? * 10;
419 let c = bar? as usize;
420 None
421}",
422 );
423 }
424
425 #[test]
426 fn test_ref_expr() {
427 check_assist(
428 inline_local_varialbe,
429 "
430fn foo() {
431 let bar = 10;
432 let a<|> = &bar;
433 let b = a * 10;
434}",
435 "
436fn foo() {
437 let bar = 10;
438 <|>let b = &bar * 10;
439}",
440 );
441 }
442
443 #[test]
444 fn test_tuple_expr() {
445 check_assist(
446 inline_local_varialbe,
447 "
448fn foo() {
449 let a<|> = (10, 20);
450 let b = a[0];
451}",
452 "
453fn foo() {
454 <|>let b = (10, 20)[0];
455}",
456 );
457 }
458
459 #[test]
460 fn test_array_expr() {
461 check_assist(
462 inline_local_varialbe,
463 "
464fn foo() {
465 let a<|> = [1, 2, 3];
466 let b = a.len();
467}",
468 "
469fn foo() {
470 <|>let b = [1, 2, 3].len();
471}",
472 );
473 }
474
475 #[test]
476 fn test_paren() {
477 check_assist(
478 inline_local_varialbe,
479 "
480fn foo() {
481 let a<|> = (10 + 20);
482 let b = a * 10;
483 let c = a as usize;
484}",
485 "
486fn foo() {
487 <|>let b = (10 + 20) * 10;
488 let c = (10 + 20) as usize;
489}",
490 );
491 }
492
493 #[test]
494 fn test_path_expr() {
495 check_assist(
496 inline_local_varialbe,
497 "
498fn foo() {
499 let d = 10;
500 let a<|> = d;
501 let b = a * 10;
502 let c = a as usize;
503}",
504 "
505fn foo() {
506 let d = 10;
507 <|>let b = d * 10;
508 let c = d as usize;
509}",
510 );
511 }
512
513 #[test]
514 fn test_block_expr() {
515 check_assist(
516 inline_local_varialbe,
517 "
518fn foo() {
519 let a<|> = { 10 };
520 let b = a * 10;
521 let c = a as usize;
522}",
523 "
524fn foo() {
525 <|>let b = { 10 } * 10;
526 let c = { 10 } as usize;
527}",
528 );
529 }
530
531 #[test]
532 fn test_used_in_different_expr1() {
533 check_assist(
534 inline_local_varialbe,
535 "
536fn foo() {
537 let a<|> = 10 + 20;
538 let b = a * 10;
539 let c = (a, 20);
540 let d = [a, 10];
541 let e = (a);
542}",
543 "
544fn foo() {
545 <|>let b = (10 + 20) * 10;
546 let c = (10 + 20, 20);
547 let d = [10 + 20, 10];
548 let e = (10 + 20);
549}",
550 );
551 }
552
553 #[test]
554 fn test_used_in_for_expr() {
555 check_assist(
556 inline_local_varialbe,
557 "
558fn foo() {
559 let a<|> = vec![10, 20];
560 for i in a {}
561}",
562 "
563fn foo() {
564 <|>for i in vec![10, 20] {}
565}",
566 );
567 }
568
569 #[test]
570 fn test_used_in_while_expr() {
571 check_assist(
572 inline_local_varialbe,
573 "
574fn foo() {
575 let a<|> = 1 > 0;
576 while a {}
577}",
578 "
579fn foo() {
580 <|>while 1 > 0 {}
581}",
582 );
583 }
584
585 #[test]
586 fn test_used_in_break_expr() {
587 check_assist(
588 inline_local_varialbe,
589 "
590fn foo() {
591 let a<|> = 1 + 1;
592 loop {
593 break a;
594 }
595}",
596 "
597fn foo() {
598 <|>loop {
599 break 1 + 1;
600 }
601}",
602 );
603 }
604
605 #[test]
606 fn test_used_in_return_expr() {
607 check_assist(
608 inline_local_varialbe,
609 "
610fn foo() {
611 let a<|> = 1 > 0;
612 return a;
613}",
614 "
615fn foo() {
616 <|>return 1 > 0;
617}",
618 );
619 }
620
621 #[test]
622 fn test_used_in_match_expr() {
623 check_assist(
624 inline_local_varialbe,
625 "
626fn foo() {
627 let a<|> = 1 > 0;
628 match a {}
629}",
630 "
631fn foo() {
632 <|>match 1 > 0 {}
633}",
634 );
635 }
636}
diff --git a/crates/ra_assists/src/assists/introduce_variable.rs b/crates/ra_assists/src/assists/introduce_variable.rs
new file mode 100644
index 000000000..470ffe120
--- /dev/null
+++ b/crates/ra_assists/src/assists/introduce_variable.rs
@@ -0,0 +1,516 @@
1use format_buf::format;
2use hir::db::HirDatabase;
3use ra_syntax::{
4 ast::{self, AstNode},
5 SyntaxKind::{
6 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
7 WHITESPACE,
8 },
9 SyntaxNode, TextUnit,
10};
11use test_utils::tested_by;
12
13use crate::{Assist, AssistCtx, AssistId};
14
15pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
16 if ctx.frange.range.is_empty() {
17 return None;
18 }
19 let node = ctx.covering_element();
20 if node.kind() == COMMENT {
21 tested_by!(introduce_var_in_comment_is_not_applicable);
22 return None;
23 }
24 let expr = node.ancestors().find_map(valid_target_expr)?;
25 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?;
26 let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone();
27 if indent.kind() != WHITESPACE {
28 return None;
29 }
30 ctx.add_action(AssistId("introduce_variable"), "introduce variable", move |edit| {
31 let mut buf = String::new();
32
33 let cursor_offset = if wrap_in_block {
34 buf.push_str("{ let var_name = ");
35 TextUnit::of_str("{ let ")
36 } else {
37 buf.push_str("let var_name = ");
38 TextUnit::of_str("let ")
39 };
40 format!(buf, "{}", expr.syntax());
41 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
42 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
43 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
44 } else {
45 false
46 };
47 if is_full_stmt {
48 tested_by!(test_introduce_var_expr_stmt);
49 if !full_stmt.unwrap().has_semi() {
50 buf.push_str(";");
51 }
52 edit.replace(expr.syntax().text_range(), buf);
53 } else {
54 buf.push_str(";");
55
56 // We want to maintain the indent level,
57 // but we do not want to duplicate possible
58 // extra newlines in the indent block
59 let text = indent.text();
60 if text.starts_with('\n') {
61 buf.push_str("\n");
62 buf.push_str(text.trim_start_matches('\n'));
63 } else {
64 buf.push_str(text);
65 }
66
67 edit.target(expr.syntax().text_range());
68 edit.replace(expr.syntax().text_range(), "var_name".to_string());
69 edit.insert(anchor_stmt.text_range().start(), buf);
70 if wrap_in_block {
71 edit.insert(anchor_stmt.text_range().end(), " }");
72 }
73 }
74 edit.set_cursor(anchor_stmt.text_range().start() + cursor_offset);
75 });
76
77 ctx.build()
78}
79
80/// Check whether the node is a valid expression which can be extracted to a variable.
81/// In general that's true for any expression, but in some cases that would produce invalid code.
82fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
83 match node.kind() {
84 PATH_EXPR | LOOP_EXPR => None,
85 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
86 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
87 BLOCK_EXPR => {
88 ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
89 }
90 _ => ast::Expr::cast(node),
91 }
92}
93
94/// Returns the syntax node which will follow the freshly introduced var
95/// and a boolean indicating whether we have to wrap it within a { } block
96/// to produce correct code.
97/// It can be a statement, the last in a block expression or a wanna be block
98/// expression like a lambda or match arm.
99fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
100 expr.syntax().ancestors().find_map(|node| {
101 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
102 if expr.syntax() == &node {
103 tested_by!(test_introduce_var_last_expr);
104 return Some((node, false));
105 }
106 }
107
108 if let Some(parent) = node.parent() {
109 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
110 return Some((node, true));
111 }
112 }
113
114 if ast::Stmt::cast(node.clone()).is_some() {
115 return Some((node, false));
116 }
117
118 None
119 })
120}
121
122#[cfg(test)]
123mod tests {
124 use test_utils::covers;
125
126 use crate::helpers::{
127 check_assist_range, check_assist_range_not_applicable, check_assist_range_target,
128 };
129
130 use super::*;
131
132 #[test]
133 fn test_introduce_var_simple() {
134 check_assist_range(
135 introduce_variable,
136 "
137fn foo() {
138 foo(<|>1 + 1<|>);
139}",
140 "
141fn foo() {
142 let <|>var_name = 1 + 1;
143 foo(var_name);
144}",
145 );
146 }
147
148 #[test]
149 fn introduce_var_in_comment_is_not_applicable() {
150 covers!(introduce_var_in_comment_is_not_applicable);
151 check_assist_range_not_applicable(
152 introduce_variable,
153 "fn main() { 1 + /* <|>comment<|> */ 1; }",
154 );
155 }
156
157 #[test]
158 fn test_introduce_var_expr_stmt() {
159 covers!(test_introduce_var_expr_stmt);
160 check_assist_range(
161 introduce_variable,
162 "
163fn foo() {
164 <|>1 + 1<|>;
165}",
166 "
167fn foo() {
168 let <|>var_name = 1 + 1;
169}",
170 );
171 check_assist_range(
172 introduce_variable,
173 "
174fn foo() {
175 <|>{ let x = 0; x }<|>
176 something_else();
177}",
178 "
179fn foo() {
180 let <|>var_name = { let x = 0; x };
181 something_else();
182}",
183 );
184 }
185
186 #[test]
187 fn test_introduce_var_part_of_expr_stmt() {
188 check_assist_range(
189 introduce_variable,
190 "
191fn foo() {
192 <|>1<|> + 1;
193}",
194 "
195fn foo() {
196 let <|>var_name = 1;
197 var_name + 1;
198}",
199 );
200 }
201
202 #[test]
203 fn test_introduce_var_last_expr() {
204 covers!(test_introduce_var_last_expr);
205 check_assist_range(
206 introduce_variable,
207 "
208fn foo() {
209 bar(<|>1 + 1<|>)
210}",
211 "
212fn foo() {
213 let <|>var_name = 1 + 1;
214 bar(var_name)
215}",
216 );
217 check_assist_range(
218 introduce_variable,
219 "
220fn foo() {
221 <|>bar(1 + 1)<|>
222}",
223 "
224fn foo() {
225 let <|>var_name = bar(1 + 1);
226 var_name
227}",
228 )
229 }
230
231 #[test]
232 fn test_introduce_var_in_match_arm_no_block() {
233 check_assist_range(
234 introduce_variable,
235 "
236fn main() {
237 let x = true;
238 let tuple = match x {
239 true => (<|>2 + 2<|>, true)
240 _ => (0, false)
241 };
242}
243",
244 "
245fn main() {
246 let x = true;
247 let tuple = match x {
248 true => { let <|>var_name = 2 + 2; (var_name, true) }
249 _ => (0, false)
250 };
251}
252",
253 );
254 }
255
256 #[test]
257 fn test_introduce_var_in_match_arm_with_block() {
258 check_assist_range(
259 introduce_variable,
260 "
261fn main() {
262 let x = true;
263 let tuple = match x {
264 true => {
265 let y = 1;
266 (<|>2 + y<|>, true)
267 }
268 _ => (0, false)
269 };
270}
271",
272 "
273fn main() {
274 let x = true;
275 let tuple = match x {
276 true => {
277 let y = 1;
278 let <|>var_name = 2 + y;
279 (var_name, true)
280 }
281 _ => (0, false)
282 };
283}
284",
285 );
286 }
287
288 #[test]
289 fn test_introduce_var_in_closure_no_block() {
290 check_assist_range(
291 introduce_variable,
292 "
293fn main() {
294 let lambda = |x: u32| <|>x * 2<|>;
295}
296",
297 "
298fn main() {
299 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
300}
301",
302 );
303 }
304
305 #[test]
306 fn test_introduce_var_in_closure_with_block() {
307 check_assist_range(
308 introduce_variable,
309 "
310fn main() {
311 let lambda = |x: u32| { <|>x * 2<|> };
312}
313",
314 "
315fn main() {
316 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
317}
318",
319 );
320 }
321
322 #[test]
323 fn test_introduce_var_path_simple() {
324 check_assist_range(
325 introduce_variable,
326 "
327fn main() {
328 let o = <|>Some(true)<|>;
329}
330",
331 "
332fn main() {
333 let <|>var_name = Some(true);
334 let o = var_name;
335}
336",
337 );
338 }
339
340 #[test]
341 fn test_introduce_var_path_method() {
342 check_assist_range(
343 introduce_variable,
344 "
345fn main() {
346 let v = <|>bar.foo()<|>;
347}
348",
349 "
350fn main() {
351 let <|>var_name = bar.foo();
352 let v = var_name;
353}
354",
355 );
356 }
357
358 #[test]
359 fn test_introduce_var_return() {
360 check_assist_range(
361 introduce_variable,
362 "
363fn foo() -> u32 {
364 <|>return 2 + 2<|>;
365}
366",
367 "
368fn foo() -> u32 {
369 let <|>var_name = 2 + 2;
370 return var_name;
371}
372",
373 );
374 }
375
376 #[test]
377 fn test_introduce_var_does_not_add_extra_whitespace() {
378 check_assist_range(
379 introduce_variable,
380 "
381fn foo() -> u32 {
382
383
384 <|>return 2 + 2<|>;
385}
386",
387 "
388fn foo() -> u32 {
389
390
391 let <|>var_name = 2 + 2;
392 return var_name;
393}
394",
395 );
396
397 check_assist_range(
398 introduce_variable,
399 "
400fn foo() -> u32 {
401
402 <|>return 2 + 2<|>;
403}
404",
405 "
406fn foo() -> u32 {
407
408 let <|>var_name = 2 + 2;
409 return var_name;
410}
411",
412 );
413
414 check_assist_range(
415 introduce_variable,
416 "
417fn foo() -> u32 {
418 let foo = 1;
419
420 // bar
421
422
423 <|>return 2 + 2<|>;
424}
425",
426 "
427fn foo() -> u32 {
428 let foo = 1;
429
430 // bar
431
432
433 let <|>var_name = 2 + 2;
434 return var_name;
435}
436",
437 );
438 }
439
440 #[test]
441 fn test_introduce_var_break() {
442 check_assist_range(
443 introduce_variable,
444 "
445fn main() {
446 let result = loop {
447 <|>break 2 + 2<|>;
448 };
449}
450",
451 "
452fn main() {
453 let result = loop {
454 let <|>var_name = 2 + 2;
455 break var_name;
456 };
457}
458",
459 );
460 }
461
462 #[test]
463 fn test_introduce_var_for_cast() {
464 check_assist_range(
465 introduce_variable,
466 "
467fn main() {
468 let v = <|>0f32 as u32<|>;
469}
470",
471 "
472fn main() {
473 let <|>var_name = 0f32 as u32;
474 let v = var_name;
475}
476",
477 );
478 }
479
480 #[test]
481 fn test_introduce_var_for_return_not_applicable() {
482 check_assist_range_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } ");
483 }
484
485 #[test]
486 fn test_introduce_var_for_break_not_applicable() {
487 check_assist_range_not_applicable(
488 introduce_variable,
489 "fn main() { loop { <|>break<|>; }; }",
490 );
491 }
492
493 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
494 #[test]
495 fn introduce_var_target() {
496 check_assist_range_target(
497 introduce_variable,
498 "fn foo() -> u32 { <|>return 2 + 2<|>; }",
499 "2 + 2",
500 );
501
502 check_assist_range_target(
503 introduce_variable,
504 "
505fn main() {
506 let x = true;
507 let tuple = match x {
508 true => (<|>2 + 2<|>, true)
509 _ => (0, false)
510 };
511}
512",
513 "2 + 2",
514 );
515 }
516}
diff --git a/crates/ra_assists/src/assists/merge_match_arms.rs b/crates/ra_assists/src/assists/merge_match_arms.rs
new file mode 100644
index 000000000..3b6a99895
--- /dev/null
+++ b/crates/ra_assists/src/assists/merge_match_arms.rs
@@ -0,0 +1,188 @@
1use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
2use hir::db::HirDatabase;
3use ra_syntax::ast::{AstNode, MatchArm};
4
5pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
6 let current_arm = ctx.node_at_offset::<MatchArm>()?;
7
8 // We check if the following match arm matches this one. We could, but don't,
9 // compare to the previous match arm as well.
10 let next = current_arm.syntax().next_sibling();
11 let next_arm = MatchArm::cast(next?)?;
12
13 // Don't try to handle arms with guards for now - can add support for this later
14 if current_arm.guard().is_some() || next_arm.guard().is_some() {
15 return None;
16 }
17
18 let current_expr = current_arm.expr()?;
19 let next_expr = next_arm.expr()?;
20
21 // Check for match arm equality by comparing lengths and then string contents
22 if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() {
23 return None;
24 }
25 if current_expr.syntax().text() != next_expr.syntax().text() {
26 return None;
27 }
28
29 let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
30
31 ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| {
32 fn contains_placeholder(a: &MatchArm) -> bool {
33 a.pats().any(|x| match x {
34 ra_syntax::ast::Pat::PlaceholderPat(..) => true,
35 _ => false,
36 })
37 }
38
39 let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
40 "_".into()
41 } else {
42 let ps: Vec<String> = current_arm
43 .pats()
44 .map(|x| x.syntax().to_string())
45 .chain(next_arm.pats().map(|x| x.syntax().to_string()))
46 .collect();
47 ps.join(" | ")
48 };
49
50 let arm = format!("{} => {}", pats, current_expr.syntax().text());
51 let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
52
53 let start = current_arm.syntax().text_range().start();
54 let end = next_arm.syntax().text_range().end();
55
56 edit.target(current_arm.syntax().text_range());
57 edit.replace(TextRange::from_to(start, end), arm);
58 edit.set_cursor(start + offset);
59 });
60
61 ctx.build()
62}
63
64#[cfg(test)]
65mod tests {
66 use super::merge_match_arms;
67 use crate::helpers::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn merge_match_arms_single_patterns() {
71 check_assist(
72 merge_match_arms,
73 r#"
74 #[derive(Debug)]
75 enum X { A, B, C }
76
77 fn main() {
78 let x = X::A;
79 let y = match x {
80 X::A => { 1i32<|> }
81 X::B => { 1i32 }
82 X::C => { 2i32 }
83 }
84 }
85 "#,
86 r#"
87 #[derive(Debug)]
88 enum X { A, B, C }
89
90 fn main() {
91 let x = X::A;
92 let y = match x {
93 X::A | X::B => { 1i32<|> }
94 X::C => { 2i32 }
95 }
96 }
97 "#,
98 );
99 }
100
101 #[test]
102 fn merge_match_arms_multiple_patterns() {
103 check_assist(
104 merge_match_arms,
105 r#"
106 #[derive(Debug)]
107 enum X { A, B, C, D, E }
108
109 fn main() {
110 let x = X::A;
111 let y = match x {
112 X::A | X::B => {<|> 1i32 },
113 X::C | X::D => { 1i32 },
114 X::E => { 2i32 },
115 }
116 }
117 "#,
118 r#"
119 #[derive(Debug)]
120 enum X { A, B, C, D, E }
121
122 fn main() {
123 let x = X::A;
124 let y = match x {
125 X::A | X::B | X::C | X::D => {<|> 1i32 },
126 X::E => { 2i32 },
127 }
128 }
129 "#,
130 );
131 }
132
133 #[test]
134 fn merge_match_arms_placeholder_pattern() {
135 check_assist(
136 merge_match_arms,
137 r#"
138 #[derive(Debug)]
139 enum X { A, B, C, D, E }
140
141 fn main() {
142 let x = X::A;
143 let y = match x {
144 X::A => { 1i32 },
145 X::B => { 2i<|>32 },
146 _ => { 2i32 }
147 }
148 }
149 "#,
150 r#"
151 #[derive(Debug)]
152 enum X { A, B, C, D, E }
153
154 fn main() {
155 let x = X::A;
156 let y = match x {
157 X::A => { 1i32 },
158 _ => { 2i<|>32 }
159 }
160 }
161 "#,
162 );
163 }
164
165 #[test]
166 fn merge_match_arms_rejects_guards() {
167 check_assist_not_applicable(
168 merge_match_arms,
169 r#"
170 #[derive(Debug)]
171 enum X {
172 A(i32),
173 B,
174 C
175 }
176
177 fn main() {
178 let x = X::A;
179 let y = match x {
180 X::A(a) if a > 5 => { <|>1i32 },
181 X::B => { 1i32 },
182 X::C => { 2i32 }
183 }
184 }
185 "#,
186 );
187 }
188}
diff --git a/crates/ra_assists/src/assists/move_bounds.rs b/crates/ra_assists/src/assists/move_bounds.rs
new file mode 100644
index 000000000..526de1d98
--- /dev/null
+++ b/crates/ra_assists/src/assists/move_bounds.rs
@@ -0,0 +1,135 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement,
5 SyntaxKind::*,
6 TextRange,
7};
8
9use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId};
10
11pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?;
13
14 let mut type_params = type_param_list.type_params();
15 if type_params.all(|p| p.type_bound_list().is_none()) {
16 return None;
17 }
18
19 let parent = type_param_list.syntax().parent()?;
20 if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() {
21 return None;
22 }
23
24 let anchor: SyntaxElement = match parent.kind() {
25 FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(),
26 TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(),
27 IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(),
28 ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(),
29 STRUCT_DEF => parent
30 .children_with_tokens()
31 .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?,
32 _ => return None,
33 };
34
35 ctx.add_action(
36 AssistId("move_bounds_to_where_clause"),
37 "move_bounds_to_where_clause",
38 |edit| {
39 let type_params = type_param_list.type_params().collect::<Vec<_>>();
40
41 for param in &type_params {
42 if let Some(bounds) = param.type_bound_list() {
43 let colon = param
44 .syntax()
45 .children_with_tokens()
46 .find(|it| it.kind() == COLON)
47 .unwrap();
48 let start = colon.text_range().start();
49 let end = bounds.syntax().text_range().end();
50 edit.delete(TextRange::from_to(start, end));
51 }
52 }
53
54 let predicates = type_params.iter().filter_map(build_predicate);
55 let where_clause = AstBuilder::<ast::WhereClause>::from_predicates(predicates);
56
57 let to_insert = match anchor.prev_sibling_or_token() {
58 Some(ref elem) if elem.kind() == WHITESPACE => {
59 format!("{} ", where_clause.syntax())
60 }
61 _ => format!(" {}", where_clause.syntax()),
62 };
63 edit.insert(anchor.text_range().start(), to_insert);
64 edit.target(type_param_list.syntax().text_range());
65 },
66 );
67
68 ctx.build()
69}
70
71fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> {
72 let path = AstBuilder::<ast::Path>::from_name(param.name()?);
73 let predicate =
74 AstBuilder::<ast::WherePred>::from_pieces(path, param.type_bound_list()?.bounds());
75 Some(predicate)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 use crate::helpers::check_assist;
83
84 #[test]
85 fn move_bounds_to_where_clause_fn() {
86 check_assist(
87 move_bounds_to_where_clause,
88 r#"
89 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
90 "#,
91 r#"
92 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {}
93 "#,
94 );
95 }
96
97 #[test]
98 fn move_bounds_to_where_clause_impl() {
99 check_assist(
100 move_bounds_to_where_clause,
101 r#"
102 impl<U: u32, <|>T> A<U, T> {}
103 "#,
104 r#"
105 impl<U, <|>T> A<U, T> where U: u32 {}
106 "#,
107 );
108 }
109
110 #[test]
111 fn move_bounds_to_where_clause_struct() {
112 check_assist(
113 move_bounds_to_where_clause,
114 r#"
115 struct A<<|>T: Iterator<Item = u32>> {}
116 "#,
117 r#"
118 struct A<<|>T> where T: Iterator<Item = u32> {}
119 "#,
120 );
121 }
122
123 #[test]
124 fn move_bounds_to_where_clause_tuple_struct() {
125 check_assist(
126 move_bounds_to_where_clause,
127 r#"
128 struct Pair<<|>T: u32>(T, T);
129 "#,
130 r#"
131 struct Pair<<|>T>(T, T) where T: u32;
132 "#,
133 );
134 }
135}
diff --git a/crates/ra_assists/src/assists/move_guard.rs b/crates/ra_assists/src/assists/move_guard.rs
new file mode 100644
index 000000000..699221e33
--- /dev/null
+++ b/crates/ra_assists/src/assists/move_guard.rs
@@ -0,0 +1,261 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast,
4 ast::{AstNode, AstToken, IfExpr, MatchArm},
5 TextUnit,
6};
7
8use crate::{Assist, AssistCtx, AssistId};
9
10pub(crate) fn move_guard_to_arm_body(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11 let match_arm = ctx.node_at_offset::<MatchArm>()?;
12 let guard = match_arm.guard()?;
13 let space_before_guard = guard.syntax().prev_sibling_or_token();
14
15 let guard_conditions = guard.expr()?;
16 let arm_expr = match_arm.expr()?;
17 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
18
19 ctx.add_action(AssistId("move_guard_to_arm_body"), "move guard to arm body", |edit| {
20 edit.target(guard.syntax().text_range());
21 let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) {
22 Some(tok) => {
23 if let Some(_) = ast::Whitespace::cast(tok.clone()) {
24 let ele = tok.text_range();
25 edit.delete(ele);
26 ele.len()
27 } else {
28 TextUnit::from(0)
29 }
30 }
31 _ => TextUnit::from(0),
32 };
33
34 edit.delete(guard.syntax().text_range());
35 edit.replace_node_and_indent(arm_expr.syntax(), buf);
36 edit.set_cursor(
37 arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount,
38 );
39 });
40 ctx.build()
41}
42
43pub(crate) fn move_arm_cond_to_match_guard(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
44 let match_arm: MatchArm = ctx.node_at_offset::<MatchArm>()?;
45 let last_match_pat = match_arm.pats().last()?;
46
47 let arm_body = match_arm.expr()?;
48 let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
49 let cond = if_expr.condition()?;
50 let then_block = if_expr.then_branch()?;
51
52 // Not support if with else branch
53 if let Some(_) = if_expr.else_branch() {
54 return None;
55 }
56 // Not support moving if let to arm guard
57 if let Some(_) = cond.pat() {
58 return None;
59 }
60
61 let buf = format!(" if {}", cond.syntax().text());
62
63 ctx.add_action(
64 AssistId("move_arm_cond_to_match_guard"),
65 "move condition to match guard",
66 |edit| {
67 edit.target(if_expr.syntax().text_range());
68 let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none();
69
70 match &then_block.block().and_then(|it| it.expr()) {
71 Some(then_expr) if then_only_expr => {
72 edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
73 }
74 _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
75 }
76
77 edit.insert(last_match_pat.syntax().text_range().end(), buf);
78 edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1));
79 },
80 );
81 ctx.build()
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
89
90 #[test]
91 fn move_guard_to_arm_body_target() {
92 check_assist_target(
93 move_guard_to_arm_body,
94 r#"
95 fn f() {
96 let t = 'a';
97 let chars = "abcd";
98 match t {
99 '\r' <|>if chars.clone().next() == Some('\n') => false,
100 _ => true
101 }
102 }
103 "#,
104 r#"if chars.clone().next() == Some('\n')"#,
105 );
106 }
107
108 #[test]
109 fn move_guard_to_arm_body_works() {
110 check_assist(
111 move_guard_to_arm_body,
112 r#"
113 fn f() {
114 let t = 'a';
115 let chars = "abcd";
116 match t {
117 '\r' <|>if chars.clone().next() == Some('\n') => false,
118 _ => true
119 }
120 }
121 "#,
122 r#"
123 fn f() {
124 let t = 'a';
125 let chars = "abcd";
126 match t {
127 '\r' => if chars.clone().next() == Some('\n') { <|>false },
128 _ => true
129 }
130 }
131 "#,
132 );
133 }
134
135 #[test]
136 fn move_guard_to_arm_body_works_complex_match() {
137 check_assist(
138 move_guard_to_arm_body,
139 r#"
140 fn f() {
141 match x {
142 <|>y @ 4 | y @ 5 if y > 5 => true,
143 _ => false
144 }
145 }
146 "#,
147 r#"
148 fn f() {
149 match x {
150 y @ 4 | y @ 5 => if y > 5 { <|>true },
151 _ => false
152 }
153 }
154 "#,
155 );
156 }
157
158 #[test]
159 fn move_arm_cond_to_match_guard_works() {
160 check_assist(
161 move_arm_cond_to_match_guard,
162 r#"
163 fn f() {
164 let t = 'a';
165 let chars = "abcd";
166 match t {
167 '\r' => if chars.clone().next() == Some('\n') { <|>false },
168 _ => true
169 }
170 }
171 "#,
172 r#"
173 fn f() {
174 let t = 'a';
175 let chars = "abcd";
176 match t {
177 '\r' <|>if chars.clone().next() == Some('\n') => false,
178 _ => true
179 }
180 }
181 "#,
182 );
183 }
184
185 #[test]
186 fn move_arm_cond_to_match_guard_if_let_not_works() {
187 check_assist_not_applicable(
188 move_arm_cond_to_match_guard,
189 r#"
190 fn f() {
191 let t = 'a';
192 let chars = "abcd";
193 match t {
194 '\r' => if let Some(_) = chars.clone().next() { <|>false },
195 _ => true
196 }
197 }
198 "#,
199 );
200 }
201
202 #[test]
203 fn move_arm_cond_to_match_guard_if_empty_body_works() {
204 check_assist(
205 move_arm_cond_to_match_guard,
206 r#"
207 fn f() {
208 let t = 'a';
209 let chars = "abcd";
210 match t {
211 '\r' => if chars.clone().next().is_some() { <|> },
212 _ => true
213 }
214 }
215 "#,
216 r#"
217 fn f() {
218 let t = 'a';
219 let chars = "abcd";
220 match t {
221 '\r' <|>if chars.clone().next().is_some() => { },
222 _ => true
223 }
224 }
225 "#,
226 );
227 }
228
229 #[test]
230 fn move_arm_cond_to_match_guard_if_multiline_body_works() {
231 check_assist(
232 move_arm_cond_to_match_guard,
233 r#"
234 fn f() {
235 let mut t = 'a';
236 let chars = "abcd";
237 match t {
238 '\r' => if chars.clone().next().is_some() {
239 t = 'e';<|>
240 false
241 },
242 _ => true
243 }
244 }
245 "#,
246 r#"
247 fn f() {
248 let mut t = 'a';
249 let chars = "abcd";
250 match t {
251 '\r' <|>if chars.clone().next().is_some() => {
252 t = 'e';
253 false
254 },
255 _ => true
256 }
257 }
258 "#,
259 );
260 }
261}
diff --git a/crates/ra_assists/src/assists/raw_string.rs b/crates/ra_assists/src/assists/raw_string.rs
new file mode 100644
index 000000000..965a64c98
--- /dev/null
+++ b/crates/ra_assists/src/assists/raw_string.rs
@@ -0,0 +1,370 @@
1use hir::db::HirDatabase;
2use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
3
4use crate::{Assist, AssistCtx, AssistId};
5
6pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
7 let literal = ctx.node_at_offset::<Literal>()?;
8 if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
9 return None;
10 }
11 ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
12 edit.target(literal.syntax().text_range());
13 edit.insert(literal.syntax().text_range().start(), "r");
14 });
15 ctx.build()
16}
17
18fn find_usual_string_range(s: &str) -> Option<TextRange> {
19 Some(TextRange::from_to(
20 TextUnit::from(s.find('"')? as u32),
21 TextUnit::from(s.rfind('"')? as u32),
22 ))
23}
24
25pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
26 let literal = ctx.node_at_offset::<Literal>()?;
27 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
28 return None;
29 }
30 let token = literal.token();
31 let text = token.text().as_str();
32 let usual_string_range = find_usual_string_range(text)?;
33 ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
34 edit.target(literal.syntax().text_range());
35 // parse inside string to escape `"`
36 let start_of_inside = usual_string_range.start().to_usize() + 1;
37 let end_of_inside = usual_string_range.end().to_usize();
38 let inside_str = &text[start_of_inside..end_of_inside];
39 let escaped = inside_str.escape_default().to_string();
40 edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));
41 });
42 ctx.build()
43}
44
45pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
46 let literal = ctx.node_at_offset::<Literal>()?;
47 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
48 return None;
49 }
50 ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
51 edit.target(literal.syntax().text_range());
52 edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
53 edit.insert(literal.syntax().text_range().end(), "#");
54 });
55 ctx.build()
56}
57
58pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
59 let literal = ctx.node_at_offset::<Literal>()?;
60 if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
61 return None;
62 }
63 let token = literal.token();
64 let text = token.text().as_str();
65 if text.starts_with("r\"") {
66 // no hash to remove
67 return None;
68 }
69 ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
70 edit.target(literal.syntax().text_range());
71 let result = &text[2..text.len() - 1];
72 let result = if result.starts_with("\"") {
73 // no more hash, escape
74 let internal_str = &result[1..result.len() - 1];
75 format!("\"{}\"", internal_str.escape_default().to_string())
76 } else {
77 result.to_owned()
78 };
79 edit.replace(literal.syntax().text_range(), format!("r{}", result));
80 });
81 ctx.build()
82}
83
84#[cfg(test)]
85mod test {
86 use super::*;
87 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
88
89 #[test]
90 fn make_raw_string_target() {
91 check_assist_target(
92 make_raw_string,
93 r#"
94 fn f() {
95 let s = <|>"random string";
96 }
97 "#,
98 r#""random string""#,
99 );
100 }
101
102 #[test]
103 fn make_raw_string_works() {
104 check_assist(
105 make_raw_string,
106 r#"
107 fn f() {
108 let s = <|>"random string";
109 }
110 "#,
111 r#"
112 fn f() {
113 let s = <|>r"random string";
114 }
115 "#,
116 )
117 }
118
119 #[test]
120 fn make_raw_string_with_escaped_works() {
121 check_assist(
122 make_raw_string,
123 r#"
124 fn f() {
125 let s = <|>"random\nstring";
126 }
127 "#,
128 r#"
129 fn f() {
130 let s = <|>r"random\nstring";
131 }
132 "#,
133 )
134 }
135
136 #[test]
137 fn make_raw_string_not_works() {
138 check_assist_not_applicable(
139 make_raw_string,
140 r#"
141 fn f() {
142 let s = <|>r"random string";
143 }
144 "#,
145 );
146 }
147
148 #[test]
149 fn add_hash_target() {
150 check_assist_target(
151 add_hash,
152 r#"
153 fn f() {
154 let s = <|>r"random string";
155 }
156 "#,
157 r#"r"random string""#,
158 );
159 }
160
161 #[test]
162 fn add_hash_works() {
163 check_assist(
164 add_hash,
165 r#"
166 fn f() {
167 let s = <|>r"random string";
168 }
169 "#,
170 r##"
171 fn f() {
172 let s = <|>r#"random string"#;
173 }
174 "##,
175 )
176 }
177
178 #[test]
179 fn add_more_hash_works() {
180 check_assist(
181 add_hash,
182 r##"
183 fn f() {
184 let s = <|>r#"random"string"#;
185 }
186 "##,
187 r###"
188 fn f() {
189 let s = <|>r##"random"string"##;
190 }
191 "###,
192 )
193 }
194
195 #[test]
196 fn add_hash_not_works() {
197 check_assist_not_applicable(
198 add_hash,
199 r#"
200 fn f() {
201 let s = <|>"random string";
202 }
203 "#,
204 );
205 }
206
207 #[test]
208 fn remove_hash_target() {
209 check_assist_target(
210 remove_hash,
211 r##"
212 fn f() {
213 let s = <|>r#"random string"#;
214 }
215 "##,
216 r##"r#"random string"#"##,
217 );
218 }
219
220 #[test]
221 fn remove_hash_works() {
222 check_assist(
223 remove_hash,
224 r##"
225 fn f() {
226 let s = <|>r#"random string"#;
227 }
228 "##,
229 r#"
230 fn f() {
231 let s = <|>r"random string";
232 }
233 "#,
234 )
235 }
236
237 #[test]
238 fn remove_hash_with_quote_works() {
239 check_assist(
240 remove_hash,
241 r##"
242 fn f() {
243 let s = <|>r#"random"str"ing"#;
244 }
245 "##,
246 r#"
247 fn f() {
248 let s = <|>r"random\"str\"ing";
249 }
250 "#,
251 )
252 }
253
254 #[test]
255 fn remove_more_hash_works() {
256 check_assist(
257 remove_hash,
258 r###"
259 fn f() {
260 let s = <|>r##"random string"##;
261 }
262 "###,
263 r##"
264 fn f() {
265 let s = <|>r#"random string"#;
266 }
267 "##,
268 )
269 }
270
271 #[test]
272 fn remove_hash_not_works() {
273 check_assist_not_applicable(
274 remove_hash,
275 r#"
276 fn f() {
277 let s = <|>"random string";
278 }
279 "#,
280 );
281 }
282
283 #[test]
284 fn remove_hash_no_hash_not_works() {
285 check_assist_not_applicable(
286 remove_hash,
287 r#"
288 fn f() {
289 let s = <|>r"random string";
290 }
291 "#,
292 );
293 }
294
295 #[test]
296 fn make_usual_string_target() {
297 check_assist_target(
298 make_usual_string,
299 r##"
300 fn f() {
301 let s = <|>r#"random string"#;
302 }
303 "##,
304 r##"r#"random string"#"##,
305 );
306 }
307
308 #[test]
309 fn make_usual_string_works() {
310 check_assist(
311 make_usual_string,
312 r##"
313 fn f() {
314 let s = <|>r#"random string"#;
315 }
316 "##,
317 r#"
318 fn f() {
319 let s = <|>"random string";
320 }
321 "#,
322 )
323 }
324
325 #[test]
326 fn make_usual_string_with_quote_works() {
327 check_assist(
328 make_usual_string,
329 r##"
330 fn f() {
331 let s = <|>r#"random"str"ing"#;
332 }
333 "##,
334 r#"
335 fn f() {
336 let s = <|>"random\"str\"ing";
337 }
338 "#,
339 )
340 }
341
342 #[test]
343 fn make_usual_string_more_hash_works() {
344 check_assist(
345 make_usual_string,
346 r###"
347 fn f() {
348 let s = <|>r##"random string"##;
349 }
350 "###,
351 r##"
352 fn f() {
353 let s = <|>"random string";
354 }
355 "##,
356 )
357 }
358
359 #[test]
360 fn make_usual_string_not_works() {
361 check_assist_not_applicable(
362 make_usual_string,
363 r#"
364 fn f() {
365 let s = <|>"random string";
366 }
367 "#,
368 );
369 }
370}
diff --git a/crates/ra_assists/src/assists/remove_dbg.rs b/crates/ra_assists/src/assists/remove_dbg.rs
new file mode 100644
index 000000000..870133fda
--- /dev/null
+++ b/crates/ra_assists/src/assists/remove_dbg.rs
@@ -0,0 +1,137 @@
1use crate::{Assist, AssistCtx, AssistId};
2use hir::db::HirDatabase;
3use ra_syntax::{
4 ast::{self, AstNode},
5 TextUnit, T,
6};
7
8pub(crate) fn remove_dbg(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let macro_call = ctx.node_at_offset::<ast::MacroCall>()?;
10
11 if !is_valid_macrocall(&macro_call, "dbg")? {
12 return None;
13 }
14
15 let macro_range = macro_call.syntax().text_range();
16
17 // If the cursor is inside the macro call, we'll try to maintain the cursor
18 // position by subtracting the length of dbg!( from the start of the file
19 // range, otherwise we'll default to using the start of the macro call
20 let cursor_pos = {
21 let file_range = ctx.frange.range;
22
23 let offset_start = file_range
24 .start()
25 .checked_sub(macro_range.start())
26 .unwrap_or_else(|| TextUnit::from(0));
27
28 let dbg_size = TextUnit::of_str("dbg!(");
29
30 if offset_start > dbg_size {
31 file_range.start() - dbg_size
32 } else {
33 macro_range.start()
34 }
35 };
36
37 let macro_content = {
38 let macro_args = macro_call.token_tree()?.syntax().clone();
39
40 let text = macro_args.text();
41 let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')');
42 text.slice(without_parens).to_string()
43 };
44
45 ctx.add_action(AssistId("remove_dbg"), "remove dbg!()", |edit| {
46 edit.target(macro_call.syntax().text_range());
47 edit.replace(macro_range, macro_content);
48 edit.set_cursor(cursor_pos);
49 });
50
51 ctx.build()
52}
53
54/// Verifies that the given macro_call actually matches the given name
55/// and contains proper ending tokens
56fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
57 let path = macro_call.path()?;
58 let name_ref = path.segment()?.name_ref()?;
59
60 // Make sure it is actually a dbg-macro call, dbg followed by !
61 let excl = path.syntax().next_sibling_or_token()?;
62
63 if name_ref.text() != macro_name || excl.kind() != T![!] {
64 return None;
65 }
66
67 let node = macro_call.token_tree()?.syntax().clone();
68 let first_child = node.first_child_or_token()?;
69 let last_child = node.last_child_or_token()?;
70
71 match (first_child.kind(), last_child.kind()) {
72 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
73 _ => Some(false),
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
81
82 #[test]
83 fn test_remove_dbg() {
84 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1");
85
86 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)");
87
88 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1");
89
90 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1");
91
92 check_assist(
93 remove_dbg,
94 "
95fn foo(n: usize) {
96 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
97 // ...
98 }
99}
100",
101 "
102fn foo(n: usize) {
103 if let Some(_) = n.<|>checked_sub(4) {
104 // ...
105 }
106}
107",
108 );
109 }
110 #[test]
111 fn test_remove_dbg_with_brackets_and_braces() {
112 check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1");
113 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1");
114 }
115
116 #[test]
117 fn test_remove_dbg_not_applicable() {
118 check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
119 check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
120 check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
121 }
122
123 #[test]
124 fn remove_dbg_target() {
125 check_assist_target(
126 remove_dbg,
127 "
128fn foo(n: usize) {
129 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
130 // ...
131 }
132}
133",
134 "dbg!(n.checked_sub(4))",
135 );
136 }
137}
diff --git a/crates/ra_assists/src/assists/replace_if_let_with_match.rs b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
new file mode 100644
index 000000000..401835c57
--- /dev/null
+++ b/crates/ra_assists/src/assists/replace_if_let_with_match.rs
@@ -0,0 +1,102 @@
1use format_buf::format;
2use hir::db::HirDatabase;
3use ra_fmt::extract_trivial_expression;
4use ra_syntax::{ast, AstNode};
5
6use crate::{Assist, AssistCtx, AssistId};
7
8pub(crate) fn replace_if_let_with_match(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let if_expr: ast::IfExpr = ctx.node_at_offset()?;
10 let cond = if_expr.condition()?;
11 let pat = cond.pat()?;
12 let expr = cond.expr()?;
13 let then_block = if_expr.then_branch()?;
14 let else_block = match if_expr.else_branch()? {
15 ast::ElseBranch::Block(it) => it,
16 ast::ElseBranch::IfExpr(_) => return None,
17 };
18
19 ctx.add_action(AssistId("replace_if_let_with_match"), "replace with match", |edit| {
20 let match_expr = build_match_expr(expr, pat, then_block, else_block);
21 edit.target(if_expr.syntax().text_range());
22 edit.replace_node_and_indent(if_expr.syntax(), match_expr);
23 edit.set_cursor(if_expr.syntax().text_range().start())
24 });
25
26 ctx.build()
27}
28
29fn build_match_expr(
30 expr: ast::Expr,
31 pat1: ast::Pat,
32 arm1: ast::BlockExpr,
33 arm2: ast::BlockExpr,
34) -> String {
35 let mut buf = String::new();
36 format!(buf, "match {} {{\n", expr.syntax().text());
37 format!(buf, " {} => {}\n", pat1.syntax().text(), format_arm(&arm1));
38 format!(buf, " _ => {}\n", format_arm(&arm2));
39 buf.push_str("}");
40 buf
41}
42
43fn format_arm(block: &ast::BlockExpr) -> String {
44 match extract_trivial_expression(block) {
45 None => block.syntax().text().to_string(),
46 Some(e) => format!("{},", e.syntax().text()),
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::helpers::{check_assist, check_assist_target};
54
55 #[test]
56 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
57 check_assist(
58 replace_if_let_with_match,
59 "
60impl VariantData {
61 pub fn is_struct(&self) -> bool {
62 if <|>let VariantData::Struct(..) = *self {
63 true
64 } else {
65 false
66 }
67 }
68} ",
69 "
70impl VariantData {
71 pub fn is_struct(&self) -> bool {
72 <|>match *self {
73 VariantData::Struct(..) => true,
74 _ => false,
75 }
76 }
77} ",
78 )
79 }
80
81 #[test]
82 fn replace_if_let_with_match_target() {
83 check_assist_target(
84 replace_if_let_with_match,
85 "
86impl VariantData {
87 pub fn is_struct(&self) -> bool {
88 if <|>let VariantData::Struct(..) = *self {
89 true
90 } else {
91 false
92 }
93 }
94} ",
95 "if let VariantData::Struct(..) = *self {
96 true
97 } else {
98 false
99 }",
100 );
101 }
102}
diff --git a/crates/ra_assists/src/assists/split_import.rs b/crates/ra_assists/src/assists/split_import.rs
new file mode 100644
index 000000000..2c1edddb9
--- /dev/null
+++ b/crates/ra_assists/src/assists/split_import.rs
@@ -0,0 +1,61 @@
1use std::iter::successors;
2
3use hir::db::HirDatabase;
4use ra_syntax::{ast, AstNode, TextUnit, T};
5
6use crate::{Assist, AssistCtx, AssistId};
7
8pub(crate) fn split_import(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
9 let colon_colon = ctx.token_at_offset().find(|leaf| leaf.kind() == T![::])?;
10 let path = ast::Path::cast(colon_colon.parent())?;
11 let top_path = successors(Some(path), |it| it.parent_path()).last()?;
12
13 let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast);
14 if use_tree.is_none() {
15 return None;
16 }
17
18 let l_curly = colon_colon.text_range().end();
19 let r_curly = match top_path.syntax().parent().and_then(ast::UseTree::cast) {
20 Some(tree) => tree.syntax().text_range().end(),
21 None => top_path.syntax().text_range().end(),
22 };
23
24 ctx.add_action(AssistId("split_import"), "split import", |edit| {
25 edit.target(colon_colon.text_range());
26 edit.insert(l_curly, "{");
27 edit.insert(r_curly, "}");
28 edit.set_cursor(l_curly + TextUnit::of_str("{"));
29 });
30
31 ctx.build()
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37 use crate::helpers::{check_assist, check_assist_target};
38
39 #[test]
40 fn test_split_import() {
41 check_assist(
42 split_import,
43 "use crate::<|>db::RootDatabase;",
44 "use crate::{<|>db::RootDatabase};",
45 )
46 }
47
48 #[test]
49 fn split_import_works_with_trees() {
50 check_assist(
51 split_import,
52 "use algo:<|>:visitor::{Visitor, visit}",
53 "use algo::{<|>visitor::{Visitor, visit}}",
54 )
55 }
56
57 #[test]
58 fn split_import_target() {
59 check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::");
60 }
61}