aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs8
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs22
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs98
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs52
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs42
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs17
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs160
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs12
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs35
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs160
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs17
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs202
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs251
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs120
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs21
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs15
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs15
-rw-r--r--crates/ra_assists/src/handlers/generate_derive.rs (renamed from crates/ra_assists/src/handlers/add_derive.rs)67
-rw-r--r--crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs (renamed from crates/ra_assists/src/handlers/add_from_impl_for_enum.rs)28
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs (renamed from crates/ra_assists/src/handlers/add_function.rs)97
-rw-r--r--crates/ra_assists/src/handlers/generate_impl.rs109
-rw-r--r--crates/ra_assists/src/handlers/generate_new.rs (renamed from crates/ra_assists/src/handlers/add_new.rs)70
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs24
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs28
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs4
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs15
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs45
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs69
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs29
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs176
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs98
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs13
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs27
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs50
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs41
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs10
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs60
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs4
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs4
39 files changed, 1372 insertions, 943 deletions
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index fa70c8496..b67438b6b 100644
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ b/crates/ra_assists/src/handlers/add_custom_impl.rs
@@ -8,7 +8,7 @@ use stdx::SepBy;
8 8
9use crate::{ 9use crate::{
10 assist_context::{AssistContext, Assists}, 10 assist_context::{AssistContext, Assists},
11 AssistId, 11 AssistId, AssistKind,
12}; 12};
13 13
14// Assist: add_custom_impl 14// Assist: add_custom_impl
@@ -29,8 +29,8 @@ use crate::{
29// } 29// }
30// ``` 30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let input = ctx.find_node_at_offset::<ast::AttrInput>()?; 32 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let attr = input.syntax().parent().and_then(ast::Attr::cast)?; 33 let input = attr.token_tree()?;
34 34
35 let attr_name = attr 35 let attr_name = attr
36 .syntax() 36 .syntax()
@@ -52,7 +52,7 @@ pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name); 52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53 53
54 let target = attr.syntax().text_range(); 54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl"), label, target, |builder| { 55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input 56 let new_attr_input = input
57 .syntax() 57 .syntax()
58 .descendants_with_tokens() 58 .descendants_with_tokens()
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index 90b06a625..e69f0a89b 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -1,10 +1,10 @@
1use hir::HirDisplay; 1use hir::HirDisplay;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner, TypeAscriptionOwner}, 3 ast::{self, AstNode, LetStmt, NameOwner},
4 TextRange, 4 TextRange,
5}; 5};
6 6
7use crate::{AssistContext, AssistId, Assists}; 7use crate::{AssistContext, AssistId, AssistKind, Assists};
8 8
9// Assist: add_explicit_type 9// Assist: add_explicit_type
10// 10//
@@ -22,11 +22,11 @@ use crate::{AssistContext, AssistId, Assists};
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let stmt = ctx.find_node_at_offset::<LetStmt>()?; 25 let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(stmt.syntax()).module()?; 26 let module = ctx.sema.scope(let_stmt.syntax()).module()?;
27 let expr = stmt.initializer()?; 27 let expr = let_stmt.initializer()?;
28 // Must be a binding 28 // Must be a binding
29 let pat = match stmt.pat()? { 29 let pat = match let_stmt.pat()? {
30 ast::Pat::BindPat(bind_pat) => bind_pat, 30 ast::Pat::BindPat(bind_pat) => bind_pat,
31 _ => return None, 31 _ => return None,
32 }; 32 };
@@ -34,8 +34,8 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
34 // The binding must have a name 34 // The binding must have a name
35 let name = pat.name()?; 35 let name = pat.name()?;
36 let name_range = name.syntax().text_range(); 36 let name_range = name.syntax().text_range();
37 let stmt_range = stmt.syntax().text_range(); 37 let stmt_range = let_stmt.syntax().text_range();
38 let eq_range = stmt.eq_token()?.text_range(); 38 let eq_range = let_stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '=' 39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::new(stmt_range.start(), eq_range.start()); 40 let let_range = TextRange::new(stmt_range.start(), eq_range.start());
41 let cursor_in_range = let_range.contains_range(ctx.frange.range); 41 let cursor_in_range = let_range.contains_range(ctx.frange.range);
@@ -44,7 +44,7 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
44 } 44 }
45 // Assist not applicable if the type has already been specified 45 // Assist not applicable if the type has already been specified
46 // and it has no placeholders 46 // and it has no placeholders
47 let ascribed_ty = stmt.ascribed_type(); 47 let ascribed_ty = let_stmt.ty();
48 if let Some(ty) = &ascribed_ty { 48 if let Some(ty) = &ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() { 49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
50 return None; 50 return None;
@@ -57,9 +57,9 @@ pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Optio
57 return None; 57 return None;
58 } 58 }
59 59
60 let inferred_type = ty.display_source_code(ctx.db, module.into()).ok()?; 60 let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
61 acc.add( 61 acc.add(
62 AssistId("add_explicit_type"), 62 AssistId("add_explicit_type", AssistKind::RefactorRewrite),
63 format!("Insert explicit type `{}`", inferred_type), 63 format!("Insert explicit type `{}`", inferred_type),
64 pat_range, 64 pat_range,
65 |builder| match ascribed_ty { 65 |builder| match ascribed_ty {
diff --git a/crates/ra_assists/src/handlers/add_impl.rs b/crates/ra_assists/src/handlers/add_impl.rs
deleted file mode 100644
index eceba7d0a..000000000
--- a/crates/ra_assists/src/handlers/add_impl.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
2use stdx::{format_to, SepBy};
3
4use crate::{AssistContext, AssistId, Assists};
5
6// Assist: add_impl
7//
8// Adds a new inherent impl for a type.
9//
10// ```
11// struct Ctx<T: Clone> {
12// data: T,<|>
13// }
14// ```
15// ->
16// ```
17// struct Ctx<T: Clone> {
18// data: T,
19// }
20//
21// impl<T: Clone> Ctx<T> {
22// $0
23// }
24// ```
25pub(crate) fn add_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
27 let name = nominal.name()?;
28 let target = nominal.syntax().text_range();
29 acc.add(AssistId("add_impl"), format!("Implement {}", name.text().as_str()), target, |edit| {
30 let type_params = nominal.type_param_list();
31 let start_offset = nominal.syntax().text_range().end();
32 let mut buf = String::new();
33 buf.push_str("\n\nimpl");
34 if let Some(type_params) = &type_params {
35 format_to!(buf, "{}", type_params.syntax());
36 }
37 buf.push_str(" ");
38 buf.push_str(name.text().as_str());
39 if let Some(type_params) = type_params {
40 let lifetime_params = type_params
41 .lifetime_params()
42 .filter_map(|it| it.lifetime_token())
43 .map(|it| it.text().clone());
44 let type_params =
45 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
46
47 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
48 format_to!(buf, "<{}>", generic_params)
49 }
50 match ctx.config.snippet_cap {
51 Some(cap) => {
52 buf.push_str(" {\n $0\n}");
53 edit.insert_snippet(cap, start_offset, buf);
54 }
55 None => {
56 buf.push_str(" {\n}");
57 edit.insert(start_offset, buf);
58 }
59 }
60 })
61}
62
63#[cfg(test)]
64mod tests {
65 use crate::tests::{check_assist, check_assist_target};
66
67 use super::*;
68
69 #[test]
70 fn test_add_impl() {
71 check_assist(add_impl, "struct Foo {<|>}\n", "struct Foo {}\n\nimpl Foo {\n $0\n}\n");
72 check_assist(
73 add_impl,
74 "struct Foo<T: Clone> {<|>}",
75 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
76 );
77 check_assist(
78 add_impl,
79 "struct Foo<'a, T: Foo<'a>> {<|>}",
80 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
81 );
82 }
83
84 #[test]
85 fn add_impl_target() {
86 check_assist_target(
87 add_impl,
88 "
89struct SomeThingIrrelevant;
90/// Has a lifetime parameter
91struct Foo<'a, T: Foo<'a>> {<|>}
92struct EvenMoreIrrelevant;
93",
94 "/// Has a lifetime parameter
95struct Foo<'a, T: Foo<'a>> {}",
96 );
97 }
98}
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index abacd4065..95a750aee 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -12,7 +12,7 @@ use crate::{
12 assist_context::{AssistContext, Assists}, 12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, 13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor}, 14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, 15 AssistId, AssistKind,
16}; 16};
17 17
18#[derive(PartialEq)] 18#[derive(PartialEq)]
@@ -111,16 +111,17 @@ fn add_missing_impl_members_inner(
111 label: &'static str, 111 label: &'static str,
112) -> Option<()> { 112) -> Option<()> {
113 let _p = ra_prof::profile("add_missing_impl_members_inner"); 113 let _p = ra_prof::profile("add_missing_impl_members_inner");
114 let impl_def = ctx.find_node_at_offset::<ast::ImplDef>()?; 114 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115 let impl_item_list = impl_def.item_list()?; 115 let impl_item_list = impl_def.assoc_item_list()?;
116 116
117 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; 117 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
118 118
119 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> { 119 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
120 match item { 120 match item {
121 ast::AssocItem::FnDef(def) => def.name(), 121 ast::AssocItem::Fn(def) => def.name(),
122 ast::AssocItem::TypeAliasDef(def) => def.name(), 122 ast::AssocItem::TypeAlias(def) => def.name(),
123 ast::AssocItem::ConstDef(def) => def.name(), 123 ast::AssocItem::Const(def) => def.name(),
124 ast::AssocItem::MacroCall(_) => None,
124 } 125 }
125 .map(|it| it.text().clone()) 126 .map(|it| it.text().clone())
126 }; 127 };
@@ -128,13 +129,13 @@ fn add_missing_impl_members_inner(
128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) 129 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
129 .iter() 130 .iter()
130 .map(|i| match i { 131 .map(|i| match i {
131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 132 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), 133 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
133 hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), 134 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
134 }) 135 })
135 .filter(|t| def_name(&t).is_some()) 136 .filter(|t| def_name(&t).is_some())
136 .filter(|t| match t { 137 .filter(|t| match t {
137 ast::AssocItem::FnDef(def) => match mode { 138 ast::AssocItem::Fn(def) => match mode {
138 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), 139 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
139 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), 140 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
140 }, 141 },
@@ -147,7 +148,7 @@ fn add_missing_impl_members_inner(
147 } 148 }
148 149
149 let target = impl_def.syntax().text_range(); 150 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id), label, target, |builder| { 151 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count(); 152 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 153 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 154 let target_scope = ctx.sema.scope(impl_item_list.syntax());
@@ -157,7 +158,8 @@ fn add_missing_impl_members_inner(
157 .into_iter() 158 .into_iter()
158 .map(|it| ast_transform::apply(&*ast_transform, it)) 159 .map(|it| ast_transform::apply(&*ast_transform, it))
159 .map(|it| match it { 160 .map(|it| match it {
160 ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), 161 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
162 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
161 _ => it, 163 _ => it,
162 }) 164 })
163 .map(|it| edit::remove_attrs_and_docs(&it)); 165 .map(|it| edit::remove_attrs_and_docs(&it));
@@ -170,7 +172,7 @@ fn add_missing_impl_members_inner(
170 Some(cap) => { 172 Some(cap) => {
171 let mut cursor = Cursor::Before(first_new_item.syntax()); 173 let mut cursor = Cursor::Before(first_new_item.syntax());
172 let placeholder; 174 let placeholder;
173 if let ast::AssocItem::FnDef(func) = &first_new_item { 175 if let ast::AssocItem::Fn(func) = &first_new_item {
174 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) { 176 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
175 if m.syntax().text() == "todo!()" { 177 if m.syntax().text() == "todo!()" {
176 placeholder = m; 178 placeholder = m;
@@ -188,7 +190,7 @@ fn add_missing_impl_members_inner(
188 }) 190 })
189} 191}
190 192
191fn add_body(fn_def: ast::FnDef) -> ast::FnDef { 193fn add_body(fn_def: ast::Fn) -> ast::Fn {
192 if fn_def.body().is_some() { 194 if fn_def.body().is_some() {
193 return fn_def; 195 return fn_def;
194 } 196 }
@@ -684,4 +686,26 @@ impl Foo<T> for S<T> {
684}"#, 686}"#,
685 ) 687 )
686 } 688 }
689
690 #[test]
691 fn test_assoc_type_bounds_are_removed() {
692 check_assist(
693 add_missing_impl_members,
694 r#"
695trait Tr {
696 type Ty: Copy + 'static;
697}
698
699impl Tr for ()<|> {
700}"#,
701 r#"
702trait Tr {
703 type Ty: Copy + 'static;
704}
705
706impl Tr for () {
707 $0type Ty;
708}"#,
709 )
710 }
687} 711}
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
index 26acf81f2..0c565e89a 100644
--- a/crates/ra_assists/src/handlers/add_turbo_fish.rs
+++ b/crates/ra_assists/src/handlers/add_turbo_fish.rs
@@ -4,7 +4,7 @@ use test_utils::mark;
4 4
5use crate::{ 5use crate::{
6 assist_context::{AssistContext, Assists}, 6 assist_context::{AssistContext, Assists},
7 AssistId, 7 AssistId, AssistKind,
8}; 8};
9 9
10// Assist: add_turbo_fish 10// Assist: add_turbo_fish
@@ -25,7 +25,14 @@ use crate::{
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT)?; 28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| {
29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30 if arg_list.args().count() > 0 {
31 return None;
32 }
33 mark::hit!(add_turbo_fish_after_call);
34 arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
35 })?;
29 let next_token = ident.next_token()?; 36 let next_token = ident.next_token()?;
30 if next_token.kind() == T![::] { 37 if next_token.kind() == T![::] {
31 mark::hit!(add_turbo_fish_one_fish_is_enough); 38 mark::hit!(add_turbo_fish_one_fish_is_enough);
@@ -45,12 +52,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<(
45 mark::hit!(add_turbo_fish_non_generic); 52 mark::hit!(add_turbo_fish_non_generic);
46 return None; 53 return None;
47 } 54 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { 55 acc.add(
49 match ctx.config.snippet_cap { 56 AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
57 "Add `::<>`",
58 ident.text_range(),
59 |builder| match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), 60 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"), 61 None => builder.insert(ident.text_range().end(), "::<_>"),
52 } 62 },
53 }) 63 )
54} 64}
55 65
56#[cfg(test)] 66#[cfg(test)]
@@ -80,6 +90,26 @@ fn main() {
80 } 90 }
81 91
82 #[test] 92 #[test]
93 fn add_turbo_fish_after_call() {
94 mark::check!(add_turbo_fish_after_call);
95 check_assist(
96 add_turbo_fish,
97 r#"
98fn make<T>() -> T {}
99fn main() {
100 make()<|>;
101}
102"#,
103 r#"
104fn make<T>() -> T {}
105fn main() {
106 make::<${0:_}>();
107}
108"#,
109 );
110 }
111
112 #[test]
83 fn add_turbo_fish_method() { 113 fn add_turbo_fish_method() {
84 check_assist( 114 check_assist(
85 add_turbo_fish, 115 add_turbo_fish,
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
index 233e8fb8e..de701f8b8 100644
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ra_assists/src/handlers/apply_demorgan.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{self, AstNode}; 1use ra_syntax::ast::{self, AstNode};
2 2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, Assists}; 3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4 4
5// Assist: apply_demorgan 5// Assist: apply_demorgan
6// 6//
@@ -39,11 +39,16 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
39 let rhs_range = rhs.syntax().text_range(); 39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs); 40 let not_rhs = invert_boolean_expression(rhs);
41 41
42 acc.add(AssistId("apply_demorgan"), "Apply De Morgan's law", op_range, |edit| { 42 acc.add(
43 edit.replace(op_range, opposite_op); 43 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
44 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 44 "Apply De Morgan's law",
45 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 45 op_range,
46 }) 46 |edit| {
47 edit.replace(op_range, opposite_op);
48 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
49 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
50 },
51 )
47} 52}
48 53
49// Return the opposite text for a given logical operator, if it makes sense 54// Return the opposite text for a given logical operator, if it makes sense
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index d1cafa7d9..01e7b7a44 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -5,7 +5,7 @@ use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type, 6 Type,
7}; 7};
8use ra_ide_db::{imports_locator::ImportsLocator, RootDatabase}; 8use ra_ide_db::{imports_locator, RootDatabase};
9use ra_prof::profile; 9use ra_prof::profile;
10use ra_syntax::{ 10use ra_syntax::{
11 ast::{self, AstNode}, 11 ast::{self, AstNode},
@@ -13,7 +13,9 @@ use ra_syntax::{
13}; 13};
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15 15
16use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, GroupLabel}; 16use crate::{
17 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
18};
17 19
18// Assist: auto_import 20// Assist: auto_import
19// 21//
@@ -35,8 +37,8 @@ use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists, Group
35// # pub mod std { pub mod collections { pub struct HashMap { } } } 37// # pub mod std { pub mod collections { pub struct HashMap { } } }
36// ``` 38// ```
37pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let auto_import_assets = AutoImportAssets::new(&ctx)?; 40 let auto_import_assets = AutoImportAssets::new(ctx)?;
39 let proposed_imports = auto_import_assets.search_for_imports(ctx.db); 41 let proposed_imports = auto_import_assets.search_for_imports(ctx);
40 if proposed_imports.is_empty() { 42 if proposed_imports.is_empty() {
41 return None; 43 return None;
42 } 44 }
@@ -46,7 +48,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
46 for import in proposed_imports { 48 for import in proposed_imports {
47 acc.add_group( 49 acc.add_group(
48 &group, 50 &group,
49 AssistId("auto_import"), 51 AssistId("auto_import", AssistKind::QuickFix),
50 format!("Import `{}`", &import), 52 format!("Import `{}`", &import),
51 range, 53 range,
52 |builder| { 54 |builder| {
@@ -90,7 +92,7 @@ impl AutoImportAssets {
90 92
91 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> { 93 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
92 let syntax_under_caret = path_under_caret.syntax().to_owned(); 94 let syntax_under_caret = path_under_caret.syntax().to_owned();
93 if syntax_under_caret.ancestors().find_map(ast::UseItem::cast).is_some() { 95 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
94 return None; 96 return None;
95 } 97 }
96 98
@@ -127,11 +129,11 @@ impl AutoImportAssets {
127 GroupLabel(name) 129 GroupLabel(name)
128 } 130 }
129 131
130 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 132 fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
131 let _p = profile("auto_import::search_for_imports"); 133 let _p = profile("auto_import::search_for_imports");
134 let db = ctx.db();
132 let current_crate = self.module_with_name_to_import.krate(); 135 let current_crate = self.module_with_name_to_import.krate();
133 ImportsLocator::new(db, current_crate) 136 imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
134 .find_imports(&self.get_search_query())
135 .into_iter() 137 .into_iter()
136 .filter_map(|candidate| match &self.import_candidate { 138 .filter_map(|candidate| match &self.import_candidate {
137 ImportCandidate::TraitAssocItem(assoc_item_type, _) => { 139 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
@@ -811,6 +813,146 @@ fn main() {
811 } 813 }
812 814
813 #[test] 815 #[test]
816 fn trait_method_cross_crate() {
817 check_assist(
818 auto_import,
819 r"
820 //- /main.rs crate:main deps:dep
821 fn main() {
822 let test_struct = dep::test_mod::TestStruct {};
823 test_struct.test_meth<|>od()
824 }
825 //- /dep.rs crate:dep
826 pub mod test_mod {
827 pub trait TestTrait {
828 fn test_method(&self);
829 }
830 pub struct TestStruct {}
831 impl TestTrait for TestStruct {
832 fn test_method(&self) {}
833 }
834 }
835 ",
836 r"
837 use dep::test_mod::TestTrait;
838
839 fn main() {
840 let test_struct = dep::test_mod::TestStruct {};
841 test_struct.test_method()
842 }
843 ",
844 );
845 }
846
847 #[test]
848 fn assoc_fn_cross_crate() {
849 check_assist(
850 auto_import,
851 r"
852 //- /main.rs crate:main deps:dep
853 fn main() {
854 dep::test_mod::TestStruct::test_func<|>tion
855 }
856 //- /dep.rs crate:dep
857 pub mod test_mod {
858 pub trait TestTrait {
859 fn test_function();
860 }
861 pub struct TestStruct {}
862 impl TestTrait for TestStruct {
863 fn test_function() {}
864 }
865 }
866 ",
867 r"
868 use dep::test_mod::TestTrait;
869
870 fn main() {
871 dep::test_mod::TestStruct::test_function
872 }
873 ",
874 );
875 }
876
877 #[test]
878 fn assoc_const_cross_crate() {
879 check_assist(
880 auto_import,
881 r"
882 //- /main.rs crate:main deps:dep
883 fn main() {
884 dep::test_mod::TestStruct::CONST<|>
885 }
886 //- /dep.rs crate:dep
887 pub mod test_mod {
888 pub trait TestTrait {
889 const CONST: bool;
890 }
891 pub struct TestStruct {}
892 impl TestTrait for TestStruct {
893 const CONST: bool = true;
894 }
895 }
896 ",
897 r"
898 use dep::test_mod::TestTrait;
899
900 fn main() {
901 dep::test_mod::TestStruct::CONST
902 }
903 ",
904 );
905 }
906
907 #[test]
908 fn assoc_fn_as_method_cross_crate() {
909 check_assist_not_applicable(
910 auto_import,
911 r"
912 //- /main.rs crate:main deps:dep
913 fn main() {
914 let test_struct = dep::test_mod::TestStruct {};
915 test_struct.test_func<|>tion()
916 }
917 //- /dep.rs crate:dep
918 pub mod test_mod {
919 pub trait TestTrait {
920 fn test_function();
921 }
922 pub struct TestStruct {}
923 impl TestTrait for TestStruct {
924 fn test_function() {}
925 }
926 }
927 ",
928 );
929 }
930
931 #[test]
932 fn private_trait_cross_crate() {
933 check_assist_not_applicable(
934 auto_import,
935 r"
936 //- /main.rs crate:main deps:dep
937 fn main() {
938 let test_struct = dep::test_mod::TestStruct {};
939 test_struct.test_meth<|>od()
940 }
941 //- /dep.rs crate:dep
942 pub mod test_mod {
943 trait TestTrait {
944 fn test_method(&self);
945 }
946 pub struct TestStruct {}
947 impl TestTrait for TestStruct {
948 fn test_method(&self) {}
949 }
950 }
951 ",
952 );
953 }
954
955 #[test]
814 fn not_applicable_for_imported_trait_for_method() { 956 fn not_applicable_for_imported_trait_for_method() {
815 check_assist_not_applicable( 957 check_assist_not_applicable(
816 auto_import, 958 auto_import,
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
index 855baf187..167e162d8 100644
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 AstNode, SyntaxNode, 3 AstNode, SyntaxNode,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
7use test_utils::mark; 7use test_utils::mark;
8 8
9// Assist: change_return_type_to_result 9// Assist: change_return_type_to_result
@@ -20,9 +20,9 @@ use test_utils::mark;
20pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 20pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?; 21 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
22 // FIXME: extend to lambdas as well 22 // FIXME: extend to lambdas as well
23 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; 23 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
24 24
25 let type_ref = &ret_type.type_ref()?; 25 let type_ref = &ret_type.ty()?;
26 let ret_type_str = type_ref.syntax().text().to_string(); 26 let ret_type_str = type_ref.syntax().text().to_string();
27 let first_part_ret_type = ret_type_str.splitn(2, '<').next(); 27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type { 28 if let Some(ret_type_first_part) = first_part_ret_type {
@@ -35,8 +35,8 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
35 let block_expr = &fn_def.body()?; 35 let block_expr = &fn_def.body()?;
36 36
37 acc.add( 37 acc.add(
38 AssistId("change_return_type_to_result"), 38 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
39 "Change return type to Result", 39 "Wrap return type in Result",
40 type_ref.syntax().text_range(), 40 type_ref.syntax().text_range(),
41 |builder| { 41 |builder| {
42 let mut tail_return_expr_collector = TailReturnCollector::new(); 42 let mut tail_return_expr_collector = TailReturnCollector::new();
@@ -240,7 +240,7 @@ fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
240 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 240 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 241 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 242 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::RecordLit(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 243 Expr::RecordExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 244 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 245 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]), 246 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index 157c7b665..724daa93f 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -1,12 +1,12 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner}, 2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode, 3 AstNode,
4 SyntaxKind::{CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY}, 4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY},
5 T, 5 T,
6}; 6};
7use test_utils::mark; 7use test_utils::mark;
8 8
9use crate::{utils::vis_offset, AssistContext, AssistId, Assists}; 9use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
10 10
11// Assist: change_visibility 11// Assist: change_visibility
12// 12//
@@ -28,12 +28,15 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
28 28
29fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 29fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let item_keyword = ctx.token_at_offset().find(|leaf| { 30 let item_keyword = ctx.token_at_offset().find(|leaf| {
31 matches!(leaf.kind(), T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]) 31 matches!(
32 leaf.kind(),
33 T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
34 )
32 }); 35 });
33 36
34 let (offset, target) = if let Some(keyword) = item_keyword { 37 let (offset, target) = if let Some(keyword) = item_keyword {
35 let parent = keyword.parent(); 38 let parent = keyword.parent();
36 let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; 39 let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT];
37 // Parent is not a definition, can't add visibility 40 // Parent is not a definition, can't add visibility
38 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { 41 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
39 return None; 42 return None;
@@ -44,7 +47,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 } 47 }
45 (vis_offset(&parent), keyword.text_range()) 48 (vis_offset(&parent), keyword.text_range())
46 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() { 49 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
47 let field = field_name.syntax().ancestors().find_map(ast::RecordFieldDef::cast)?; 50 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
48 if field.name()? != field_name { 51 if field.name()? != field_name {
49 mark::hit!(change_visibility_field_false_positive); 52 mark::hit!(change_visibility_field_false_positive);
50 return None; 53 return None;
@@ -53,7 +56,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
53 return None; 56 return None;
54 } 57 }
55 (vis_offset(field.syntax()), field_name.syntax().text_range()) 58 (vis_offset(field.syntax()), field_name.syntax().text_range())
56 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleFieldDef>() { 59 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
57 if field.visibility().is_some() { 60 if field.visibility().is_some() {
58 return None; 61 return None;
59 } 62 }
@@ -62,16 +65,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
62 return None; 65 return None;
63 }; 66 };
64 67
65 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { 68 acc.add(
66 edit.insert(offset, "pub(crate) "); 69 AssistId("change_visibility", AssistKind::RefactorRewrite),
67 }) 70 "Change visibility to pub(crate)",
71 target,
72 |edit| {
73 edit.insert(offset, "pub(crate) ");
74 },
75 )
68} 76}
69 77
70fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { 78fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
71 if vis.syntax().text() == "pub" { 79 if vis.syntax().text() == "pub" {
72 let target = vis.syntax().text_range(); 80 let target = vis.syntax().text_range();
73 return acc.add( 81 return acc.add(
74 AssistId("change_visibility"), 82 AssistId("change_visibility", AssistKind::RefactorRewrite),
75 "Change Visibility to pub(crate)", 83 "Change Visibility to pub(crate)",
76 target, 84 target,
77 |edit| { 85 |edit| {
@@ -82,7 +90,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
82 if vis.syntax().text() == "pub(crate)" { 90 if vis.syntax().text() == "pub(crate)" {
83 let target = vis.syntax().text_range(); 91 let target = vis.syntax().text_range();
84 return acc.add( 92 return acc.add(
85 AssistId("change_visibility"), 93 AssistId("change_visibility", AssistKind::RefactorRewrite),
86 "Change visibility to pub", 94 "Change visibility to pub",
87 target, 95 target,
88 |edit| { 96 |edit| {
@@ -147,6 +155,11 @@ mod tests {
147 } 155 }
148 156
149 #[test] 157 #[test]
158 fn change_visibility_static() {
159 check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
160 }
161
162 #[test]
150 fn change_visibility_handles_comment_attrs() { 163 fn change_visibility_handles_comment_attrs() {
151 check_assist( 164 check_assist(
152 change_visibility, 165 change_visibility,
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index dfade7432..3650289fd 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -8,14 +8,14 @@ use ra_syntax::{
8 make, 8 make,
9 }, 9 },
10 AstNode, 10 AstNode,
11 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE}, 11 SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
12 SyntaxNode, 12 SyntaxNode,
13}; 13};
14 14
15use crate::{ 15use crate::{
16 assist_context::{AssistContext, Assists}, 16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression, 17 utils::invert_boolean_expression,
18 AssistId, 18 AssistId, AssistKind,
19}; 19};
20 20
21// Assist: convert_to_guarded_return 21// Assist: convert_to_guarded_return
@@ -88,7 +88,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
88 88
89 let early_expression: ast::Expr = match parent_container.kind() { 89 let early_expression: ast::Expr = match parent_container.kind() {
90 WHILE_EXPR | LOOP_EXPR => make::expr_continue(), 90 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
91 FN_DEF => make::expr_return(), 91 FN => make::expr_return(),
92 _ => return None, 92 _ => return None,
93 }; 93 };
94 94
@@ -99,86 +99,92 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?; 99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
100 100
101 let target = if_expr.syntax().text_range(); 101 let target = if_expr.syntax().text_range();
102 acc.add(AssistId("convert_to_guarded_return"), "Convert to guarded return", target, |edit| { 102 acc.add(
103 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 103 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
104 let new_block = match if_let_pat { 104 "Convert to guarded return",
105 None => { 105 target,
106 // If. 106 |edit| {
107 let new_expr = { 107 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
108 let then_branch = 108 let new_block = match if_let_pat {
109 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 109 None => {
110 let cond = invert_boolean_expression(cond_expr); 110 // If.
111 make::expr_if(make::condition(cond, None), then_branch).indent(if_indent_level) 111 let new_expr = {
112 }; 112 let then_branch =
113 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 } 114 let cond = invert_boolean_expression(cond_expr);
115 Some((path, bound_ident)) => { 115 make::expr_if(make::condition(cond, None), then_branch)
116 // If-let. 116 .indent(if_indent_level)
117 let match_expr = {
118 let happy_arm = {
119 let pat = make::tuple_struct_pat(
120 path,
121 once(make::bind_pat(make::name("it")).into()),
122 );
123 let expr = {
124 let name_ref = make::name_ref("it");
125 let segment = make::path_segment(name_ref);
126 let path = make::path_unqualified(segment);
127 make::expr_path(path)
128 };
129 make::match_arm(once(pat.into()), expr)
130 }; 117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
119 }
120 Some((path, bound_ident)) => {
121 // If-let.
122 let match_expr = {
123 let happy_arm = {
124 let pat = make::tuple_struct_pat(
125 path,
126 once(make::bind_pat(make::name("it")).into()),
127 );
128 let expr = {
129 let name_ref = make::name_ref("it");
130 let segment = make::path_segment(name_ref);
131 let path = make::path_unqualified(segment);
132 make::expr_path(path)
133 };
134 make::match_arm(once(pat.into()), expr)
135 };
131 136
132 let sad_arm = make::match_arm( 137 let sad_arm = make::match_arm(
133 // FIXME: would be cool to use `None` or `Err(_)` if appropriate 138 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
134 once(make::placeholder_pat().into()), 139 once(make::placeholder_pat().into()),
135 early_expression, 140 early_expression,
136 ); 141 );
137 142
138 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm])) 143 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
139 }; 144 };
140 145
141 let let_stmt = make::let_stmt( 146 let let_stmt = make::let_stmt(
142 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), 147 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
143 Some(match_expr), 148 Some(match_expr),
149 );
150 let let_stmt = let_stmt.indent(if_indent_level);
151 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
152 }
153 };
154 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
155
156 fn replace(
157 new_expr: &SyntaxNode,
158 then_block: &ast::BlockExpr,
159 parent_block: &ast::BlockExpr,
160 if_expr: &ast::IfExpr,
161 ) -> SyntaxNode {
162 let then_block_items = then_block.dedent(IndentLevel(1));
163 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
164 let end_of_then =
165 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
166 end_of_then.prev_sibling_or_token().unwrap()
167 } else {
168 end_of_then
169 };
170 let mut then_statements = new_expr.children_with_tokens().chain(
171 then_block_items
172 .syntax()
173 .children_with_tokens()
174 .skip(1)
175 .take_while(|i| *i != end_of_then),
144 ); 176 );
145 let let_stmt = let_stmt.indent(if_indent_level); 177 replace_children(
146 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr) 178 &parent_block.syntax(),
179 RangeInclusive::new(
180 if_expr.clone().syntax().clone().into(),
181 if_expr.syntax().clone().into(),
182 ),
183 &mut then_statements,
184 )
147 } 185 }
148 }; 186 },
149 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap()); 187 )
150
151 fn replace(
152 new_expr: &SyntaxNode,
153 then_block: &ast::BlockExpr,
154 parent_block: &ast::BlockExpr,
155 if_expr: &ast::IfExpr,
156 ) -> SyntaxNode {
157 let then_block_items = then_block.dedent(IndentLevel(1));
158 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
159 let end_of_then =
160 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
161 end_of_then.prev_sibling_or_token().unwrap()
162 } else {
163 end_of_then
164 };
165 let mut then_statements = new_expr.children_with_tokens().chain(
166 then_block_items
167 .syntax()
168 .children_with_tokens()
169 .skip(1)
170 .take_while(|i| *i != end_of_then),
171 );
172 replace_children(
173 &parent_block.syntax(),
174 RangeInclusive::new(
175 if_expr.clone().syntax().clone().into(),
176 if_expr.syntax().clone().into(),
177 ),
178 &mut then_statements,
179 )
180 }
181 })
182} 188}
183 189
184#[cfg(test)] 190#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
index 43b4584b4..ccec688ca 100644
--- a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -10,7 +10,8 @@ use ra_syntax::{
10use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
11 11
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists, 13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
14 AssistKind, Assists,
14}; 15};
15 16
16// Assist: extract_struct_from_enum_variant 17// Assist: extract_struct_from_enum_variant
@@ -30,30 +31,30 @@ pub(crate) fn extract_struct_from_enum_variant(
30 acc: &mut Assists, 31 acc: &mut Assists,
31 ctx: &AssistContext, 32 ctx: &AssistContext,
32) -> Option<()> { 33) -> Option<()> {
33 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; 34 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
34 let field_list = match variant.kind() { 35 let field_list = match variant.kind() {
35 ast::StructKind::Tuple(field_list) => field_list, 36 ast::StructKind::Tuple(field_list) => field_list,
36 _ => return None, 37 _ => return None,
37 }; 38 };
38 let variant_name = variant.name()?.to_string(); 39 let variant_name = variant.name()?.to_string();
39 let variant_hir = ctx.sema.to_def(&variant)?; 40 let variant_hir = ctx.sema.to_def(&variant)?;
40 if existing_struct_def(ctx.db, &variant_name, &variant_hir) { 41 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
41 return None; 42 return None;
42 } 43 }
43 let enum_ast = variant.parent_enum(); 44 let enum_ast = variant.parent_enum();
44 let visibility = enum_ast.visibility(); 45 let visibility = enum_ast.visibility();
45 let enum_hir = ctx.sema.to_def(&enum_ast)?; 46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
46 let variant_hir_name = variant_hir.name(ctx.db); 47 let variant_hir_name = variant_hir.name(ctx.db());
47 let enum_module_def = ModuleDef::from(enum_hir); 48 let enum_module_def = ModuleDef::from(enum_hir);
48 let current_module = enum_hir.module(ctx.db); 49 let current_module = enum_hir.module(ctx.db());
49 let target = variant.syntax().text_range(); 50 let target = variant.syntax().text_range();
50 acc.add( 51 acc.add(
51 AssistId("extract_struct_from_enum_variant"), 52 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
52 "Extract struct from enum variant", 53 "Extract struct from enum variant",
53 target, 54 target,
54 |builder| { 55 |builder| {
55 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); 56 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
56 let res = definition.find_usages(&ctx.db, None); 57 let res = definition.find_usages(&ctx.sema, None);
57 let start_offset = variant.parent_enum().syntax().text_range().start(); 58 let start_offset = variant.parent_enum().syntax().text_range().start();
58 let mut visited_modules_set = FxHashSet::default(); 59 let mut visited_modules_set = FxHashSet::default();
59 visited_modules_set.insert(current_module); 60 visited_modules_set.insert(current_module);
@@ -101,7 +102,7 @@ fn insert_import(
101 enum_module_def: &ModuleDef, 102 enum_module_def: &ModuleDef,
102 variant_hir_name: &Name, 103 variant_hir_name: &Name,
103) -> Option<()> { 104) -> Option<()> {
104 let db = ctx.db; 105 let db = ctx.db();
105 let mod_path = module.find_use_path(db, enum_module_def.clone()); 106 let mod_path = module.find_use_path(db, enum_module_def.clone());
106 if let Some(mut mod_path) = mod_path { 107 if let Some(mut mod_path) = mod_path {
107 mod_path.segments.pop(); 108 mod_path.segments.pop();
diff --git a/crates/ra_assists/src/handlers/extract_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
index c4150d2bb..b925a2884 100644
--- a/crates/ra_assists/src/handlers/extract_variable.rs
+++ b/crates/ra_assists/src/handlers/extract_variable.rs
@@ -2,14 +2,13 @@ use ra_syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 SyntaxKind::{ 3 SyntaxKind::{
4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, 4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
5 WHITESPACE,
6 }, 5 },
7 SyntaxNode, 6 SyntaxNode,
8}; 7};
9use stdx::format_to; 8use stdx::format_to;
10use test_utils::mark; 9use test_utils::mark;
11 10
12use crate::{AssistContext, AssistId, Assists}; 11use crate::{AssistContext, AssistId, AssistKind, Assists};
13 12
14// Assist: extract_variable 13// Assist: extract_variable
15// 14//
@@ -36,87 +35,84 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
36 mark::hit!(extract_var_in_comment_is_not_applicable); 35 mark::hit!(extract_var_in_comment_is_not_applicable);
37 return None; 36 return None;
38 } 37 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 38 let to_extract = node.ancestors().find_map(valid_target_expr)?;
40 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr.clone())?; 39 let anchor = Anchor::from(&to_extract)?;
41 let indent = anchor_stmt.prev_sibling_or_token()?.as_token()?.clone(); 40 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
42 if indent.kind() != WHITESPACE { 41 let target = to_extract.syntax().text_range();
43 return None; 42 acc.add(
44 } 43 AssistId("extract_variable", AssistKind::RefactorExtract),
45 let target = expr.syntax().text_range(); 44 "Extract into variable",
46 acc.add(AssistId("extract_variable"), "Extract into variable", target, move |edit| { 45 target,
47 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) { 46 move |edit| {
48 Some(field) => field.name_ref(), 47 let field_shorthand =
49 None => None, 48 match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
50 }; 49 Some(field) => field.name_ref(),
51 50 None => None,
52 let mut buf = String::new(); 51 };
53 52
54 let var_name = match &field_shorthand { 53 let mut buf = String::new();
55 Some(it) => it.to_string(), 54
56 None => "var_name".to_string(), 55 let var_name = match &field_shorthand {
57 }; 56 Some(it) => it.to_string(),
58 let expr_range = match &field_shorthand { 57 None => "var_name".to_string(),
59 Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()), 58 };
60 None => expr.syntax().text_range(), 59 let expr_range = match &field_shorthand {
61 }; 60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
62 61 None => to_extract.syntax().text_range(),
63 if wrap_in_block { 62 };
64 format_to!(buf, "{{ let {} = ", var_name); 63
65 } else { 64 if let Anchor::WrapInBlock(_) = anchor {
66 format_to!(buf, "let {} = ", var_name); 65 format_to!(buf, "{{ let {} = ", var_name);
67 }; 66 } else {
68 format_to!(buf, "{}", expr.syntax()); 67 format_to!(buf, "let {} = ", var_name);
69 68 };
70 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 69 format_to!(buf, "{}", to_extract.syntax());
71 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 70
72 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 71 if let Anchor::Replace(stmt) = anchor {
73 } else { 72 mark::hit!(test_extract_var_expr_stmt);
74 false 73 if stmt.semicolon_token().is_none() {
75 }; 74 buf.push_str(";");
76 if is_full_stmt { 75 }
77 mark::hit!(test_extract_var_expr_stmt); 76 match ctx.config.snippet_cap {
78 if full_stmt.unwrap().semicolon_token().is_none() { 77 Some(cap) => {
79 buf.push_str(";"); 78 let snip = buf
79 .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
80 edit.replace_snippet(cap, expr_range, snip)
81 }
82 None => edit.replace(expr_range, buf),
83 }
84 return;
80 } 85 }
86
87 buf.push_str(";");
88
89 // We want to maintain the indent level,
90 // but we do not want to duplicate possible
91 // extra newlines in the indent block
92 let text = indent.text();
93 if text.starts_with('\n') {
94 buf.push_str("\n");
95 buf.push_str(text.trim_start_matches('\n'));
96 } else {
97 buf.push_str(text);
98 }
99
100 edit.replace(expr_range, var_name.clone());
101 let offset = anchor.syntax().text_range().start();
81 match ctx.config.snippet_cap { 102 match ctx.config.snippet_cap {
82 Some(cap) => { 103 Some(cap) => {
83 let snip = 104 let snip =
84 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name)); 105 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
85 edit.replace_snippet(cap, expr_range, snip) 106 edit.insert_snippet(cap, offset, snip)
86 } 107 }
87 None => edit.replace(expr_range, buf), 108 None => edit.insert(offset, buf),
88 } 109 }
89 return;
90 }
91
92 buf.push_str(";");
93
94 // We want to maintain the indent level,
95 // but we do not want to duplicate possible
96 // extra newlines in the indent block
97 let text = indent.text();
98 if text.starts_with('\n') {
99 buf.push_str("\n");
100 buf.push_str(text.trim_start_matches('\n'));
101 } else {
102 buf.push_str(text);
103 }
104 110
105 edit.replace(expr_range, var_name.clone()); 111 if let Anchor::WrapInBlock(_) = anchor {
106 let offset = anchor_stmt.text_range().start(); 112 edit.insert(anchor.syntax().text_range().end(), " }");
107 match ctx.config.snippet_cap {
108 Some(cap) => {
109 let snip =
110 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
111 edit.insert_snippet(cap, offset, snip)
112 } 113 }
113 None => edit.insert(offset, buf), 114 },
114 } 115 )
115
116 if wrap_in_block {
117 edit.insert(anchor_stmt.text_range().end(), " }");
118 }
119 })
120} 116}
121 117
122/// Check whether the node is a valid expression which can be extracted to a variable. 118/// Check whether the node is a valid expression which can be extracted to a variable.
@@ -133,32 +129,48 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
133 } 129 }
134} 130}
135 131
136/// Returns the syntax node which will follow the freshly extractd var 132enum Anchor {
137/// and a boolean indicating whether we have to wrap it within a { } block 133 Before(SyntaxNode),
138/// to produce correct code. 134 Replace(ast::ExprStmt),
139/// It can be a statement, the last in a block expression or a wanna be block 135 WrapInBlock(SyntaxNode),
140/// expression like a lambda or match arm. 136}
141fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> { 137
142 expr.syntax().ancestors().find_map(|node| { 138impl Anchor {
143 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 139 fn from(to_extract: &ast::Expr) -> Option<Anchor> {
144 if expr.syntax() == &node { 140 to_extract.syntax().ancestors().find_map(|node| {
145 mark::hit!(test_extract_var_last_expr); 141 if let Some(expr) =
146 return Some((node, false)); 142 node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr())
143 {
144 if expr.syntax() == &node {
145 mark::hit!(test_extract_var_last_expr);
146 return Some(Anchor::Before(node));
147 }
147 } 148 }
148 }
149 149
150 if let Some(parent) = node.parent() { 150 if let Some(parent) = node.parent() {
151 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { 151 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
152 return Some((node, true)); 152 return Some(Anchor::WrapInBlock(node));
153 }
153 } 154 }
154 }
155 155
156 if ast::Stmt::cast(node.clone()).is_some() { 156 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
157 return Some((node, false)); 157 if let ast::Stmt::ExprStmt(stmt) = stmt {
158 } 158 if stmt.expr().as_ref() == Some(to_extract) {
159 return Some(Anchor::Replace(stmt));
160 }
161 }
162 return Some(Anchor::Before(node));
163 }
164 None
165 })
166 }
159 167
160 None 168 fn syntax(&self) -> &SyntaxNode {
161 }) 169 match self {
170 Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
171 Anchor::Replace(stmt) => stmt.syntax(),
172 }
173 }
162} 174}
163 175
164#[cfg(test)] 176#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index 64270c86f..708e1bc6c 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -8,7 +8,7 @@ use test_utils::mark;
8 8
9use crate::{ 9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs}, 10 utils::{render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, Assists, 11 AssistContext, AssistId, AssistKind, Assists,
12}; 12};
13 13
14// Assist: fill_match_arms 14// Assist: fill_match_arms
@@ -51,11 +51,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
51 let module = ctx.sema.scope(expr.syntax()).module()?; 51 let module = ctx.sema.scope(expr.syntax()).module()?;
52 52
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { 53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
54 let variants = enum_def.variants(ctx.db); 54 let variants = enum_def.variants(ctx.db());
55 55
56 let mut variants = variants 56 let mut variants = variants
57 .into_iter() 57 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db, module, variant)) 58 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>(); 61 .collect::<Vec<_>>();
@@ -84,11 +84,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
84 // where each tuple represents a proposed match arm. 84 // where each tuple represents a proposed match arm.
85 enum_defs 85 enum_defs
86 .into_iter() 86 .into_iter()
87 .map(|enum_def| enum_def.variants(ctx.db)) 87 .map(|enum_def| enum_def.variants(ctx.db()))
88 .multi_cartesian_product() 88 .multi_cartesian_product()
89 .map(|variants| { 89 .map(|variants| {
90 let patterns = 90 let patterns =
91 variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant)); 91 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
92 ast::Pat::from(make::tuple_pat(patterns)) 92 ast::Pat::from(make::tuple_pat(patterns))
93 }) 93 })
94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) 94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
@@ -103,24 +103,37 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
103 } 103 }
104 104
105 let target = match_expr.syntax().text_range(); 105 let target = match_expr.syntax().text_range();
106 acc.add(AssistId("fill_match_arms"), "Fill match arms", target, |builder| { 106 acc.add(
107 let new_arm_list = match_arm_list.remove_placeholder(); 107 AssistId("fill_match_arms", AssistKind::QuickFix),
108 let n_old_arms = new_arm_list.arms().count(); 108 "Fill match arms",
109 let new_arm_list = new_arm_list.append_arms(missing_arms); 109 target,
110 let first_new_arm = new_arm_list.arms().nth(n_old_arms); 110 |builder| {
111 let old_range = match_arm_list.syntax().text_range(); 111 let new_arm_list = match_arm_list.remove_placeholder();
112 match (first_new_arm, ctx.config.snippet_cap) { 112 let n_old_arms = new_arm_list.arms().count();
113 (Some(first_new_arm), Some(cap)) => { 113 let new_arm_list = new_arm_list.append_arms(missing_arms);
114 let snippet = render_snippet( 114 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
115 cap, 115 let old_range = match_arm_list.syntax().text_range();
116 new_arm_list.syntax(), 116 match (first_new_arm, ctx.config.snippet_cap) {
117 Cursor::Before(first_new_arm.syntax()), 117 (Some(first_new_arm), Some(cap)) => {
118 ); 118 let extend_lifetime;
119 builder.replace_snippet(cap, old_range, snippet); 119 let cursor = match first_new_arm
120 } 120 .syntax()
121 _ => builder.replace(old_range, new_arm_list.to_string()), 121 .descendants()
122 } 122 .find_map(ast::PlaceholderPat::cast)
123 }) 123 {
124 Some(it) => {
125 extend_lifetime = it.syntax().clone();
126 Cursor::Replace(&extend_lifetime)
127 }
128 None => Cursor::Before(first_new_arm.syntax()),
129 };
130 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
131 builder.replace_snippet(cap, old_range, snippet);
132 }
133 _ => builder.replace(old_range, new_arm_list.to_string()),
134 }
135 },
136 )
124} 137}
125 138
126fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool { 139fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
@@ -286,30 +299,22 @@ mod tests {
286 check_assist( 299 check_assist(
287 fill_match_arms, 300 fill_match_arms,
288 r#" 301 r#"
289 enum A { 302enum A { As, Bs, Cs(Option<i32>) }
290 As, 303fn main() {
291 Bs, 304 match A::As<|> {
292 Cs(Option<i32>), 305 A::Cs(_) | A::Bs => {}
293 } 306 }
294 fn main() { 307}
295 match A::As<|> { 308"#,
296 A::Cs(_) | A::Bs => {}
297 }
298 }
299 "#,
300 r#" 309 r#"
301 enum A { 310enum A { As, Bs, Cs(Option<i32>) }
302 As, 311fn main() {
303 Bs, 312 match A::As {
304 Cs(Option<i32>), 313 A::Cs(_) | A::Bs => {}
305 } 314 $0A::As => {}
306 fn main() { 315 }
307 match A::As { 316}
308 A::Cs(_) | A::Bs => {} 317"#,
309 $0A::As => {}
310 }
311 }
312 "#,
313 ); 318 );
314 } 319 }
315 320
@@ -318,47 +323,29 @@ mod tests {
318 check_assist( 323 check_assist(
319 fill_match_arms, 324 fill_match_arms,
320 r#" 325 r#"
321 enum A { 326enum A { As, Bs, Cs, Ds(String), Es(B) }
322 As, 327enum B { Xs, Ys }
323 Bs, 328fn main() {
324 Cs, 329 match A::As<|> {
325 Ds(String), 330 A::Bs if 0 < 1 => {}
326 Es(B), 331 A::Ds(_value) => { let x = 1; }
327 } 332 A::Es(B::Xs) => (),
328 enum B { 333 }
329 Xs, 334}
330 Ys, 335"#,
331 }
332 fn main() {
333 match A::As<|> {
334 A::Bs if 0 < 1 => {}
335 A::Ds(_value) => { let x = 1; }
336 A::Es(B::Xs) => (),
337 }
338 }
339 "#,
340 r#" 336 r#"
341 enum A { 337enum A { As, Bs, Cs, Ds(String), Es(B) }
342 As, 338enum B { Xs, Ys }
343 Bs, 339fn main() {
344 Cs, 340 match A::As {
345 Ds(String), 341 A::Bs if 0 < 1 => {}
346 Es(B), 342 A::Ds(_value) => { let x = 1; }
347 } 343 A::Es(B::Xs) => (),
348 enum B { 344 $0A::As => {}
349 Xs, 345 A::Cs => {}
350 Ys, 346 }
351 } 347}
352 fn main() { 348"#,
353 match A::As {
354 A::Bs if 0 < 1 => {}
355 A::Ds(_value) => { let x = 1; }
356 A::Es(B::Xs) => (),
357 $0A::As => {}
358 A::Cs => {}
359 }
360 }
361 "#,
362 ); 349 );
363 } 350 }
364 351
@@ -367,32 +354,24 @@ mod tests {
367 check_assist( 354 check_assist(
368 fill_match_arms, 355 fill_match_arms,
369 r#" 356 r#"
370 enum A { 357enum A { As, Bs, Cs(Option<i32>) }
371 As, 358fn main() {
372 Bs, 359 match A::As<|> {
373 Cs(Option<i32>), 360 A::As(_) => {}
374 } 361 a @ A::Bs(_) => {}
375 fn main() { 362 }
376 match A::As<|> { 363}
377 A::As(_) => {} 364"#,
378 a @ A::Bs(_) => {}
379 }
380 }
381 "#,
382 r#" 365 r#"
383 enum A { 366enum A { As, Bs, Cs(Option<i32>) }
384 As, 367fn main() {
385 Bs, 368 match A::As {
386 Cs(Option<i32>), 369 A::As(_) => {}
387 } 370 a @ A::Bs(_) => {}
388 fn main() { 371 A::Cs(${0:_}) => {}
389 match A::As { 372 }
390 A::As(_) => {} 373}
391 a @ A::Bs(_) => {} 374"#,
392 $0A::Cs(_) => {}
393 }
394 }
395 "#,
396 ); 375 );
397 } 376 }
398 377
@@ -401,39 +380,27 @@ mod tests {
401 check_assist( 380 check_assist(
402 fill_match_arms, 381 fill_match_arms,
403 r#" 382 r#"
404 enum A { 383enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
405 As,
406 Bs,
407 Cs(String),
408 Ds(String, String),
409 Es { x: usize, y: usize }
410 }
411 384
412 fn main() { 385fn main() {
413 let a = A::As; 386 let a = A::As;
414 match a<|> {} 387 match a<|> {}
415 } 388}
416 "#, 389"#,
417 r#" 390 r#"
418 enum A { 391enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
419 As,
420 Bs,
421 Cs(String),
422 Ds(String, String),
423 Es { x: usize, y: usize }
424 }
425 392
426 fn main() { 393fn main() {
427 let a = A::As; 394 let a = A::As;
428 match a { 395 match a {
429 $0A::As => {} 396 $0A::As => {}
430 A::Bs => {} 397 A::Bs => {}
431 A::Cs(_) => {} 398 A::Cs(_) => {}
432 A::Ds(_, _) => {} 399 A::Ds(_, _) => {}
433 A::Es { x, y } => {} 400 A::Es { x, y } => {}
434 } 401 }
435 } 402}
436 "#, 403"#,
437 ); 404 );
438 } 405 }
439 406
@@ -773,7 +740,7 @@ fn foo(opt: Option<i32>) {
773 r#" 740 r#"
774fn foo(opt: Option<i32>) { 741fn foo(opt: Option<i32>) {
775 match opt { 742 match opt {
776 $0Some(_) => {} 743 Some(${0:_}) => {}
777 None => {} 744 None => {}
778 } 745 }
779} 746}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
index 19d4dac5e..1aefa79cc 100644
--- a/crates/ra_assists/src/handlers/fix_visibility.rs
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -2,7 +2,8 @@ use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ra_db::FileId; 2use ra_db::FileId;
3use ra_syntax::{ast, AstNode, TextRange, TextSize}; 3use ra_syntax::{ast, AstNode, TextRange, TextSize};
4 4
5use crate::{utils::vis_offset, AssistContext, AssistId, Assists}; 5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
6 7
7// FIXME: this really should be a fix for diagnostic, rather than an assist. 8// FIXME: this really should be a fix for diagnostic, rather than an assist.
8 9
@@ -41,14 +42,15 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
41 }; 42 };
42 43
43 let current_module = ctx.sema.scope(&path.syntax()).module()?; 44 let current_module = ctx.sema.scope(&path.syntax()).module()?;
44 let target_module = def.module(ctx.db)?; 45 let target_module = def.module(ctx.db())?;
45 46
46 let vis = target_module.visibility_of(ctx.db, &def)?; 47 let vis = target_module.visibility_of(ctx.db(), &def)?;
47 if vis.is_visible_from(ctx.db, current_module.into()) { 48 if vis.is_visible_from(ctx.db(), current_module.into()) {
48 return None; 49 return None;
49 }; 50 };
50 51
51 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; 52 let (offset, current_visibility, target, target_file, target_name) =
53 target_data_for_def(ctx.db(), def)?;
52 54
53 let missing_visibility = 55 let missing_visibility =
54 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; 56 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
@@ -58,54 +60,78 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
58 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), 60 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
59 }; 61 };
60 62
61 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 63 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
62 builder.edit_file(target_file); 64 builder.edit_file(target_file);
63 match ctx.config.snippet_cap { 65 match ctx.config.snippet_cap {
64 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 66 Some(cap) => match current_visibility {
65 None => builder.insert(offset, format!("{} ", missing_visibility)), 67 Some(current_visibility) => builder.replace_snippet(
68 cap,
69 current_visibility.syntax().text_range(),
70 format!("$0{}", missing_visibility),
71 ),
72 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
73 },
74 None => match current_visibility {
75 Some(current_visibility) => {
76 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
77 }
78 None => builder.insert(offset, format!("{} ", missing_visibility)),
79 },
66 } 80 }
67 }) 81 })
68} 82}
69 83
70fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 84fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
71 let record_field: ast::RecordField = ctx.find_node_at_offset()?; 85 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
72 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; 86 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
73 87
74 let current_module = ctx.sema.scope(record_field.syntax()).module()?; 88 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
75 let visibility = record_field_def.visibility(ctx.db); 89 let visibility = record_field_def.visibility(ctx.db());
76 if visibility.is_visible_from(ctx.db, current_module.into()) { 90 if visibility.is_visible_from(ctx.db(), current_module.into()) {
77 return None; 91 return None;
78 } 92 }
79 93
80 let parent = record_field_def.parent_def(ctx.db); 94 let parent = record_field_def.parent_def(ctx.db());
81 let parent_name = parent.name(ctx.db); 95 let parent_name = parent.name(ctx.db());
82 let target_module = parent.module(ctx.db); 96 let target_module = parent.module(ctx.db());
83 97
84 let in_file_source = record_field_def.source(ctx.db); 98 let in_file_source = record_field_def.source(ctx.db());
85 let (offset, target) = match in_file_source.value { 99 let (offset, current_visibility, target) = match in_file_source.value {
86 hir::FieldSource::Named(it) => { 100 hir::FieldSource::Named(it) => {
87 let s = it.syntax(); 101 let s = it.syntax();
88 (vis_offset(s), s.text_range()) 102 (vis_offset(s), it.visibility(), s.text_range())
89 } 103 }
90 hir::FieldSource::Pos(it) => { 104 hir::FieldSource::Pos(it) => {
91 let s = it.syntax(); 105 let s = it.syntax();
92 (vis_offset(s), s.text_range()) 106 (vis_offset(s), it.visibility(), s.text_range())
93 } 107 }
94 }; 108 };
95 109
96 let missing_visibility = 110 let missing_visibility =
97 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; 111 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
98 let target_file = in_file_source.file_id.original_file(ctx.db); 112 let target_file = in_file_source.file_id.original_file(ctx.db());
99 113
100 let target_name = record_field_def.name(ctx.db); 114 let target_name = record_field_def.name(ctx.db());
101 let assist_label = 115 let assist_label =
102 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); 116 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
103 117
104 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 118 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
105 builder.edit_file(target_file); 119 builder.edit_file(target_file);
106 match ctx.config.snippet_cap { 120 match ctx.config.snippet_cap {
107 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 121 Some(cap) => match current_visibility {
108 None => builder.insert(offset, format!("{} ", missing_visibility)), 122 Some(current_visibility) => builder.replace_snippet(
123 cap,
124 dbg!(current_visibility.syntax()).text_range(),
125 format!("$0{}", missing_visibility),
126 ),
127 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
128 },
129 None => match current_visibility {
130 Some(current_visibility) => {
131 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
132 }
133 None => builder.insert(offset, format!("{} ", missing_visibility)),
134 },
109 } 135 }
110 }) 136 })
111} 137}
@@ -113,24 +139,30 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
113fn target_data_for_def( 139fn target_data_for_def(
114 db: &dyn HirDatabase, 140 db: &dyn HirDatabase,
115 def: hir::ModuleDef, 141 def: hir::ModuleDef,
116) -> Option<(TextSize, TextRange, FileId, Option<hir::Name>)> { 142) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
117 fn offset_target_and_file_id<S, Ast>( 143 fn offset_target_and_file_id<S, Ast>(
118 db: &dyn HirDatabase, 144 db: &dyn HirDatabase,
119 x: S, 145 x: S,
120 ) -> (TextSize, TextRange, FileId) 146 ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
121 where 147 where
122 S: HasSource<Ast = Ast>, 148 S: HasSource<Ast = Ast>,
123 Ast: AstNode, 149 Ast: AstNode + ast::VisibilityOwner,
124 { 150 {
125 let source = x.source(db); 151 let source = x.source(db);
126 let in_file_syntax = source.syntax(); 152 let in_file_syntax = source.syntax();
127 let file_id = in_file_syntax.file_id; 153 let file_id = in_file_syntax.file_id;
128 let syntax = in_file_syntax.value; 154 let syntax = in_file_syntax.value;
129 (vis_offset(syntax), syntax.text_range(), file_id.original_file(db.upcast())) 155 let current_visibility = source.value.visibility();
156 (
157 vis_offset(syntax),
158 current_visibility,
159 syntax.text_range(),
160 file_id.original_file(db.upcast()),
161 )
130 } 162 }
131 163
132 let target_name; 164 let target_name;
133 let (offset, target, target_file) = match def { 165 let (offset, current_visibility, target, target_file) = match def {
134 hir::ModuleDef::Function(f) => { 166 hir::ModuleDef::Function(f) => {
135 target_name = Some(f.name(db)); 167 target_name = Some(f.name(db));
136 offset_target_and_file_id(db, f) 168 offset_target_and_file_id(db, f)
@@ -164,13 +196,13 @@ fn target_data_for_def(
164 let in_file_source = m.declaration_source(db)?; 196 let in_file_source = m.declaration_source(db)?;
165 let file_id = in_file_source.file_id.original_file(db.upcast()); 197 let file_id = in_file_source.file_id.original_file(db.upcast());
166 let syntax = in_file_source.value.syntax(); 198 let syntax = in_file_source.value.syntax();
167 (vis_offset(syntax), syntax.text_range(), file_id) 199 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
168 } 200 }
169 // Enum variants can't be private, we can't modify builtin types 201 // Enum variants can't be private, we can't modify builtin types
170 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None, 202 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
171 }; 203 };
172 204
173 Some((offset, target, target_file, target_name)) 205 Some((offset, current_visibility, target, target_file, target_name))
174} 206}
175 207
176#[cfg(test)] 208#[cfg(test)]
@@ -523,6 +555,34 @@ struct Bar;
523 } 555 }
524 556
525 #[test] 557 #[test]
558 fn replaces_pub_crate_with_pub() {
559 check_assist(
560 fix_visibility,
561 r"
562//- /main.rs crate:a deps:foo
563foo::Bar<|>
564//- /lib.rs crate:foo
565pub(crate) struct Bar;
566",
567 r"$0pub struct Bar;
568",
569 );
570 check_assist(
571 fix_visibility,
572 r"
573//- /main.rs crate:a deps:foo
574fn main() {
575 foo::Foo { <|>bar: () };
576}
577//- /lib.rs crate:foo
578pub struct Foo { pub(crate) bar: () }
579",
580 r"pub struct Foo { $0pub bar: () }
581",
582 );
583 }
584
585 #[test]
526 #[ignore] 586 #[ignore]
527 // FIXME handle reexports properly 587 // FIXME handle reexports properly
528 fn fix_visibility_of_reexport() { 588 fn fix_visibility_of_reexport() {
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
index 573196576..3cd532650 100644
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ b/crates/ra_assists/src/handlers/flip_binexpr.rs
@@ -1,6 +1,6 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp}; 1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2 2
3use crate::{AssistContext, AssistId, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
4 4
5// Assist: flip_binexpr 5// Assist: flip_binexpr
6// 6//
@@ -33,13 +33,18 @@ pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
33 return None; 33 return None;
34 } 34 }
35 35
36 acc.add(AssistId("flip_binexpr"), "Flip binary expression", op_range, |edit| { 36 acc.add(
37 if let FlipAction::FlipAndReplaceOp(new_op) = action { 37 AssistId("flip_binexpr", AssistKind::RefactorRewrite),
38 edit.replace(op_range, new_op); 38 "Flip binary expression",
39 } 39 op_range,
40 edit.replace(lhs.text_range(), rhs.text()); 40 |edit| {
41 edit.replace(rhs.text_range(), lhs.text()); 41 if let FlipAction::FlipAndReplaceOp(new_op) = action {
42 }) 42 edit.replace(op_range, new_op);
43 }
44 edit.replace(lhs.text_range(), rhs.text());
45 edit.replace(rhs.text_range(), lhs.text());
46 },
47 )
43} 48}
44 49
45enum FlipAction { 50enum FlipAction {
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
index a57a1c463..55a971dc7 100644
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ b/crates/ra_assists/src/handlers/flip_comma.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T}; 1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2 2
3use crate::{AssistContext, AssistId, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
4 4
5// Assist: flip_comma 5// Assist: flip_comma
6// 6//
@@ -28,10 +28,15 @@ pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 return None; 28 return None;
29 } 29 }
30 30
31 acc.add(AssistId("flip_comma"), "Flip comma", comma.text_range(), |edit| { 31 acc.add(
32 edit.replace(prev.text_range(), next.to_string()); 32 AssistId("flip_comma", AssistKind::RefactorRewrite),
33 edit.replace(next.text_range(), prev.to_string()); 33 "Flip comma",
34 }) 34 comma.text_range(),
35 |edit| {
36 edit.replace(prev.text_range(), next.to_string());
37 edit.replace(next.text_range(), prev.to_string());
38 },
39 )
35} 40}
36 41
37#[cfg(test)] 42#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
index 0115adc8b..1234f4d29 100644
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ b/crates/ra_assists/src/handlers/flip_trait_bound.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 Direction, T, 4 Direction, T,
5}; 5};
6 6
7use crate::{AssistContext, AssistId, Assists}; 7use crate::{AssistContext, AssistId, AssistKind, Assists};
8 8
9// Assist: flip_trait_bound 9// Assist: flip_trait_bound
10// 10//
@@ -33,10 +33,15 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option
33 ); 33 );
34 34
35 let target = plus.text_range(); 35 let target = plus.text_range();
36 acc.add(AssistId("flip_trait_bound"), "Flip trait bounds", target, |edit| { 36 acc.add(
37 edit.replace(before.text_range(), after.to_string()); 37 AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
38 edit.replace(after.text_range(), before.to_string()); 38 "Flip trait bounds",
39 }) 39 target,
40 |edit| {
41 edit.replace(before.text_range(), after.to_string());
42 edit.replace(after.text_range(), before.to_string());
43 },
44 )
40} 45}
41 46
42#[cfg(test)] 47#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/add_derive.rs b/crates/ra_assists/src/handlers/generate_derive.rs
index b123b8498..90ece9fab 100644
--- a/crates/ra_assists/src/handlers/add_derive.rs
+++ b/crates/ra_assists/src/handlers/generate_derive.rs
@@ -4,9 +4,9 @@ use ra_syntax::{
4 TextSize, 4 TextSize,
5}; 5};
6 6
7use crate::{AssistContext, AssistId, Assists}; 7use crate::{AssistContext, AssistId, AssistKind, Assists};
8 8
9// Assist: add_derive 9// Assist: generate_derive
10// 10//
11// Adds a new `#[derive()]` clause to a struct or enum. 11// Adds a new `#[derive()]` clause to a struct or enum.
12// 12//
@@ -24,36 +24,41 @@ use crate::{AssistContext, AssistId, Assists};
24// y: u32, 24// y: u32,
25// } 25// }
26// ``` 26// ```
27pub(crate) fn add_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 27pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?; 28 let cap = ctx.config.snippet_cap?;
29 let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?; 29 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
30 let node_start = derive_insertion_offset(&nominal)?; 30 let node_start = derive_insertion_offset(&nominal)?;
31 let target = nominal.syntax().text_range(); 31 let target = nominal.syntax().text_range();
32 acc.add(AssistId("add_derive"), "Add `#[derive]`", target, |builder| { 32 acc.add(
33 let derive_attr = nominal 33 AssistId("generate_derive", AssistKind::Generate),
34 .attrs() 34 "Add `#[derive]`",
35 .filter_map(|x| x.as_simple_call()) 35 target,
36 .filter(|(name, _arg)| name == "derive") 36 |builder| {
37 .map(|(_name, arg)| arg) 37 let derive_attr = nominal
38 .next(); 38 .attrs()
39 match derive_attr { 39 .filter_map(|x| x.as_simple_call())
40 None => { 40 .filter(|(name, _arg)| name == "derive")
41 builder.insert_snippet(cap, node_start, "#[derive($0)]\n"); 41 .map(|(_name, arg)| arg)
42 } 42 .next();
43 Some(tt) => { 43 match derive_attr {
44 // Just move the cursor. 44 None => {
45 builder.insert_snippet( 45 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
46 cap, 46 }
47 tt.syntax().text_range().end() - TextSize::of(')'), 47 Some(tt) => {
48 "$0", 48 // Just move the cursor.
49 ) 49 builder.insert_snippet(
50 } 50 cap,
51 }; 51 tt.syntax().text_range().end() - TextSize::of(')'),
52 }) 52 "$0",
53 )
54 }
55 };
56 },
57 )
53} 58}
54 59
55// Insert `derive` after doc comments. 60// Insert `derive` after doc comments.
56fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> { 61fn derive_insertion_offset(nominal: &ast::AdtDef) -> Option<TextSize> {
57 let non_ws_child = nominal 62 let non_ws_child = nominal
58 .syntax() 63 .syntax()
59 .children_with_tokens() 64 .children_with_tokens()
@@ -70,12 +75,12 @@ mod tests {
70 #[test] 75 #[test]
71 fn add_derive_new() { 76 fn add_derive_new() {
72 check_assist( 77 check_assist(
73 add_derive, 78 generate_derive,
74 "struct Foo { a: i32, <|>}", 79 "struct Foo { a: i32, <|>}",
75 "#[derive($0)]\nstruct Foo { a: i32, }", 80 "#[derive($0)]\nstruct Foo { a: i32, }",
76 ); 81 );
77 check_assist( 82 check_assist(
78 add_derive, 83 generate_derive,
79 "struct Foo { <|> a: i32, }", 84 "struct Foo { <|> a: i32, }",
80 "#[derive($0)]\nstruct Foo { a: i32, }", 85 "#[derive($0)]\nstruct Foo { a: i32, }",
81 ); 86 );
@@ -84,7 +89,7 @@ mod tests {
84 #[test] 89 #[test]
85 fn add_derive_existing() { 90 fn add_derive_existing() {
86 check_assist( 91 check_assist(
87 add_derive, 92 generate_derive,
88 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", 93 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
89 "#[derive(Clone$0)]\nstruct Foo { a: i32, }", 94 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
90 ); 95 );
@@ -93,7 +98,7 @@ mod tests {
93 #[test] 98 #[test]
94 fn add_derive_new_with_doc_comment() { 99 fn add_derive_new_with_doc_comment() {
95 check_assist( 100 check_assist(
96 add_derive, 101 generate_derive,
97 " 102 "
98/// `Foo` is a pretty important struct. 103/// `Foo` is a pretty important struct.
99/// It does stuff. 104/// It does stuff.
@@ -111,7 +116,7 @@ struct Foo { a: i32, }
111 #[test] 116 #[test]
112 fn add_derive_target() { 117 fn add_derive_target() {
113 check_assist_target( 118 check_assist_target(
114 add_derive, 119 generate_derive,
115 " 120 "
116struct SomeThingIrrelevant; 121struct SomeThingIrrelevant;
117/// `Foo` is a pretty important struct. 122/// `Foo` is a pretty important struct.
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs
index b0e56e1b5..9da23640a 100644
--- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs
+++ b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs
@@ -2,9 +2,9 @@ use ra_ide_db::RootDatabase;
2use ra_syntax::ast::{self, AstNode, NameOwner}; 2use ra_syntax::ast::{self, AstNode, NameOwner};
3use test_utils::mark; 3use test_utils::mark;
4 4
5use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; 5use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
6 6
7// Assist: add_from_impl_for_enum 7// Assist: generate_from_impl_for_enum
8// 8//
9// Adds a From impl for an enum variant with one tuple field. 9// Adds a From impl for an enum variant with one tuple field.
10// 10//
@@ -21,8 +21,8 @@ use crate::{utils::FamousDefs, AssistContext, AssistId, Assists};
21// } 21// }
22// } 22// }
23// ``` 23// ```
24pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 24pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?; 25 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
26 let variant_name = variant.name()?; 26 let variant_name = variant.name()?;
27 let enum_name = variant.parent_enum().name()?; 27 let enum_name = variant.parent_enum().name()?;
28 let field_list = match variant.kind() { 28 let field_list = match variant.kind() {
@@ -32,7 +32,7 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
32 if field_list.fields().count() != 1 { 32 if field_list.fields().count() != 1 {
33 return None; 33 return None;
34 } 34 }
35 let field_type = field_list.fields().next()?.type_ref()?; 35 let field_type = field_list.fields().next()?.ty()?;
36 let path = match field_type { 36 let path = match field_type {
37 ast::TypeRef::PathType(it) => it, 37 ast::TypeRef::PathType(it) => it,
38 _ => return None, 38 _ => return None,
@@ -45,8 +45,8 @@ pub(crate) fn add_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) ->
45 45
46 let target = variant.syntax().text_range(); 46 let target = variant.syntax().text_range();
47 acc.add( 47 acc.add(
48 AssistId("add_from_impl_for_enum"), 48 AssistId("generate_from_impl_for_enum", AssistKind::Generate),
49 "Add From impl for this enum variant", 49 "Generate `From` impl for this enum variant",
50 target, 50 target,
51 |edit| { 51 |edit| {
52 let start_offset = variant.parent_enum().syntax().text_range().end(); 52 let start_offset = variant.parent_enum().syntax().text_range().end();
@@ -69,7 +69,7 @@ impl From<{0}> for {1} {{
69 69
70fn existing_from_impl( 70fn existing_from_impl(
71 sema: &'_ hir::Semantics<'_, RootDatabase>, 71 sema: &'_ hir::Semantics<'_, RootDatabase>,
72 variant: &ast::EnumVariant, 72 variant: &ast::Variant,
73) -> Option<()> { 73) -> Option<()> {
74 let variant = sema.to_def(variant)?; 74 let variant = sema.to_def(variant)?;
75 let enum_ = variant.parent_enum(sema.db); 75 let enum_ = variant.parent_enum(sema.db);
@@ -97,9 +97,9 @@ mod tests {
97 use super::*; 97 use super::*;
98 98
99 #[test] 99 #[test]
100 fn test_add_from_impl_for_enum() { 100 fn test_generate_from_impl_for_enum() {
101 check_assist( 101 check_assist(
102 add_from_impl_for_enum, 102 generate_from_impl_for_enum,
103 "enum A { <|>One(u32) }", 103 "enum A { <|>One(u32) }",
104 r#"enum A { One(u32) } 104 r#"enum A { One(u32) }
105 105
@@ -112,9 +112,9 @@ impl From<u32> for A {
112 } 112 }
113 113
114 #[test] 114 #[test]
115 fn test_add_from_impl_for_enum_complicated_path() { 115 fn test_generate_from_impl_for_enum_complicated_path() {
116 check_assist( 116 check_assist(
117 add_from_impl_for_enum, 117 generate_from_impl_for_enum,
118 r#"enum A { <|>One(foo::bar::baz::Boo) }"#, 118 r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
119 r#"enum A { One(foo::bar::baz::Boo) } 119 r#"enum A { One(foo::bar::baz::Boo) }
120 120
@@ -129,7 +129,7 @@ impl From<foo::bar::baz::Boo> for A {
129 fn check_not_applicable(ra_fixture: &str) { 129 fn check_not_applicable(ra_fixture: &str) {
130 let fixture = 130 let fixture =
131 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); 131 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
132 check_assist_not_applicable(add_from_impl_for_enum, &fixture) 132 check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
133 } 133 }
134 134
135 #[test] 135 #[test]
@@ -166,7 +166,7 @@ impl From<u32> for A {
166 #[test] 166 #[test]
167 fn test_add_from_impl_different_variant_impl_exists() { 167 fn test_add_from_impl_different_variant_impl_exists() {
168 check_assist( 168 check_assist(
169 add_from_impl_for_enum, 169 generate_from_impl_for_enum,
170 r#"enum A { <|>One(u32), Two(String), } 170 r#"enum A { <|>One(u32), Two(String), }
171 171
172impl From<String> for A { 172impl From<String> for A {
diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/generate_function.rs
index 1cfbd75aa..56510861d 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/generate_function.rs
@@ -13,10 +13,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
13use crate::{ 13use crate::{
14 assist_config::SnippetCap, 14 assist_config::SnippetCap,
15 utils::{render_snippet, Cursor}, 15 utils::{render_snippet, Cursor},
16 AssistContext, AssistId, Assists, 16 AssistContext, AssistId, AssistKind, Assists,
17}; 17};
18 18
19// Assist: add_function 19// Assist: generate_function
20// 20//
21// Adds a stub function with a signature matching the function under the cursor. 21// Adds a stub function with a signature matching the function under the cursor.
22// 22//
@@ -41,7 +41,7 @@ use crate::{
41// } 41// }
42// 42//
43// ``` 43// ```
44pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 44pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?; 45 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
46 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; 46 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
47 let path = path_expr.path()?; 47 let path = path_expr.path()?;
@@ -62,22 +62,27 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?; 62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
63 63
64 let target = call.syntax().text_range(); 64 let target = call.syntax().text_range();
65 acc.add(AssistId("add_function"), "Add function", target, |builder| { 65 acc.add(
66 let function_template = function_builder.render(); 66 AssistId("generate_function", AssistKind::Generate),
67 builder.edit_file(function_template.file); 67 format!("Generate `{}` function", function_builder.fn_name),
68 let new_fn = function_template.to_string(ctx.config.snippet_cap); 68 target,
69 match ctx.config.snippet_cap { 69 |builder| {
70 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), 70 let function_template = function_builder.render();
71 None => builder.insert(function_template.insert_offset, new_fn), 71 builder.edit_file(function_template.file);
72 } 72 let new_fn = function_template.to_string(ctx.config.snippet_cap);
73 }) 73 match ctx.config.snippet_cap {
74 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
75 None => builder.insert(function_template.insert_offset, new_fn),
76 }
77 },
78 )
74} 79}
75 80
76struct FunctionTemplate { 81struct FunctionTemplate {
77 insert_offset: TextSize, 82 insert_offset: TextSize,
78 placeholder_expr: ast::MacroCall, 83 placeholder_expr: ast::MacroCall,
79 leading_ws: String, 84 leading_ws: String,
80 fn_def: ast::FnDef, 85 fn_def: ast::Fn,
81 trailing_ws: String, 86 trailing_ws: String,
82 file: FileId, 87 file: FileId,
83} 88}
@@ -99,7 +104,7 @@ impl FunctionTemplate {
99struct FunctionBuilder { 104struct FunctionBuilder {
100 target: GeneratedFunctionTarget, 105 target: GeneratedFunctionTarget,
101 fn_name: ast::Name, 106 fn_name: ast::Name,
102 type_params: Option<ast::TypeParamList>, 107 type_params: Option<ast::GenericParamList>,
103 params: ast::ParamList, 108 params: ast::ParamList,
104 file: FileId, 109 file: FileId,
105 needs_pub: bool, 110 needs_pub: bool,
@@ -117,7 +122,7 @@ impl FunctionBuilder {
117 let mut file = ctx.frange.file_id; 122 let mut file = ctx.frange.file_id;
118 let target = match &target_module { 123 let target = match &target_module {
119 Some(target_module) => { 124 Some(target_module) => {
120 let module_source = target_module.definition_source(ctx.db); 125 let module_source = target_module.definition_source(ctx.db());
121 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?; 126 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
122 file = in_file; 127 file = in_file;
123 target 128 target
@@ -195,7 +200,7 @@ fn fn_args(
195 ctx: &AssistContext, 200 ctx: &AssistContext,
196 target_module: hir::Module, 201 target_module: hir::Module,
197 call: &ast::CallExpr, 202 call: &ast::CallExpr,
198) -> Option<(Option<ast::TypeParamList>, ast::ParamList)> { 203) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
199 let mut arg_names = Vec::new(); 204 let mut arg_names = Vec::new();
200 let mut arg_types = Vec::new(); 205 let mut arg_types = Vec::new();
201 for arg in call.arg_list()?.args() { 206 for arg in call.arg_list()?.args() {
@@ -269,7 +274,7 @@ fn fn_arg_type(
269 return None; 274 return None;
270 } 275 }
271 276
272 if let Ok(rendered) = ty.display_source_code(ctx.db, target_module.into()) { 277 if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
273 Some(rendered) 278 Some(rendered)
274 } else { 279 } else {
275 None 280 None
@@ -333,7 +338,7 @@ mod tests {
333 #[test] 338 #[test]
334 fn add_function_with_no_args() { 339 fn add_function_with_no_args() {
335 check_assist( 340 check_assist(
336 add_function, 341 generate_function,
337 r" 342 r"
338fn foo() { 343fn foo() {
339 bar<|>(); 344 bar<|>();
@@ -356,7 +361,7 @@ fn bar() {
356 // This ensures that the function is correctly generated 361 // This ensures that the function is correctly generated
357 // in the next outer mod or file 362 // in the next outer mod or file
358 check_assist( 363 check_assist(
359 add_function, 364 generate_function,
360 r" 365 r"
361impl Foo { 366impl Foo {
362 fn foo() { 367 fn foo() {
@@ -382,7 +387,7 @@ fn bar() {
382 fn add_function_directly_after_current_block() { 387 fn add_function_directly_after_current_block() {
383 // The new fn should not be created at the end of the file or module 388 // The new fn should not be created at the end of the file or module
384 check_assist( 389 check_assist(
385 add_function, 390 generate_function,
386 r" 391 r"
387fn foo1() { 392fn foo1() {
388 bar<|>(); 393 bar<|>();
@@ -407,7 +412,7 @@ fn foo2() {}
407 #[test] 412 #[test]
408 fn add_function_with_no_args_in_same_module() { 413 fn add_function_with_no_args_in_same_module() {
409 check_assist( 414 check_assist(
410 add_function, 415 generate_function,
411 r" 416 r"
412mod baz { 417mod baz {
413 fn foo() { 418 fn foo() {
@@ -432,7 +437,7 @@ mod baz {
432 #[test] 437 #[test]
433 fn add_function_with_function_call_arg() { 438 fn add_function_with_function_call_arg() {
434 check_assist( 439 check_assist(
435 add_function, 440 generate_function,
436 r" 441 r"
437struct Baz; 442struct Baz;
438fn baz() -> Baz { todo!() } 443fn baz() -> Baz { todo!() }
@@ -457,7 +462,7 @@ fn bar(baz: Baz) {
457 #[test] 462 #[test]
458 fn add_function_with_method_call_arg() { 463 fn add_function_with_method_call_arg() {
459 check_assist( 464 check_assist(
460 add_function, 465 generate_function,
461 r" 466 r"
462struct Baz; 467struct Baz;
463impl Baz { 468impl Baz {
@@ -490,7 +495,7 @@ fn bar(baz: Baz) {
490 #[test] 495 #[test]
491 fn add_function_with_string_literal_arg() { 496 fn add_function_with_string_literal_arg() {
492 check_assist( 497 check_assist(
493 add_function, 498 generate_function,
494 r#" 499 r#"
495fn foo() { 500fn foo() {
496 <|>bar("bar") 501 <|>bar("bar")
@@ -511,7 +516,7 @@ fn bar(arg: &str) {
511 #[test] 516 #[test]
512 fn add_function_with_char_literal_arg() { 517 fn add_function_with_char_literal_arg() {
513 check_assist( 518 check_assist(
514 add_function, 519 generate_function,
515 r#" 520 r#"
516fn foo() { 521fn foo() {
517 <|>bar('x') 522 <|>bar('x')
@@ -532,7 +537,7 @@ fn bar(arg: char) {
532 #[test] 537 #[test]
533 fn add_function_with_int_literal_arg() { 538 fn add_function_with_int_literal_arg() {
534 check_assist( 539 check_assist(
535 add_function, 540 generate_function,
536 r" 541 r"
537fn foo() { 542fn foo() {
538 <|>bar(42) 543 <|>bar(42)
@@ -553,7 +558,7 @@ fn bar(arg: i32) {
553 #[test] 558 #[test]
554 fn add_function_with_cast_int_literal_arg() { 559 fn add_function_with_cast_int_literal_arg() {
555 check_assist( 560 check_assist(
556 add_function, 561 generate_function,
557 r" 562 r"
558fn foo() { 563fn foo() {
559 <|>bar(42 as u8) 564 <|>bar(42 as u8)
@@ -576,7 +581,7 @@ fn bar(arg: u8) {
576 // Ensures that the name of the cast type isn't used 581 // Ensures that the name of the cast type isn't used
577 // in the generated function signature. 582 // in the generated function signature.
578 check_assist( 583 check_assist(
579 add_function, 584 generate_function,
580 r" 585 r"
581fn foo() { 586fn foo() {
582 let x = 42; 587 let x = 42;
@@ -599,7 +604,7 @@ fn bar(x: u8) {
599 #[test] 604 #[test]
600 fn add_function_with_variable_arg() { 605 fn add_function_with_variable_arg() {
601 check_assist( 606 check_assist(
602 add_function, 607 generate_function,
603 r" 608 r"
604fn foo() { 609fn foo() {
605 let worble = (); 610 let worble = ();
@@ -622,7 +627,7 @@ fn bar(worble: ()) {
622 #[test] 627 #[test]
623 fn add_function_with_impl_trait_arg() { 628 fn add_function_with_impl_trait_arg() {
624 check_assist( 629 check_assist(
625 add_function, 630 generate_function,
626 r" 631 r"
627trait Foo {} 632trait Foo {}
628fn foo() -> impl Foo { 633fn foo() -> impl Foo {
@@ -651,7 +656,7 @@ fn bar(foo: impl Foo) {
651 #[test] 656 #[test]
652 fn borrowed_arg() { 657 fn borrowed_arg() {
653 check_assist( 658 check_assist(
654 add_function, 659 generate_function,
655 r" 660 r"
656struct Baz; 661struct Baz;
657fn baz() -> Baz { todo!() } 662fn baz() -> Baz { todo!() }
@@ -678,7 +683,7 @@ fn bar(baz: &Baz) {
678 #[test] 683 #[test]
679 fn add_function_with_qualified_path_arg() { 684 fn add_function_with_qualified_path_arg() {
680 check_assist( 685 check_assist(
681 add_function, 686 generate_function,
682 r" 687 r"
683mod Baz { 688mod Baz {
684 pub struct Bof; 689 pub struct Bof;
@@ -709,7 +714,7 @@ fn bar(baz: Baz::Bof) {
709 // FIXME fix printing the generics of a `Ty` to make this test pass 714 // FIXME fix printing the generics of a `Ty` to make this test pass
710 fn add_function_with_generic_arg() { 715 fn add_function_with_generic_arg() {
711 check_assist( 716 check_assist(
712 add_function, 717 generate_function,
713 r" 718 r"
714fn foo<T>(t: T) { 719fn foo<T>(t: T) {
715 <|>bar(t) 720 <|>bar(t)
@@ -732,7 +737,7 @@ fn bar<T>(t: T) {
732 // FIXME Fix function type printing to make this test pass 737 // FIXME Fix function type printing to make this test pass
733 fn add_function_with_fn_arg() { 738 fn add_function_with_fn_arg() {
734 check_assist( 739 check_assist(
735 add_function, 740 generate_function,
736 r" 741 r"
737struct Baz; 742struct Baz;
738impl Baz { 743impl Baz {
@@ -763,7 +768,7 @@ fn bar(arg: fn() -> Baz) {
763 // FIXME Fix closure type printing to make this test pass 768 // FIXME Fix closure type printing to make this test pass
764 fn add_function_with_closure_arg() { 769 fn add_function_with_closure_arg() {
765 check_assist( 770 check_assist(
766 add_function, 771 generate_function,
767 r" 772 r"
768fn foo() { 773fn foo() {
769 let closure = |x: i64| x - 1; 774 let closure = |x: i64| x - 1;
@@ -786,7 +791,7 @@ fn bar(closure: impl Fn(i64) -> i64) {
786 #[test] 791 #[test]
787 fn unresolveable_types_default_to_unit() { 792 fn unresolveable_types_default_to_unit() {
788 check_assist( 793 check_assist(
789 add_function, 794 generate_function,
790 r" 795 r"
791fn foo() { 796fn foo() {
792 <|>bar(baz) 797 <|>bar(baz)
@@ -807,7 +812,7 @@ fn bar(baz: ()) {
807 #[test] 812 #[test]
808 fn arg_names_dont_overlap() { 813 fn arg_names_dont_overlap() {
809 check_assist( 814 check_assist(
810 add_function, 815 generate_function,
811 r" 816 r"
812struct Baz; 817struct Baz;
813fn baz() -> Baz { Baz } 818fn baz() -> Baz { Baz }
@@ -832,7 +837,7 @@ fn bar(baz_1: Baz, baz_2: Baz) {
832 #[test] 837 #[test]
833 fn arg_name_counters_start_at_1_per_name() { 838 fn arg_name_counters_start_at_1_per_name() {
834 check_assist( 839 check_assist(
835 add_function, 840 generate_function,
836 r#" 841 r#"
837struct Baz; 842struct Baz;
838fn baz() -> Baz { Baz } 843fn baz() -> Baz { Baz }
@@ -857,7 +862,7 @@ fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
857 #[test] 862 #[test]
858 fn add_function_in_module() { 863 fn add_function_in_module() {
859 check_assist( 864 check_assist(
860 add_function, 865 generate_function,
861 r" 866 r"
862mod bar {} 867mod bar {}
863 868
@@ -885,7 +890,7 @@ fn foo() {
885 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165 890 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
886 fn qualified_path_uses_correct_scope() { 891 fn qualified_path_uses_correct_scope() {
887 check_assist( 892 check_assist(
888 add_function, 893 generate_function,
889 " 894 "
890mod foo { 895mod foo {
891 pub struct Foo; 896 pub struct Foo;
@@ -916,7 +921,7 @@ fn baz(foo: foo::Foo) {
916 #[test] 921 #[test]
917 fn add_function_in_module_containing_other_items() { 922 fn add_function_in_module_containing_other_items() {
918 check_assist( 923 check_assist(
919 add_function, 924 generate_function,
920 r" 925 r"
921mod bar { 926mod bar {
922 fn something_else() {} 927 fn something_else() {}
@@ -945,7 +950,7 @@ fn foo() {
945 #[test] 950 #[test]
946 fn add_function_in_nested_module() { 951 fn add_function_in_nested_module() {
947 check_assist( 952 check_assist(
948 add_function, 953 generate_function,
949 r" 954 r"
950mod bar { 955mod bar {
951 mod baz {} 956 mod baz {}
@@ -974,7 +979,7 @@ fn foo() {
974 #[test] 979 #[test]
975 fn add_function_in_another_file() { 980 fn add_function_in_another_file() {
976 check_assist( 981 check_assist(
977 add_function, 982 generate_function,
978 r" 983 r"
979//- /main.rs 984//- /main.rs
980mod foo; 985mod foo;
@@ -996,7 +1001,7 @@ pub(crate) fn bar() {
996 #[test] 1001 #[test]
997 fn add_function_not_applicable_if_function_already_exists() { 1002 fn add_function_not_applicable_if_function_already_exists() {
998 check_assist_not_applicable( 1003 check_assist_not_applicable(
999 add_function, 1004 generate_function,
1000 r" 1005 r"
1001fn foo() { 1006fn foo() {
1002 bar<|>(); 1007 bar<|>();
@@ -1013,7 +1018,7 @@ fn bar() {}
1013 // bar is resolved, but baz isn't. 1018 // bar is resolved, but baz isn't.
1014 // The assist is only active if the cursor is on an unresolved path, 1019 // The assist is only active if the cursor is on an unresolved path,
1015 // but the assist should only be offered if the path is a function call. 1020 // but the assist should only be offered if the path is a function call.
1016 add_function, 1021 generate_function,
1017 r" 1022 r"
1018fn foo() { 1023fn foo() {
1019 bar(b<|>az); 1024 bar(b<|>az);
@@ -1028,7 +1033,7 @@ fn bar(baz: ()) {}
1028 #[ignore] 1033 #[ignore]
1029 fn create_method_with_no_args() { 1034 fn create_method_with_no_args() {
1030 check_assist( 1035 check_assist(
1031 add_function, 1036 generate_function,
1032 r" 1037 r"
1033struct Foo; 1038struct Foo;
1034impl Foo { 1039impl Foo {
diff --git a/crates/ra_assists/src/handlers/generate_impl.rs b/crates/ra_assists/src/handlers/generate_impl.rs
new file mode 100644
index 000000000..d9b87c9c0
--- /dev/null
+++ b/crates/ra_assists/src/handlers/generate_impl.rs
@@ -0,0 +1,109 @@
1use ra_syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner};
2use stdx::{format_to, SepBy};
3
4use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6// Assist: generate_impl
7//
8// Adds a new inherent impl for a type.
9//
10// ```
11// struct Ctx<T: Clone> {
12// data: T,<|>
13// }
14// ```
15// ->
16// ```
17// struct Ctx<T: Clone> {
18// data: T,
19// }
20//
21// impl<T: Clone> Ctx<T> {
22// $0
23// }
24// ```
25pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
27 let name = nominal.name()?;
28 let target = nominal.syntax().text_range();
29 acc.add(
30 AssistId("generate_impl", AssistKind::Generate),
31 format!("Generate impl for `{}`", name),
32 target,
33 |edit| {
34 let type_params = nominal.generic_param_list();
35 let start_offset = nominal.syntax().text_range().end();
36 let mut buf = String::new();
37 buf.push_str("\n\nimpl");
38 if let Some(type_params) = &type_params {
39 format_to!(buf, "{}", type_params.syntax());
40 }
41 buf.push_str(" ");
42 buf.push_str(name.text().as_str());
43 if let Some(type_params) = type_params {
44 let lifetime_params = type_params
45 .lifetime_params()
46 .filter_map(|it| it.lifetime_token())
47 .map(|it| it.text().clone());
48 let type_params = type_params
49 .type_params()
50 .filter_map(|it| it.name())
51 .map(|it| it.text().clone());
52
53 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
54 format_to!(buf, "<{}>", generic_params)
55 }
56 match ctx.config.snippet_cap {
57 Some(cap) => {
58 buf.push_str(" {\n $0\n}");
59 edit.insert_snippet(cap, start_offset, buf);
60 }
61 None => {
62 buf.push_str(" {\n}");
63 edit.insert(start_offset, buf);
64 }
65 }
66 },
67 )
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::tests::{check_assist, check_assist_target};
73
74 use super::*;
75
76 #[test]
77 fn test_add_impl() {
78 check_assist(
79 generate_impl,
80 "struct Foo {<|>}\n",
81 "struct Foo {}\n\nimpl Foo {\n $0\n}\n",
82 );
83 check_assist(
84 generate_impl,
85 "struct Foo<T: Clone> {<|>}",
86 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
87 );
88 check_assist(
89 generate_impl,
90 "struct Foo<'a, T: Foo<'a>> {<|>}",
91 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
92 );
93 }
94
95 #[test]
96 fn add_impl_target() {
97 check_assist_target(
98 generate_impl,
99 "
100struct SomeThingIrrelevant;
101/// Has a lifetime parameter
102struct Foo<'a, T: Foo<'a>> {<|>}
103struct EvenMoreIrrelevant;
104",
105 "/// Has a lifetime parameter
106struct Foo<'a, T: Foo<'a>> {}",
107 );
108 }
109}
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/generate_new.rs
index 837aa8377..b84aa24b6 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/generate_new.rs
@@ -1,15 +1,13 @@
1use hir::Adt; 1use hir::Adt;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{ 3 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
4 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
5 },
6 T, 4 T,
7}; 5};
8use stdx::{format_to, SepBy}; 6use stdx::{format_to, SepBy};
9 7
10use crate::{AssistContext, AssistId, Assists}; 8use crate::{AssistContext, AssistId, AssistKind, Assists};
11 9
12// Assist: add_new 10// Assist: generate_new
13// 11//
14// Adds a new inherent impl for a type. 12// Adds a new inherent impl for a type.
15// 13//
@@ -29,8 +27,8 @@ use crate::{AssistContext, AssistId, Assists};
29// } 27// }
30// 28//
31// ``` 29// ```
32pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 31 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
34 32
35 // We want to only apply this to non-union structs with named fields 33 // We want to only apply this to non-union structs with named fields
36 let field_list = match strukt.kind() { 34 let field_list = match strukt.kind() {
@@ -42,7 +40,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 40 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 41
44 let target = strukt.syntax().text_range(); 42 let target = strukt.syntax().text_range();
45 acc.add(AssistId("add_new"), "Add default constructor", target, |builder| { 43 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
46 let mut buf = String::with_capacity(512); 44 let mut buf = String::with_capacity(512);
47 45
48 if impl_def.is_some() { 46 if impl_def.is_some() {
@@ -53,9 +51,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
53 51
54 let params = field_list 52 let params = field_list
55 .fields() 53 .fields()
56 .filter_map(|f| { 54 .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
57 Some(format!("{}: {}", f.name()?.syntax(), f.ascribed_type()?.syntax()))
58 })
59 .sep_by(", "); 55 .sep_by(", ");
60 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", "); 56 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
61 57
@@ -90,8 +86,8 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
90 86
91// Generates the surrounding `impl Type { <code> }` including type and lifetime 87// Generates the surrounding `impl Type { <code> }` including type and lifetime
92// parameters 88// parameters
93fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { 89fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
94 let type_params = strukt.type_param_list(); 90 let type_params = strukt.generic_param_list();
95 let mut buf = String::with_capacity(code.len()); 91 let mut buf = String::with_capacity(code.len());
96 buf.push_str("\n\nimpl"); 92 buf.push_str("\n\nimpl");
97 if let Some(type_params) = &type_params { 93 if let Some(type_params) = &type_params {
@@ -121,15 +117,15 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
121// 117//
122// FIXME: change the new fn checking to a more semantic approach when that's more 118// FIXME: change the new fn checking to a more semantic approach when that's more
123// viable (e.g. we process proc macros, etc) 119// viable (e.g. we process proc macros, etc)
124fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { 120fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
125 let db = ctx.db; 121 let db = ctx.db();
126 let module = strukt.syntax().ancestors().find(|node| { 122 let module = strukt.syntax().ancestors().find(|node| {
127 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 123 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
128 })?; 124 })?;
129 125
130 let struct_def = ctx.sema.to_def(strukt)?; 126 let struct_def = ctx.sema.to_def(strukt)?;
131 127
132 let block = module.descendants().filter_map(ast::ImplDef::cast).find_map(|impl_blk| { 128 let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
133 let blk = ctx.sema.to_def(&impl_blk)?; 129 let blk = ctx.sema.to_def(&impl_blk)?;
134 130
135 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` 131 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
@@ -157,10 +153,10 @@ fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Opti
157 Some(block) 153 Some(block)
158} 154}
159 155
160fn has_new_fn(imp: &ast::ImplDef) -> bool { 156fn has_new_fn(imp: &ast::Impl) -> bool {
161 if let Some(il) = imp.item_list() { 157 if let Some(il) = imp.assoc_item_list() {
162 for item in il.assoc_items() { 158 for item in il.assoc_items() {
163 if let ast::AssocItem::FnDef(f) = item { 159 if let ast::AssocItem::Fn(f) = item {
164 if let Some(name) = f.name() { 160 if let Some(name) = f.name() {
165 if name.text().eq_ignore_ascii_case("new") { 161 if name.text().eq_ignore_ascii_case("new") {
166 return true; 162 return true;
@@ -181,10 +177,10 @@ mod tests {
181 177
182 #[test] 178 #[test]
183 #[rustfmt::skip] 179 #[rustfmt::skip]
184 fn test_add_new() { 180 fn test_generate_new() {
185 // Check output of generation 181 // Check output of generation
186 check_assist( 182 check_assist(
187 add_new, 183 generate_new,
188"struct Foo {<|>}", 184"struct Foo {<|>}",
189"struct Foo {} 185"struct Foo {}
190 186
@@ -194,7 +190,7 @@ impl Foo {
194", 190",
195 ); 191 );
196 check_assist( 192 check_assist(
197 add_new, 193 generate_new,
198"struct Foo<T: Clone> {<|>}", 194"struct Foo<T: Clone> {<|>}",
199"struct Foo<T: Clone> {} 195"struct Foo<T: Clone> {}
200 196
@@ -204,7 +200,7 @@ impl<T: Clone> Foo<T> {
204", 200",
205 ); 201 );
206 check_assist( 202 check_assist(
207 add_new, 203 generate_new,
208"struct Foo<'a, T: Foo<'a>> {<|>}", 204"struct Foo<'a, T: Foo<'a>> {<|>}",
209"struct Foo<'a, T: Foo<'a>> {} 205"struct Foo<'a, T: Foo<'a>> {}
210 206
@@ -214,7 +210,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
214", 210",
215 ); 211 );
216 check_assist( 212 check_assist(
217 add_new, 213 generate_new,
218"struct Foo { baz: String <|>}", 214"struct Foo { baz: String <|>}",
219"struct Foo { baz: String } 215"struct Foo { baz: String }
220 216
@@ -224,7 +220,7 @@ impl Foo {
224", 220",
225 ); 221 );
226 check_assist( 222 check_assist(
227 add_new, 223 generate_new,
228"struct Foo { baz: String, qux: Vec<i32> <|>}", 224"struct Foo { baz: String, qux: Vec<i32> <|>}",
229"struct Foo { baz: String, qux: Vec<i32> } 225"struct Foo { baz: String, qux: Vec<i32> }
230 226
@@ -236,7 +232,7 @@ impl Foo {
236 232
237 // Check that visibility modifiers don't get brought in for fields 233 // Check that visibility modifiers don't get brought in for fields
238 check_assist( 234 check_assist(
239 add_new, 235 generate_new,
240"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", 236"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
241"struct Foo { pub baz: String, pub qux: Vec<i32> } 237"struct Foo { pub baz: String, pub qux: Vec<i32> }
242 238
@@ -248,7 +244,7 @@ impl Foo {
248 244
249 // Check that it reuses existing impls 245 // Check that it reuses existing impls
250 check_assist( 246 check_assist(
251 add_new, 247 generate_new,
252"struct Foo {<|>} 248"struct Foo {<|>}
253 249
254impl Foo {} 250impl Foo {}
@@ -261,7 +257,7 @@ impl Foo {
261", 257",
262 ); 258 );
263 check_assist( 259 check_assist(
264 add_new, 260 generate_new,
265"struct Foo {<|>} 261"struct Foo {<|>}
266 262
267impl Foo { 263impl Foo {
@@ -279,7 +275,7 @@ impl Foo {
279 ); 275 );
280 276
281 check_assist( 277 check_assist(
282 add_new, 278 generate_new,
283"struct Foo {<|>} 279"struct Foo {<|>}
284 280
285impl Foo { 281impl Foo {
@@ -304,7 +300,7 @@ impl Foo {
304 300
305 // Check visibility of new fn based on struct 301 // Check visibility of new fn based on struct
306 check_assist( 302 check_assist(
307 add_new, 303 generate_new,
308"pub struct Foo {<|>}", 304"pub struct Foo {<|>}",
309"pub struct Foo {} 305"pub struct Foo {}
310 306
@@ -314,7 +310,7 @@ impl Foo {
314", 310",
315 ); 311 );
316 check_assist( 312 check_assist(
317 add_new, 313 generate_new,
318"pub(crate) struct Foo {<|>}", 314"pub(crate) struct Foo {<|>}",
319"pub(crate) struct Foo {} 315"pub(crate) struct Foo {}
320 316
@@ -326,9 +322,9 @@ impl Foo {
326 } 322 }
327 323
328 #[test] 324 #[test]
329 fn add_new_not_applicable_if_fn_exists() { 325 fn generate_new_not_applicable_if_fn_exists() {
330 check_assist_not_applicable( 326 check_assist_not_applicable(
331 add_new, 327 generate_new,
332 " 328 "
333struct Foo {<|>} 329struct Foo {<|>}
334 330
@@ -340,7 +336,7 @@ impl Foo {
340 ); 336 );
341 337
342 check_assist_not_applicable( 338 check_assist_not_applicable(
343 add_new, 339 generate_new,
344 " 340 "
345struct Foo {<|>} 341struct Foo {<|>}
346 342
@@ -353,9 +349,9 @@ impl Foo {
353 } 349 }
354 350
355 #[test] 351 #[test]
356 fn add_new_target() { 352 fn generate_new_target() {
357 check_assist_target( 353 check_assist_target(
358 add_new, 354 generate_new,
359 " 355 "
360struct SomeThingIrrelevant; 356struct SomeThingIrrelevant;
361/// Has a lifetime parameter 357/// Has a lifetime parameter
@@ -370,7 +366,7 @@ struct Foo<'a, T: Foo<'a>> {}",
370 #[test] 366 #[test]
371 fn test_unrelated_new() { 367 fn test_unrelated_new() {
372 check_assist( 368 check_assist(
373 add_new, 369 generate_new,
374 r##" 370 r##"
375pub struct AstId<N: AstNode> { 371pub struct AstId<N: AstNode> {
376 file_id: HirFileId, 372 file_id: HirFileId,
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
index d26e68847..2fdfabaf5 100644
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ra_assists/src/handlers/inline_local_variable.rs
@@ -7,7 +7,7 @@ use test_utils::mark;
7 7
8use crate::{ 8use crate::{
9 assist_context::{AssistContext, Assists}, 9 assist_context::{AssistContext, Assists},
10 AssistId, 10 AssistId, AssistKind,
11}; 11};
12 12
13// Assist: inline_local_variable 13// Assist: inline_local_variable
@@ -44,7 +44,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
44 44
45 let def = ctx.sema.to_def(&bind_pat)?; 45 let def = ctx.sema.to_def(&bind_pat)?;
46 let def = Definition::Local(def); 46 let def = Definition::Local(def);
47 let refs = def.find_usages(ctx.db, None); 47 let refs = def.find_usages(&ctx.sema, None);
48 if refs.is_empty() { 48 if refs.is_empty() {
49 mark::hit!(test_not_applicable_if_variable_unused); 49 mark::hit!(test_not_applicable_if_variable_unused);
50 return None; 50 return None;
@@ -110,13 +110,19 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
110 let init_in_paren = format!("({})", &init_str); 110 let init_in_paren = format!("({})", &init_str);
111 111
112 let target = bind_pat.syntax().text_range(); 112 let target = bind_pat.syntax().text_range();
113 acc.add(AssistId("inline_local_variable"), "Inline variable", target, move |builder| { 113 acc.add(
114 builder.delete(delete_range); 114 AssistId("inline_local_variable", AssistKind::RefactorInline),
115 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) { 115 "Inline variable",
116 let replacement = if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 116 target,
117 builder.replace(desc.file_range.range, replacement) 117 move |builder| {
118 } 118 builder.delete(delete_range);
119 }) 119 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
120 let replacement =
121 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
122 builder.replace(desc.file_range.range, replacement)
123 }
124 },
125 )
120} 126}
121 127
122#[cfg(test)] 128#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
index 28fcbc9ba..c3134f64d 100644
--- a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
@@ -1,10 +1,10 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, 2 ast::{self, GenericParamsOwner, NameOwner},
3 AstNode, SyntaxKind, TextRange, TextSize, 3 AstNode, SyntaxKind, TextRange, TextSize,
4}; 4};
5use rustc_hash::FxHashSet; 5use rustc_hash::FxHashSet;
6 6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; 7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8 8
9static ASSIST_NAME: &str = "introduce_named_lifetime"; 9static ASSIST_NAME: &str = "introduce_named_lifetime";
10static ASSIST_LABEL: &str = "Introduce named lifetime"; 10static ASSIST_LABEL: &str = "Introduce named lifetime";
@@ -38,9 +38,9 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
38 let lifetime_token = ctx 38 let lifetime_token = ctx
39 .find_token_at_offset(SyntaxKind::LIFETIME) 39 .find_token_at_offset(SyntaxKind::LIFETIME)
40 .filter(|lifetime| lifetime.text() == "'_")?; 40 .filter(|lifetime| lifetime.text() == "'_")?;
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { 41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { 43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::Impl::cast) {
44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) 44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
45 } else { 45 } else {
46 None 46 None
@@ -50,11 +50,11 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
50/// Generate the assist for the fn def case 50/// Generate the assist for the fn def case
51fn generate_fn_def_assist( 51fn generate_fn_def_assist(
52 acc: &mut Assists, 52 acc: &mut Assists,
53 fn_def: &ast::FnDef, 53 fn_def: &ast::Fn,
54 lifetime_loc: TextRange, 54 lifetime_loc: TextRange,
55) -> Option<()> { 55) -> Option<()> {
56 let param_list: ast::ParamList = fn_def.param_list()?; 56 let param_list: ast::ParamList = fn_def.param_list()?;
57 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; 57 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
58 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); 58 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
59 let self_param = 59 let self_param =
60 // use the self if it's a reference and has no explicit lifetime 60 // use the self if it's a reference and has no explicit lifetime
@@ -67,7 +67,7 @@ fn generate_fn_def_assist(
67 // otherwise, if there's a single reference parameter without a named liftime, use that 67 // otherwise, if there's a single reference parameter without a named liftime, use that
68 let fn_params_without_lifetime: Vec<_> = param_list 68 let fn_params_without_lifetime: Vec<_> = param_list
69 .params() 69 .params()
70 .filter_map(|param| match param.ascribed_type() { 70 .filter_map(|param| match param.ty() {
71 Some(ast::TypeRef::ReferenceType(ascribed_type)) 71 Some(ast::TypeRef::ReferenceType(ascribed_type))
72 if ascribed_type.lifetime_token() == None => 72 if ascribed_type.lifetime_token() == None =>
73 { 73 {
@@ -83,7 +83,7 @@ fn generate_fn_def_assist(
83 _ => return None, 83 _ => return None,
84 } 84 }
85 }; 85 };
86 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { 86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
87 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param); 87 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
88 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 88 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
89 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); 89 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
@@ -93,12 +93,12 @@ fn generate_fn_def_assist(
93/// Generate the assist for the impl def case 93/// Generate the assist for the impl def case
94fn generate_impl_def_assist( 94fn generate_impl_def_assist(
95 acc: &mut Assists, 95 acc: &mut Assists,
96 impl_def: &ast::ImplDef, 96 impl_def: &ast::Impl,
97 lifetime_loc: TextRange, 97 lifetime_loc: TextRange,
98) -> Option<()> { 98) -> Option<()> {
99 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?; 99 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
100 let end_of_impl_kw = impl_def.impl_token()?.text_range().end(); 100 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
101 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { 101 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
102 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param); 102 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
103 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 103 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
104 }) 104 })
@@ -107,7 +107,7 @@ fn generate_impl_def_assist(
107/// Given a type parameter list, generate a unique lifetime parameter name 107/// Given a type parameter list, generate a unique lifetime parameter name
108/// which is not in the list 108/// which is not in the list
109fn generate_unique_lifetime_param_name( 109fn generate_unique_lifetime_param_name(
110 existing_type_param_list: &Option<ast::TypeParamList>, 110 existing_type_param_list: &Option<ast::GenericParamList>,
111) -> Option<char> { 111) -> Option<char> {
112 match existing_type_param_list { 112 match existing_type_param_list {
113 Some(type_params) => { 113 Some(type_params) => {
@@ -123,13 +123,13 @@ fn generate_unique_lifetime_param_name(
123 123
124/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise 124/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
125/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. 125/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
126fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>( 126fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
127 type_params_owner: &TypeParamsOwner, 127 type_params_owner: &TypeParamsOwner,
128 builder: &mut AssistBuilder, 128 builder: &mut AssistBuilder,
129 new_type_params_loc: TextSize, 129 new_type_params_loc: TextSize,
130 new_lifetime_param: char, 130 new_lifetime_param: char,
131) { 131) {
132 match type_params_owner.type_param_list() { 132 match type_params_owner.generic_param_list() {
133 // add the new lifetime parameter to an existing type param list 133 // add the new lifetime parameter to an existing type param list
134 Some(type_params) => { 134 Some(type_params) => {
135 builder.insert( 135 builder.insert(
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
index 59d278eb9..bbe3f3643 100644
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -6,7 +6,7 @@ use ra_syntax::{
6use crate::{ 6use crate::{
7 assist_context::{AssistContext, Assists}, 7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression, 8 utils::invert_boolean_expression,
9 AssistId, 9 AssistId, AssistKind,
10}; 10};
11 11
12// Assist: invert_if 12// Assist: invert_if
@@ -54,7 +54,7 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
54 let else_node = else_block.syntax(); 54 let else_node = else_block.syntax();
55 let else_range = else_node.text_range(); 55 let else_range = else_node.text_range();
56 let then_range = then_node.text_range(); 56 let then_range = then_node.text_range();
57 acc.add(AssistId("invert_if"), "Invert if", if_range, |edit| { 57 acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
58 edit.replace(cond_range, flip_cond.syntax().text()); 58 edit.replace(cond_range, flip_cond.syntax().text());
59 edit.replace(else_range, then_node.text()); 59 edit.replace(else_range, then_node.text());
60 edit.replace(then_range, else_node.text()); 60 edit.replace(then_range, else_node.text());
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
index ac0b3035c..c775fe25c 100644
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ b/crates/ra_assists/src/handlers/merge_imports.rs
@@ -8,7 +8,7 @@ use ra_syntax::{
8 8
9use crate::{ 9use crate::{
10 assist_context::{AssistContext, Assists}, 10 assist_context::{AssistContext, Assists},
11 AssistId, 11 AssistId, AssistKind,
12}; 12};
13 13
14// Assist: merge_imports 14// Assist: merge_imports
@@ -28,7 +28,7 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
28 let mut rewriter = SyntaxRewriter::default(); 28 let mut rewriter = SyntaxRewriter::default();
29 let mut offset = ctx.offset(); 29 let mut offset = ctx.offset();
30 30
31 if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { 31 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
32 let (merged, to_delete) = next_prev() 32 let (merged, to_delete) = next_prev()
33 .filter_map(|dir| neighbor(&use_item, dir)) 33 .filter_map(|dir| neighbor(&use_item, dir))
34 .filter_map(|it| Some((it.clone(), it.use_tree()?))) 34 .filter_map(|it| Some((it.clone(), it.use_tree()?)))
@@ -56,9 +56,14 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
56 }; 56 };
57 57
58 let target = tree.syntax().text_range(); 58 let target = tree.syntax().text_range();
59 acc.add(AssistId("merge_imports"), "Merge imports", target, |builder| { 59 acc.add(
60 builder.rewrite(rewriter); 60 AssistId("merge_imports", AssistKind::RefactorRewrite),
61 }) 61 "Merge imports",
62 target,
63 |builder| {
64 builder.rewrite(rewriter);
65 },
66 )
62} 67}
63 68
64fn next_prev() -> impl Iterator<Item = Direction> { 69fn next_prev() -> impl Iterator<Item = Direction> {
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index 90ce66378..186a1f618 100644
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ b/crates/ra_assists/src/handlers/merge_match_arms.rs
@@ -6,7 +6,7 @@ use ra_syntax::{
6 Direction, 6 Direction,
7}; 7};
8 8
9use crate::{AssistContext, AssistId, Assists, TextRange}; 9use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
10 10
11// Assist: merge_match_arms 11// Assist: merge_match_arms
12// 12//
@@ -59,25 +59,30 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option
59 return None; 59 return None;
60 } 60 }
61 61
62 acc.add(AssistId("merge_match_arms"), "Merge match arms", current_text_range, |edit| { 62 acc.add(
63 let pats = if arms_to_merge.iter().any(contains_placeholder) { 63 AssistId("merge_match_arms", AssistKind::RefactorRewrite),
64 "_".into() 64 "Merge match arms",
65 } else { 65 current_text_range,
66 arms_to_merge 66 |edit| {
67 .iter() 67 let pats = if arms_to_merge.iter().any(contains_placeholder) {
68 .filter_map(ast::MatchArm::pat) 68 "_".into()
69 .map(|x| x.syntax().to_string()) 69 } else {
70 .collect::<Vec<String>>() 70 arms_to_merge
71 .join(" | ") 71 .iter()
72 }; 72 .filter_map(ast::MatchArm::pat)
73 73 .map(|x| x.syntax().to_string())
74 let arm = format!("{} => {}", pats, current_expr.syntax().text()); 74 .collect::<Vec<String>>()
75 75 .join(" | ")
76 let start = arms_to_merge.first().unwrap().syntax().text_range().start(); 76 };
77 let end = arms_to_merge.last().unwrap().syntax().text_range().end(); 77
78 78 let arm = format!("{} => {}", pats, current_expr.syntax().text());
79 edit.replace(TextRange::new(start, end), arm); 79
80 }) 80 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
81 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
82
83 edit.replace(TextRange::new(start, end), arm);
84 },
85 )
81} 86}
82 87
83fn contains_placeholder(a: &ast::MatchArm) -> bool { 88fn contains_placeholder(a: &ast::MatchArm) -> bool {
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index be2a7eddc..6d394443e 100644
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ b/crates/ra_assists/src/handlers/move_bounds.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 T, 5 T,
6}; 6};
7 7
8use crate::{AssistContext, AssistId, Assists}; 8use crate::{AssistContext, AssistId, AssistKind, Assists};
9 9
10// Assist: move_bounds_to_where_clause 10// Assist: move_bounds_to_where_clause
11// 11//
@@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
23// } 23// }
24// ``` 24// ```
25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::TypeParamList>()?; 26 let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
27 27
28 let mut type_params = type_param_list.type_params(); 28 let mut type_params = type_param_list.type_params();
29 if type_params.all(|p| p.type_bound_list().is_none()) { 29 if type_params.all(|p| p.type_bound_list().is_none()) {
@@ -37,42 +37,49 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
37 37
38 let anchor = match_ast! { 38 let anchor = match_ast! {
39 match parent { 39 match parent {
40 ast::FnDef(it) => it.body()?.syntax().clone().into(), 40 ast::Fn(it) => it.body()?.syntax().clone().into(),
41 ast::TraitDef(it) => it.item_list()?.syntax().clone().into(), 41 ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(),
42 ast::ImplDef(it) => it.item_list()?.syntax().clone().into(), 42 ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(),
43 ast::EnumDef(it) => it.variant_list()?.syntax().clone().into(), 43 ast::Enum(it) => it.variant_list()?.syntax().clone().into(),
44 ast::StructDef(it) => { 44 ast::Struct(it) => {
45 it.syntax().children_with_tokens() 45 it.syntax().children_with_tokens()
46 .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == T![;])? 46 .find(|it| it.kind() == RECORD_FIELD_LIST || it.kind() == T![;])?
47 }, 47 },
48 _ => return None 48 _ => return None
49 } 49 }
50 }; 50 };
51 51
52 let target = type_param_list.syntax().text_range(); 52 let target = type_param_list.syntax().text_range();
53 acc.add(AssistId("move_bounds_to_where_clause"), "Move to where clause", target, |edit| { 53 acc.add(
54 let new_params = type_param_list 54 AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
55 .type_params() 55 "Move to where clause",
56 .filter(|it| it.type_bound_list().is_some()) 56 target,
57 .map(|type_param| { 57 |edit| {
58 let without_bounds = type_param.remove_bounds(); 58 let new_params = type_param_list
59 (type_param, without_bounds) 59 .type_params()
60 }); 60 .filter(|it| it.type_bound_list().is_some())
61 61 .map(|type_param| {
62 let new_type_param_list = type_param_list.replace_descendants(new_params); 62 let without_bounds = type_param.remove_bounds();
63 edit.replace_ast(type_param_list.clone(), new_type_param_list); 63 (type_param, without_bounds)
64 64 });
65 let where_clause = { 65
66 let predicates = type_param_list.type_params().filter_map(build_predicate); 66 let new_type_param_list = type_param_list.replace_descendants(new_params);
67 make::where_clause(predicates) 67 edit.replace_ast(type_param_list.clone(), new_type_param_list);
68 }; 68
69 69 let where_clause = {
70 let to_insert = match anchor.prev_sibling_or_token() { 70 let predicates = type_param_list.type_params().filter_map(build_predicate);
71 Some(ref elem) if elem.kind() == WHITESPACE => format!("{} ", where_clause.syntax()), 71 make::where_clause(predicates)
72 _ => format!(" {}", where_clause.syntax()), 72 };
73 }; 73
74 edit.insert(anchor.text_range().start(), to_insert); 74 let to_insert = match anchor.prev_sibling_or_token() {
75 }) 75 Some(ref elem) if elem.kind() == WHITESPACE => {
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
76} 83}
77 84
78fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> { 85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
index 7edcf0748..4060d34c6 100644
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ b/crates/ra_assists/src/handlers/move_guard.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 SyntaxKind::WHITESPACE, 3 SyntaxKind::WHITESPACE,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
7 7
8// Assist: move_guard_to_arm_body 8// Assist: move_guard_to_arm_body
9// 9//
@@ -40,17 +40,22 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
40 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); 40 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
41 41
42 let target = guard.syntax().text_range(); 42 let target = guard.syntax().text_range();
43 acc.add(AssistId("move_guard_to_arm_body"), "Move guard to arm body", target, |edit| { 43 acc.add(
44 match space_before_guard { 44 AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
45 Some(element) if element.kind() == WHITESPACE => { 45 "Move guard to arm body",
46 edit.delete(element.text_range()); 46 target,
47 } 47 |edit| {
48 _ => (), 48 match space_before_guard {
49 }; 49 Some(element) if element.kind() == WHITESPACE => {
50 edit.delete(element.text_range());
51 }
52 _ => (),
53 };
50 54
51 edit.delete(guard.syntax().text_range()); 55 edit.delete(guard.syntax().text_range());
52 edit.replace_node_and_indent(arm_expr.syntax(), buf); 56 edit.replace_node_and_indent(arm_expr.syntax(), buf);
53 }) 57 },
58 )
54} 59}
55 60
56// Assist: move_arm_cond_to_match_guard 61// Assist: move_arm_cond_to_match_guard
@@ -100,7 +105,7 @@ pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContex
100 105
101 let target = if_expr.syntax().text_range(); 106 let target = if_expr.syntax().text_range();
102 acc.add( 107 acc.add(
103 AssistId("move_arm_cond_to_match_guard"), 108 AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
104 "Move condition to match guard", 109 "Move condition to match guard",
105 target, 110 target,
106 |edit| { 111 |edit| {
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
index d22d0aa55..4e8a0c2db 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -1,11 +1,14 @@
1use std::borrow::Cow;
2
1use ra_syntax::{ 3use ra_syntax::{
2 ast::{self, HasStringValue}, 4 ast::{self, HasQuotes, HasStringValue},
3 AstToken, 5 AstToken,
4 SyntaxKind::{RAW_STRING, STRING}, 6 SyntaxKind::{RAW_STRING, STRING},
5 TextSize, 7 TextRange, TextSize,
6}; 8};
9use test_utils::mark;
7 10
8use crate::{AssistContext, AssistId, Assists}; 11use crate::{AssistContext, AssistId, AssistKind, Assists};
9 12
10// Assist: make_raw_string 13// Assist: make_raw_string
11// 14//
@@ -26,14 +29,24 @@ pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<
26 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?; 29 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 30 let value = token.value()?;
28 let target = token.syntax().text_range(); 31 let target = token.syntax().text_range();
29 acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { 32 acc.add(
30 let max_hash_streak = count_hashes(&value); 33 AssistId("make_raw_string", AssistKind::RefactorRewrite),
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 34 "Rewrite as raw string",
32 for _ in 0..hashes.capacity() { 35 target,
33 hashes.push('#'); 36 |edit| {
34 } 37 let hashes = "#".repeat(required_hashes(&value).max(1));
35 edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); 38 if matches!(value, Cow::Borrowed(_)) {
36 }) 39 // Avoid replacing the whole string to better position the cursor.
40 edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
41 edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
42 } else {
43 edit.replace(
44 token.syntax().text_range(),
45 format!("r{}\"{}\"{}", hashes, value, hashes),
46 );
47 }
48 },
49 )
37} 50}
38 51
39// Assist: make_usual_string 52// Assist: make_usual_string
@@ -55,11 +68,24 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
55 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?; 68 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
56 let value = token.value()?; 69 let value = token.value()?;
57 let target = token.syntax().text_range(); 70 let target = token.syntax().text_range();
58 acc.add(AssistId("make_usual_string"), "Rewrite as regular string", target, |edit| { 71 acc.add(
59 // parse inside string to escape `"` 72 AssistId("make_usual_string", AssistKind::RefactorRewrite),
60 let escaped = value.escape_default().to_string(); 73 "Rewrite as regular string",
61 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped)); 74 target,
62 }) 75 |edit| {
76 // parse inside string to escape `"`
77 let escaped = value.escape_default().to_string();
78 if let Some(offsets) = token.quote_offsets() {
79 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
80 edit.replace(offsets.quotes.0, "\"");
81 edit.replace(offsets.quotes.1, "\"");
82 return;
83 }
84 }
85
86 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
87 },
88 )
63} 89}
64 90
65// Assist: add_hash 91// Assist: add_hash
@@ -80,7 +106,7 @@ pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Optio
80pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 106pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
81 let token = ctx.find_token_at_offset(RAW_STRING)?; 107 let token = ctx.find_token_at_offset(RAW_STRING)?;
82 let target = token.text_range(); 108 let target = token.text_range();
83 acc.add(AssistId("add_hash"), "Add # to raw string", target, |edit| { 109 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
84 edit.insert(token.text_range().start() + TextSize::of('r'), "#"); 110 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
85 edit.insert(token.text_range().end(), "#"); 111 edit.insert(token.text_range().end(), "#");
86 }) 112 })
@@ -102,44 +128,58 @@ pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
102// } 128// }
103// ``` 129// ```
104pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 130pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
105 let token = ctx.find_token_at_offset(RAW_STRING)?; 131 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
132
106 let text = token.text().as_str(); 133 let text = token.text().as_str();
107 if text.starts_with("r\"") { 134 if !text.starts_with("r#") && text.ends_with('#') {
108 // no hash to remove
109 return None; 135 return None;
110 } 136 }
111 let target = token.text_range(); 137
112 acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { 138 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
113 let result = &text[2..text.len() - 1]; 139
114 let result = if result.starts_with('\"') { 140 let text_range = token.syntax().text_range();
115 // FIXME: this logic is wrong, not only the last has has to handled specially 141 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
116 // no more hash, escape 142
117 let internal_str = &result[1..result.len() - 1]; 143 if existing_hashes == required_hashes(internal_text) {
118 format!("\"{}\"", internal_str.escape_default().to_string()) 144 mark::hit!(cant_remove_required_hash);
119 } else { 145 return None;
120 result.to_owned() 146 }
121 }; 147
122 edit.replace(token.text_range(), format!("r{}", result)); 148 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
149 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
150 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
123 }) 151 })
124} 152}
125 153
126fn count_hashes(s: &str) -> usize { 154fn required_hashes(s: &str) -> usize {
127 let mut max_hash_streak = 0usize; 155 let mut res = 0usize;
128 for idx in s.match_indices("\"#").map(|(i, _)| i) { 156 for idx in s.match_indices('"').map(|(i, _)| i) {
129 let (_, sub) = s.split_at(idx + 1); 157 let (_, sub) = s.split_at(idx + 1);
130 let nb_hash = sub.chars().take_while(|c| *c == '#').count(); 158 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
131 if nb_hash > max_hash_streak { 159 res = res.max(n_hashes + 1)
132 max_hash_streak = nb_hash;
133 }
134 } 160 }
135 max_hash_streak 161 res
162}
163
164#[test]
165fn test_required_hashes() {
166 assert_eq!(0, required_hashes("abc"));
167 assert_eq!(0, required_hashes("###"));
168 assert_eq!(1, required_hashes("\""));
169 assert_eq!(2, required_hashes("\"#abc"));
170 assert_eq!(0, required_hashes("#abc"));
171 assert_eq!(3, required_hashes("#ab\"##c"));
172 assert_eq!(5, required_hashes("#ab\"##\"####c"));
136} 173}
137 174
138#[cfg(test)] 175#[cfg(test)]
139mod test { 176mod test {
140 use super::*; 177 use test_utils::mark;
178
141 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 179 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
142 180
181 use super::*;
182
143 #[test] 183 #[test]
144 fn make_raw_string_target() { 184 fn make_raw_string_target() {
145 check_assist_target( 185 check_assist_target(
@@ -341,33 +381,21 @@ string"###;
341 fn remove_hash_works() { 381 fn remove_hash_works() {
342 check_assist( 382 check_assist(
343 remove_hash, 383 remove_hash,
344 r##" 384 r##"fn f() { let s = <|>r#"random string"#; }"##,
345 fn f() { 385 r#"fn f() { let s = r"random string"; }"#,
346 let s = <|>r#"random string"#;
347 }
348 "##,
349 r#"
350 fn f() {
351 let s = r"random string";
352 }
353 "#,
354 ) 386 )
355 } 387 }
356 388
357 #[test] 389 #[test]
358 fn remove_hash_with_quote_works() { 390 fn cant_remove_required_hash() {
359 check_assist( 391 mark::check!(cant_remove_required_hash);
392 check_assist_not_applicable(
360 remove_hash, 393 remove_hash,
361 r##" 394 r##"
362 fn f() { 395 fn f() {
363 let s = <|>r#"random"str"ing"#; 396 let s = <|>r#"random"str"ing"#;
364 } 397 }
365 "##, 398 "##,
366 r#"
367 fn f() {
368 let s = r"random\"str\"ing";
369 }
370 "#,
371 ) 399 )
372 } 400 }
373 401
@@ -389,27 +417,13 @@ string"###;
389 } 417 }
390 418
391 #[test] 419 #[test]
392 fn remove_hash_not_works() { 420 fn remove_hash_doesnt_work() {
393 check_assist_not_applicable( 421 check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
394 remove_hash,
395 r#"
396 fn f() {
397 let s = <|>"random string";
398 }
399 "#,
400 );
401 } 422 }
402 423
403 #[test] 424 #[test]
404 fn remove_hash_no_hash_not_works() { 425 fn remove_hash_no_hash_doesnt_work() {
405 check_assist_not_applicable( 426 check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
406 remove_hash,
407 r#"
408 fn f() {
409 let s = <|>r"random string";
410 }
411 "#,
412 );
413 } 427 }
414 428
415 #[test] 429 #[test]
@@ -487,14 +501,4 @@ string"###;
487 "#, 501 "#,
488 ); 502 );
489 } 503 }
490
491 #[test]
492 fn count_hashes_test() {
493 assert_eq!(0, count_hashes("abc"));
494 assert_eq!(0, count_hashes("###"));
495 assert_eq!(1, count_hashes("\"#abc"));
496 assert_eq!(0, count_hashes("#abc"));
497 assert_eq!(2, count_hashes("#ab\"##c"));
498 assert_eq!(4, count_hashes("#ab\"##\"####c"));
499 }
500} 504}
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index 961ee1731..9430ce1b5 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -1,9 +1,9 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 TextSize, T, 3 TextRange, TextSize, T,
4}; 4};
5 5
6use crate::{AssistContext, AssistId, Assists}; 6use crate::{AssistContext, AssistId, AssistKind, Assists};
7 7
8// Assist: remove_dbg 8// Assist: remove_dbg
9// 9//
@@ -27,19 +27,33 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 return None; 27 return None;
28 } 28 }
29 29
30 let macro_range = macro_call.syntax().text_range(); 30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31 31
32 let macro_content = { 32 let macro_end = if macro_call.semicolon_token().is_some() {
33 let macro_args = macro_call.token_tree()?.syntax().clone(); 33 macro_call.syntax().text_range().end() - TextSize::of(';')
34 } else {
35 macro_call.syntax().text_range().end()
36 };
34 37
35 let text = macro_args.text(); 38 // macro_range determines what will be deleted and replaced with macro_content
36 let without_parens = TextSize::of('(')..text.len() - TextSize::of(')'); 39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
37 text.slice(without_parens).to_string() 40 let paste_instead_of_dbg = {
41 let text = macro_call.token_tree()?.syntax().text();
42
43 // leafiness determines if we should include the parenthesis or not
44 let slice_index: TextRange = if is_leaf {
45 // leaf means - we can extract the contents of the dbg! in text
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')'))
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
38 }; 52 };
39 53
40 let target = macro_call.syntax().text_range(); 54 let target = macro_call.syntax().text_range();
41 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { 55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
42 builder.replace(macro_range, macro_content); 56 builder.replace(macro_range, paste_instead_of_dbg);
43 }) 57 })
44} 58}
45 59
@@ -99,6 +113,7 @@ fn foo(n: usize) {
99", 113",
100 ); 114 );
101 } 115 }
116
102 #[test] 117 #[test]
103 fn test_remove_dbg_with_brackets_and_braces() { 118 fn test_remove_dbg_with_brackets_and_braces() {
104 check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1"); 119 check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
@@ -113,7 +128,7 @@ fn foo(n: usize) {
113 } 128 }
114 129
115 #[test] 130 #[test]
116 fn remove_dbg_target() { 131 fn test_remove_dbg_target() {
117 check_assist_target( 132 check_assist_target(
118 remove_dbg, 133 remove_dbg,
119 " 134 "
@@ -126,4 +141,65 @@ fn foo(n: usize) {
126 "dbg!(n.checked_sub(4))", 141 "dbg!(n.checked_sub(4))",
127 ); 142 );
128 } 143 }
144
145 #[test]
146 fn test_remove_dbg_keep_semicolon() {
147 // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
148 // not quite though
149 // adding a comment at the end of the line makes
150 // the ast::MacroCall to include the semicolon at the end
151 check_assist(
152 remove_dbg,
153 r#"let res = <|>dbg!(1 * 20); // needless comment"#,
154 r#"let res = 1 * 20; // needless comment"#,
155 );
156 }
157
158 #[test]
159 fn test_remove_dbg_keep_expression() {
160 check_assist(
161 remove_dbg,
162 r#"let res = <|>dbg!(a + b).foo();"#,
163 r#"let res = (a + b).foo();"#,
164 );
165 }
166
167 #[test]
168 fn test_remove_dbg_from_inside_fn() {
169 check_assist_target(
170 remove_dbg,
171 r#"
172fn square(x: u32) -> u32 {
173 x * x
174}
175
176fn main() {
177 let x = square(dbg<|>!(5 + 10));
178 println!("{}", x);
179}"#,
180 "dbg!(5 + 10)",
181 );
182
183 check_assist(
184 remove_dbg,
185 r#"
186fn square(x: u32) -> u32 {
187 x * x
188}
189
190fn main() {
191 let x = square(dbg<|>!(5 + 10));
192 println!("{}", x);
193}"#,
194 r#"
195fn square(x: u32) -> u32 {
196 x * x
197}
198
199fn main() {
200 let x = square(5 + 10);
201 println!("{}", x);
202}"#,
203 );
204 }
129} 205}
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
index fe4eada03..ef55c354e 100644
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ b/crates/ra_assists/src/handlers/remove_mut.rs
@@ -1,6 +1,6 @@
1use ra_syntax::{SyntaxKind, TextRange, T}; 1use ra_syntax::{SyntaxKind, TextRange, T};
2 2
3use crate::{AssistContext, AssistId, Assists}; 3use crate::{AssistContext, AssistId, AssistKind, Assists};
4 4
5// Assist: remove_mut 5// Assist: remove_mut
6// 6//
@@ -26,7 +26,12 @@ pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 }; 26 };
27 27
28 let target = mut_token.text_range(); 28 let target = mut_token.text_range();
29 acc.add(AssistId("remove_mut"), "Remove `mut` keyword", target, |builder| { 29 acc.add(
30 builder.delete(TextRange::new(delete_from, delete_to)); 30 AssistId("remove_mut", AssistKind::Refactor),
31 }) 31 "Remove `mut` keyword",
32 target,
33 |builder| {
34 builder.delete(TextRange::new(delete_from, delete_to));
35 },
36 )
32} 37}
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
index bc58ce5fe..120250e79 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -5,7 +5,7 @@ use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode}; 6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7 7
8use crate::{AssistContext, AssistId, Assists}; 8use crate::{AssistContext, AssistId, AssistKind, Assists};
9 9
10// Assist: reorder_fields 10// Assist: reorder_fields
11// 11//
@@ -23,7 +23,7 @@ use crate::{AssistContext, AssistId, Assists};
23// ``` 23// ```
24// 24//
25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 reorder::<ast::RecordLit>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx)) 26 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
27} 27}
28 28
29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -42,16 +42,21 @@ fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 } 42 }
43 43
44 let target = record.syntax().text_range(); 44 let target = record.syntax().text_range();
45 acc.add(AssistId("reorder_fields"), "Reorder record fields", target, |edit| { 45 acc.add(
46 for (old, new) in fields.iter().zip(&sorted_fields) { 46 AssistId("reorder_fields", AssistKind::RefactorRewrite),
47 algo::diff(old, new).into_text_edit(edit.text_edit_builder()); 47 "Reorder record fields",
48 } 48 target,
49 }) 49 |edit| {
50 for (old, new) in fields.iter().zip(&sorted_fields) {
51 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
52 }
53 },
54 )
50} 55}
51 56
52fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> { 57fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
53 match node.kind() { 58 match node.kind() {
54 RECORD_LIT => vec![RECORD_FIELD], 59 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
55 RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT], 60 RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT],
56 _ => vec![], 61 _ => vec![],
57 } 62 }
@@ -60,7 +65,7 @@ fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
60fn get_field_name(node: &SyntaxNode) -> String { 65fn get_field_name(node: &SyntaxNode) -> String {
61 let res = match_ast! { 66 let res = match_ast! {
62 match node { 67 match node {
63 ast::RecordField(field) => field.field_name().map(|it| it.to_string()), 68 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
64 ast::RecordFieldPat(field) => field.field_name().map(|it| it.to_string()), 69 ast::RecordFieldPat(field) => field.field_name().map(|it| it.to_string()),
65 _ => None, 70 _ => None,
66 } 71 }
@@ -90,10 +95,10 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
90fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> { 95fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
91 Some( 96 Some(
92 struct_definition(path, &ctx.sema)? 97 struct_definition(path, &ctx.sema)?
93 .fields(ctx.db) 98 .fields(ctx.db())
94 .iter() 99 .iter()
95 .enumerate() 100 .enumerate()
96 .map(|(idx, field)| (field.name(ctx.db).to_string(), idx)) 101 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
97 .collect(), 102 .collect(),
98 ) 103 )
99} 104}
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
index dfcd787de..b7e30a7f2 100644
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
@@ -8,7 +8,7 @@ use ra_syntax::{
8 AstNode, 8 AstNode,
9}; 9};
10 10
11use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; 11use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
12 12
13// Assist: replace_if_let_with_match 13// Assist: replace_if_let_with_match
14// 14//
@@ -48,29 +48,35 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
48 }; 48 };
49 49
50 let target = if_expr.syntax().text_range(); 50 let target = if_expr.syntax().text_range();
51 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { 51 acc.add(
52 let match_expr = { 52 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
53 let then_arm = { 53 "Replace with match",
54 let then_block = then_block.reset_indent().indent(IndentLevel(1)); 54 target,
55 let then_expr = unwrap_trivial_block(then_block); 55 move |edit| {
56 make::match_arm(vec![pat.clone()], then_expr) 56 let match_expr = {
57 let then_arm = {
58 let then_block = then_block.reset_indent().indent(IndentLevel(1));
59 let then_expr = unwrap_trivial_block(then_block);
60 make::match_arm(vec![pat.clone()], then_expr)
61 };
62 let else_arm = {
63 let pattern = ctx
64 .sema
65 .type_of_pat(&pat)
66 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
67 .map(|it| it.sad_pattern())
68 .unwrap_or_else(|| make::placeholder_pat().into());
69 let else_expr = unwrap_trivial_block(else_block);
70 make::match_arm(vec![pattern], else_expr)
71 };
72 let match_expr =
73 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
74 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
57 }; 75 };
58 let else_arm = {
59 let pattern = ctx
60 .sema
61 .type_of_pat(&pat)
62 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
63 .map(|it| it.sad_pattern())
64 .unwrap_or_else(|| make::placeholder_pat().into());
65 let else_expr = unwrap_trivial_block(else_block);
66 make::match_arm(vec![pattern], else_expr)
67 };
68 let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
69 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
70 };
71 76
72 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 77 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
73 }) 78 },
79 )
74} 80}
75 81
76#[cfg(test)] 82#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
index 761557ac0..a49292c97 100644
--- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs
@@ -9,7 +9,7 @@ use ra_syntax::{
9 AstNode, T, 9 AstNode, T,
10}; 10};
11 11
12use crate::{utils::TryEnum, AssistContext, AssistId, Assists}; 12use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
13 13
14// Assist: replace_let_with_if_let 14// Assist: replace_let_with_if_let
15// 15//
@@ -44,24 +44,31 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case()); 44 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
45 45
46 let target = let_kw.text_range(); 46 let target = let_kw.text_range();
47 acc.add(AssistId("replace_let_with_if_let"), "Replace with if-let", target, |edit| { 47 acc.add(
48 let with_placeholder: ast::Pat = match happy_variant { 48 AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
49 None => make::placeholder_pat().into(), 49 "Replace with if-let",
50 Some(var_name) => make::tuple_struct_pat( 50 target,
51 make::path_unqualified(make::path_segment(make::name_ref(var_name))), 51 |edit| {
52 once(make::placeholder_pat().into()), 52 let with_placeholder: ast::Pat = match happy_variant {
53 ) 53 None => make::placeholder_pat().into(),
54 .into(), 54 Some(var_name) => make::tuple_struct_pat(
55 }; 55 make::path_unqualified(make::path_segment(make::name_ref(var_name))),
56 let block = make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); 56 once(make::placeholder_pat().into()),
57 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 57 )
58 let stmt = make::expr_stmt(if_); 58 .into(),
59 };
60 let block =
61 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
62 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block);
63 let stmt = make::expr_stmt(if_);
59 64
60 let placeholder = stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap(); 65 let placeholder =
61 let stmt = stmt.replace_descendant(placeholder.into(), original_pat); 66 stmt.syntax().descendants().find_map(ast::PlaceholderPat::cast).unwrap();
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
62 68
63 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt)); 69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
64 }) 70 },
71 )
65} 72}
66 73
67#[cfg(test)] 74#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index b4784c333..53496ede1 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -3,7 +3,7 @@ use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNo
3 3
4use crate::{ 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement}, 5 utils::{find_insert_use_container, insert_use_statement},
6 AssistContext, AssistId, Assists, 6 AssistContext, AssistId, AssistKind, Assists,
7}; 7};
8 8
9// Assist: replace_qualified_name_with_use 9// Assist: replace_qualified_name_with_use
@@ -25,7 +25,7 @@ pub(crate) fn replace_qualified_name_with_use(
25) -> Option<()> { 25) -> Option<()> {
26 let path: ast::Path = ctx.find_node_at_offset()?; 26 let path: ast::Path = ctx.find_node_at_offset()?;
27 // We don't want to mess with use statements 27 // We don't want to mess with use statements
28 if path.syntax().ancestors().find_map(ast::UseItem::cast).is_some() { 28 if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
29 return None; 29 return None;
30 } 30 }
31 31
@@ -37,7 +37,7 @@ pub(crate) fn replace_qualified_name_with_use(
37 37
38 let target = path.syntax().text_range(); 38 let target = path.syntax().text_range();
39 acc.add( 39 acc.add(
40 AssistId("replace_qualified_name_with_use"), 40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
41 "Replace qualified path with use", 41 "Replace qualified path with use",
42 target, 42 target,
43 |builder| { 43 |builder| {
@@ -85,7 +85,7 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
85 match child { 85 match child {
86 // Don't modify `use` items, as this can break the `use` item when injecting a new 86 // Don't modify `use` items, as this can break the `use` item when injecting a new
87 // import into the use tree. 87 // import into the use tree.
88 ast::UseItem(_it) => continue, 88 ast::Use(_it) => continue,
89 // Don't descend into submodules, they don't have the same `use` items in scope. 89 // Don't descend into submodules, they don't have the same `use` items in scope.
90 ast::Module(_it) => continue, 90 ast::Module(_it) => continue,
91 91
@@ -106,7 +106,7 @@ fn maybe_replace_path(
106 path: ast::Path, 106 path: ast::Path,
107 target: ast::Path, 107 target: ast::Path,
108) -> Option<()> { 108) -> Option<()> {
109 if !path_eq(path.clone(), target.clone()) { 109 if !path_eq(path.clone(), target) {
110 return None; 110 return None;
111 } 111 }
112 112
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
index cff7dfb81..e5a4bb23c 100644
--- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
+++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11 11
12use crate::{ 12use crate::{
13 utils::{render_snippet, Cursor, TryEnum}, 13 utils::{render_snippet, Cursor, TryEnum},
14 AssistContext, AssistId, Assists, 14 AssistContext, AssistId, AssistKind, Assists,
15}; 15};
16 16
17// Assist: replace_unwrap_with_match 17// Assist: replace_unwrap_with_match
@@ -46,37 +46,43 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext)
46 let ty = ctx.sema.type_of_expr(&caller)?; 46 let ty = ctx.sema.type_of_expr(&caller)?;
47 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case(); 47 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
48 let target = method_call.syntax().text_range(); 48 let target = method_call.syntax().text_range();
49 acc.add(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", target, |builder| { 49 acc.add(
50 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); 50 AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite),
51 let it = make::bind_pat(make::name("a")).into(); 51 "Replace unwrap with match",
52 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); 52 target,
53 |builder| {
54 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
55 let it = make::bind_pat(make::name("a")).into();
56 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
53 57
54 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); 58 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
55 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); 59 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
56 60
57 let unreachable_call = make::expr_unreachable(); 61 let unreachable_call = make::expr_unreachable();
58 let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); 62 let err_arm =
63 make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call);
59 64
60 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); 65 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
61 let match_expr = make::expr_match(caller.clone(), match_arm_list) 66 let match_expr = make::expr_match(caller.clone(), match_arm_list)
62 .indent(IndentLevel::from_node(method_call.syntax())); 67 .indent(IndentLevel::from_node(method_call.syntax()));
63 68
64 let range = method_call.syntax().text_range(); 69 let range = method_call.syntax().text_range();
65 match ctx.config.snippet_cap { 70 match ctx.config.snippet_cap {
66 Some(cap) => { 71 Some(cap) => {
67 let err_arm = match_expr 72 let err_arm = match_expr
68 .syntax() 73 .syntax()
69 .descendants() 74 .descendants()
70 .filter_map(ast::MatchArm::cast) 75 .filter_map(ast::MatchArm::cast)
71 .last() 76 .last()
72 .unwrap(); 77 .unwrap();
73 let snippet = 78 let snippet =
74 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax())); 79 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
75 builder.replace_snippet(cap, range, snippet) 80 builder.replace_snippet(cap, range, snippet)
81 }
82 None => builder.replace(range, match_expr.to_string()),
76 } 83 }
77 None => builder.replace(range, match_expr.to_string()), 84 },
78 } 85 )
79 })
80} 86}
81 87
82#[cfg(test)] 88#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/split_import.rs b/crates/ra_assists/src/handlers/split_import.rs
index 38aa199a0..4ca5c3ca1 100644
--- a/crates/ra_assists/src/handlers/split_import.rs
+++ b/crates/ra_assists/src/handlers/split_import.rs
@@ -2,7 +2,7 @@ use std::iter::successors;
2 2
3use ra_syntax::{ast, AstNode, T}; 3use ra_syntax::{ast, AstNode, T};
4 4
5use crate::{AssistContext, AssistId, Assists}; 5use crate::{AssistContext, AssistId, AssistKind, Assists};
6 6
7// Assist: split_import 7// Assist: split_import
8// 8//
@@ -28,7 +28,7 @@ pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
28 } 28 }
29 29
30 let target = colon_colon.text_range(); 30 let target = colon_colon.text_range();
31 acc.add(AssistId("split_import"), "Split import", target, |edit| { 31 acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
32 edit.replace_ast(use_tree, new_tree); 32 edit.replace_ast(use_tree, new_tree);
33 }) 33 })
34} 34}
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 1fb13f481..8b38695a9 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -7,7 +7,7 @@ use ra_syntax::{
7 AstNode, TextRange, T, 7 AstNode, TextRange, T,
8}; 8};
9 9
10use crate::{AssistContext, AssistId, Assists}; 10use crate::{AssistContext, AssistId, AssistKind, Assists};
11 11
12// Assist: unwrap_block 12// Assist: unwrap_block
13// 13//
@@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists};
27// } 27// }
28// ``` 28// ```
29pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 29pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let assist_id = AssistId("unwrap_block"); 30 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
31 let assist_label = "Unwrap block"; 31 let assist_label = "Unwrap block";
32 32
33 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 33 let l_curly_token = ctx.find_token_at_offset(T!['{'])?;