aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assist_context.rs39
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs4
-rw-r--r--crates/ra_assists/src/handlers/add_function.rs2
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs103
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs2
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs320
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs4
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs19
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs37
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs164
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/tests/generated.rs15
12 files changed, 609 insertions, 102 deletions
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index 5b1a4680b..edd8255f4 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};
@@ -170,13 +172,32 @@ impl Assists {
170 172
171pub(crate) struct AssistBuilder { 173pub(crate) struct AssistBuilder {
172 edit: TextEditBuilder, 174 edit: TextEditBuilder,
173 file: FileId, 175 file_id: FileId,
174 is_snippet: bool, 176 is_snippet: bool,
177 edits: Vec<SourceFileEdit>,
175} 178}
176 179
177impl AssistBuilder { 180impl AssistBuilder {
178 pub(crate) fn new(file: FileId) -> AssistBuilder { 181 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
179 AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } 182 AssistBuilder {
183 edit: TextEditBuilder::default(),
184 file_id,
185 is_snippet: false,
186 edits: Vec::new(),
187 }
188 }
189
190 pub(crate) fn edit_file(&mut self, file_id: FileId) {
191 self.file_id = file_id;
192 }
193
194 fn commit(&mut self) {
195 let edit = mem::take(&mut self.edit).finish();
196 if !edit.is_empty() {
197 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
198 assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id));
199 self.edits.push(new_edit);
200 }
180 } 201 }
181 202
182 /// Remove specified `range` of text. 203 /// Remove specified `range` of text.
@@ -234,21 +255,15 @@ impl AssistBuilder {
234 algo::diff(&node, &new).into_text_edit(&mut self.edit) 255 algo::diff(&node, &new).into_text_edit(&mut self.edit)
235 } 256 }
236 257
237 // FIXME: better API
238 pub(crate) fn set_file(&mut self, assist_file: FileId) {
239 self.file = assist_file;
240 }
241
242 // FIXME: kill this API 258 // FIXME: kill this API
243 /// Get access to the raw `TextEditBuilder`. 259 /// Get access to the raw `TextEditBuilder`.
244 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { 260 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
245 &mut self.edit 261 &mut self.edit
246 } 262 }
247 263
248 fn finish(self) -> SourceChange { 264 fn finish(mut self) -> SourceChange {
249 let edit = self.edit.finish(); 265 self.commit();
250 let source_file_edit = SourceFileEdit { file_id: self.file, edit }; 266 let mut res: SourceChange = mem::take(&mut self.edits).into();
251 let mut res: SourceChange = source_file_edit.into();
252 if self.is_snippet { 267 if self.is_snippet {
253 res.is_snippet = true; 268 res.is_snippet = true;
254 } 269 }
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
index ab20c6649..90b06a625 100644
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ b/crates/ra_assists/src/handlers/add_explicit_type.rs
@@ -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_function.rs b/crates/ra_assists/src/handlers/add_function.rs
index 24f931a85..1cfbd75aa 100644
--- a/crates/ra_assists/src/handlers/add_function.rs
+++ b/crates/ra_assists/src/handlers/add_function.rs
@@ -64,7 +64,7 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
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(AssistId("add_function"), "Add function", target, |builder| {
66 let function_template = function_builder.render(); 66 let function_template = function_builder.render();
67 builder.set_file(function_template.file); 67 builder.edit_file(function_template.file);
68 let new_fn = function_template.to_string(ctx.config.snippet_cap); 68 let new_fn = function_template.to_string(ctx.config.snippet_cap);
69 match ctx.config.snippet_cap { 69 match ctx.config.snippet_cap {
70 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), 70 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
index edf96d50e..5092bf336 100644
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -130,7 +130,7 @@ impl AutoImportAssets {
130 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> { 130 fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet<ModPath> {
131 let _p = profile("auto_import::search_for_imports"); 131 let _p = profile("auto_import::search_for_imports");
132 let current_crate = self.module_with_name_to_import.krate(); 132 let current_crate = self.module_with_name_to_import.krate();
133 ImportsLocator::new(db) 133 ImportsLocator::new(db, current_crate)
134 .find_imports(&self.get_search_query()) 134 .find_imports(&self.get_search_query())
135 .into_iter() 135 .into_iter()
136 .filter_map(|candidate| match &self.import_candidate { 136 .filter_map(|candidate| match &self.import_candidate {
@@ -841,4 +841,105 @@ fn main() {
841 ", 841 ",
842 ) 842 )
843 } 843 }
844
845 #[test]
846 fn dep_import() {
847 check_assist(
848 auto_import,
849 r"
850 //- /lib.rs crate:dep
851 pub struct Struct;
852
853 //- /main.rs crate:main deps:dep
854 fn main() {
855 Struct<|>
856 }",
857 r"use dep::Struct;
858
859fn main() {
860 Struct
861}
862",
863 );
864 }
865
866 #[test]
867 fn whole_segment() {
868 // Tests that only imports whose last segment matches the identifier get suggested.
869 check_assist(
870 auto_import,
871 r"
872 //- /lib.rs crate:dep
873 pub mod fmt {
874 pub trait Display {}
875 }
876
877 pub fn panic_fmt() {}
878
879 //- /main.rs crate:main deps:dep
880 struct S;
881
882 impl f<|>mt::Display for S {}",
883 r"use dep::fmt;
884
885struct S;
886impl fmt::Display for S {}
887",
888 );
889 }
890
891 #[test]
892 fn macro_generated() {
893 // Tests that macro-generated items are suggested from external crates.
894 check_assist(
895 auto_import,
896 r"
897 //- /lib.rs crate:dep
898
899 macro_rules! mac {
900 () => {
901 pub struct Cheese;
902 };
903 }
904
905 mac!();
906
907 //- /main.rs crate:main deps:dep
908
909 fn main() {
910 Cheese<|>;
911 }",
912 r"use dep::Cheese;
913
914fn main() {
915 Cheese;
916}
917",
918 );
919 }
920
921 #[test]
922 fn casing() {
923 // Tests that differently cased names don't interfere and we only suggest the matching one.
924 check_assist(
925 auto_import,
926 r"
927 //- /lib.rs crate:dep
928
929 pub struct FMT;
930 pub struct fmt;
931
932 //- /main.rs crate:main deps:dep
933
934 fn main() {
935 FMT<|>;
936 }",
937 r"use dep::FMT;
938
939fn main() {
940 FMT;
941}
942",
943 );
944 }
844} 945}
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
index 4cc75a7ce..dfade7432 100644
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ b/crates/ra_assists/src/handlers/early_return.rs
@@ -154,7 +154,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
154 parent_block: &ast::BlockExpr, 154 parent_block: &ast::BlockExpr,
155 if_expr: &ast::IfExpr, 155 if_expr: &ast::IfExpr,
156 ) -> SyntaxNode { 156 ) -> SyntaxNode {
157 let then_block_items = then_block.dedent(IndentLevel::from(1)); 157 let then_block_items = then_block.dedent(IndentLevel(1));
158 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); 158 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
159 let end_of_then = 159 let end_of_then =
160 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { 160 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
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..44db7917a
--- /dev/null
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,320 @@
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, Assists,
14};
15
16// Assist: extract_struct_from_enum_variant
17//
18// Extracts a struct from enum variant.
19//
20// ```
21// enum A { <|>One(u32, u32) }
22// ```
23// ->
24// ```
25// struct One(pub u32, pub u32);
26//
27// enum A { One(One) }
28// ```
29pub(crate) fn extract_struct_from_enum_variant(
30 acc: &mut Assists,
31 ctx: &AssistContext,
32) -> Option<()> {
33 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
34 let field_list = match variant.kind() {
35 ast::StructKind::Tuple(field_list) => field_list,
36 _ => return None,
37 };
38 let variant_name = variant.name()?.to_string();
39 let variant_hir = ctx.sema.to_def(&variant)?;
40 if existing_struct_def(ctx.db, &variant_name, &variant_hir) {
41 return None;
42 }
43 let enum_ast = variant.parent_enum();
44 let visibility = enum_ast.visibility();
45 let enum_hir = ctx.sema.to_def(&enum_ast)?;
46 let variant_hir_name = variant_hir.name(ctx.db);
47 let enum_module_def = ModuleDef::from(enum_hir);
48 let current_module = enum_hir.module(ctx.db);
49 let target = variant.syntax().text_range();
50 acc.add(
51 AssistId("extract_struct_from_enum_variant"),
52 "Extract struct from enum variant",
53 target,
54 |builder| {
55 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
56 let res = definition.find_usages(&ctx.db, None);
57 let start_offset = variant.parent_enum().syntax().text_range().start();
58 let mut visited_modules_set = FxHashSet::default();
59 visited_modules_set.insert(current_module);
60 for reference in res {
61 let source_file = ctx.sema.parse(reference.file_range.file_id);
62 update_reference(
63 ctx,
64 builder,
65 reference,
66 &source_file,
67 &enum_module_def,
68 &variant_hir_name,
69 &mut visited_modules_set,
70 );
71 }
72 extract_struct_def(
73 builder,
74 enum_ast.syntax(),
75 &variant_name,
76 &field_list.to_string(),
77 start_offset,
78 ctx.frange.file_id,
79 &visibility,
80 );
81 let list_range = field_list.syntax().text_range();
82 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
83 },
84 )
85}
86
87fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
88 variant
89 .parent_enum(db)
90 .module(db)
91 .scope(db, None)
92 .into_iter()
93 .any(|(name, _)| name.to_string() == variant_name.to_string())
94}
95
96fn insert_import(
97 ctx: &AssistContext,
98 builder: &mut AssistBuilder,
99 path: &ast::PathExpr,
100 module: &Module,
101 enum_module_def: &ModuleDef,
102 variant_hir_name: &Name,
103) -> Option<()> {
104 let db = ctx.db;
105 let mod_path = module.find_use_path(db, enum_module_def.clone());
106 if let Some(mut mod_path) = mod_path {
107 mod_path.segments.pop();
108 mod_path.segments.push(variant_hir_name.clone());
109 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
110 }
111 Some(())
112}
113
114fn extract_struct_def(
115 builder: &mut AssistBuilder,
116 enum_ast: &SyntaxNode,
117 variant_name: &str,
118 variant_list: &str,
119 start_offset: TextSize,
120 file_id: FileId,
121 visibility: &Option<ast::Visibility>,
122) -> Option<()> {
123 let visibility_string = if let Some(visibility) = visibility {
124 format!("{} ", visibility.to_string())
125 } else {
126 "".to_string()
127 };
128 let indent = if let Some(indent) = leading_indent(enum_ast) {
129 indent.to_string()
130 } else {
131 "".to_string()
132 };
133 let struct_def = format!(
134 r#"{}struct {}{};
135
136{}"#,
137 visibility_string,
138 variant_name,
139 list_with_visibility(variant_list),
140 indent
141 );
142 builder.edit_file(file_id);
143 builder.insert(start_offset, struct_def);
144 Some(())
145}
146
147fn update_variant(
148 builder: &mut AssistBuilder,
149 variant_name: &str,
150 file_id: FileId,
151 list_range: TextRange,
152) -> Option<()> {
153 let inside_variant_range = TextRange::new(
154 list_range.start().checked_add(TextSize::from(1))?,
155 list_range.end().checked_sub(TextSize::from(1))?,
156 );
157 builder.edit_file(file_id);
158 builder.replace(inside_variant_range, variant_name);
159 Some(())
160}
161
162fn update_reference(
163 ctx: &AssistContext,
164 builder: &mut AssistBuilder,
165 reference: Reference,
166 source_file: &SourceFile,
167 enum_module_def: &ModuleDef,
168 variant_hir_name: &Name,
169 visited_modules_set: &mut FxHashSet<Module>,
170) -> Option<()> {
171 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
172 source_file.syntax(),
173 reference.file_range.range.start(),
174 )?;
175 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
176 let list = call.arg_list()?;
177 let segment = path_expr.path()?.segment()?;
178 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
179 let list_range = list.syntax().text_range();
180 let inside_list_range = TextRange::new(
181 list_range.start().checked_add(TextSize::from(1))?,
182 list_range.end().checked_sub(TextSize::from(1))?,
183 );
184 builder.edit_file(reference.file_range.file_id);
185 if !visited_modules_set.contains(&module) {
186 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
187 .is_some()
188 {
189 visited_modules_set.insert(module);
190 }
191 }
192 builder.replace(inside_list_range, format!("{}{}", segment, list));
193 Some(())
194}
195
196fn list_with_visibility(list: &str) -> String {
197 list.split(',')
198 .map(|part| {
199 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
200 let mut mod_part = part.trim().to_string();
201 mod_part.insert_str(index, "pub ");
202 mod_part
203 })
204 .collect::<Vec<String>>()
205 .join(", ")
206}
207
208#[cfg(test)]
209mod tests {
210
211 use crate::{
212 tests::{check_assist, check_assist_not_applicable},
213 utils::FamousDefs,
214 };
215
216 use super::*;
217
218 #[test]
219 fn test_extract_struct_several_fields() {
220 check_assist(
221 extract_struct_from_enum_variant,
222 "enum A { <|>One(u32, u32) }",
223 r#"struct One(pub u32, pub u32);
224
225enum A { One(One) }"#,
226 );
227 }
228
229 #[test]
230 fn test_extract_struct_one_field() {
231 check_assist(
232 extract_struct_from_enum_variant,
233 "enum A { <|>One(u32) }",
234 r#"struct One(pub u32);
235
236enum A { One(One) }"#,
237 );
238 }
239
240 #[test]
241 fn test_extract_struct_pub_visibility() {
242 check_assist(
243 extract_struct_from_enum_variant,
244 "pub enum A { <|>One(u32, u32) }",
245 r#"pub struct One(pub u32, pub u32);
246
247pub enum A { One(One) }"#,
248 );
249 }
250
251 #[test]
252 fn test_extract_struct_with_complex_imports() {
253 check_assist(
254 extract_struct_from_enum_variant,
255 r#"mod my_mod {
256 fn another_fn() {
257 let m = my_other_mod::MyEnum::MyField(1, 1);
258 }
259
260 pub mod my_other_mod {
261 fn another_fn() {
262 let m = MyEnum::MyField(1, 1);
263 }
264
265 pub enum MyEnum {
266 <|>MyField(u8, u8),
267 }
268 }
269}
270
271fn another_fn() {
272 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
273}"#,
274 r#"use my_mod::my_other_mod::MyField;
275
276mod my_mod {
277 use my_other_mod::MyField;
278
279 fn another_fn() {
280 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
281 }
282
283 pub mod my_other_mod {
284 fn another_fn() {
285 let m = MyEnum::MyField(MyField(1, 1));
286 }
287
288 pub struct MyField(pub u8, pub u8);
289
290 pub enum MyEnum {
291 MyField(MyField),
292 }
293 }
294}
295
296fn another_fn() {
297 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
298}"#,
299 );
300 }
301
302 fn check_not_applicable(ra_fixture: &str) {
303 let fixture =
304 format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
305 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
306 }
307
308 #[test]
309 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
310 check_not_applicable("enum A { <|>One }");
311 }
312
313 #[test]
314 fn test_extract_enum_not_applicable_if_struct_exists() {
315 check_not_applicable(
316 r#"struct One;
317 enum A { <|>One(u8) }"#,
318 );
319 }
320}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
index 9ec42f568..531b3560f 100644
--- a/crates/ra_assists/src/handlers/fix_visibility.rs
+++ b/crates/ra_assists/src/handlers/fix_visibility.rs
@@ -63,7 +63,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O
63 }; 63 };
64 64
65 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 65 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
66 builder.set_file(target_file); 66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap { 67 match ctx.config.snippet_cap {
68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 68 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
69 None => builder.insert(offset, format!("{} ", missing_visibility)), 69 None => builder.insert(offset, format!("{} ", missing_visibility)),
@@ -106,7 +106,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) ->
106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); 106 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
107 107
108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { 108 acc.add(AssistId("fix_visibility"), assist_label, target, |builder| {
109 builder.set_file(target_file); 109 builder.edit_file(target_file);
110 match ctx.config.snippet_cap { 110 match ctx.config.snippet_cap {
111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), 111 Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
112 None => builder.insert(offset, format!("{} ", missing_visibility)), 112 None => builder.insert(offset, format!("{} ", missing_visibility)),
diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
index beb5b7366..28fcbc9ba 100644
--- a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
@@ -41,8 +41,6 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { 41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { 43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
44 // only allow naming the last anonymous lifetime
45 lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
46 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) 44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
47 } else { 45 } else {
48 None 46 None
@@ -191,6 +189,23 @@ mod tests {
191 } 189 }
192 190
193 #[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]
194 fn test_example_case_cursor_before_tick() { 209 fn test_example_case_cursor_before_tick() {
195 check_assist( 210 check_assist(
196 introduce_named_lifetime, 211 introduce_named_lifetime,
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..dfcd787de 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
@@ -51,6 +51,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
51 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { 51 acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| {
52 let match_expr = { 52 let match_expr = {
53 let then_arm = { 53 let then_arm = {
54 let then_block = then_block.reset_indent().indent(IndentLevel(1));
54 let then_expr = unwrap_trivial_block(then_block); 55 let then_expr = unwrap_trivial_block(then_block);
55 make::match_arm(vec![pat.clone()], then_expr) 56 make::match_arm(vec![pat.clone()], then_expr)
56 }; 57 };
@@ -64,8 +65,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
64 let else_expr = unwrap_trivial_block(else_block); 65 let else_expr = unwrap_trivial_block(else_block);
65 make::match_arm(vec![pattern], else_expr) 66 make::match_arm(vec![pattern], else_expr)
66 }; 67 };
67 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) 68 let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
68 .indent(IndentLevel::from_node(if_expr.syntax())) 69 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
69 }; 70 };
70 71
71 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr); 72 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
@@ -213,4 +214,36 @@ fn foo(x: Result<i32, ()>) {
213 "#, 214 "#,
214 ); 215 );
215 } 216 }
217
218 #[test]
219 fn nested_indent() {
220 check_assist(
221 replace_if_let_with_match,
222 r#"
223fn main() {
224 if true {
225 <|>if let Ok(rel_path) = path.strip_prefix(root_path) {
226 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
227 Some((*id, rel_path))
228 } else {
229 None
230 }
231 }
232}
233"#,
234 r#"
235fn main() {
236 if true {
237 match path.strip_prefix(root_path) {
238 Ok(rel_path) => {
239 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
240 Some((*id, rel_path))
241 }
242 _ => None,
243 }
244 }
245}
246"#,
247 )
248 }
216} 249}
diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs
index 8440c7d0f..1fb13f481 100644
--- a/crates/ra_assists/src/handlers/unwrap_block.rs
+++ b/crates/ra_assists/src/handlers/unwrap_block.rs
@@ -1,7 +1,10 @@
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, Assists};
@@ -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!['{'])?;
28 let block = ast::BlockExpr::cast(l_curly_token.parent())?;
29 let parent = block.syntax().parent()?;
30 let assist_id = AssistId("unwrap_block"); 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 fb5d59a87..185428bd5 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -115,6 +115,7 @@ mod handlers {
115 mod change_return_type_to_result; 115 mod change_return_type_to_result;
116 mod change_visibility; 116 mod change_visibility;
117 mod early_return; 117 mod early_return;
118 mod extract_struct_from_enum_variant;
118 mod fill_match_arms; 119 mod fill_match_arms;
119 mod fix_visibility; 120 mod fix_visibility;
120 mod flip_binexpr; 121 mod flip_binexpr;
@@ -155,6 +156,7 @@ mod handlers {
155 change_return_type_to_result::change_return_type_to_result, 156 change_return_type_to_result::change_return_type_to_result,
156 change_visibility::change_visibility, 157 change_visibility::change_visibility,
157 early_return::convert_to_guarded_return, 158 early_return::convert_to_guarded_return,
159 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
158 fill_match_arms::fill_match_arms, 160 fill_match_arms::fill_match_arms,
159 fix_visibility::fix_visibility, 161 fix_visibility::fix_visibility,
160 flip_binexpr::flip_binexpr, 162 flip_binexpr::flip_binexpr,
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs
index d17504529..40a223727 100644
--- a/crates/ra_assists/src/tests/generated.rs
+++ b/crates/ra_assists/src/tests/generated.rs
@@ -338,6 +338,21 @@ fn main() {
338} 338}
339 339
340#[test] 340#[test]
341fn doctest_extract_struct_from_enum_variant() {
342 check_doc_test(
343 "extract_struct_from_enum_variant",
344 r#####"
345enum A { <|>One(u32, u32) }
346"#####,
347 r#####"
348struct One(pub u32, pub u32);
349
350enum A { One(One) }
351"#####,
352 )
353}
354
355#[test]
341fn doctest_fill_match_arms() { 356fn doctest_fill_match_arms() {
342 check_doc_test( 357 check_doc_test(
343 "fill_match_arms", 358 "fill_match_arms",