diff options
author | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
---|---|---|
committer | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
commit | 16bbf4ab7f132e6e5e5318dccdef9a5d71afdd7f (patch) | |
tree | 4b79fa8c046be56b02427ba843e70cdf3ac05767 /crates/ra_assists/src/handlers | |
parent | eeb8b9e236796da8734ba81a49164864497f7226 (diff) | |
parent | b56ad148db0c69eb279c225f45d324b4e80e7367 (diff) |
Merge branch 'master' into keyword_completion
# Conflicts:
# docs/user/generated_features.adoc
Diffstat (limited to 'crates/ra_assists/src/handlers')
9 files changed, 565 insertions, 90 deletions
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 | ||
197 | fn main() { | 197 | fn main() { |
198 | let test<|> = Test { t: 23, k: 33 }; | 198 | let test<|> = Test { t: 23u8, k: 33 }; |
199 | }"#, | 199 | }"#, |
200 | r#" | 200 | r#" |
201 | struct Test<K, T = u8> { | 201 | struct Test<K, T = u8> { |
@@ -204,7 +204,7 @@ struct Test<K, T = u8> { | |||
204 | } | 204 | } |
205 | 205 | ||
206 | fn main() { | 206 | fn 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 | |||
859 | fn 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 | |||
885 | struct S; | ||
886 | impl 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 | |||
914 | fn 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 | |||
939 | fn 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 @@ | |||
1 | use hir::{EnumVariant, Module, ModuleDef, Name}; | ||
2 | use ra_db::FileId; | ||
3 | use ra_fmt::leading_indent; | ||
4 | use ra_ide_db::{defs::Definition, search::Reference, RootDatabase}; | ||
5 | use ra_syntax::{ | ||
6 | algo::find_node_at_offset, | ||
7 | ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, | ||
8 | SourceFile, SyntaxNode, TextRange, TextSize, | ||
9 | }; | ||
10 | use rustc_hash::FxHashSet; | ||
11 | |||
12 | use 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 | // ``` | ||
29 | pub(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 | |||
87 | fn 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 | |||
96 | fn 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 | |||
114 | fn 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 | |||
147 | fn 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 | |||
162 | fn 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 | |||
196 | fn 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)] | ||
209 | mod 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 | |||
225 | enum 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 | |||
236 | enum 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 | |||
247 | pub 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 | |||
271 | fn 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 | |||
276 | mod 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 | |||
296 | fn 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#" | ||
223 | fn 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#" | ||
235 | fn 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 @@ | |||
1 | use ra_fmt::unwrap_trivial_block; | 1 | use ra_fmt::unwrap_trivial_block; |
2 | use ra_syntax::{ | 2 | use 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 | ||
7 | use crate::{AssistContext, AssistId, Assists}; | 10 | use crate::{AssistContext, AssistId, Assists}; |
@@ -24,94 +27,73 @@ use crate::{AssistContext, AssistId, Assists}; | |||
24 | // } | 27 | // } |
25 | // ``` | 28 | // ``` |
26 | pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 29 | pub(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 | ||
105 | fn 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 | |||
115 | fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { | 97 | fn 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#" | ||
479 | fn 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#" | ||
490 | fn 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, |