aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assist_config.rs5
-rw-r--r--crates/ra_assists/src/assist_context.rs87
-rw-r--r--crates/ra_assists/src/ast_transform.rs18
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs10
-rw-r--r--crates/ra_assists/src/handlers/add_impl.rs98
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs35
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs13
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs17
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs279
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs37
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs48
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs156
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs321
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs (renamed from crates/ra_assists/src/handlers/introduce_variable.rs)270
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs257
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs208
-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)63
-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)24
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs (renamed from crates/ra_assists/src/handlers/add_function.rs)91
-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)46
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs24
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs (renamed from crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs)85
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs4
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs25
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs50
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs55
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs29
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs135
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs4
-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.rs81
-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.rs282
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs60
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs14
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs168
-rw-r--r--crates/ra_assists/src/lib.rs62
-rw-r--r--crates/ra_assists/src/tests.rs88
-rw-r--r--crates/ra_assists/src/tests/generated.rs297
-rw-r--r--crates/ra_assists/src/utils.rs16
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs24
48 files changed, 2515 insertions, 1317 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 3bcf58ba4..bd2905f08 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -3,6 +3,7 @@ edition = "2018"
3name = "ra_assists" 3name = "ra_assists"
4version = "0.1.0" 4version = "0.1.0"
5authors = ["rust-analyzer developers"] 5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
6 7
7[lib] 8[lib]
8doctest = false 9doctest = false
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
index c0a0226fb..cda2abfb9 100644
--- a/crates/ra_assists/src/assist_config.rs
+++ b/crates/ra_assists/src/assist_config.rs
@@ -4,9 +4,12 @@
4//! module, and we use to statically check that we only produce snippet 4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to. 5//! assists if we are allowed to.
6 6
7use crate::AssistKind;
8
7#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct AssistConfig { 10pub struct AssistConfig {
9 pub snippet_cap: Option<SnippetCap>, 11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>,
10} 13}
11 14
12impl AssistConfig { 15impl AssistConfig {
@@ -22,6 +25,6 @@ pub struct SnippetCap {
22 25
23impl Default for AssistConfig { 26impl Default for AssistConfig {
24 fn default() -> Self { 27 fn default() -> Self {
25 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }) } 28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
26 } 29 }
27} 30}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index 5b1a4680b..3407df856 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -1,5 +1,7 @@
1//! See `AssistContext` 1//! See `AssistContext`
2 2
3use std::mem;
4
3use algo::find_covering_element; 5use algo::find_covering_element;
4use hir::Semantics; 6use hir::Semantics;
5use ra_db::{FileId, FileRange}; 7use ra_db::{FileId, FileRange};
@@ -17,7 +19,7 @@ use ra_text_edit::TextEditBuilder;
17 19
18use crate::{ 20use crate::{
19 assist_config::{AssistConfig, SnippetCap}, 21 assist_config::{AssistConfig, SnippetCap},
20 Assist, AssistId, GroupLabel, ResolvedAssist, 22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
21}; 23};
22 24
23/// `AssistContext` allows to apply an assist or check if it could be applied. 25/// `AssistContext` allows to apply an assist or check if it could be applied.
@@ -53,7 +55,6 @@ use crate::{
53pub(crate) struct AssistContext<'a> { 55pub(crate) struct AssistContext<'a> {
54 pub(crate) config: &'a AssistConfig, 56 pub(crate) config: &'a AssistConfig,
55 pub(crate) sema: Semantics<'a, RootDatabase>, 57 pub(crate) sema: Semantics<'a, RootDatabase>,
56 pub(crate) db: &'a RootDatabase,
57 pub(crate) frange: FileRange, 58 pub(crate) frange: FileRange,
58 source_file: SourceFile, 59 source_file: SourceFile,
59} 60}
@@ -65,8 +66,11 @@ impl<'a> AssistContext<'a> {
65 frange: FileRange, 66 frange: FileRange,
66 ) -> AssistContext<'a> { 67 ) -> AssistContext<'a> {
67 let source_file = sema.parse(frange.file_id); 68 let source_file = sema.parse(frange.file_id);
68 let db = sema.db; 69 AssistContext { config, sema, frange, source_file }
69 AssistContext { config, sema, db, frange, source_file } 70 }
71
72 pub(crate) fn db(&self) -> &RootDatabase {
73 self.sema.db
70 } 74 }
71 75
72 // NB, this ignores active selection. 76 // NB, this ignores active selection.
@@ -99,14 +103,26 @@ pub(crate) struct Assists {
99 resolve: bool, 103 resolve: bool,
100 file: FileId, 104 file: FileId,
101 buf: Vec<(Assist, Option<SourceChange>)>, 105 buf: Vec<(Assist, Option<SourceChange>)>,
106 allowed: Option<Vec<AssistKind>>,
102} 107}
103 108
104impl Assists { 109impl Assists {
105 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists { 110 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
106 Assists { resolve: true, file: ctx.frange.file_id, buf: Vec::new() } 111 Assists {
112 resolve: true,
113 file: ctx.frange.file_id,
114 buf: Vec::new(),
115 allowed: ctx.config.allowed.clone(),
116 }
107 } 117 }
118
108 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists { 119 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
109 Assists { resolve: false, file: ctx.frange.file_id, buf: Vec::new() } 120 Assists {
121 resolve: false,
122 file: ctx.frange.file_id,
123 buf: Vec::new(),
124 allowed: ctx.config.allowed.clone(),
125 }
110 } 126 }
111 127
112 pub(crate) fn finish_unresolved(self) -> Vec<Assist> { 128 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
@@ -135,9 +151,13 @@ impl Assists {
135 target: TextRange, 151 target: TextRange,
136 f: impl FnOnce(&mut AssistBuilder), 152 f: impl FnOnce(&mut AssistBuilder),
137 ) -> Option<()> { 153 ) -> Option<()> {
154 if !self.is_allowed(&id) {
155 return None;
156 }
138 let label = Assist::new(id, label.into(), None, target); 157 let label = Assist::new(id, label.into(), None, target);
139 self.add_impl(label, f) 158 self.add_impl(label, f)
140 } 159 }
160
141 pub(crate) fn add_group( 161 pub(crate) fn add_group(
142 &mut self, 162 &mut self,
143 group: &GroupLabel, 163 group: &GroupLabel,
@@ -146,9 +166,14 @@ impl Assists {
146 target: TextRange, 166 target: TextRange,
147 f: impl FnOnce(&mut AssistBuilder), 167 f: impl FnOnce(&mut AssistBuilder),
148 ) -> Option<()> { 168 ) -> Option<()> {
169 if !self.is_allowed(&id) {
170 return None;
171 }
172
149 let label = Assist::new(id, label.into(), Some(group.clone()), target); 173 let label = Assist::new(id, label.into(), Some(group.clone()), target);
150 self.add_impl(label, f) 174 self.add_impl(label, f)
151 } 175 }
176
152 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { 177 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
153 let source_change = if self.resolve { 178 let source_change = if self.resolve {
154 let mut builder = AssistBuilder::new(self.file); 179 let mut builder = AssistBuilder::new(self.file);
@@ -166,17 +191,43 @@ impl Assists {
166 self.buf.sort_by_key(|(label, _edit)| label.target.len()); 191 self.buf.sort_by_key(|(label, _edit)| label.target.len());
167 self.buf 192 self.buf
168 } 193 }
194
195 fn is_allowed(&self, id: &AssistId) -> bool {
196 match &self.allowed {
197 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
198 None => true,
199 }
200 }
169} 201}
170 202
171pub(crate) struct AssistBuilder { 203pub(crate) struct AssistBuilder {
172 edit: TextEditBuilder, 204 edit: TextEditBuilder,
173 file: FileId, 205 file_id: FileId,
174 is_snippet: bool, 206 is_snippet: bool,
207 change: SourceChange,
175} 208}
176 209
177impl AssistBuilder { 210impl AssistBuilder {
178 pub(crate) fn new(file: FileId) -> AssistBuilder { 211 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
179 AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } 212 AssistBuilder {
213 edit: TextEditBuilder::default(),
214 file_id,
215 is_snippet: false,
216 change: SourceChange::default(),
217 }
218 }
219
220 pub(crate) fn edit_file(&mut self, file_id: FileId) {
221 self.file_id = file_id;
222 }
223
224 fn commit(&mut self) {
225 let edit = mem::take(&mut self.edit).finish();
226 if !edit.is_empty() {
227 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
228 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
229 self.change.source_file_edits.push(new_edit);
230 }
180 } 231 }
181 232
182 /// Remove specified `range` of text. 233 /// Remove specified `range` of text.
@@ -231,12 +282,7 @@ impl AssistBuilder {
231 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { 282 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
232 let node = rewriter.rewrite_root().unwrap(); 283 let node = rewriter.rewrite_root().unwrap();
233 let new = rewriter.rewrite(&node); 284 let new = rewriter.rewrite(&node);
234 algo::diff(&node, &new).into_text_edit(&mut self.edit) 285 algo::diff(&node, &new).into_text_edit(&mut self.edit);
235 }
236
237 // FIXME: better API
238 pub(crate) fn set_file(&mut self, assist_file: FileId) {
239 self.file = assist_file;
240 } 286 }
241 287
242 // FIXME: kill this API 288 // FIXME: kill this API
@@ -245,13 +291,12 @@ impl AssistBuilder {
245 &mut self.edit 291 &mut self.edit
246 } 292 }
247 293
248 fn finish(self) -> SourceChange { 294 fn finish(mut self) -> SourceChange {
249 let edit = self.edit.finish(); 295 self.commit();
250 let source_file_edit = SourceFileEdit { file_id: self.file, edit }; 296 let mut change = mem::take(&mut self.change);
251 let mut res: SourceChange = source_file_edit.into();
252 if self.is_snippet { 297 if self.is_snippet {
253 res.is_snippet = true; 298 change.is_snippet = true;
254 } 299 }
255 res 300 change
256 } 301 }
257} 302}
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
index 3079a02a2..01adb834c 100644
--- a/crates/ra_assists/src/ast_transform.rs
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -2,7 +2,6 @@
2use rustc_hash::FxHashMap; 2use rustc_hash::FxHashMap;
3 3
4use hir::{HirDisplay, PathResolution, SemanticsScope}; 4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 5use ra_syntax::{
7 algo::SyntaxRewriter, 6 algo::SyntaxRewriter,
8 ast::{self, AstNode}, 7 ast::{self, AstNode},
@@ -32,14 +31,14 @@ impl<'a> AstTransform<'a> for NullTransformer {
32} 31}
33 32
34pub struct SubstituteTypeParams<'a> { 33pub struct SubstituteTypeParams<'a> {
35 source_scope: &'a SemanticsScope<'a, RootDatabase>, 34 source_scope: &'a SemanticsScope<'a>,
36 substs: FxHashMap<hir::TypeParam, ast::TypeRef>, 35 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
37 previous: Box<dyn AstTransform<'a> + 'a>, 36 previous: Box<dyn AstTransform<'a> + 'a>,
38} 37}
39 38
40impl<'a> SubstituteTypeParams<'a> { 39impl<'a> SubstituteTypeParams<'a> {
41 pub fn for_trait_impl( 40 pub fn for_trait_impl(
42 source_scope: &'a SemanticsScope<'a, RootDatabase>, 41 source_scope: &'a SemanticsScope<'a>,
43 // FIXME: there's implicit invariant that `trait_` and `source_scope` match... 42 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
44 trait_: hir::Trait, 43 trait_: hir::Trait,
45 impl_def: ast::ImplDef, 44 impl_def: ast::ImplDef,
@@ -106,6 +105,7 @@ impl<'a> SubstituteTypeParams<'a> {
106 _ => return None, 105 _ => return None,
107 }; 106 };
108 // FIXME: use `hir::Path::from_src` instead. 107 // FIXME: use `hir::Path::from_src` instead.
108 #[allow(deprecated)]
109 let path = hir::Path::from_ast(path)?; 109 let path = hir::Path::from_ast(path)?;
110 let resolution = self.source_scope.resolve_hir_path(&path)?; 110 let resolution = self.source_scope.resolve_hir_path(&path)?;
111 match resolution { 111 match resolution {
@@ -125,16 +125,13 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
125} 125}
126 126
127pub struct QualifyPaths<'a> { 127pub struct QualifyPaths<'a> {
128 target_scope: &'a SemanticsScope<'a, RootDatabase>, 128 target_scope: &'a SemanticsScope<'a>,
129 source_scope: &'a SemanticsScope<'a, RootDatabase>, 129 source_scope: &'a SemanticsScope<'a>,
130 previous: Box<dyn AstTransform<'a> + 'a>, 130 previous: Box<dyn AstTransform<'a> + 'a>,
131} 131}
132 132
133impl<'a> QualifyPaths<'a> { 133impl<'a> QualifyPaths<'a> {
134 pub fn new( 134 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
135 target_scope: &'a SemanticsScope<'a, RootDatabase>,
136 source_scope: &'a SemanticsScope<'a, RootDatabase>,
137 ) -> Self {
138 Self { target_scope, source_scope, previous: Box::new(NullTransformer) } 135 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
139 } 136 }
140 137
@@ -150,11 +147,12 @@ impl<'a> QualifyPaths<'a> {
150 return None; 147 return None;
151 } 148 }
152 // FIXME: use `hir::Path::from_src` instead. 149 // FIXME: use `hir::Path::from_src` instead.
150 #[allow(deprecated)]
153 let hir_path = hir::Path::from_ast(p.clone()); 151 let hir_path = hir::Path::from_ast(p.clone());
154 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; 152 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
155 match resolution { 153 match resolution {
156 PathResolution::Def(def) => { 154 PathResolution::Def(def) => {
157 let found_path = from.find_use_path(self.source_scope.db, def)?; 155 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
158 let mut path = path_to_ast(found_path); 156 let mut path = path_to_ast(found_path);
159 157
160 let type_args = p 158 let type_args = p
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
index fa70c8496..acb07e36a 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
@@ -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 ab20c6649..39a5321d1 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
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//
@@ -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 {
@@ -195,7 +195,7 @@ struct Test<K, T = u8> {
195} 195}
196 196
197fn main() { 197fn main() {
198 let test<|> = Test { t: 23, k: 33 }; 198 let test<|> = Test { t: 23u8, k: 33 };
199}"#, 199}"#,
200 r#" 200 r#"
201struct Test<K, T = u8> { 201struct Test<K, T = u8> {
@@ -204,7 +204,7 @@ struct Test<K, T = u8> {
204} 204}
205 205
206fn main() { 206fn main() {
207 let test: Test<i32> = Test { t: 23, k: 33 }; 207 let test: Test<i32> = Test { t: 23u8, k: 33 };
208}"#, 208}"#,
209 ); 209 );
210 } 210 }
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..f185e61e5 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)]
@@ -128,9 +128,9 @@ fn add_missing_impl_members_inner(
128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def) 128 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
129 .iter() 129 .iter()
130 .map(|i| match i { 130 .map(|i| match i {
131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db).value), 131 hir::AssocItem::Function(i) => ast::AssocItem::FnDef(i.source(ctx.db()).value),
132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db).value), 132 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAliasDef(i.source(ctx.db()).value),
133 hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db).value), 133 hir::AssocItem::Const(i) => ast::AssocItem::ConstDef(i.source(ctx.db()).value),
134 }) 134 })
135 .filter(|t| def_name(&t).is_some()) 135 .filter(|t| def_name(&t).is_some())
136 .filter(|t| match t { 136 .filter(|t| match t {
@@ -147,7 +147,7 @@ fn add_missing_impl_members_inner(
147 } 147 }
148 148
149 let target = impl_def.syntax().text_range(); 149 let target = impl_def.syntax().text_range();
150 acc.add(AssistId(assist_id), label, target, |builder| { 150 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
153 let target_scope = ctx.sema.scope(impl_item_list.syntax()); 153 let target_scope = ctx.sema.scope(impl_item_list.syntax());
@@ -158,6 +158,9 @@ fn add_missing_impl_members_inner(
158 .map(|it| ast_transform::apply(&*ast_transform, it)) 158 .map(|it| ast_transform::apply(&*ast_transform, it))
159 .map(|it| match it { 159 .map(|it| match it {
160 ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)), 160 ast::AssocItem::FnDef(def) => ast::AssocItem::FnDef(add_body(def)),
161 ast::AssocItem::TypeAliasDef(def) => {
162 ast::AssocItem::TypeAliasDef(def.remove_bounds())
163 }
161 _ => it, 164 _ => it,
162 }) 165 })
163 .map(|it| edit::remove_attrs_and_docs(&it)); 166 .map(|it| edit::remove_attrs_and_docs(&it));
@@ -684,4 +687,26 @@ impl Foo<T> for S<T> {
684}"#, 687}"#,
685 ) 688 )
686 } 689 }
690
691 #[test]
692 fn test_assoc_type_bounds_are_removed() {
693 check_assist(
694 add_missing_impl_members,
695 r#"
696trait Tr {
697 type Ty: Copy + 'static;
698}
699
700impl Tr for ()<|> {
701}"#,
702 r#"
703trait Tr {
704 type Ty: Copy + 'static;
705}
706
707impl Tr for () {
708 $0type Ty;
709}"#,
710 )
711 }
687} 712}
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
index 26acf81f2..f7e1a7b05 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
@@ -45,12 +45,15 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<(
45 mark::hit!(add_turbo_fish_non_generic); 45 mark::hit!(add_turbo_fish_non_generic);
46 return None; 46 return None;
47 } 47 }
48 acc.add(AssistId("add_turbo_fish"), "Add `::<>`", ident.text_range(), |builder| { 48 acc.add(
49 match ctx.config.snippet_cap { 49 AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
50 "Add `::<>`",
51 ident.text_range(),
52 |builder| match ctx.config.snippet_cap {
50 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"), 53 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
51 None => builder.insert(ident.text_range().end(), "::<_>"), 54 None => builder.insert(ident.text_range().end(), "::<_>"),
52 } 55 },
53 }) 56 )
54} 57}
55 58
56#[cfg(test)] 59#[cfg(test)]
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 edf96d50e..947be3b9b 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| {
@@ -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) 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, _) => {
@@ -488,16 +490,17 @@ mod tests {
488 check_assist( 490 check_assist(
489 auto_import, 491 auto_import,
490 r" 492 r"
491 //- /lib.rs crate:crate_with_macro 493//- /lib.rs crate:crate_with_macro
492 #[macro_export] 494#[macro_export]
493 macro_rules! foo { 495macro_rules! foo {
494 () => () 496 () => ()
495 } 497}
496 498
497 //- /main.rs crate:main deps:crate_with_macro 499//- /main.rs crate:main deps:crate_with_macro
498 fn main() { 500fn main() {
499 foo<|> 501 foo<|>
500 }", 502}
503",
501 r"use crate_with_macro::foo; 504 r"use crate_with_macro::foo;
502 505
503fn main() { 506fn main() {
@@ -810,6 +813,146 @@ fn main() {
810 } 813 }
811 814
812 #[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]
813 fn not_applicable_for_imported_trait_for_method() { 956 fn not_applicable_for_imported_trait_for_method() {
814 check_assist_not_applicable( 957 check_assist_not_applicable(
815 auto_import, 958 auto_import,
@@ -841,4 +984,106 @@ fn main() {
841 ", 984 ",
842 ) 985 )
843 } 986 }
987
988 #[test]
989 fn dep_import() {
990 check_assist(
991 auto_import,
992 r"
993//- /lib.rs crate:dep
994pub struct Struct;
995
996//- /main.rs crate:main deps:dep
997fn main() {
998 Struct<|>
999}
1000",
1001 r"use dep::Struct;
1002
1003fn main() {
1004 Struct
1005}
1006",
1007 );
1008 }
1009
1010 #[test]
1011 fn whole_segment() {
1012 // Tests that only imports whose last segment matches the identifier get suggested.
1013 check_assist(
1014 auto_import,
1015 r"
1016//- /lib.rs crate:dep
1017pub mod fmt {
1018 pub trait Display {}
1019}
1020
1021pub fn panic_fmt() {}
1022
1023//- /main.rs crate:main deps:dep
1024struct S;
1025
1026impl f<|>mt::Display for S {}
1027",
1028 r"use dep::fmt;
1029
1030struct S;
1031
1032impl fmt::Display for S {}
1033",
1034 );
1035 }
1036
1037 #[test]
1038 fn macro_generated() {
1039 // Tests that macro-generated items are suggested from external crates.
1040 check_assist(
1041 auto_import,
1042 r"
1043//- /lib.rs crate:dep
1044macro_rules! mac {
1045 () => {
1046 pub struct Cheese;
1047 };
1048}
1049
1050mac!();
1051
1052//- /main.rs crate:main deps:dep
1053fn main() {
1054 Cheese<|>;
1055}
1056",
1057 r"use dep::Cheese;
1058
1059fn main() {
1060 Cheese;
1061}
1062",
1063 );
1064 }
1065
1066 #[test]
1067 fn casing() {
1068 // Tests that differently cased names don't interfere and we only suggest the matching one.
1069 check_assist(
1070 auto_import,
1071 r"
1072//- /lib.rs crate:dep
1073pub struct FMT;
1074pub struct fmt;
1075
1076//- /main.rs crate:main deps:dep
1077fn main() {
1078 FMT<|>;
1079}
1080",
1081 r"use dep::FMT;
1082
1083fn main() {
1084 FMT;
1085}
1086",
1087 );
1088 }
844} 1089}
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 c6baa0a57..24e5f6963 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,8 @@ 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;
7 8
8// Assist: change_return_type_to_result 9// Assist: change_return_type_to_result
9// 10//
@@ -22,14 +23,19 @@ pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContex
22 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?; 23 let fn_def = ret_type.syntax().parent().and_then(ast::FnDef::cast)?;
23 24
24 let type_ref = &ret_type.type_ref()?; 25 let type_ref = &ret_type.type_ref()?;
25 if type_ref.syntax().text().to_string().starts_with("Result<") { 26 let ret_type_str = type_ref.syntax().text().to_string();
26 return None; 27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type {
29 if ret_type_first_part.ends_with("Result") {
30 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
31 return None;
32 }
27 } 33 }
28 34
29 let block_expr = &fn_def.body()?; 35 let block_expr = &fn_def.body()?;
30 36
31 acc.add( 37 acc.add(
32 AssistId("change_return_type_to_result"), 38 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
33 "Change return type to Result", 39 "Change return type to Result",
34 type_ref.syntax().text_range(), 40 type_ref.syntax().text_range(),
35 |builder| { 41 |builder| {
@@ -297,6 +303,29 @@ mod tests {
297 } 303 }
298 304
299 #[test] 305 #[test]
306 fn change_return_type_to_result_simple_return_type_already_result_std() {
307 check_assist_not_applicable(
308 change_return_type_to_result,
309 r#"fn foo() -> std::result::Result<i32<|>, String> {
310 let test = "test";
311 return 42i32;
312 }"#,
313 );
314 }
315
316 #[test]
317 fn change_return_type_to_result_simple_return_type_already_result() {
318 mark::check!(change_return_type_to_result_simple_return_type_already_result);
319 check_assist_not_applicable(
320 change_return_type_to_result,
321 r#"fn foo() -> Result<i32<|>, String> {
322 let test = "test";
323 return 42i32;
324 }"#,
325 );
326 }
327
328 #[test]
300 fn change_return_type_to_result_simple_with_cursor() { 329 fn change_return_type_to_result_simple_with_cursor() {
301 check_assist( 330 check_assist(
302 change_return_type_to_result, 331 change_return_type_to_result,
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
index c21d75be0..4343b423c 100644
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -2,14 +2,13 @@ use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner}, 2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode, 3 AstNode,
4 SyntaxKind::{ 4 SyntaxKind::{
5 ATTR, COMMENT, CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY, 5 CONST_DEF, ENUM_DEF, FN_DEF, MODULE, STATIC_DEF, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
6 WHITESPACE,
7 }, 6 },
8 SyntaxNode, TextSize, T, 7 T,
9}; 8};
10use test_utils::mark; 9use test_utils::mark;
11 10
12use crate::{AssistContext, AssistId, Assists}; 11use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
13 12
14// Assist: change_visibility 13// Assist: change_visibility
15// 14//
@@ -30,14 +29,16 @@ pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Optio
30} 29}
31 30
32fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() { 32 let item_keyword = ctx.token_at_offset().find(|leaf| {
34 T![const] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true, 33 matches!(
35 _ => false, 34 leaf.kind(),
35 T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
36 )
36 }); 37 });
37 38
38 let (offset, target) = if let Some(keyword) = item_keyword { 39 let (offset, target) = if let Some(keyword) = item_keyword {
39 let parent = keyword.parent(); 40 let parent = keyword.parent();
40 let def_kws = vec![CONST_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF]; 41 let def_kws = vec![CONST_DEF, STATIC_DEF, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
41 // Parent is not a definition, can't add visibility 42 // Parent is not a definition, can't add visibility
42 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { 43 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
43 return None; 44 return None;
@@ -66,27 +67,21 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
66 return None; 67 return None;
67 }; 68 };
68 69
69 acc.add(AssistId("change_visibility"), "Change visibility to pub(crate)", target, |edit| { 70 acc.add(
70 edit.insert(offset, "pub(crate) "); 71 AssistId("change_visibility", AssistKind::RefactorRewrite),
71 }) 72 "Change visibility to pub(crate)",
72} 73 target,
73 74 |edit| {
74fn vis_offset(node: &SyntaxNode) -> TextSize { 75 edit.insert(offset, "pub(crate) ");
75 node.children_with_tokens() 76 },
76 .skip_while(|it| match it.kind() { 77 )
77 WHITESPACE | COMMENT | ATTR => true,
78 _ => false,
79 })
80 .next()
81 .map(|it| it.text_range().start())
82 .unwrap_or_else(|| node.text_range().start())
83} 78}
84 79
85fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { 80fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
86 if vis.syntax().text() == "pub" { 81 if vis.syntax().text() == "pub" {
87 let target = vis.syntax().text_range(); 82 let target = vis.syntax().text_range();
88 return acc.add( 83 return acc.add(
89 AssistId("change_visibility"), 84 AssistId("change_visibility", AssistKind::RefactorRewrite),
90 "Change Visibility to pub(crate)", 85 "Change Visibility to pub(crate)",
91 target, 86 target,
92 |edit| { 87 |edit| {
@@ -97,7 +92,7 @@ fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
97 if vis.syntax().text() == "pub(crate)" { 92 if vis.syntax().text() == "pub(crate)" {
98 let target = vis.syntax().text_range(); 93 let target = vis.syntax().text_range();
99 return acc.add( 94 return acc.add(
100 AssistId("change_visibility"), 95 AssistId("change_visibility", AssistKind::RefactorRewrite),
101 "Change visibility to pub", 96 "Change visibility to pub",
102 target, 97 target,
103 |edit| { 98 |edit| {
@@ -162,6 +157,11 @@ mod tests {
162 } 157 }
163 158
164 #[test] 159 #[test]
160 fn change_visibility_static() {
161 check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
162 }
163
164 #[test]
165 fn change_visibility_handles_comment_attrs() { 165 fn change_visibility_handles_comment_attrs() {
166 check_assist( 166 check_assist(
167 change_visibility, 167 change_visibility,
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 4cc75a7ce..330459f3c 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -15,7 +15,7 @@ use ra_syntax::{
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
@@ -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::from(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
new file mode 100644
index 000000000..2b8e273b3
--- /dev/null
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,321 @@
1use hir::{EnumVariant, Module, ModuleDef, Name};
2use ra_db::FileId;
3use ra_fmt::leading_indent;
4use ra_ide_db::{defs::Definition, search::Reference, RootDatabase};
5use ra_syntax::{
6 algo::find_node_at_offset,
7 ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8 SourceFile, SyntaxNode, TextRange, TextSize,
9};
10use rustc_hash::FxHashSet;
11
12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
14 AssistKind, Assists,
15};
16
17// Assist: extract_struct_from_enum_variant
18//
19// Extracts a struct from enum variant.
20//
21// ```
22// enum A { <|>One(u32, u32) }
23// ```
24// ->
25// ```
26// struct One(pub u32, pub u32);
27//
28// enum A { One(One) }
29// ```
30pub(crate) fn extract_struct_from_enum_variant(
31 acc: &mut Assists,
32 ctx: &AssistContext,
33) -> Option<()> {
34 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
35 let field_list = match variant.kind() {
36 ast::StructKind::Tuple(field_list) => field_list,
37 _ => return None,
38 };
39 let variant_name = variant.name()?.to_string();
40 let variant_hir = ctx.sema.to_def(&variant)?;
41 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
42 return None;
43 }
44 let enum_ast = variant.parent_enum();
45 let visibility = enum_ast.visibility();
46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
47 let variant_hir_name = variant_hir.name(ctx.db());
48 let enum_module_def = ModuleDef::from(enum_hir);
49 let current_module = enum_hir.module(ctx.db());
50 let target = variant.syntax().text_range();
51 acc.add(
52 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
53 "Extract struct from enum variant",
54 target,
55 |builder| {
56 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
57 let res = definition.find_usages(&ctx.sema, None);
58 let start_offset = variant.parent_enum().syntax().text_range().start();
59 let mut visited_modules_set = FxHashSet::default();
60 visited_modules_set.insert(current_module);
61 for reference in res {
62 let source_file = ctx.sema.parse(reference.file_range.file_id);
63 update_reference(
64 ctx,
65 builder,
66 reference,
67 &source_file,
68 &enum_module_def,
69 &variant_hir_name,
70 &mut visited_modules_set,
71 );
72 }
73 extract_struct_def(
74 builder,
75 enum_ast.syntax(),
76 &variant_name,
77 &field_list.to_string(),
78 start_offset,
79 ctx.frange.file_id,
80 &visibility,
81 );
82 let list_range = field_list.syntax().text_range();
83 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
84 },
85 )
86}
87
88fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
89 variant
90 .parent_enum(db)
91 .module(db)
92 .scope(db, None)
93 .into_iter()
94 .any(|(name, _)| name.to_string() == variant_name.to_string())
95}
96
97fn insert_import(
98 ctx: &AssistContext,
99 builder: &mut AssistBuilder,
100 path: &ast::PathExpr,
101 module: &Module,
102 enum_module_def: &ModuleDef,
103 variant_hir_name: &Name,
104) -> Option<()> {
105 let db = ctx.db();
106 let mod_path = module.find_use_path(db, enum_module_def.clone());
107 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
111 }
112 Some(())
113}
114
115fn extract_struct_def(
116 builder: &mut AssistBuilder,
117 enum_ast: &SyntaxNode,
118 variant_name: &str,
119 variant_list: &str,
120 start_offset: TextSize,
121 file_id: FileId,
122 visibility: &Option<ast::Visibility>,
123) -> Option<()> {
124 let visibility_string = if let Some(visibility) = visibility {
125 format!("{} ", visibility.to_string())
126 } else {
127 "".to_string()
128 };
129 let indent = if let Some(indent) = leading_indent(enum_ast) {
130 indent.to_string()
131 } else {
132 "".to_string()
133 };
134 let struct_def = format!(
135 r#"{}struct {}{};
136
137{}"#,
138 visibility_string,
139 variant_name,
140 list_with_visibility(variant_list),
141 indent
142 );
143 builder.edit_file(file_id);
144 builder.insert(start_offset, struct_def);
145 Some(())
146}
147
148fn update_variant(
149 builder: &mut AssistBuilder,
150 variant_name: &str,
151 file_id: FileId,
152 list_range: TextRange,
153) -> Option<()> {
154 let inside_variant_range = TextRange::new(
155 list_range.start().checked_add(TextSize::from(1))?,
156 list_range.end().checked_sub(TextSize::from(1))?,
157 );
158 builder.edit_file(file_id);
159 builder.replace(inside_variant_range, variant_name);
160 Some(())
161}
162
163fn update_reference(
164 ctx: &AssistContext,
165 builder: &mut AssistBuilder,
166 reference: Reference,
167 source_file: &SourceFile,
168 enum_module_def: &ModuleDef,
169 variant_hir_name: &Name,
170 visited_modules_set: &mut FxHashSet<Module>,
171) -> Option<()> {
172 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
173 source_file.syntax(),
174 reference.file_range.range.start(),
175 )?;
176 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
177 let list = call.arg_list()?;
178 let segment = path_expr.path()?.segment()?;
179 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
180 let list_range = list.syntax().text_range();
181 let inside_list_range = TextRange::new(
182 list_range.start().checked_add(TextSize::from(1))?,
183 list_range.end().checked_sub(TextSize::from(1))?,
184 );
185 builder.edit_file(reference.file_range.file_id);
186 if !visited_modules_set.contains(&module) {
187 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
188 .is_some()
189 {
190 visited_modules_set.insert(module);
191 }
192 }
193 builder.replace(inside_list_range, format!("{}{}", segment, list));
194 Some(())
195}
196
197fn list_with_visibility(list: &str) -> String {
198 list.split(',')
199 .map(|part| {
200 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
201 let mut mod_part = part.trim().to_string();
202 mod_part.insert_str(index, "pub ");
203 mod_part
204 })
205 .collect::<Vec<String>>()
206 .join(", ")
207}
208
209#[cfg(test)]
210mod tests {
211
212 use crate::{
213 tests::{check_assist, check_assist_not_applicable},
214 utils::FamousDefs,
215 };
216
217 use super::*;
218
219 #[test]
220 fn test_extract_struct_several_fields() {
221 check_assist(
222 extract_struct_from_enum_variant,
223 "enum A { <|>One(u32, u32) }",
224 r#"struct One(pub u32, pub u32);
225
226enum A { One(One) }"#,
227 );
228 }
229
230 #[test]
231 fn test_extract_struct_one_field() {
232 check_assist(
233 extract_struct_from_enum_variant,
234 "enum A { <|>One(u32) }",
235 r#"struct One(pub u32);
236
237enum A { One(One) }"#,
238 );
239 }
240
241 #[test]
242 fn test_extract_struct_pub_visibility() {
243 check_assist(
244 extract_struct_from_enum_variant,
245 "pub enum A { <|>One(u32, u32) }",
246 r#"pub struct One(pub u32, pub u32);
247
248pub enum A { One(One) }"#,
249 );
250 }
251
252 #[test]
253 fn test_extract_struct_with_complex_imports() {
254 check_assist(
255 extract_struct_from_enum_variant,
256 r#"mod my_mod {
257 fn another_fn() {
258 let m = my_other_mod::MyEnum::MyField(1, 1);
259 }
260
261 pub mod my_other_mod {
262 fn another_fn() {
263 let m = MyEnum::MyField(1, 1);
264 }
265
266 pub enum MyEnum {
267 <|>MyField(u8, u8),
268 }
269 }
270}
271
272fn another_fn() {
273 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
274}"#,
275 r#"use my_mod::my_other_mod::MyField;
276
277mod my_mod {
278 use my_other_mod::MyField;
279
280 fn another_fn() {
281 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
282 }
283
284 pub mod my_other_mod {
285 fn another_fn() {
286 let m = MyEnum::MyField(MyField(1, 1));
287 }
288
289 pub struct MyField(pub u8, pub u8);
290
291 pub enum MyEnum {
292 MyField(MyField),
293 }
294 }
295}
296
297fn another_fn() {
298 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
299}"#,
300 );
301 }
302
303 fn check_not_applicable(ra_fixture: &str) {
304 let fixture =
305 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
306 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
307 }
308
309 #[test]
310 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
311 check_not_applicable("enum A { <|>One }");
312 }
313
314 #[test]
315 fn test_extract_enum_not_applicable_if_struct_exists() {
316 check_not_applicable(
317 r#"struct One;
318 enum A { <|>One(u8) }"#,
319 );
320 }
321}
diff --git a/crates/ra_assists/src/handlers/introduce_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
index 31d6539f7..481baf1a4 100644
--- a/crates/ra_assists/src/handlers/introduce_variable.rs
+++ b/crates/ra_assists/src/handlers/extract_variable.rs
@@ -9,9 +9,9 @@ use ra_syntax::{
9use stdx::format_to; 9use stdx::format_to;
10use test_utils::mark; 10use test_utils::mark;
11 11
12use crate::{AssistContext, AssistId, Assists}; 12use crate::{AssistContext, AssistId, AssistKind, Assists};
13 13
14// Assist: introduce_variable 14// Assist: extract_variable
15// 15//
16// Extracts subexpression into a variable. 16// Extracts subexpression into a variable.
17// 17//
@@ -27,13 +27,13 @@ use crate::{AssistContext, AssistId, Assists};
27// var_name * 4; 27// var_name * 4;
28// } 28// }
29// ``` 29// ```
30pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 if ctx.frange.range.is_empty() { 31 if ctx.frange.range.is_empty() {
32 return None; 32 return None;
33 } 33 }
34 let node = ctx.covering_element(); 34 let node = ctx.covering_element();
35 if node.kind() == COMMENT { 35 if node.kind() == COMMENT {
36 mark::hit!(introduce_var_in_comment_is_not_applicable); 36 mark::hit!(extract_var_in_comment_is_not_applicable);
37 return None; 37 return None;
38 } 38 }
39 let expr = node.ancestors().find_map(valid_target_expr)?; 39 let expr = node.ancestors().find_map(valid_target_expr)?;
@@ -43,65 +43,85 @@ pub(crate) fn introduce_variable(acc: &mut Assists, ctx: &AssistContext) -> Opti
43 return None; 43 return None;
44 } 44 }
45 let target = expr.syntax().text_range(); 45 let target = expr.syntax().text_range();
46 acc.add(AssistId("introduce_variable"), "Extract into variable", target, move |edit| { 46 acc.add(
47 let mut buf = String::new(); 47 AssistId("extract_variable", AssistKind::RefactorExtract),
48 48 "Extract into variable",
49 if wrap_in_block { 49 target,
50 buf.push_str("{ let var_name = "); 50 move |edit| {
51 } else { 51 let field_shorthand = match expr.syntax().parent().and_then(ast::RecordField::cast) {
52 buf.push_str("let var_name = "); 52 Some(field) => field.name_ref(),
53 }; 53 None => None,
54 format_to!(buf, "{}", expr.syntax()); 54 };
55 55
56 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone()); 56 let mut buf = String::new();
57 let is_full_stmt = if let Some(expr_stmt) = &full_stmt { 57
58 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone()) 58 let var_name = match &field_shorthand {
59 } else { 59 Some(it) => it.to_string(),
60 false 60 None => "var_name".to_string(),
61 }; 61 };
62 if is_full_stmt { 62 let expr_range = match &field_shorthand {
63 mark::hit!(test_introduce_var_expr_stmt); 63 Some(it) => it.syntax().text_range().cover(expr.syntax().text_range()),
64 if full_stmt.unwrap().semicolon_token().is_none() { 64 None => expr.syntax().text_range(),
65 buf.push_str(";"); 65 };
66
67 if wrap_in_block {
68 format_to!(buf, "{{ let {} = ", var_name);
69 } else {
70 format_to!(buf, "let {} = ", var_name);
71 };
72 format_to!(buf, "{}", expr.syntax());
73
74 let full_stmt = ast::ExprStmt::cast(anchor_stmt.clone());
75 let is_full_stmt = if let Some(expr_stmt) = &full_stmt {
76 Some(expr.syntax().clone()) == expr_stmt.expr().map(|e| e.syntax().clone())
77 } else {
78 false
79 };
80 if is_full_stmt {
81 mark::hit!(test_extract_var_expr_stmt);
82 if full_stmt.unwrap().semicolon_token().is_none() {
83 buf.push_str(";");
84 }
85 match ctx.config.snippet_cap {
86 Some(cap) => {
87 let snip = buf
88 .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
89 edit.replace_snippet(cap, expr_range, snip)
90 }
91 None => edit.replace(expr_range, buf),
92 }
93 return;
94 }
95
96 buf.push_str(";");
97
98 // We want to maintain the indent level,
99 // but we do not want to duplicate possible
100 // extra newlines in the indent block
101 let text = indent.text();
102 if text.starts_with('\n') {
103 buf.push_str("\n");
104 buf.push_str(text.trim_start_matches('\n'));
105 } else {
106 buf.push_str(text);
66 } 107 }
67 let offset = expr.syntax().text_range(); 108
109 edit.replace(expr_range, var_name.clone());
110 let offset = anchor_stmt.text_range().start();
68 match ctx.config.snippet_cap { 111 match ctx.config.snippet_cap {
69 Some(cap) => { 112 Some(cap) => {
70 let snip = buf.replace("let var_name", "let $0var_name"); 113 let snip =
71 edit.replace_snippet(cap, offset, snip) 114 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
115 edit.insert_snippet(cap, offset, snip)
72 } 116 }
73 None => edit.replace(offset, buf), 117 None => edit.insert(offset, buf),
74 } 118 }
75 return;
76 }
77 119
78 buf.push_str(";"); 120 if wrap_in_block {
79 121 edit.insert(anchor_stmt.text_range().end(), " }");
80 // We want to maintain the indent level,
81 // but we do not want to duplicate possible
82 // extra newlines in the indent block
83 let text = indent.text();
84 if text.starts_with('\n') {
85 buf.push_str("\n");
86 buf.push_str(text.trim_start_matches('\n'));
87 } else {
88 buf.push_str(text);
89 }
90
91 edit.replace(expr.syntax().text_range(), "var_name".to_string());
92 let offset = anchor_stmt.text_range().start();
93 match ctx.config.snippet_cap {
94 Some(cap) => {
95 let snip = buf.replace("let var_name", "let $0var_name");
96 edit.insert_snippet(cap, offset, snip)
97 } 122 }
98 None => edit.insert(offset, buf), 123 },
99 } 124 )
100
101 if wrap_in_block {
102 edit.insert(anchor_stmt.text_range().end(), " }");
103 }
104 })
105} 125}
106 126
107/// Check whether the node is a valid expression which can be extracted to a variable. 127/// Check whether the node is a valid expression which can be extracted to a variable.
@@ -118,7 +138,7 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
118 } 138 }
119} 139}
120 140
121/// Returns the syntax node which will follow the freshly introduced var 141/// Returns the syntax node which will follow the freshly extractd var
122/// and a boolean indicating whether we have to wrap it within a { } block 142/// and a boolean indicating whether we have to wrap it within a { } block
123/// to produce correct code. 143/// to produce correct code.
124/// It can be a statement, the last in a block expression or a wanna be block 144/// It can be a statement, the last in a block expression or a wanna be block
@@ -127,7 +147,7 @@ fn anchor_stmt(expr: ast::Expr) -> Option<(SyntaxNode, bool)> {
127 expr.syntax().ancestors().find_map(|node| { 147 expr.syntax().ancestors().find_map(|node| {
128 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) { 148 if let Some(expr) = node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr()) {
129 if expr.syntax() == &node { 149 if expr.syntax() == &node {
130 mark::hit!(test_introduce_var_last_expr); 150 mark::hit!(test_extract_var_last_expr);
131 return Some((node, false)); 151 return Some((node, false));
132 } 152 }
133 } 153 }
@@ -155,9 +175,9 @@ mod tests {
155 use super::*; 175 use super::*;
156 176
157 #[test] 177 #[test]
158 fn test_introduce_var_simple() { 178 fn test_extract_var_simple() {
159 check_assist( 179 check_assist(
160 introduce_variable, 180 extract_variable,
161 r#" 181 r#"
162fn foo() { 182fn foo() {
163 foo(<|>1 + 1<|>); 183 foo(<|>1 + 1<|>);
@@ -171,16 +191,16 @@ fn foo() {
171 } 191 }
172 192
173 #[test] 193 #[test]
174 fn introduce_var_in_comment_is_not_applicable() { 194 fn extract_var_in_comment_is_not_applicable() {
175 mark::check!(introduce_var_in_comment_is_not_applicable); 195 mark::check!(extract_var_in_comment_is_not_applicable);
176 check_assist_not_applicable(introduce_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }"); 196 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
177 } 197 }
178 198
179 #[test] 199 #[test]
180 fn test_introduce_var_expr_stmt() { 200 fn test_extract_var_expr_stmt() {
181 mark::check!(test_introduce_var_expr_stmt); 201 mark::check!(test_extract_var_expr_stmt);
182 check_assist( 202 check_assist(
183 introduce_variable, 203 extract_variable,
184 r#" 204 r#"
185fn foo() { 205fn foo() {
186 <|>1 + 1<|>; 206 <|>1 + 1<|>;
@@ -191,7 +211,7 @@ fn foo() {
191}"#, 211}"#,
192 ); 212 );
193 check_assist( 213 check_assist(
194 introduce_variable, 214 extract_variable,
195 " 215 "
196fn foo() { 216fn foo() {
197 <|>{ let x = 0; x }<|> 217 <|>{ let x = 0; x }<|>
@@ -206,9 +226,9 @@ fn foo() {
206 } 226 }
207 227
208 #[test] 228 #[test]
209 fn test_introduce_var_part_of_expr_stmt() { 229 fn test_extract_var_part_of_expr_stmt() {
210 check_assist( 230 check_assist(
211 introduce_variable, 231 extract_variable,
212 " 232 "
213fn foo() { 233fn foo() {
214 <|>1<|> + 1; 234 <|>1<|> + 1;
@@ -222,38 +242,42 @@ fn foo() {
222 } 242 }
223 243
224 #[test] 244 #[test]
225 fn test_introduce_var_last_expr() { 245 fn test_extract_var_last_expr() {
226 mark::check!(test_introduce_var_last_expr); 246 mark::check!(test_extract_var_last_expr);
227 check_assist( 247 check_assist(
228 introduce_variable, 248 extract_variable,
229 " 249 r#"
230fn foo() { 250fn foo() {
231 bar(<|>1 + 1<|>) 251 bar(<|>1 + 1<|>)
232}", 252}
233 " 253"#,
254 r#"
234fn foo() { 255fn foo() {
235 let $0var_name = 1 + 1; 256 let $0var_name = 1 + 1;
236 bar(var_name) 257 bar(var_name)
237}", 258}
259"#,
238 ); 260 );
239 check_assist( 261 check_assist(
240 introduce_variable, 262 extract_variable,
241 " 263 r#"
242fn foo() { 264fn foo() {
243 <|>bar(1 + 1)<|> 265 <|>bar(1 + 1)<|>
244}", 266}
245 " 267"#,
268 r#"
246fn foo() { 269fn foo() {
247 let $0var_name = bar(1 + 1); 270 let $0var_name = bar(1 + 1);
248 var_name 271 var_name
249}", 272}
273"#,
250 ) 274 )
251 } 275 }
252 276
253 #[test] 277 #[test]
254 fn test_introduce_var_in_match_arm_no_block() { 278 fn test_extract_var_in_match_arm_no_block() {
255 check_assist( 279 check_assist(
256 introduce_variable, 280 extract_variable,
257 " 281 "
258fn main() { 282fn main() {
259 let x = true; 283 let x = true;
@@ -276,9 +300,9 @@ fn main() {
276 } 300 }
277 301
278 #[test] 302 #[test]
279 fn test_introduce_var_in_match_arm_with_block() { 303 fn test_extract_var_in_match_arm_with_block() {
280 check_assist( 304 check_assist(
281 introduce_variable, 305 extract_variable,
282 " 306 "
283fn main() { 307fn main() {
284 let x = true; 308 let x = true;
@@ -308,9 +332,9 @@ fn main() {
308 } 332 }
309 333
310 #[test] 334 #[test]
311 fn test_introduce_var_in_closure_no_block() { 335 fn test_extract_var_in_closure_no_block() {
312 check_assist( 336 check_assist(
313 introduce_variable, 337 extract_variable,
314 " 338 "
315fn main() { 339fn main() {
316 let lambda = |x: u32| <|>x * 2<|>; 340 let lambda = |x: u32| <|>x * 2<|>;
@@ -325,9 +349,9 @@ fn main() {
325 } 349 }
326 350
327 #[test] 351 #[test]
328 fn test_introduce_var_in_closure_with_block() { 352 fn test_extract_var_in_closure_with_block() {
329 check_assist( 353 check_assist(
330 introduce_variable, 354 extract_variable,
331 " 355 "
332fn main() { 356fn main() {
333 let lambda = |x: u32| { <|>x * 2<|> }; 357 let lambda = |x: u32| { <|>x * 2<|> };
@@ -342,9 +366,9 @@ fn main() {
342 } 366 }
343 367
344 #[test] 368 #[test]
345 fn test_introduce_var_path_simple() { 369 fn test_extract_var_path_simple() {
346 check_assist( 370 check_assist(
347 introduce_variable, 371 extract_variable,
348 " 372 "
349fn main() { 373fn main() {
350 let o = <|>Some(true)<|>; 374 let o = <|>Some(true)<|>;
@@ -360,9 +384,9 @@ fn main() {
360 } 384 }
361 385
362 #[test] 386 #[test]
363 fn test_introduce_var_path_method() { 387 fn test_extract_var_path_method() {
364 check_assist( 388 check_assist(
365 introduce_variable, 389 extract_variable,
366 " 390 "
367fn main() { 391fn main() {
368 let v = <|>bar.foo()<|>; 392 let v = <|>bar.foo()<|>;
@@ -378,9 +402,9 @@ fn main() {
378 } 402 }
379 403
380 #[test] 404 #[test]
381 fn test_introduce_var_return() { 405 fn test_extract_var_return() {
382 check_assist( 406 check_assist(
383 introduce_variable, 407 extract_variable,
384 " 408 "
385fn foo() -> u32 { 409fn foo() -> u32 {
386 <|>return 2 + 2<|>; 410 <|>return 2 + 2<|>;
@@ -396,9 +420,9 @@ fn foo() -> u32 {
396 } 420 }
397 421
398 #[test] 422 #[test]
399 fn test_introduce_var_does_not_add_extra_whitespace() { 423 fn test_extract_var_does_not_add_extra_whitespace() {
400 check_assist( 424 check_assist(
401 introduce_variable, 425 extract_variable,
402 " 426 "
403fn foo() -> u32 { 427fn foo() -> u32 {
404 428
@@ -417,7 +441,7 @@ fn foo() -> u32 {
417 ); 441 );
418 442
419 check_assist( 443 check_assist(
420 introduce_variable, 444 extract_variable,
421 " 445 "
422fn foo() -> u32 { 446fn foo() -> u32 {
423 447
@@ -434,7 +458,7 @@ fn foo() -> u32 {
434 ); 458 );
435 459
436 check_assist( 460 check_assist(
437 introduce_variable, 461 extract_variable,
438 " 462 "
439fn foo() -> u32 { 463fn foo() -> u32 {
440 let foo = 1; 464 let foo = 1;
@@ -460,9 +484,9 @@ fn foo() -> u32 {
460 } 484 }
461 485
462 #[test] 486 #[test]
463 fn test_introduce_var_break() { 487 fn test_extract_var_break() {
464 check_assist( 488 check_assist(
465 introduce_variable, 489 extract_variable,
466 " 490 "
467fn main() { 491fn main() {
468 let result = loop { 492 let result = loop {
@@ -482,9 +506,9 @@ fn main() {
482 } 506 }
483 507
484 #[test] 508 #[test]
485 fn test_introduce_var_for_cast() { 509 fn test_extract_var_for_cast() {
486 check_assist( 510 check_assist(
487 introduce_variable, 511 extract_variable,
488 " 512 "
489fn main() { 513fn main() {
490 let v = <|>0f32 as u32<|>; 514 let v = <|>0f32 as u32<|>;
@@ -500,22 +524,48 @@ fn main() {
500 } 524 }
501 525
502 #[test] 526 #[test]
503 fn test_introduce_var_for_return_not_applicable() { 527 fn extract_var_field_shorthand() {
504 check_assist_not_applicable(introduce_variable, "fn foo() { <|>return<|>; } "); 528 check_assist(
529 extract_variable,
530 r#"
531struct S {
532 foo: i32
533}
534
535fn main() {
536 S { foo: <|>1 + 1<|> }
537}
538"#,
539 r#"
540struct S {
541 foo: i32
542}
543
544fn main() {
545 let $0foo = 1 + 1;
546 S { foo }
547}
548"#,
549 )
550 }
551
552 #[test]
553 fn test_extract_var_for_return_not_applicable() {
554 check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
505 } 555 }
506 556
507 #[test] 557 #[test]
508 fn test_introduce_var_for_break_not_applicable() { 558 fn test_extract_var_for_break_not_applicable() {
509 check_assist_not_applicable(introduce_variable, "fn main() { loop { <|>break<|>; }; }"); 559 check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
510 } 560 }
511 561
512 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic 562 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
513 #[test] 563 #[test]
514 fn introduce_var_target() { 564 fn extract_var_target() {
515 check_assist_target(introduce_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2"); 565 check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");
516 566
517 check_assist_target( 567 check_assist_target(
518 introduce_variable, 568 extract_variable,
519 " 569 "
520fn main() { 570fn main() {
521 let x = true; 571 let x = true;
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index cc303285b..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 {
@@ -136,8 +149,20 @@ fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
136} 149}
137 150
138fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { 151fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
139 let pat_head = pat.syntax().first_child().map(|node| node.text()); 152 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
140 let var_head = var.syntax().first_child().map(|node| node.text()); 153
154 let pat_head = match pat {
155 Pat::BindPat(bind_pat) => {
156 if let Some(p) = bind_pat.pat() {
157 first_node_text(&p)
158 } else {
159 return false;
160 }
161 }
162 pat => first_node_text(pat),
163 };
164
165 let var_head = first_node_text(var);
141 166
142 pat_head == var_head 167 pat_head == var_head
143} 168}
@@ -274,30 +299,22 @@ mod tests {
274 check_assist( 299 check_assist(
275 fill_match_arms, 300 fill_match_arms,
276 r#" 301 r#"
277 enum A { 302enum A { As, Bs, Cs(Option<i32>) }
278 As, 303fn main() {
279 Bs, 304 match A::As<|> {
280 Cs(Option<i32>), 305 A::Cs(_) | A::Bs => {}
281 } 306 }
282 fn main() { 307}
283 match A::As<|> { 308"#,
284 A::Cs(_) | A::Bs => {}
285 }
286 }
287 "#,
288 r#" 309 r#"
289 enum A { 310enum A { As, Bs, Cs(Option<i32>) }
290 As, 311fn main() {
291 Bs, 312 match A::As {
292 Cs(Option<i32>), 313 A::Cs(_) | A::Bs => {}
293 } 314 $0A::As => {}
294 fn main() { 315 }
295 match A::As { 316}
296 A::Cs(_) | A::Bs => {} 317"#,
297 $0A::As => {}
298 }
299 }
300 "#,
301 ); 318 );
302 } 319 }
303 320
@@ -306,47 +323,55 @@ mod tests {
306 check_assist( 323 check_assist(
307 fill_match_arms, 324 fill_match_arms,
308 r#" 325 r#"
309 enum A { 326enum A { As, Bs, Cs, Ds(String), Es(B) }
310 As, 327enum B { Xs, Ys }
311 Bs, 328fn main() {
312 Cs, 329 match A::As<|> {
313 Ds(String), 330 A::Bs if 0 < 1 => {}
314 Es(B), 331 A::Ds(_value) => { let x = 1; }
315 } 332 A::Es(B::Xs) => (),
316 enum B { 333 }
317 Xs, 334}
318 Ys, 335"#,
319 }
320 fn main() {
321 match A::As<|> {
322 A::Bs if 0 < 1 => {}
323 A::Ds(_value) => { let x = 1; }
324 A::Es(B::Xs) => (),
325 }
326 }
327 "#,
328 r#" 336 r#"
329 enum A { 337enum A { As, Bs, Cs, Ds(String), Es(B) }
330 As, 338enum B { Xs, Ys }
331 Bs, 339fn main() {
332 Cs, 340 match A::As {
333 Ds(String), 341 A::Bs if 0 < 1 => {}
334 Es(B), 342 A::Ds(_value) => { let x = 1; }
335 } 343 A::Es(B::Xs) => (),
336 enum B { 344 $0A::As => {}
337 Xs, 345 A::Cs => {}
338 Ys, 346 }
339 } 347}
340 fn main() { 348"#,
341 match A::As { 349 );
342 A::Bs if 0 < 1 => {} 350 }
343 A::Ds(_value) => { let x = 1; } 351
344 A::Es(B::Xs) => (), 352 #[test]
345 $0A::As => {} 353 fn partial_fill_bind_pat() {
346 A::Cs => {} 354 check_assist(
347 } 355 fill_match_arms,
348 } 356 r#"
349 "#, 357enum A { As, Bs, Cs(Option<i32>) }
358fn main() {
359 match A::As<|> {
360 A::As(_) => {}
361 a @ A::Bs(_) => {}
362 }
363}
364"#,
365 r#"
366enum A { As, Bs, Cs(Option<i32>) }
367fn main() {
368 match A::As {
369 A::As(_) => {}
370 a @ A::Bs(_) => {}
371 A::Cs(${0:_}) => {}
372 }
373}
374"#,
350 ); 375 );
351 } 376 }
352 377
@@ -355,39 +380,27 @@ mod tests {
355 check_assist( 380 check_assist(
356 fill_match_arms, 381 fill_match_arms,
357 r#" 382 r#"
358 enum A { 383enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
359 As,
360 Bs,
361 Cs(String),
362 Ds(String, String),
363 Es { x: usize, y: usize }
364 }
365 384
366 fn main() { 385fn main() {
367 let a = A::As; 386 let a = A::As;
368 match a<|> {} 387 match a<|> {}
369 } 388}
370 "#, 389"#,
371 r#" 390 r#"
372 enum A { 391enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
373 As,
374 Bs,
375 Cs(String),
376 Ds(String, String),
377 Es { x: usize, y: usize }
378 }
379 392
380 fn main() { 393fn main() {
381 let a = A::As; 394 let a = A::As;
382 match a { 395 match a {
383 $0A::As => {} 396 $0A::As => {}
384 A::Bs => {} 397 A::Bs => {}
385 A::Cs(_) => {} 398 A::Cs(_) => {}
386 A::Ds(_, _) => {} 399 A::Ds(_, _) => {}
387 A::Es { x, y } => {} 400 A::Es { x, y } => {}
388 } 401 }
389 } 402}
390 "#, 403"#,
391 ); 404 );
392 } 405 }
393 406
@@ -717,9 +730,9 @@ mod tests {
717fn foo(opt: Option<i32>) { 730fn foo(opt: Option<i32>) {
718 match opt<|> { 731 match opt<|> {
719 } 732 }
720}"#; 733}
721 let before = 734"#;
722 &format!("//- main.rs crate:main deps:core\n{}{}", before, FamousDefs::FIXTURE); 735 let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
723 736
724 check_assist( 737 check_assist(
725 fill_match_arms, 738 fill_match_arms,
@@ -727,7 +740,7 @@ fn foo(opt: Option<i32>) {
727 r#" 740 r#"
728fn foo(opt: Option<i32>) { 741fn foo(opt: Option<i32>) {
729 match opt { 742 match opt {
730 $0Some(_) => {} 743 Some(${0:_}) => {}
731 None => {} 744 None => {}
732 } 745 }
733} 746}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
index 9ec42f568..e212557c8 100644
--- a/crates/ra_assists/src/handlers/fix_visibility.rs
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -1,12 +1,8 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution}; 1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ra_db::FileId; 2use ra_db::FileId;
3use ra_syntax::{ 3use ra_syntax::{ast, AstNode, TextRange, TextSize};
4 ast, AstNode,
5 SyntaxKind::{ATTR, COMMENT, WHITESPACE},
6 SyntaxNode, TextRange, TextSize,
7};
8 4
9use crate::{AssistContext, AssistId, Assists}; 5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
10 6
11// FIXME: this really should be a fix for diagnostic, rather than an assist. 7// FIXME: this really should be a fix for diagnostic, rather than an assist.
12 8
@@ -45,14 +41,14 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
45 }; 41 };
46 42
47 let current_module = ctx.sema.scope(&path.syntax()).module()?; 43 let current_module = ctx.sema.scope(&path.syntax()).module()?;
48 let target_module = def.module(ctx.db)?; 44 let target_module = def.module(ctx.db())?;
49 45
50 let vis = target_module.visibility_of(ctx.db, &def)?; 46 let vis = target_module.visibility_of(ctx.db(), &def)?;
51 if vis.is_visible_from(ctx.db, current_module.into()) { 47 if vis.is_visible_from(ctx.db(), current_module.into()) {
52 return None; 48 return None;
53 }; 49 };
54 50
55 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db, def)?; 51 let (offset, target, target_file, target_name) = target_data_for_def(ctx.db(), def)?;
56 52
57 let missing_visibility = 53 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; 54 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
@@ -62,8 +58,8 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility), 58 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
63 }; 59 };
64 60
65 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 61 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
66 builder.set_file(target_file); 62 builder.edit_file(target_file);
67 match ctx.config.snippet_cap { 63 match ctx.config.snippet_cap {
68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 64 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
69 None => builder.insert(offset, format!("{} ", missing_visibility)), 65 None => builder.insert(offset, format!("{} ", missing_visibility)),
@@ -76,16 +72,16 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
76 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?; 72 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
77 73
78 let current_module = ctx.sema.scope(record_field.syntax()).module()?; 74 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
79 let visibility = record_field_def.visibility(ctx.db); 75 let visibility = record_field_def.visibility(ctx.db());
80 if visibility.is_visible_from(ctx.db, current_module.into()) { 76 if visibility.is_visible_from(ctx.db(), current_module.into()) {
81 return None; 77 return None;
82 } 78 }
83 79
84 let parent = record_field_def.parent_def(ctx.db); 80 let parent = record_field_def.parent_def(ctx.db());
85 let parent_name = parent.name(ctx.db); 81 let parent_name = parent.name(ctx.db());
86 let target_module = parent.module(ctx.db); 82 let target_module = parent.module(ctx.db());
87 83
88 let in_file_source = record_field_def.source(ctx.db); 84 let in_file_source = record_field_def.source(ctx.db());
89 let (offset, target) = match in_file_source.value { 85 let (offset, target) = match in_file_source.value {
90 hir::FieldSource::Named(it) => { 86 hir::FieldSource::Named(it) => {
91 let s = it.syntax(); 87 let s = it.syntax();
@@ -99,14 +95,14 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
99 95
100 let missing_visibility = 96 let missing_visibility =
101 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" }; 97 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
102 let target_file = in_file_source.file_id.original_file(ctx.db); 98 let target_file = in_file_source.file_id.original_file(ctx.db());
103 99
104 let target_name = record_field_def.name(ctx.db); 100 let target_name = record_field_def.name(ctx.db());
105 let assist_label = 101 let assist_label =
106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); 102 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
107 103
108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 104 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
109 builder.set_file(target_file); 105 builder.edit_file(target_file);
110 match ctx.config.snippet_cap { 106 match ctx.config.snippet_cap {
111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 107 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
112 None => builder.insert(offset, format!("{} ", missing_visibility)), 108 None => builder.insert(offset, format!("{} ", missing_visibility)),
@@ -177,17 +173,6 @@ fn target_data_for_def(
177 Some((offset, target, target_file, target_name)) 173 Some((offset, target, target_file, target_name))
178} 174}
179 175
180fn vis_offset(node: &SyntaxNode) -> TextSize {
181 node.children_with_tokens()
182 .skip_while(|it| match it.kind() {
183 WHITESPACE | COMMENT | ATTR => true,
184 _ => false,
185 })
186 .next()
187 .map(|it| it.text_range().start())
188 .unwrap_or_else(|| node.text_range().start())
189}
190
191#[cfg(test)] 176#[cfg(test)]
192mod tests { 177mod tests {
193 use crate::tests::{check_assist, check_assist_not_applicable}; 178 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -255,15 +240,14 @@ mod tests {
255 check_assist( 240 check_assist(
256 fix_visibility, 241 fix_visibility,
257 r" 242 r"
258 //- /main.rs 243//- /main.rs
259 mod foo; 244mod foo;
260 fn main() { foo::Foo<|> } 245fn main() { foo::Foo<|> }
261 246
262 //- /foo.rs 247//- /foo.rs
263 struct Foo; 248struct Foo;
264 ", 249",
265 r"$0pub(crate) struct Foo; 250 r"$0pub(crate) struct Foo;
266
267", 251",
268 ); 252 );
269 } 253 }
@@ -279,14 +263,14 @@ mod tests {
279 ); 263 );
280 check_assist( 264 check_assist(
281 fix_visibility, 265 fix_visibility,
282 r"//- /lib.rs 266 r"
283 mod foo; 267//- /lib.rs
284 fn main() { foo::Foo { <|>bar: () }; } 268mod foo;
285 //- /foo.rs 269fn main() { foo::Foo { <|>bar: () }; }
286 pub struct Foo { bar: () } 270//- /foo.rs
287 ", 271pub struct Foo { bar: () }
272",
288 r"pub struct Foo { $0pub(crate) bar: () } 273 r"pub struct Foo { $0pub(crate) bar: () }
289
290", 274",
291 ); 275 );
292 check_assist_not_applicable( 276 check_assist_not_applicable(
@@ -296,12 +280,13 @@ mod tests {
296 ); 280 );
297 check_assist_not_applicable( 281 check_assist_not_applicable(
298 fix_visibility, 282 fix_visibility,
299 r"//- /lib.rs 283 r"
300 mod foo; 284//- /lib.rs
301 fn main() { foo::Foo { <|>bar: () }; } 285mod foo;
302 //- /foo.rs 286fn main() { foo::Foo { <|>bar: () }; }
303 pub struct Foo { pub bar: () } 287//- /foo.rs
304 ", 288pub struct Foo { pub bar: () }
289",
305 ); 290 );
306 } 291 }
307 292
@@ -316,14 +301,14 @@ mod tests {
316 ); 301 );
317 check_assist( 302 check_assist(
318 fix_visibility, 303 fix_visibility,
319 r"//- /lib.rs 304 r"
320 mod foo; 305//- /lib.rs
321 fn main() { foo::Foo::Bar { <|>bar: () }; } 306mod foo;
322 //- /foo.rs 307fn main() { foo::Foo::Bar { <|>bar: () }; }
323 pub enum Foo { Bar { bar: () } } 308//- /foo.rs
324 ", 309pub enum Foo { Bar { bar: () } }
310",
325 r"pub enum Foo { Bar { $0pub(crate) bar: () } } 311 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
326
327", 312",
328 ); 313 );
329 check_assist_not_applicable( 314 check_assist_not_applicable(
@@ -333,12 +318,13 @@ mod tests {
333 ); 318 );
334 check_assist_not_applicable( 319 check_assist_not_applicable(
335 fix_visibility, 320 fix_visibility,
336 r"//- /lib.rs 321 r"
337 mod foo; 322//- /lib.rs
338 fn main() { foo::Foo { <|>bar: () }; } 323mod foo;
339 //- /foo.rs 324fn main() { foo::Foo { <|>bar: () }; }
340 pub struct Foo { pub bar: () } 325//- /foo.rs
341 ", 326pub struct Foo { pub bar: () }
327",
342 ); 328 );
343 } 329 }
344 330
@@ -355,14 +341,14 @@ mod tests {
355 ); 341 );
356 check_assist( 342 check_assist(
357 fix_visibility, 343 fix_visibility,
358 r"//- /lib.rs 344 r"
359 mod foo; 345//- /lib.rs
360 fn main() { foo::Foo { <|>bar: () }; } 346mod foo;
361 //- /foo.rs 347fn main() { foo::Foo { <|>bar: () }; }
362 pub union Foo { bar: () } 348//- /foo.rs
363 ", 349pub union Foo { bar: () }
350",
364 r"pub union Foo { $0pub(crate) bar: () } 351 r"pub union Foo { $0pub(crate) bar: () }
365
366", 352",
367 ); 353 );
368 check_assist_not_applicable( 354 check_assist_not_applicable(
@@ -372,12 +358,13 @@ mod tests {
372 ); 358 );
373 check_assist_not_applicable( 359 check_assist_not_applicable(
374 fix_visibility, 360 fix_visibility,
375 r"//- /lib.rs 361 r"
376 mod foo; 362//- /lib.rs
377 fn main() { foo::Foo { <|>bar: () }; } 363mod foo;
378 //- /foo.rs 364fn main() { foo::Foo { <|>bar: () }; }
379 pub union Foo { pub bar: () } 365//- /foo.rs
380 ", 366pub union Foo { pub bar: () }
367",
381 ); 368 );
382 } 369 }
383 370
@@ -458,19 +445,18 @@ mod tests {
458 check_assist( 445 check_assist(
459 fix_visibility, 446 fix_visibility,
460 r" 447 r"
461 //- /main.rs 448//- /main.rs
462 mod foo; 449mod foo;
463 fn main() { foo::bar<|>::baz(); } 450fn main() { foo::bar<|>::baz(); }
464 451
465 //- /foo.rs 452//- /foo.rs
466 mod bar { 453mod bar {
467 pub fn baz() {} 454 pub fn baz() {}
468 } 455}
469 ", 456",
470 r"$0pub(crate) mod bar { 457 r"$0pub(crate) mod bar {
471 pub fn baz() {} 458 pub fn baz() {}
472} 459}
473
474", 460",
475 ); 461 );
476 462
@@ -486,17 +472,15 @@ mod tests {
486 check_assist( 472 check_assist(
487 fix_visibility, 473 fix_visibility,
488 r" 474 r"
489 //- /main.rs 475//- /main.rs
490 mod foo; 476mod foo;
491 fn main() { foo::bar<|>::baz(); } 477fn main() { foo::bar<|>::baz(); }
492 478
493 //- /foo.rs 479//- /foo.rs
494 mod bar; 480mod bar;
495 481//- /foo/bar.rs
496 //- /foo/bar.rs 482pub fn baz() {}
497 pub fn baz() {} 483",
498 }
499 ",
500 r"$0pub(crate) mod bar; 484 r"$0pub(crate) mod bar;
501", 485",
502 ); 486 );
@@ -506,14 +490,16 @@ mod tests {
506 fn fix_visibility_of_module_declaration_in_other_file() { 490 fn fix_visibility_of_module_declaration_in_other_file() {
507 check_assist( 491 check_assist(
508 fix_visibility, 492 fix_visibility,
509 r"//- /main.rs 493 r"
510 mod foo; 494//- /main.rs
511 fn main() { foo::bar<|>>::baz(); } 495mod foo;
496fn main() { foo::bar<|>>::baz(); }
512 497
513 //- /foo.rs 498//- /foo.rs
514 mod bar { 499mod bar {
515 pub fn baz() {} 500 pub fn baz() {}
516 }", 501}
502",
517 r"$0pub(crate) mod bar { 503 r"$0pub(crate) mod bar {
518 pub fn baz() {} 504 pub fn baz() {}
519} 505}
@@ -525,10 +511,12 @@ mod tests {
525 fn adds_pub_when_target_is_in_another_crate() { 511 fn adds_pub_when_target_is_in_another_crate() {
526 check_assist( 512 check_assist(
527 fix_visibility, 513 fix_visibility,
528 r"//- /main.rs crate:a deps:foo 514 r"
529 foo::Bar<|> 515//- /main.rs crate:a deps:foo
530 //- /lib.rs crate:foo 516foo::Bar<|>
531 struct Bar;", 517//- /lib.rs crate:foo
518struct Bar;
519",
532 r"$0pub struct Bar; 520 r"$0pub struct Bar;
533", 521",
534 ) 522 )
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..6ccf39900 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,32 +24,37 @@ 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::NominalDef>()?;
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.
@@ -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 776bddf91..a347e3c2e 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,7 +21,7 @@ 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::EnumVariant>()?;
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()?;
@@ -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();
@@ -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
@@ -128,8 +128,8 @@ impl From<foo::bar::baz::Boo> for A {
128 128
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 24f931a85..b721b96bb 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,15 +62,20 @@ 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.set_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 {
@@ -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
@@ -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..cbbac1d7f
--- /dev/null
+++ b/crates/ra_assists/src/handlers/generate_impl.rs
@@ -0,0 +1,109 @@
1use ra_syntax::ast::{self, AstNode, NameOwner, TypeParamsOwner};
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::NominalDef>()?;
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.type_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..e27def1d8 100644
--- a/crates/ra_assists/src/handlers/add_new.rs
+++ b/crates/ra_assists/src/handlers/generate_new.rs
@@ -7,9 +7,9 @@ use ra_syntax::{
7}; 7};
8use stdx::{format_to, SepBy}; 8use stdx::{format_to, SepBy};
9 9
10use crate::{AssistContext, AssistId, Assists}; 10use crate::{AssistContext, AssistId, AssistKind, Assists};
11 11
12// Assist: add_new 12// Assist: generate_new
13// 13//
14// Adds a new inherent impl for a type. 14// Adds a new inherent impl for a type.
15// 15//
@@ -29,7 +29,7 @@ use crate::{AssistContext, AssistId, Assists};
29// } 29// }
30// 30//
31// ``` 31// ```
32pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 32pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; 33 let strukt = ctx.find_node_at_offset::<ast::StructDef>()?;
34 34
35 // We want to only apply this to non-union structs with named fields 35 // We want to only apply this to non-union structs with named fields
@@ -42,7 +42,7 @@ pub(crate) fn add_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let impl_def = find_struct_impl(&ctx, &strukt)?; 42 let impl_def = find_struct_impl(&ctx, &strukt)?;
43 43
44 let target = strukt.syntax().text_range(); 44 let target = strukt.syntax().text_range();
45 acc.add(AssistId("add_new"), "Add default constructor", target, |builder| { 45 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
46 let mut buf = String::with_capacity(512); 46 let mut buf = String::with_capacity(512);
47 47
48 if impl_def.is_some() { 48 if impl_def.is_some() {
@@ -122,7 +122,7 @@ fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String {
122// FIXME: change the new fn checking to a more semantic approach when that's more 122// FIXME: change the new fn checking to a more semantic approach when that's more
123// viable (e.g. we process proc macros, etc) 123// viable (e.g. we process proc macros, etc)
124fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> { 124fn find_struct_impl(ctx: &AssistContext, strukt: &ast::StructDef) -> Option<Option<ast::ImplDef>> {
125 let db = ctx.db; 125 let db = ctx.db();
126 let module = strukt.syntax().ancestors().find(|node| { 126 let module = strukt.syntax().ancestors().find(|node| {
127 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) 127 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
128 })?; 128 })?;
@@ -181,10 +181,10 @@ mod tests {
181 181
182 #[test] 182 #[test]
183 #[rustfmt::skip] 183 #[rustfmt::skip]
184 fn test_add_new() { 184 fn test_generate_new() {
185 // Check output of generation 185 // Check output of generation
186 check_assist( 186 check_assist(
187 add_new, 187 generate_new,
188"struct Foo {<|>}", 188"struct Foo {<|>}",
189"struct Foo {} 189"struct Foo {}
190 190
@@ -194,7 +194,7 @@ impl Foo {
194", 194",
195 ); 195 );
196 check_assist( 196 check_assist(
197 add_new, 197 generate_new,
198"struct Foo<T: Clone> {<|>}", 198"struct Foo<T: Clone> {<|>}",
199"struct Foo<T: Clone> {} 199"struct Foo<T: Clone> {}
200 200
@@ -204,7 +204,7 @@ impl<T: Clone> Foo<T> {
204", 204",
205 ); 205 );
206 check_assist( 206 check_assist(
207 add_new, 207 generate_new,
208"struct Foo<'a, T: Foo<'a>> {<|>}", 208"struct Foo<'a, T: Foo<'a>> {<|>}",
209"struct Foo<'a, T: Foo<'a>> {} 209"struct Foo<'a, T: Foo<'a>> {}
210 210
@@ -214,7 +214,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
214", 214",
215 ); 215 );
216 check_assist( 216 check_assist(
217 add_new, 217 generate_new,
218"struct Foo { baz: String <|>}", 218"struct Foo { baz: String <|>}",
219"struct Foo { baz: String } 219"struct Foo { baz: String }
220 220
@@ -224,7 +224,7 @@ impl Foo {
224", 224",
225 ); 225 );
226 check_assist( 226 check_assist(
227 add_new, 227 generate_new,
228"struct Foo { baz: String, qux: Vec<i32> <|>}", 228"struct Foo { baz: String, qux: Vec<i32> <|>}",
229"struct Foo { baz: String, qux: Vec<i32> } 229"struct Foo { baz: String, qux: Vec<i32> }
230 230
@@ -236,7 +236,7 @@ impl Foo {
236 236
237 // Check that visibility modifiers don't get brought in for fields 237 // Check that visibility modifiers don't get brought in for fields
238 check_assist( 238 check_assist(
239 add_new, 239 generate_new,
240"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", 240"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
241"struct Foo { pub baz: String, pub qux: Vec<i32> } 241"struct Foo { pub baz: String, pub qux: Vec<i32> }
242 242
@@ -248,7 +248,7 @@ impl Foo {
248 248
249 // Check that it reuses existing impls 249 // Check that it reuses existing impls
250 check_assist( 250 check_assist(
251 add_new, 251 generate_new,
252"struct Foo {<|>} 252"struct Foo {<|>}
253 253
254impl Foo {} 254impl Foo {}
@@ -261,7 +261,7 @@ impl Foo {
261", 261",
262 ); 262 );
263 check_assist( 263 check_assist(
264 add_new, 264 generate_new,
265"struct Foo {<|>} 265"struct Foo {<|>}
266 266
267impl Foo { 267impl Foo {
@@ -279,7 +279,7 @@ impl Foo {
279 ); 279 );
280 280
281 check_assist( 281 check_assist(
282 add_new, 282 generate_new,
283"struct Foo {<|>} 283"struct Foo {<|>}
284 284
285impl Foo { 285impl Foo {
@@ -304,7 +304,7 @@ impl Foo {
304 304
305 // Check visibility of new fn based on struct 305 // Check visibility of new fn based on struct
306 check_assist( 306 check_assist(
307 add_new, 307 generate_new,
308"pub struct Foo {<|>}", 308"pub struct Foo {<|>}",
309"pub struct Foo {} 309"pub struct Foo {}
310 310
@@ -314,7 +314,7 @@ impl Foo {
314", 314",
315 ); 315 );
316 check_assist( 316 check_assist(
317 add_new, 317 generate_new,
318"pub(crate) struct Foo {<|>}", 318"pub(crate) struct Foo {<|>}",
319"pub(crate) struct Foo {} 319"pub(crate) struct Foo {}
320 320
@@ -326,9 +326,9 @@ impl Foo {
326 } 326 }
327 327
328 #[test] 328 #[test]
329 fn add_new_not_applicable_if_fn_exists() { 329 fn generate_new_not_applicable_if_fn_exists() {
330 check_assist_not_applicable( 330 check_assist_not_applicable(
331 add_new, 331 generate_new,
332 " 332 "
333struct Foo {<|>} 333struct Foo {<|>}
334 334
@@ -340,7 +340,7 @@ impl Foo {
340 ); 340 );
341 341
342 check_assist_not_applicable( 342 check_assist_not_applicable(
343 add_new, 343 generate_new,
344 " 344 "
345struct Foo {<|>} 345struct Foo {<|>}
346 346
@@ -353,9 +353,9 @@ impl Foo {
353 } 353 }
354 354
355 #[test] 355 #[test]
356 fn add_new_target() { 356 fn generate_new_target() {
357 check_assist_target( 357 check_assist_target(
358 add_new, 358 generate_new,
359 " 359 "
360struct SomeThingIrrelevant; 360struct SomeThingIrrelevant;
361/// Has a lifetime parameter 361/// Has a lifetime parameter
@@ -370,7 +370,7 @@ struct Foo<'a, T: Foo<'a>> {}",
370 #[test] 370 #[test]
371 fn test_unrelated_new() { 371 fn test_unrelated_new() {
372 check_assist( 372 check_assist(
373 add_new, 373 generate_new,
374 r##" 374 r##"
375pub struct AstId<N: AstNode> { 375pub struct AstId<N: AstNode> {
376 file_id: HirFileId, 376 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/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
index 999aec421..967593031 100644
--- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
+++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
@@ -1,12 +1,15 @@
1use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; 1use ra_syntax::{
2use ast::{NameOwner, ParamList, TypeAscriptionOwner, TypeParamList, TypeRef}; 2 ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
3use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind, TextRange, TextSize}; 3 AstNode, SyntaxKind, TextRange, TextSize,
4};
4use rustc_hash::FxHashSet; 5use rustc_hash::FxHashSet;
5 6
6static ASSIST_NAME: &str = "change_lifetime_anon_to_named"; 7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
7static ASSIST_LABEL: &str = "Give anonymous lifetime a name";
8 8
9// Assist: change_lifetime_anon_to_named 9static ASSIST_NAME: &str = "introduce_named_lifetime";
10static ASSIST_LABEL: &str = "Introduce named lifetime";
11
12// Assist: introduce_named_lifetime
10// 13//
11// Change an anonymous lifetime to a named lifetime. 14// Change an anonymous lifetime to a named lifetime.
12// 15//
@@ -31,15 +34,13 @@ static ASSIST_LABEL: &str = "Give anonymous lifetime a name";
31// ``` 34// ```
32// FIXME: How can we handle renaming any one of multiple anonymous lifetimes? 35// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
33// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo 36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
34pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35 let lifetime_token = ctx 38 let lifetime_token = ctx
36 .find_token_at_offset(SyntaxKind::LIFETIME) 39 .find_token_at_offset(SyntaxKind::LIFETIME)
37 .filter(|lifetime| lifetime.text() == "'_")?; 40 .filter(|lifetime| lifetime.text() == "'_")?;
38 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::FnDef::cast) {
39 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
40 } 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::ImplDef::cast) {
41 // only allow naming the last anonymous lifetime
42 lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
43 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) 44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
44 } else { 45 } else {
45 None 46 None
@@ -52,7 +53,7 @@ fn generate_fn_def_assist(
52 fn_def: &ast::FnDef, 53 fn_def: &ast::FnDef,
53 lifetime_loc: TextRange, 54 lifetime_loc: TextRange,
54) -> Option<()> { 55) -> Option<()> {
55 let param_list: ParamList = fn_def.param_list()?; 56 let param_list: ast::ParamList = fn_def.param_list()?;
56 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.type_param_list())?;
57 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();
58 let self_param = 59 let self_param =
@@ -67,7 +68,7 @@ fn generate_fn_def_assist(
67 let fn_params_without_lifetime: Vec<_> = param_list 68 let fn_params_without_lifetime: Vec<_> = param_list
68 .params() 69 .params()
69 .filter_map(|param| match param.ascribed_type() { 70 .filter_map(|param| match param.ascribed_type() {
70 Some(TypeRef::ReferenceType(ascribed_type)) 71 Some(ast::TypeRef::ReferenceType(ascribed_type))
71 if ascribed_type.lifetime_token() == None => 72 if ascribed_type.lifetime_token() == None =>
72 { 73 {
73 Some(ascribed_type.amp_token()?.text_range().end()) 74 Some(ascribed_type.amp_token()?.text_range().end())
@@ -82,7 +83,7 @@ fn generate_fn_def_assist(
82 _ => return None, 83 _ => return None,
83 } 84 }
84 }; 85 };
85 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { 86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
86 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);
87 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 88 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
88 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)));
@@ -97,7 +98,7 @@ fn generate_impl_def_assist(
97) -> Option<()> { 98) -> Option<()> {
98 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.type_param_list())?;
99 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();
100 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { 101 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
101 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);
102 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 103 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
103 }) 104 })
@@ -106,7 +107,7 @@ fn generate_impl_def_assist(
106/// Given a type parameter list, generate a unique lifetime parameter name 107/// Given a type parameter list, generate a unique lifetime parameter name
107/// which is not in the list 108/// which is not in the list
108fn generate_unique_lifetime_param_name( 109fn generate_unique_lifetime_param_name(
109 existing_type_param_list: &Option<TypeParamList>, 110 existing_type_param_list: &Option<ast::TypeParamList>,
110) -> Option<char> { 111) -> Option<char> {
111 match existing_type_param_list { 112 match existing_type_param_list {
112 Some(type_params) => { 113 Some(type_params) => {
@@ -151,7 +152,7 @@ mod tests {
151 #[test] 152 #[test]
152 fn test_example_case() { 153 fn test_example_case() {
153 check_assist( 154 check_assist(
154 change_lifetime_anon_to_named, 155 introduce_named_lifetime,
155 r#"impl Cursor<'_<|>> { 156 r#"impl Cursor<'_<|>> {
156 fn node(self) -> &SyntaxNode { 157 fn node(self) -> &SyntaxNode {
157 match self { 158 match self {
@@ -172,7 +173,7 @@ mod tests {
172 #[test] 173 #[test]
173 fn test_example_case_simplified() { 174 fn test_example_case_simplified() {
174 check_assist( 175 check_assist(
175 change_lifetime_anon_to_named, 176 introduce_named_lifetime,
176 r#"impl Cursor<'_<|>> {"#, 177 r#"impl Cursor<'_<|>> {"#,
177 r#"impl<'a> Cursor<'a> {"#, 178 r#"impl<'a> Cursor<'a> {"#,
178 ); 179 );
@@ -181,16 +182,33 @@ mod tests {
181 #[test] 182 #[test]
182 fn test_example_case_cursor_after_tick() { 183 fn test_example_case_cursor_after_tick() {
183 check_assist( 184 check_assist(
184 change_lifetime_anon_to_named, 185 introduce_named_lifetime,
185 r#"impl Cursor<'<|>_> {"#, 186 r#"impl Cursor<'<|>_> {"#,
186 r#"impl<'a> Cursor<'a> {"#, 187 r#"impl<'a> Cursor<'a> {"#,
187 ); 188 );
188 } 189 }
189 190
190 #[test] 191 #[test]
192 fn test_impl_with_other_type_param() {
193 check_assist(
194 introduce_named_lifetime,
195 "impl<I> fmt::Display for SepByBuilder<'_<|>, I>
196 where
197 I: Iterator,
198 I::Item: fmt::Display,
199 {",
200 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
201 where
202 I: Iterator,
203 I::Item: fmt::Display,
204 {",
205 )
206 }
207
208 #[test]
191 fn test_example_case_cursor_before_tick() { 209 fn test_example_case_cursor_before_tick() {
192 check_assist( 210 check_assist(
193 change_lifetime_anon_to_named, 211 introduce_named_lifetime,
194 r#"impl Cursor<<|>'_> {"#, 212 r#"impl Cursor<<|>'_> {"#,
195 r#"impl<'a> Cursor<'a> {"#, 213 r#"impl<'a> Cursor<'a> {"#,
196 ); 214 );
@@ -198,23 +216,20 @@ mod tests {
198 216
199 #[test] 217 #[test]
200 fn test_not_applicable_cursor_position() { 218 fn test_not_applicable_cursor_position() {
201 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); 219 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#);
202 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); 220 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#);
203 } 221 }
204 222
205 #[test] 223 #[test]
206 fn test_not_applicable_lifetime_already_name() { 224 fn test_not_applicable_lifetime_already_name() {
207 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); 225 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#);
208 check_assist_not_applicable( 226 check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#);
209 change_lifetime_anon_to_named,
210 r#"fn my_fun<'a>() -> X<'a<|>>"#,
211 );
212 } 227 }
213 228
214 #[test] 229 #[test]
215 fn test_with_type_parameter() { 230 fn test_with_type_parameter() {
216 check_assist( 231 check_assist(
217 change_lifetime_anon_to_named, 232 introduce_named_lifetime,
218 r#"impl<T> Cursor<T, '_<|>>"#, 233 r#"impl<T> Cursor<T, '_<|>>"#,
219 r#"impl<T, 'a> Cursor<T, 'a>"#, 234 r#"impl<T, 'a> Cursor<T, 'a>"#,
220 ); 235 );
@@ -223,7 +238,7 @@ mod tests {
223 #[test] 238 #[test]
224 fn test_with_existing_lifetime_name_conflict() { 239 fn test_with_existing_lifetime_name_conflict() {
225 check_assist( 240 check_assist(
226 change_lifetime_anon_to_named, 241 introduce_named_lifetime,
227 r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, 242 r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#,
228 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, 243 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
229 ); 244 );
@@ -232,7 +247,7 @@ mod tests {
232 #[test] 247 #[test]
233 fn test_function_return_value_anon_lifetime_param() { 248 fn test_function_return_value_anon_lifetime_param() {
234 check_assist( 249 check_assist(
235 change_lifetime_anon_to_named, 250 introduce_named_lifetime,
236 r#"fn my_fun() -> X<'_<|>>"#, 251 r#"fn my_fun() -> X<'_<|>>"#,
237 r#"fn my_fun<'a>() -> X<'a>"#, 252 r#"fn my_fun<'a>() -> X<'a>"#,
238 ); 253 );
@@ -241,7 +256,7 @@ mod tests {
241 #[test] 256 #[test]
242 fn test_function_return_value_anon_reference_lifetime() { 257 fn test_function_return_value_anon_reference_lifetime() {
243 check_assist( 258 check_assist(
244 change_lifetime_anon_to_named, 259 introduce_named_lifetime,
245 r#"fn my_fun() -> &'_<|> X"#, 260 r#"fn my_fun() -> &'_<|> X"#,
246 r#"fn my_fun<'a>() -> &'a X"#, 261 r#"fn my_fun<'a>() -> &'a X"#,
247 ); 262 );
@@ -250,7 +265,7 @@ mod tests {
250 #[test] 265 #[test]
251 fn test_function_param_anon_lifetime() { 266 fn test_function_param_anon_lifetime() {
252 check_assist( 267 check_assist(
253 change_lifetime_anon_to_named, 268 introduce_named_lifetime,
254 r#"fn my_fun(x: X<'_<|>>)"#, 269 r#"fn my_fun(x: X<'_<|>>)"#,
255 r#"fn my_fun<'a>(x: X<'a>)"#, 270 r#"fn my_fun<'a>(x: X<'a>)"#,
256 ); 271 );
@@ -259,7 +274,7 @@ mod tests {
259 #[test] 274 #[test]
260 fn test_function_add_lifetime_to_params() { 275 fn test_function_add_lifetime_to_params() {
261 check_assist( 276 check_assist(
262 change_lifetime_anon_to_named, 277 introduce_named_lifetime,
263 r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, 278 r#"fn my_fun(f: &Foo) -> X<'_<|>>"#,
264 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, 279 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
265 ); 280 );
@@ -268,7 +283,7 @@ mod tests {
268 #[test] 283 #[test]
269 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { 284 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
270 check_assist( 285 check_assist(
271 change_lifetime_anon_to_named, 286 introduce_named_lifetime,
272 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, 287 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
273 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, 288 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
274 ); 289 );
@@ -278,7 +293,7 @@ mod tests {
278 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { 293 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
279 // this is not permitted under lifetime elision rules 294 // this is not permitted under lifetime elision rules
280 check_assist_not_applicable( 295 check_assist_not_applicable(
281 change_lifetime_anon_to_named, 296 introduce_named_lifetime,
282 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, 297 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#,
283 ); 298 );
284 } 299 }
@@ -286,7 +301,7 @@ mod tests {
286 #[test] 301 #[test]
287 fn test_function_add_lifetime_to_self_ref_param() { 302 fn test_function_add_lifetime_to_self_ref_param() {
288 check_assist( 303 check_assist(
289 change_lifetime_anon_to_named, 304 introduce_named_lifetime,
290 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, 305 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
291 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, 306 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
292 ); 307 );
@@ -295,7 +310,7 @@ mod tests {
295 #[test] 310 #[test]
296 fn test_function_add_lifetime_to_param_with_non_ref_self() { 311 fn test_function_add_lifetime_to_param_with_non_ref_self() {
297 check_assist( 312 check_assist(
298 change_lifetime_anon_to_named, 313 introduce_named_lifetime,
299 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, 314 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
300 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, 315 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
301 ); 316 );
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 972d16241..1beccb61c 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
@@ -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> {
@@ -127,7 +132,7 @@ fn first_path(path: &ast::Path) -> ast::Path {
127 132
128#[cfg(test)] 133#[cfg(test)]
129mod tests { 134mod tests {
130 use crate::tests::check_assist; 135 use crate::tests::{check_assist, check_assist_not_applicable};
131 136
132 use super::*; 137 use super::*;
133 138
@@ -276,4 +281,14 @@ bar::baz};
276", 281",
277 ) 282 )
278 } 283 }
284
285 #[test]
286 fn test_empty_use() {
287 check_assist_not_applicable(
288 merge_imports,
289 r"
290use std::<|>
291fn main() {}",
292 );
293 }
279} 294}
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
index ca04ec671..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,32 +59,34 @@ 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 {
84 match a.pat() { 89 matches!(a.pat(), Some(ast::Pat::PlaceholderPat(..)))
85 Some(ra_syntax::ast::Pat::PlaceholderPat(..)) => true,
86 _ => false,
87 }
88} 90}
89 91
90#[cfg(test)] 92#[cfg(test)]
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
index be2a7eddc..ba3dafb99 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//
@@ -50,29 +50,36 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext
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 16002d2ac..6d77dff13 100644
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ b/crates/ra_assists/src/handlers/raw_string.rs
@@ -1,11 +1,13 @@
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 TextSize,
6}; 8};
7 9
8use crate::{AssistContext, AssistId, Assists}; 10use crate::{AssistContext, AssistId, AssistKind, Assists};
9 11
10// Assist: make_raw_string 12// Assist: make_raw_string
11// 13//
@@ -26,14 +28,25 @@ 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)?; 28 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
27 let value = token.value()?; 29 let value = token.value()?;
28 let target = token.syntax().text_range(); 30 let target = token.syntax().text_range();
29 acc.add(AssistId("make_raw_string"), "Rewrite as raw string", target, |edit| { 31 acc.add(
30 let max_hash_streak = count_hashes(&value); 32 AssistId("make_raw_string", AssistKind::RefactorRewrite),
31 let mut hashes = String::with_capacity(max_hash_streak + 1); 33 "Rewrite as raw string",
32 for _ in 0..hashes.capacity() { 34 target,
33 hashes.push('#'); 35 |edit| {
34 } 36 let max_hash_streak = count_hashes(&value);
35 edit.replace(token.syntax().text_range(), format!("r{}\"{}\"{}", hashes, value, hashes)); 37 let hashes = "#".repeat(max_hash_streak + 1);
36 }) 38 if matches!(value, Cow::Borrowed(_)) {
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 # to raw string", 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 })
@@ -109,18 +135,23 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
109 return None; 135 return None;
110 } 136 }
111 let target = token.text_range(); 137 let target = token.text_range();
112 acc.add(AssistId("remove_hash"), "Remove hash from raw string", target, |edit| { 138 acc.add(
113 let result = &text[2..text.len() - 1]; 139 AssistId("remove_hash", AssistKind::RefactorRewrite),
114 let result = if result.starts_with('\"') { 140 "Remove hash from raw string",
115 // FIXME: this logic is wrong, not only the last has has to handled specially 141 target,
116 // no more hash, escape 142 |edit| {
117 let internal_str = &result[1..result.len() - 1]; 143 let result = &text[2..text.len() - 1];
118 format!("\"{}\"", internal_str.escape_default().to_string()) 144 let result = if result.starts_with('\"') {
119 } else { 145 // FIXME: this logic is wrong, not only the last has has to handled specially
120 result.to_owned() 146 // no more hash, escape
121 }; 147 let internal_str = &result[1..result.len() - 1];
122 edit.replace(token.text_range(), format!("r{}", result)); 148 format!("\"{}\"", internal_str.escape_default().to_string())
123 }) 149 } else {
150 result.to_owned()
151 };
152 edit.replace(token.text_range(), format!("r{}", result));
153 },
154 )
124} 155}
125 156
126fn count_hashes(s: &str) -> usize { 157fn count_hashes(s: &str) -> usize {
@@ -158,16 +189,16 @@ mod test {
158 check_assist( 189 check_assist(
159 make_raw_string, 190 make_raw_string,
160 r#" 191 r#"
161 fn f() { 192fn f() {
162 let s = <|>"random\nstring"; 193 let s = <|>"random\nstring";
163 } 194}
164 "#, 195"#,
165 r##" 196 r##"
166 fn f() { 197fn f() {
167 let s = r#"random 198 let s = r#"random
168string"#; 199string"#;
169 } 200}
170 "##, 201"##,
171 ) 202 )
172 } 203 }
173 204
@@ -193,16 +224,16 @@ string"#;
193 check_assist( 224 check_assist(
194 make_raw_string, 225 make_raw_string,
195 r###" 226 r###"
196 fn f() { 227fn f() {
197 let s = <|>"#random##\nstring"; 228 let s = <|>"#random##\nstring";
198 } 229}
199 "###, 230"###,
200 r####" 231 r####"
201 fn f() { 232fn f() {
202 let s = r#"#random## 233 let s = r#"#random##
203string"#; 234string"#;
204 } 235}
205 "####, 236"####,
206 ) 237 )
207 } 238 }
208 239
@@ -211,16 +242,16 @@ string"#;
211 check_assist( 242 check_assist(
212 make_raw_string, 243 make_raw_string,
213 r###" 244 r###"
214 fn f() { 245fn f() {
215 let s = <|>"#random\"##\nstring"; 246 let s = <|>"#random\"##\nstring";
216 } 247}
217 "###, 248"###,
218 r####" 249 r####"
219 fn f() { 250fn f() {
220 let s = r###"#random"## 251 let s = r###"#random"##
221string"###; 252string"###;
222 } 253}
223 "####, 254"####,
224 ) 255 )
225 } 256 }
226 257
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
index 961ee1731..a616cca57 100644
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ b/crates/ra_assists/src/handlers/remove_dbg.rs
@@ -3,7 +3,7 @@ use ra_syntax::{
3 TextSize, T, 3 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//
@@ -38,7 +38,7 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 }; 38 };
39 39
40 let target = macro_call.syntax().text_range(); 40 let target = macro_call.syntax().text_range();
41 acc.add(AssistId("remove_dbg"), "Remove dbg!()", target, |builder| { 41 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
42 builder.replace(macro_range, macro_content); 42 builder.replace(macro_range, macro_content);
43 }) 43 })
44} 44}
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 897da2832..2ac1c56cf 100644
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ b/crates/ra_assists/src/handlers/reorder_fields.rs
@@ -1,11 +1,11 @@
1use std::collections::HashMap; 1use itertools::Itertools;
2use rustc_hash::FxHashMap;
2 3
3use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; 4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
4use itertools::Itertools;
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//
@@ -42,11 +42,16 @@ 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> {
@@ -87,13 +92,13 @@ fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option
87 } 92 }
88} 93}
89 94
90fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<HashMap<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 e016f51c3..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,28 +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_expr = unwrap_trivial_block(then_block); 54 target,
55 make::match_arm(vec![pat.clone()], then_expr) 55 move |edit| {
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()))
56 }; 75 };
57 let else_arm = {
58 let pattern = ctx
59 .sema
60 .type_of_pat(&pat)
61 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
62 .map(|it| it.sad_pattern())
63 .unwrap_or_else(|| make::placeholder_pat().into());
64 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr)
66 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]))
68 .indent(IndentLevel::from_node(if_expr.syntax()))
69 };
70 76
71 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 77 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
72 }) 78 },
79 )
73} 80}
74 81
75#[cfg(test)] 82#[cfg(test)]
@@ -213,4 +220,36 @@ fn foo(x: Result<i32, ()>) {
213 "#, 220 "#,
214 ); 221 );
215 } 222 }
223
224 #[test]
225 fn nested_indent() {
226 check_assist(
227 replace_if_let_with_match,
228 r#"
229fn main() {
230 if true {
231 <|>if let Ok(rel_path) = path.strip_prefix(root_path) {
232 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
233 Some((*id, rel_path))
234 } else {
235 None
236 }
237 }
238}
239"#,
240 r#"
241fn main() {
242 if true {
243 match path.strip_prefix(root_path) {
244 Ok(rel_path) => {
245 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
246 Some((*id, rel_path))
247 }
248 _ => None,
249 }
250 }
251}
252"#,
253 )
254 }
216} 255}
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 0197a8cf0..dfd314abf 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
@@ -1,7 +1,10 @@
1use hir; 1use hir;
2use ra_syntax::{ast, AstNode, SmolStr, TextRange}; 2use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};
3 3
4use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement},
6 AssistContext, AssistId, AssistKind, Assists,
7};
5 8
6// Assist: replace_qualified_name_with_use 9// Assist: replace_qualified_name_with_use
7// 10//
@@ -34,21 +37,23 @@ pub(crate) fn replace_qualified_name_with_use(
34 37
35 let target = path.syntax().text_range(); 38 let target = path.syntax().text_range();
36 acc.add( 39 acc.add(
37 AssistId("replace_qualified_name_with_use"), 40 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
38 "Replace qualified path with use", 41 "Replace qualified path with use",
39 target, 42 target,
40 |builder| { 43 |builder| {
41 let path_to_import = hir_path.mod_path().clone(); 44 let path_to_import = hir_path.mod_path().clone();
45 let container = match find_insert_use_container(path.syntax(), ctx) {
46 Some(c) => c,
47 None => return,
48 };
42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); 49 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());
43 50
44 if let Some(last) = path.segment() { 51 // Now that we've brought the name into scope, re-qualify all paths that could be
45 // Here we are assuming the assist will provide a correct use statement 52 // affected (that is, all paths inside the node we added the `use` to).
46 // so we can delete the path qualifier 53 let mut rewriter = SyntaxRewriter::default();
47 builder.delete(TextRange::new( 54 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
48 path.syntax().text_range().start(), 55 shorten_paths(&mut rewriter, syntax, path);
49 last.syntax().text_range().start(), 56 builder.rewrite(rewriter);
50 ));
51 }
52 }, 57 },
53 ) 58 )
54} 59}
@@ -73,6 +78,69 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
73 Some(ps) 78 Some(ps)
74} 79}
75 80
81/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
82fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) {
83 for child in node.children() {
84 match_ast! {
85 match child {
86 // Don't modify `use` items, as this can break the `use` item when injecting a new
87 // import into the use tree.
88 ast::UseItem(_it) => continue,
89 // Don't descend into submodules, they don't have the same `use` items in scope.
90 ast::Module(_it) => continue,
91
92 ast::Path(p) => {
93 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
94 Some(()) => {},
95 None => shorten_paths(rewriter, p.syntax().clone(), path.clone()),
96 }
97 },
98 _ => shorten_paths(rewriter, child, path.clone()),
99 }
100 }
101 }
102}
103
104fn maybe_replace_path(
105 rewriter: &mut SyntaxRewriter<'static>,
106 path: ast::Path,
107 target: ast::Path,
108) -> Option<()> {
109 if !path_eq(path.clone(), target.clone()) {
110 return None;
111 }
112
113 // Shorten `path`, leaving only its last segment.
114 if let Some(parent) = path.qualifier() {
115 rewriter.delete(parent.syntax());
116 }
117 if let Some(double_colon) = path.coloncolon_token() {
118 rewriter.delete(&double_colon);
119 }
120
121 Some(())
122}
123
124fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
125 let mut lhs_curr = lhs;
126 let mut rhs_curr = rhs;
127 loop {
128 match (lhs_curr.segment(), rhs_curr.segment()) {
129 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
130 _ => return false,
131 }
132
133 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
134 (Some(lhs), Some(rhs)) => {
135 lhs_curr = lhs;
136 rhs_curr = rhs;
137 }
138 (None, None) => return true,
139 _ => return false,
140 }
141 }
142}
143
76#[cfg(test)] 144#[cfg(test)]
77mod tests { 145mod tests {
78 use crate::tests::{check_assist, check_assist_not_applicable}; 146 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -83,10 +151,10 @@ mod tests {
83 fn test_replace_add_use_no_anchor() { 151 fn test_replace_add_use_no_anchor() {
84 check_assist( 152 check_assist(
85 replace_qualified_name_with_use, 153 replace_qualified_name_with_use,
86 " 154 r"
87std::fmt::Debug<|> 155std::fmt::Debug<|>
88 ", 156 ",
89 " 157 r"
90use std::fmt::Debug; 158use std::fmt::Debug;
91 159
92Debug 160Debug
@@ -97,13 +165,13 @@ Debug
97 fn test_replace_add_use_no_anchor_with_item_below() { 165 fn test_replace_add_use_no_anchor_with_item_below() {
98 check_assist( 166 check_assist(
99 replace_qualified_name_with_use, 167 replace_qualified_name_with_use,
100 " 168 r"
101std::fmt::Debug<|> 169std::fmt::Debug<|>
102 170
103fn main() { 171fn main() {
104} 172}
105 ", 173 ",
106 " 174 r"
107use std::fmt::Debug; 175use std::fmt::Debug;
108 176
109Debug 177Debug
@@ -118,13 +186,13 @@ fn main() {
118 fn test_replace_add_use_no_anchor_with_item_above() { 186 fn test_replace_add_use_no_anchor_with_item_above() {
119 check_assist( 187 check_assist(
120 replace_qualified_name_with_use, 188 replace_qualified_name_with_use,
121 " 189 r"
122fn main() { 190fn main() {
123} 191}
124 192
125std::fmt::Debug<|> 193std::fmt::Debug<|>
126 ", 194 ",
127 " 195 r"
128use std::fmt::Debug; 196use std::fmt::Debug;
129 197
130fn main() { 198fn main() {
@@ -139,10 +207,10 @@ Debug
139 fn test_replace_add_use_no_anchor_2seg() { 207 fn test_replace_add_use_no_anchor_2seg() {
140 check_assist( 208 check_assist(
141 replace_qualified_name_with_use, 209 replace_qualified_name_with_use,
142 " 210 r"
143std::fmt<|>::Debug 211std::fmt<|>::Debug
144 ", 212 ",
145 " 213 r"
146use std::fmt; 214use std::fmt;
147 215
148fmt::Debug 216fmt::Debug
@@ -154,13 +222,13 @@ fmt::Debug
154 fn test_replace_add_use() { 222 fn test_replace_add_use() {
155 check_assist( 223 check_assist(
156 replace_qualified_name_with_use, 224 replace_qualified_name_with_use,
157 " 225 r"
158use stdx; 226use stdx;
159 227
160impl std::fmt::Debug<|> for Foo { 228impl std::fmt::Debug<|> for Foo {
161} 229}
162 ", 230 ",
163 " 231 r"
164use stdx; 232use stdx;
165use std::fmt::Debug; 233use std::fmt::Debug;
166 234
@@ -174,11 +242,11 @@ impl Debug for Foo {
174 fn test_replace_file_use_other_anchor() { 242 fn test_replace_file_use_other_anchor() {
175 check_assist( 243 check_assist(
176 replace_qualified_name_with_use, 244 replace_qualified_name_with_use,
177 " 245 r"
178impl std::fmt::Debug<|> for Foo { 246impl std::fmt::Debug<|> for Foo {
179} 247}
180 ", 248 ",
181 " 249 r"
182use std::fmt::Debug; 250use std::fmt::Debug;
183 251
184impl Debug for Foo { 252impl Debug for Foo {
@@ -191,11 +259,11 @@ impl Debug for Foo {
191 fn test_replace_add_use_other_anchor_indent() { 259 fn test_replace_add_use_other_anchor_indent() {
192 check_assist( 260 check_assist(
193 replace_qualified_name_with_use, 261 replace_qualified_name_with_use,
194 " 262 r"
195 impl std::fmt::Debug<|> for Foo { 263 impl std::fmt::Debug<|> for Foo {
196 } 264 }
197 ", 265 ",
198 " 266 r"
199 use std::fmt::Debug; 267 use std::fmt::Debug;
200 268
201 impl Debug for Foo { 269 impl Debug for Foo {
@@ -208,13 +276,13 @@ impl Debug for Foo {
208 fn test_replace_split_different() { 276 fn test_replace_split_different() {
209 check_assist( 277 check_assist(
210 replace_qualified_name_with_use, 278 replace_qualified_name_with_use,
211 " 279 r"
212use std::fmt; 280use std::fmt;
213 281
214impl std::io<|> for Foo { 282impl std::io<|> for Foo {
215} 283}
216 ", 284 ",
217 " 285 r"
218use std::{io, fmt}; 286use std::{io, fmt};
219 287
220impl io for Foo { 288impl io for Foo {
@@ -227,13 +295,13 @@ impl io for Foo {
227 fn test_replace_split_self_for_use() { 295 fn test_replace_split_self_for_use() {
228 check_assist( 296 check_assist(
229 replace_qualified_name_with_use, 297 replace_qualified_name_with_use,
230 " 298 r"
231use std::fmt; 299use std::fmt;
232 300
233impl std::fmt::Debug<|> for Foo { 301impl std::fmt::Debug<|> for Foo {
234} 302}
235 ", 303 ",
236 " 304 r"
237use std::fmt::{self, Debug, }; 305use std::fmt::{self, Debug, };
238 306
239impl Debug for Foo { 307impl Debug for Foo {
@@ -246,13 +314,13 @@ impl Debug for Foo {
246 fn test_replace_split_self_for_target() { 314 fn test_replace_split_self_for_target() {
247 check_assist( 315 check_assist(
248 replace_qualified_name_with_use, 316 replace_qualified_name_with_use,
249 " 317 r"
250use std::fmt::Debug; 318use std::fmt::Debug;
251 319
252impl std::fmt<|> for Foo { 320impl std::fmt<|> for Foo {
253} 321}
254 ", 322 ",
255 " 323 r"
256use std::fmt::{self, Debug}; 324use std::fmt::{self, Debug};
257 325
258impl fmt for Foo { 326impl fmt for Foo {
@@ -265,13 +333,13 @@ impl fmt for Foo {
265 fn test_replace_add_to_nested_self_nested() { 333 fn test_replace_add_to_nested_self_nested() {
266 check_assist( 334 check_assist(
267 replace_qualified_name_with_use, 335 replace_qualified_name_with_use,
268 " 336 r"
269use std::fmt::{Debug, nested::{Display}}; 337use std::fmt::{Debug, nested::{Display}};
270 338
271impl std::fmt::nested<|> for Foo { 339impl std::fmt::nested<|> for Foo {
272} 340}
273", 341",
274 " 342 r"
275use std::fmt::{Debug, nested::{Display, self}}; 343use std::fmt::{Debug, nested::{Display, self}};
276 344
277impl nested for Foo { 345impl nested for Foo {
@@ -284,13 +352,13 @@ impl nested for Foo {
284 fn test_replace_add_to_nested_self_already_included() { 352 fn test_replace_add_to_nested_self_already_included() {
285 check_assist( 353 check_assist(
286 replace_qualified_name_with_use, 354 replace_qualified_name_with_use,
287 " 355 r"
288use std::fmt::{Debug, nested::{self, Display}}; 356use std::fmt::{Debug, nested::{self, Display}};
289 357
290impl std::fmt::nested<|> for Foo { 358impl std::fmt::nested<|> for Foo {
291} 359}
292", 360",
293 " 361 r"
294use std::fmt::{Debug, nested::{self, Display}}; 362use std::fmt::{Debug, nested::{self, Display}};
295 363
296impl nested for Foo { 364impl nested for Foo {
@@ -303,13 +371,13 @@ impl nested for Foo {
303 fn test_replace_add_to_nested_nested() { 371 fn test_replace_add_to_nested_nested() {
304 check_assist( 372 check_assist(
305 replace_qualified_name_with_use, 373 replace_qualified_name_with_use,
306 " 374 r"
307use std::fmt::{Debug, nested::{Display}}; 375use std::fmt::{Debug, nested::{Display}};
308 376
309impl std::fmt::nested::Debug<|> for Foo { 377impl std::fmt::nested::Debug<|> for Foo {
310} 378}
311", 379",
312 " 380 r"
313use std::fmt::{Debug, nested::{Display, Debug}}; 381use std::fmt::{Debug, nested::{Display, Debug}};
314 382
315impl Debug for Foo { 383impl Debug for Foo {
@@ -322,13 +390,13 @@ impl Debug for Foo {
322 fn test_replace_split_common_target_longer() { 390 fn test_replace_split_common_target_longer() {
323 check_assist( 391 check_assist(
324 replace_qualified_name_with_use, 392 replace_qualified_name_with_use,
325 " 393 r"
326use std::fmt::Debug; 394use std::fmt::Debug;
327 395
328impl std::fmt::nested::Display<|> for Foo { 396impl std::fmt::nested::Display<|> for Foo {
329} 397}
330", 398",
331 " 399 r"
332use std::fmt::{nested::Display, Debug}; 400use std::fmt::{nested::Display, Debug};
333 401
334impl Display for Foo { 402impl Display for Foo {
@@ -341,13 +409,13 @@ impl Display for Foo {
341 fn test_replace_split_common_use_longer() { 409 fn test_replace_split_common_use_longer() {
342 check_assist( 410 check_assist(
343 replace_qualified_name_with_use, 411 replace_qualified_name_with_use,
344 " 412 r"
345use std::fmt::nested::Debug; 413use std::fmt::nested::Debug;
346 414
347impl std::fmt::Display<|> for Foo { 415impl std::fmt::Display<|> for Foo {
348} 416}
349", 417",
350 " 418 r"
351use std::fmt::{Display, nested::Debug}; 419use std::fmt::{Display, nested::Debug};
352 420
353impl Display for Foo { 421impl Display for Foo {
@@ -360,7 +428,7 @@ impl Display for Foo {
360 fn test_replace_use_nested_import() { 428 fn test_replace_use_nested_import() {
361 check_assist( 429 check_assist(
362 replace_qualified_name_with_use, 430 replace_qualified_name_with_use,
363 " 431 r"
364use crate::{ 432use crate::{
365 ty::{Substs, Ty}, 433 ty::{Substs, Ty},
366 AssocItem, 434 AssocItem,
@@ -368,7 +436,7 @@ use crate::{
368 436
369fn foo() { crate::ty::lower<|>::trait_env() } 437fn foo() { crate::ty::lower<|>::trait_env() }
370", 438",
371 " 439 r"
372use crate::{ 440use crate::{
373 ty::{Substs, Ty, lower}, 441 ty::{Substs, Ty, lower},
374 AssocItem, 442 AssocItem,
@@ -383,13 +451,13 @@ fn foo() { lower::trait_env() }
383 fn test_replace_alias() { 451 fn test_replace_alias() {
384 check_assist( 452 check_assist(
385 replace_qualified_name_with_use, 453 replace_qualified_name_with_use,
386 " 454 r"
387use std::fmt as foo; 455use std::fmt as foo;
388 456
389impl foo::Debug<|> for Foo { 457impl foo::Debug<|> for Foo {
390} 458}
391", 459",
392 " 460 r"
393use std::fmt as foo; 461use std::fmt as foo;
394 462
395impl Debug for Foo { 463impl Debug for Foo {
@@ -402,7 +470,7 @@ impl Debug for Foo {
402 fn test_replace_not_applicable_one_segment() { 470 fn test_replace_not_applicable_one_segment() {
403 check_assist_not_applicable( 471 check_assist_not_applicable(
404 replace_qualified_name_with_use, 472 replace_qualified_name_with_use,
405 " 473 r"
406impl foo<|> for Foo { 474impl foo<|> for Foo {
407} 475}
408", 476",
@@ -413,7 +481,7 @@ impl foo<|> for Foo {
413 fn test_replace_not_applicable_in_use() { 481 fn test_replace_not_applicable_in_use() {
414 check_assist_not_applicable( 482 check_assist_not_applicable(
415 replace_qualified_name_with_use, 483 replace_qualified_name_with_use,
416 " 484 r"
417use std::fmt<|>; 485use std::fmt<|>;
418", 486",
419 ); 487 );
@@ -423,14 +491,14 @@ use std::fmt<|>;
423 fn test_replace_add_use_no_anchor_in_mod_mod() { 491 fn test_replace_add_use_no_anchor_in_mod_mod() {
424 check_assist( 492 check_assist(
425 replace_qualified_name_with_use, 493 replace_qualified_name_with_use,
426 " 494 r"
427mod foo { 495mod foo {
428 mod bar { 496 mod bar {
429 std::fmt::Debug<|> 497 std::fmt::Debug<|>
430 } 498 }
431} 499}
432 ", 500 ",
433 " 501 r"
434mod foo { 502mod foo {
435 mod bar { 503 mod bar {
436 use std::fmt::Debug; 504 use std::fmt::Debug;
@@ -446,14 +514,14 @@ mod foo {
446 fn inserts_imports_after_inner_attributes() { 514 fn inserts_imports_after_inner_attributes() {
447 check_assist( 515 check_assist(
448 replace_qualified_name_with_use, 516 replace_qualified_name_with_use,
449 " 517 r"
450#![allow(dead_code)] 518#![allow(dead_code)]
451 519
452fn main() { 520fn main() {
453 std::fmt::Debug<|> 521 std::fmt::Debug<|>
454} 522}
455 ", 523 ",
456 " 524 r"
457#![allow(dead_code)] 525#![allow(dead_code)]
458use std::fmt::Debug; 526use std::fmt::Debug;
459 527
@@ -463,4 +531,116 @@ fn main() {
463 ", 531 ",
464 ); 532 );
465 } 533 }
534
535 #[test]
536 fn replaces_all_affected_paths() {
537 check_assist(
538 replace_qualified_name_with_use,
539 r"
540fn main() {
541 std::fmt::Debug<|>;
542 let x: std::fmt::Debug = std::fmt::Debug;
543}
544 ",
545 r"
546use std::fmt::Debug;
547
548fn main() {
549 Debug;
550 let x: Debug = Debug;
551}
552 ",
553 );
554 }
555
556 #[test]
557 fn replaces_all_affected_paths_mod() {
558 check_assist(
559 replace_qualified_name_with_use,
560 r"
561mod m {
562 fn f() {
563 std::fmt::Debug<|>;
564 let x: std::fmt::Debug = std::fmt::Debug;
565 }
566 fn g() {
567 std::fmt::Debug;
568 }
569}
570
571fn f() {
572 std::fmt::Debug;
573}
574 ",
575 r"
576mod m {
577 use std::fmt::Debug;
578
579 fn f() {
580 Debug;
581 let x: Debug = Debug;
582 }
583 fn g() {
584 Debug;
585 }
586}
587
588fn f() {
589 std::fmt::Debug;
590}
591 ",
592 );
593 }
594
595 #[test]
596 fn does_not_replace_in_submodules() {
597 check_assist(
598 replace_qualified_name_with_use,
599 r"
600fn main() {
601 std::fmt::Debug<|>;
602}
603
604mod sub {
605 fn f() {
606 std::fmt::Debug;
607 }
608}
609 ",
610 r"
611use std::fmt::Debug;
612
613fn main() {
614 Debug;
615}
616
617mod sub {
618 fn f() {
619 std::fmt::Debug;
620 }
621}
622 ",
623 );
624 }
625
626 #[test]
627 fn does_not_replace_in_use() {
628 check_assist(
629 replace_qualified_name_with_use,
630 r"
631use std::fmt::Display;
632
633fn main() {
634 std::fmt<|>;
635}
636 ",
637 r"
638use std::fmt::{self, Display};
639
640fn main() {
641 fmt;
642}
643 ",
644 );
645 }
466} 646}
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 c7a874480..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}
@@ -66,4 +66,14 @@ mod tests {
66 fn issue4044() { 66 fn issue4044() {
67 check_assist_not_applicable(split_import, "use crate::<|>:::self;") 67 check_assist_not_applicable(split_import, "use crate::<|>:::self;")
68 } 68 }
69
70 #[test]
71 fn test_empty_use() {
72 check_assist_not_applicable(
73 split_import,
74 r"
75use std::<|>
76fn main() {}",
77 );
78 }
69} 79}
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 8440c7d0f..8b38695a9 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -1,10 +1,13 @@
1use ra_fmt::unwrap_trivial_block; 1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{ 2use ra_syntax::{
3 ast::{self, ElseBranch, Expr, LoopBodyOwner}, 3 ast::{
4 match_ast, AstNode, TextRange, T, 4 self,
5 edit::{AstNodeEdit, IndentLevel},
6 },
7 AstNode, TextRange, T,
5}; 8};
6 9
7use crate::{AssistContext, AssistId, Assists}; 10use crate::{AssistContext, AssistId, AssistKind, Assists};
8 11
9// Assist: unwrap_block 12// Assist: unwrap_block
10// 13//
@@ -24,94 +27,73 @@ use crate::{AssistContext, AssistId, Assists};
24// } 27// }
25// ``` 28// ```
26pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 29pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let l_curly_token = ctx.find_token_at_offset(T!['{'])?; 30 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
28 let block = ast::BlockExpr::cast(l_curly_token.parent())?;
29 let parent = block.syntax().parent()?;
30 let assist_id = AssistId("unwrap_block");
31 let assist_label = "Unwrap block"; 31 let assist_label = "Unwrap block";
32 32
33 let (expr, expr_to_unwrap) = match_ast! { 33 let l_curly_token = ctx.find_token_at_offset(T!['{'])?;
34 match parent { 34 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
35 ast::ForExpr(for_expr) => { 35 let mut parent = block.syntax().parent()?;
36 let block_expr = for_expr.loop_body()?; 36 if ast::MatchArm::can_cast(parent.kind()) {
37 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; 37 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
38 (ast::Expr::ForExpr(for_expr), expr_to_unwrap) 38 }
39 },
40 ast::WhileExpr(while_expr) => {
41 let block_expr = while_expr.loop_body()?;
42 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
43 (ast::Expr::WhileExpr(while_expr), expr_to_unwrap)
44 },
45 ast::LoopExpr(loop_expr) => {
46 let block_expr = loop_expr.loop_body()?;
47 let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?;
48 (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap)
49 },
50 ast::IfExpr(if_expr) => {
51 let mut resp = None;
52
53 let then_branch = if_expr.then_branch()?;
54 if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) {
55 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
56 // For `else if` blocks
57 let ancestor_then_branch = ancestor.then_branch()?;
58 let l_curly_token = then_branch.l_curly_token()?;
59
60 let target = then_branch.syntax().text_range();
61 return acc.add(assist_id, assist_label, target, |edit| {
62 let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
63 let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end());
64
65 edit.delete(range_to_del_rest);
66 edit.delete(range_to_del_else_if);
67 edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{']));
68 });
69 } else {
70 resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch)));
71 }
72 } else if let Some(else_branch) = if_expr.else_branch() {
73 match else_branch {
74 ElseBranch::Block(else_block) => {
75 let l_curly_token = else_block.l_curly_token()?;
76 if l_curly_token.text_range().contains_range(ctx.frange.range) {
77 let target = else_block.syntax().text_range();
78 return acc.add(assist_id, assist_label, target, |edit| {
79 let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start());
80
81 edit.delete(range_to_del);
82 edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{']));
83 });
84 }
85 },
86 ElseBranch::IfExpr(_) => {},
87 }
88 }
89 39
90 resp? 40 let parent = ast::Expr::cast(parent)?;
91 }, 41
92 _ => return None, 42 match parent.clone() {
43 ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
44 ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
45 ast::Expr::IfExpr(if_expr) => {
46 let then_branch = if_expr.then_branch()?;
47 if then_branch == block {
48 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
49 // For `else if` blocks
50 let ancestor_then_branch = ancestor.then_branch()?;
51
52 let target = then_branch.syntax().text_range();
53 return acc.add(assist_id, assist_label, target, |edit| {
54 let range_to_del_else_if = TextRange::new(
55 ancestor_then_branch.syntax().text_range().end(),
56 l_curly_token.text_range().start(),
57 );
58 let range_to_del_rest = TextRange::new(
59 then_branch.syntax().text_range().end(),
60 if_expr.syntax().text_range().end(),
61 );
62
63 edit.delete(range_to_del_rest);
64 edit.delete(range_to_del_else_if);
65 edit.replace(
66 target,
67 update_expr_string(then_branch.to_string(), &[' ', '{']),
68 );
69 });
70 }
71 } else {
72 let target = block.syntax().text_range();
73 return acc.add(assist_id, assist_label, target, |edit| {
74 let range_to_del = TextRange::new(
75 then_branch.syntax().text_range().end(),
76 l_curly_token.text_range().start(),
77 );
78
79 edit.delete(range_to_del);
80 edit.replace(target, update_expr_string(block.to_string(), &[' ', '{']));
81 });
82 }
93 } 83 }
84 _ => return None,
94 }; 85 };
95 86
96 let target = expr_to_unwrap.syntax().text_range(); 87 let unwrapped = unwrap_trivial_block(block);
97 acc.add(assist_id, assist_label, target, |edit| { 88 let target = unwrapped.syntax().text_range();
98 edit.replace( 89 acc.add(assist_id, assist_label, target, |builder| {
99 expr.syntax().text_range(), 90 builder.replace(
100 update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), 91 parent.syntax().text_range(),
92 update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']),
101 ); 93 );
102 }) 94 })
103} 95}
104 96
105fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option<ast::Expr> {
106 let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range);
107
108 if cursor_in_range {
109 Some(unwrap_trivial_block(block))
110 } else {
111 None
112 }
113}
114
115fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { 97fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
116 let expr_string = expr_str.trim_start_matches(trim_start_pat); 98 let expr_string = expr_str.trim_start_matches(trim_start_pat);
117 let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); 99 let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
@@ -490,6 +472,30 @@ mod tests {
490 } 472 }
491 473
492 #[test] 474 #[test]
475 fn unwrap_match_arm() {
476 check_assist(
477 unwrap_block,
478 r#"
479fn main() {
480 match rel_path {
481 Ok(rel_path) => {<|>
482 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
483 Some((*id, rel_path))
484 }
485 Err(_) => None,
486 }
487}
488"#,
489 r#"
490fn main() {
491 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
492 Some((*id, rel_path))
493}
494"#,
495 );
496 }
497
498 #[test]
493 fn simple_if_in_while_bad_cursor_position() { 499 fn simple_if_in_while_bad_cursor_position() {
494 check_assist_not_applicable( 500 check_assist_not_applicable(
495 unwrap_block, 501 unwrap_block,
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 3f8f7ffbf..465b90415 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -26,10 +26,40 @@ pub(crate) use crate::assist_context::{AssistContext, Assists};
26 26
27pub use assist_config::AssistConfig; 27pub use assist_config::AssistConfig;
28 28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind {
31 None,
32 QuickFix,
33 Generate,
34 Refactor,
35 RefactorExtract,
36 RefactorInline,
37 RefactorRewrite,
38}
39
40impl AssistKind {
41 pub fn contains(self, other: AssistKind) -> bool {
42 if self == other {
43 return true;
44 }
45
46 match self {
47 AssistKind::None | AssistKind::Generate => return true,
48 AssistKind::Refactor => match other {
49 AssistKind::RefactorExtract
50 | AssistKind::RefactorInline
51 | AssistKind::RefactorRewrite => return true,
52 _ => return false,
53 },
54 _ => return false,
55 }
56 }
57}
58
29/// Unique identifier of the assist, should not be shown to the user 59/// Unique identifier of the assist, should not be shown to the user
30/// directly. 60/// directly.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)] 61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct AssistId(pub &'static str); 62pub struct AssistId(pub &'static str, pub AssistKind);
33 63
34#[derive(Clone, Debug)] 64#[derive(Clone, Debug)]
35pub struct GroupLabel(pub String); 65pub struct GroupLabel(pub String);
@@ -102,27 +132,28 @@ mod handlers {
102 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>; 132 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
103 133
104 mod add_custom_impl; 134 mod add_custom_impl;
105 mod add_derive;
106 mod add_explicit_type; 135 mod add_explicit_type;
107 mod add_from_impl_for_enum;
108 mod add_function;
109 mod add_impl;
110 mod add_missing_impl_members; 136 mod add_missing_impl_members;
111 mod add_new;
112 mod add_turbo_fish; 137 mod add_turbo_fish;
113 mod apply_demorgan; 138 mod apply_demorgan;
114 mod auto_import; 139 mod auto_import;
115 mod change_lifetime_anon_to_named;
116 mod change_return_type_to_result; 140 mod change_return_type_to_result;
117 mod change_visibility; 141 mod change_visibility;
118 mod early_return; 142 mod early_return;
143 mod extract_struct_from_enum_variant;
144 mod extract_variable;
119 mod fill_match_arms; 145 mod fill_match_arms;
120 mod fix_visibility; 146 mod fix_visibility;
121 mod flip_binexpr; 147 mod flip_binexpr;
122 mod flip_comma; 148 mod flip_comma;
123 mod flip_trait_bound; 149 mod flip_trait_bound;
150 mod generate_derive;
151 mod generate_from_impl_for_enum;
152 mod generate_function;
153 mod generate_impl;
154 mod generate_new;
124 mod inline_local_variable; 155 mod inline_local_variable;
125 mod introduce_variable; 156 mod introduce_named_lifetime;
126 mod invert_if; 157 mod invert_if;
127 mod merge_imports; 158 mod merge_imports;
128 mod merge_match_arms; 159 mod merge_match_arms;
@@ -143,26 +174,27 @@ mod handlers {
143 &[ 174 &[
144 // These are alphabetic for the foolish consistency 175 // These are alphabetic for the foolish consistency
145 add_custom_impl::add_custom_impl, 176 add_custom_impl::add_custom_impl,
146 add_derive::add_derive,
147 add_explicit_type::add_explicit_type, 177 add_explicit_type::add_explicit_type,
148 add_from_impl_for_enum::add_from_impl_for_enum,
149 add_function::add_function,
150 add_impl::add_impl,
151 add_new::add_new,
152 add_turbo_fish::add_turbo_fish, 178 add_turbo_fish::add_turbo_fish,
153 apply_demorgan::apply_demorgan, 179 apply_demorgan::apply_demorgan,
154 auto_import::auto_import, 180 auto_import::auto_import,
155 change_lifetime_anon_to_named::change_lifetime_anon_to_named,
156 change_return_type_to_result::change_return_type_to_result, 181 change_return_type_to_result::change_return_type_to_result,
157 change_visibility::change_visibility, 182 change_visibility::change_visibility,
158 early_return::convert_to_guarded_return, 183 early_return::convert_to_guarded_return,
184 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
185 extract_variable::extract_variable,
159 fill_match_arms::fill_match_arms, 186 fill_match_arms::fill_match_arms,
160 fix_visibility::fix_visibility, 187 fix_visibility::fix_visibility,
161 flip_binexpr::flip_binexpr, 188 flip_binexpr::flip_binexpr,
162 flip_comma::flip_comma, 189 flip_comma::flip_comma,
163 flip_trait_bound::flip_trait_bound, 190 flip_trait_bound::flip_trait_bound,
191 generate_derive::generate_derive,
192 generate_from_impl_for_enum::generate_from_impl_for_enum,
193 generate_function::generate_function,
194 generate_impl::generate_impl,
195 generate_new::generate_new,
164 inline_local_variable::inline_local_variable, 196 inline_local_variable::inline_local_variable,
165 introduce_variable::introduce_variable, 197 introduce_named_lifetime::introduce_named_lifetime,
166 invert_if::invert_if, 198 invert_if::invert_if,
167 merge_imports::merge_imports, 199 merge_imports::merge_imports,
168 merge_match_arms::merge_match_arms, 200 merge_match_arms::merge_match_arms,
diff --git a/crates/ra_assists/src/tests.rs b/crates/ra_assists/src/tests.rs
index 62dd3547f..18fcb9049 100644
--- a/crates/ra_assists/src/tests.rs
+++ b/crates/ra_assists/src/tests.rs
@@ -1,27 +1,21 @@
1mod generated; 1mod generated;
2 2
3use std::sync::Arc;
4
5use hir::Semantics; 3use hir::Semantics;
6use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; 4use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
7use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 5use ra_ide_db::RootDatabase;
8use ra_syntax::TextRange; 6use ra_syntax::TextRange;
9use test_utils::{ 7use test_utils::{assert_eq_text, extract_offset, extract_range};
10 assert_eq_text, extract_offset, extract_range, extract_range_or_offset, RangeOrOffset,
11};
12 8
13use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, Assists}; 9use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
10use stdx::trim_indent;
14 11
15pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) { 12pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
16 let (mut db, file_id) = RootDatabase::with_single_file(text); 13 RootDatabase::with_single_file(text)
17 // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`,
18 // but it looks like this might need specialization? :(
19 db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)]));
20 (db, file_id)
21} 14}
22 15
23pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) { 16pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
24 check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after)); 17 let ra_fixture_after = trim_indent(ra_fixture_after);
18 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after));
25} 19}
26 20
27// FIXME: instead of having a separate function here, maybe use 21// FIXME: instead of having a separate function here, maybe use
@@ -36,8 +30,9 @@ pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
36} 30}
37 31
38fn check_doc_test(assist_id: &str, before: &str, after: &str) { 32fn check_doc_test(assist_id: &str, before: &str, after: &str) {
39 let (selection, before) = extract_range_or_offset(before); 33 let after = trim_indent(after);
40 let (db, file_id) = crate::tests::with_single_file(&before); 34 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
35 let before = db.file_text(file_id).to_string();
41 let frange = FileRange { file_id, range: selection.into() }; 36 let frange = FileRange { file_id, range: selection.into() };
42 37
43 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange) 38 let mut assist = Assist::resolved(&db, &AssistConfig::default(), frange)
@@ -57,11 +52,11 @@ fn check_doc_test(assist_id: &str, before: &str, after: &str) {
57 52
58 let actual = { 53 let actual = {
59 let change = assist.source_change.source_file_edits.pop().unwrap(); 54 let change = assist.source_change.source_file_edits.pop().unwrap();
60 let mut actual = before.clone(); 55 let mut actual = before;
61 change.edit.apply(&mut actual); 56 change.edit.apply(&mut actual);
62 actual 57 actual
63 }; 58 };
64 assert_eq_text!(after, &actual); 59 assert_eq_text!(&after, &actual);
65} 60}
66 61
67enum ExpectedResult<'a> { 62enum ExpectedResult<'a> {
@@ -71,20 +66,8 @@ enum ExpectedResult<'a> {
71} 66}
72 67
73fn check(handler: Handler, before: &str, expected: ExpectedResult) { 68fn check(handler: Handler, before: &str, expected: ExpectedResult) {
74 let (text_without_caret, file_with_caret_id, range_or_offset, db) = if before.contains("//-") { 69 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
75 let (mut db, position) = RootDatabase::with_position(before); 70 let text_without_caret = db.file_text(file_with_caret_id).to_string();
76 db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)]));
77 (
78 db.file_text(position.file_id).as_ref().to_owned(),
79 position.file_id,
80 RangeOrOffset::Offset(position.offset),
81 db,
82 )
83 } else {
84 let (range_or_offset, text_without_caret) = extract_range_or_offset(before);
85 let (db, file_id) = with_single_file(&text_without_caret);
86 (text_without_caret, file_id, range_or_offset, db)
87 };
88 71
89 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; 72 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
90 73
@@ -151,3 +134,46 @@ fn assist_order_if_expr() {
151 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable"); 134 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
152 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match"); 135 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
153} 136}
137
138#[test]
139fn assist_filter_works() {
140 let before = "
141 pub fn test_some_range(a: int) -> bool {
142 if let 2..6 = <|>5<|> {
143 true
144 } else {
145 false
146 }
147 }";
148 let (range, before) = extract_range(before);
149 let (db, file_id) = with_single_file(&before);
150 let frange = FileRange { file_id, range };
151
152 {
153 let mut cfg = AssistConfig::default();
154 cfg.allowed = Some(vec![AssistKind::Refactor]);
155
156 let assists = Assist::resolved(&db, &cfg, frange);
157 let mut assists = assists.iter();
158
159 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
160 assert_eq!(assists.next().expect("expected assist").assist.label, "Replace with match");
161 }
162
163 {
164 let mut cfg = AssistConfig::default();
165 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
166 let assists = Assist::resolved(&db, &cfg, frange);
167 assert_eq!(assists.len(), 1);
168
169 let mut assists = assists.iter();
170 assert_eq!(assists.next().expect("expected assist").assist.label, "Extract into variable");
171 }
172
173 {
174 let mut cfg = AssistConfig::default();
175 cfg.allowed = Some(vec![AssistKind::QuickFix]);
176 let assists = Assist::resolved(&db, &cfg, frange);
177 assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
178 }
179}
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index 73d43283d..eff7feded 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -22,26 +22,6 @@ impl Debug for S {
22} 22}
23 23
24#[test] 24#[test]
25fn doctest_add_derive() {
26 check_doc_test(
27 "add_derive",
28 r#####"
29struct Point {
30 x: u32,
31 y: u32,<|>
32}
33"#####,
34 r#####"
35#[derive($0)]
36struct Point {
37 x: u32,
38 y: u32,
39}
40"#####,
41 )
42}
43
44#[test]
45fn doctest_add_explicit_type() { 25fn doctest_add_explicit_type() {
46 check_doc_test( 26 check_doc_test(
47 "add_explicit_type", 27 "add_explicit_type",
@@ -59,52 +39,6 @@ fn main() {
59} 39}
60 40
61#[test] 41#[test]
62fn doctest_add_from_impl_for_enum() {
63 check_doc_test(
64 "add_from_impl_for_enum",
65 r#####"
66enum A { <|>One(u32) }
67"#####,
68 r#####"
69enum A { One(u32) }
70
71impl From<u32> for A {
72 fn from(v: u32) -> Self {
73 A::One(v)
74 }
75}
76"#####,
77 )
78}
79
80#[test]
81fn doctest_add_function() {
82 check_doc_test(
83 "add_function",
84 r#####"
85struct Baz;
86fn baz() -> Baz { Baz }
87fn foo() {
88 bar<|>("", baz());
89}
90
91"#####,
92 r#####"
93struct Baz;
94fn baz() -> Baz { Baz }
95fn foo() {
96 bar("", baz());
97}
98
99fn bar(arg: &str, baz: Baz) {
100 ${0:todo!()}
101}
102
103"#####,
104 )
105}
106
107#[test]
108fn doctest_add_hash() { 42fn doctest_add_hash() {
109 check_doc_test( 43 check_doc_test(
110 "add_hash", 44 "add_hash",
@@ -122,27 +56,6 @@ fn main() {
122} 56}
123 57
124#[test] 58#[test]
125fn doctest_add_impl() {
126 check_doc_test(
127 "add_impl",
128 r#####"
129struct Ctx<T: Clone> {
130 data: T,<|>
131}
132"#####,
133 r#####"
134struct Ctx<T: Clone> {
135 data: T,
136}
137
138impl<T: Clone> Ctx<T> {
139 $0
140}
141"#####,
142 )
143}
144
145#[test]
146fn doctest_add_impl_default_members() { 59fn doctest_add_impl_default_members() {
147 check_doc_test( 60 check_doc_test(
148 "add_impl_default_members", 61 "add_impl_default_members",
@@ -209,28 +122,6 @@ impl Trait<u32> for () {
209} 122}
210 123
211#[test] 124#[test]
212fn doctest_add_new() {
213 check_doc_test(
214 "add_new",
215 r#####"
216struct Ctx<T: Clone> {
217 data: T,<|>
218}
219"#####,
220 r#####"
221struct Ctx<T: Clone> {
222 data: T,
223}
224
225impl<T: Clone> Ctx<T> {
226 fn $0new(data: T) -> Self { Self { data } }
227}
228
229"#####,
230 )
231}
232
233#[test]
234fn doctest_add_turbo_fish() { 125fn doctest_add_turbo_fish() {
235 check_doc_test( 126 check_doc_test(
236 "add_turbo_fish", 127 "add_turbo_fish",
@@ -288,31 +179,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } }
288} 179}
289 180
290#[test] 181#[test]
291fn doctest_change_lifetime_anon_to_named() {
292 check_doc_test(
293 "change_lifetime_anon_to_named",
294 r#####"
295impl Cursor<'_<|>> {
296 fn node(self) -> &SyntaxNode {
297 match self {
298 Cursor::Replace(node) | Cursor::Before(node) => node,
299 }
300 }
301}
302"#####,
303 r#####"
304impl<'a> Cursor<'a> {
305 fn node(self) -> &SyntaxNode {
306 match self {
307 Cursor::Replace(node) | Cursor::Before(node) => node,
308 }
309 }
310}
311"#####,
312 )
313}
314
315#[test]
316fn doctest_change_return_type_to_result() { 182fn doctest_change_return_type_to_result() {
317 check_doc_test( 183 check_doc_test(
318 "change_return_type_to_result", 184 "change_return_type_to_result",
@@ -363,6 +229,39 @@ fn main() {
363} 229}
364 230
365#[test] 231#[test]
232fn doctest_extract_struct_from_enum_variant() {
233 check_doc_test(
234 "extract_struct_from_enum_variant",
235 r#####"
236enum A { <|>One(u32, u32) }
237"#####,
238 r#####"
239struct One(pub u32, pub u32);
240
241enum A { One(One) }
242"#####,
243 )
244}
245
246#[test]
247fn doctest_extract_variable() {
248 check_doc_test(
249 "extract_variable",
250 r#####"
251fn main() {
252 <|>(1 + 2)<|> * 4;
253}
254"#####,
255 r#####"
256fn main() {
257 let $0var_name = (1 + 2);
258 var_name * 4;
259}
260"#####,
261 )
262}
263
264#[test]
366fn doctest_fill_match_arms() { 265fn doctest_fill_match_arms() {
367 check_doc_test( 266 check_doc_test(
368 "fill_match_arms", 267 "fill_match_arms",
@@ -459,6 +358,115 @@ fn foo<T: Copy + Clone>() { }
459} 358}
460 359
461#[test] 360#[test]
361fn doctest_generate_derive() {
362 check_doc_test(
363 "generate_derive",
364 r#####"
365struct Point {
366 x: u32,
367 y: u32,<|>
368}
369"#####,
370 r#####"
371#[derive($0)]
372struct Point {
373 x: u32,
374 y: u32,
375}
376"#####,
377 )
378}
379
380#[test]
381fn doctest_generate_from_impl_for_enum() {
382 check_doc_test(
383 "generate_from_impl_for_enum",
384 r#####"
385enum A { <|>One(u32) }
386"#####,
387 r#####"
388enum A { One(u32) }
389
390impl From<u32> for A {
391 fn from(v: u32) -> Self {
392 A::One(v)
393 }
394}
395"#####,
396 )
397}
398
399#[test]
400fn doctest_generate_function() {
401 check_doc_test(
402 "generate_function",
403 r#####"
404struct Baz;
405fn baz() -> Baz { Baz }
406fn foo() {
407 bar<|>("", baz());
408}
409
410"#####,
411 r#####"
412struct Baz;
413fn baz() -> Baz { Baz }
414fn foo() {
415 bar("", baz());
416}
417
418fn bar(arg: &str, baz: Baz) {
419 ${0:todo!()}
420}
421
422"#####,
423 )
424}
425
426#[test]
427fn doctest_generate_impl() {
428 check_doc_test(
429 "generate_impl",
430 r#####"
431struct Ctx<T: Clone> {
432 data: T,<|>
433}
434"#####,
435 r#####"
436struct Ctx<T: Clone> {
437 data: T,
438}
439
440impl<T: Clone> Ctx<T> {
441 $0
442}
443"#####,
444 )
445}
446
447#[test]
448fn doctest_generate_new() {
449 check_doc_test(
450 "generate_new",
451 r#####"
452struct Ctx<T: Clone> {
453 data: T,<|>
454}
455"#####,
456 r#####"
457struct Ctx<T: Clone> {
458 data: T,
459}
460
461impl<T: Clone> Ctx<T> {
462 fn $0new(data: T) -> Self { Self { data } }
463}
464
465"#####,
466 )
467}
468
469#[test]
462fn doctest_inline_local_variable() { 470fn doctest_inline_local_variable() {
463 check_doc_test( 471 check_doc_test(
464 "inline_local_variable", 472 "inline_local_variable",
@@ -477,18 +485,25 @@ fn main() {
477} 485}
478 486
479#[test] 487#[test]
480fn doctest_introduce_variable() { 488fn doctest_introduce_named_lifetime() {
481 check_doc_test( 489 check_doc_test(
482 "introduce_variable", 490 "introduce_named_lifetime",
483 r#####" 491 r#####"
484fn main() { 492impl Cursor<'_<|>> {
485 <|>(1 + 2)<|> * 4; 493 fn node(self) -> &SyntaxNode {
494 match self {
495 Cursor::Replace(node) | Cursor::Before(node) => node,
496 }
497 }
486} 498}
487"#####, 499"#####,
488 r#####" 500 r#####"
489fn main() { 501impl<'a> Cursor<'a> {
490 let $0var_name = (1 + 2); 502 fn node(self) -> &SyntaxNode {
491 var_name * 4; 503 match self {
504 Cursor::Replace(node) | Cursor::Before(node) => node,
505 }
506 }
492} 507}
493"#####, 508"#####,
494 ) 509 )
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index 0038a9764..02de902d6 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -7,13 +7,15 @@ use hir::{Adt, Crate, Enum, ScopeDef, Semantics, Trait, Type};
7use ra_ide_db::RootDatabase; 7use ra_ide_db::RootDatabase;
8use ra_syntax::{ 8use ra_syntax::{
9 ast::{self, make, NameOwner}, 9 ast::{self, make, NameOwner},
10 AstNode, SyntaxNode, T, 10 AstNode,
11 SyntaxKind::*,
12 SyntaxNode, TextSize, T,
11}; 13};
12use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
13 15
14use crate::assist_config::SnippetCap; 16use crate::assist_config::SnippetCap;
15 17
16pub(crate) use insert_use::insert_use_statement; 18pub(crate) use insert_use::{find_insert_use_container, insert_use_statement};
17 19
18#[derive(Clone, Copy, Debug)] 20#[derive(Clone, Copy, Debug)]
19pub(crate) enum Cursor<'a> { 21pub(crate) enum Cursor<'a> {
@@ -120,6 +122,13 @@ pub(crate) fn resolve_target_trait(
120 } 122 }
121} 123}
122 124
125pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
126 node.children_with_tokens()
127 .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
128 .map(|it| it.text_range().start())
129 .unwrap_or_else(|| node.text_range().start())
130}
131
123pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr { 132pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
124 if let Some(expr) = invert_special_case(&expr) { 133 if let Some(expr) = invert_special_case(&expr) {
125 return expr; 134 return expr;
@@ -198,8 +207,7 @@ pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>,
198#[allow(non_snake_case)] 207#[allow(non_snake_case)]
199impl FamousDefs<'_, '_> { 208impl FamousDefs<'_, '_> {
200 #[cfg(test)] 209 #[cfg(test)]
201 pub(crate) const FIXTURE: &'static str = r#" 210 pub(crate) const FIXTURE: &'static str = r#"//- /libcore.rs crate:core
202//- /libcore.rs crate:core
203pub mod convert { 211pub mod convert {
204 pub trait From<T> { 212 pub trait From<T> {
205 fn from(T) -> Self; 213 fn from(T) -> Self;
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index 0ee43482f..8c4f33e59 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -12,6 +12,20 @@ use ra_syntax::{
12use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
13 13
14use crate::assist_context::AssistContext; 14use crate::assist_context::AssistContext;
15use either::Either;
16
17/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
18pub(crate) fn find_insert_use_container(
19 position: &SyntaxNode,
20 ctx: &AssistContext,
21) -> Option<Either<ast::ItemList, ast::SourceFile>> {
22 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
23 if let Some(module) = ast::Module::cast(n.clone()) {
24 return module.item_list().map(|it| Either::Left(it));
25 }
26 Some(Either::Right(ast::SourceFile::cast(n)?))
27 })
28}
15 29
16/// Creates and inserts a use statement for the given path to import. 30/// Creates and inserts a use statement for the given path to import.
17/// The use statement is inserted in the scope most appropriate to the 31/// The use statement is inserted in the scope most appropriate to the
@@ -24,15 +38,11 @@ pub(crate) fn insert_use_statement(
24 builder: &mut TextEditBuilder, 38 builder: &mut TextEditBuilder,
25) { 39) {
26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 40 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { 41 let container = find_insert_use_container(position, ctx);
28 if let Some(module) = ast::Module::cast(n.clone()) {
29 return module.item_list().map(|it| it.syntax().clone());
30 }
31 ast::SourceFile::cast(n).map(|it| it.syntax().clone())
32 });
33 42
34 if let Some(container) = container { 43 if let Some(container) = container {
35 let action = best_action_for_target(container, position.clone(), &target); 44 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
45 let action = best_action_for_target(syntax, position.clone(), &target);
36 make_assist(&action, &target, builder); 46 make_assist(&action, &target, builder);
37 } 47 }
38} 48}