diff options
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r-- | crates/ra_assists/src/assist_ctx.rs | 6 | ||||
-rw-r--r-- | crates/ra_assists/src/ast_transform.rs | 12 | ||||
-rw-r--r-- | crates/ra_assists/src/doc_tests/generated.rs | 4 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/add_missing_impl_members.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/auto_import.rs | 49 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/fill_match_arms.rs | 319 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/invert_if.rs | 13 | ||||
-rw-r--r-- | crates/ra_assists/src/handlers/merge_imports.rs | 103 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 29 |
9 files changed, 434 insertions, 103 deletions
diff --git a/crates/ra_assists/src/assist_ctx.rs b/crates/ra_assists/src/assist_ctx.rs index 62182cf03..c3e653299 100644 --- a/crates/ra_assists/src/assist_ctx.rs +++ b/crates/ra_assists/src/assist_ctx.rs | |||
@@ -11,6 +11,7 @@ use ra_syntax::{ | |||
11 | use ra_text_edit::TextEditBuilder; | 11 | use ra_text_edit::TextEditBuilder; |
12 | 12 | ||
13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; | 13 | use crate::{AssistAction, AssistId, AssistLabel, GroupLabel, ResolvedAssist}; |
14 | use algo::SyntaxRewriter; | ||
14 | 15 | ||
15 | #[derive(Clone, Debug)] | 16 | #[derive(Clone, Debug)] |
16 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); | 17 | pub(crate) struct Assist(pub(crate) Vec<AssistInfo>); |
@@ -234,6 +235,11 @@ impl ActionBuilder { | |||
234 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { | 235 | pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) { |
235 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) | 236 | algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit) |
236 | } | 237 | } |
238 | pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { | ||
239 | let node = rewriter.rewrite_root().unwrap(); | ||
240 | let new = rewriter.rewrite(&node); | ||
241 | algo::diff(&node, &new).into_text_edit(&mut self.edit) | ||
242 | } | ||
237 | 243 | ||
238 | fn build(self) -> AssistAction { | 244 | fn build(self) -> AssistAction { |
239 | AssistAction { | 245 | AssistAction { |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 45558c448..52b4c82db 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -3,7 +3,10 @@ use rustc_hash::FxHashMap; | |||
3 | 3 | ||
4 | use hir::{PathResolution, SemanticsScope}; | 4 | use hir::{PathResolution, SemanticsScope}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::ast::{self, AstNode}; | 6 | use ra_syntax::{ |
7 | algo::SyntaxRewriter, | ||
8 | ast::{self, AstNode}, | ||
9 | }; | ||
7 | 10 | ||
8 | pub trait AstTransform<'a> { | 11 | pub trait AstTransform<'a> { |
9 | fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>; | 12 | fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>; |
@@ -153,15 +156,14 @@ impl<'a> QualifyPaths<'a> { | |||
153 | } | 156 | } |
154 | 157 | ||
155 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { | 158 | pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { |
156 | let syntax = node.syntax(); | 159 | SyntaxRewriter::from_fn(|element| match element { |
157 | let result = ra_syntax::algo::replace_descendants(syntax, |element| match element { | ||
158 | ra_syntax::SyntaxElement::Node(n) => { | 160 | ra_syntax::SyntaxElement::Node(n) => { |
159 | let replacement = transformer.get_substitution(&n)?; | 161 | let replacement = transformer.get_substitution(&n)?; |
160 | Some(replacement.into()) | 162 | Some(replacement.into()) |
161 | } | 163 | } |
162 | _ => None, | 164 | _ => None, |
163 | }); | 165 | }) |
164 | N::cast(result).unwrap() | 166 | .rewrite_ast(&node) |
165 | } | 167 | } |
166 | 168 | ||
167 | impl<'a> AstTransform<'a> for QualifyPaths<'a> { | 169 | impl<'a> AstTransform<'a> for QualifyPaths<'a> { |
diff --git a/crates/ra_assists/src/doc_tests/generated.rs b/crates/ra_assists/src/doc_tests/generated.rs index aef6793e8..62dcb3808 100644 --- a/crates/ra_assists/src/doc_tests/generated.rs +++ b/crates/ra_assists/src/doc_tests/generated.rs | |||
@@ -275,8 +275,8 @@ enum Action { Move { distance: u32 }, Stop } | |||
275 | 275 | ||
276 | fn handle(action: Action) { | 276 | fn handle(action: Action) { |
277 | match action { | 277 | match action { |
278 | Action::Move { distance } => (), | 278 | Action::Move { distance } => {} |
279 | Action::Stop => (), | 279 | Action::Stop => {} |
280 | } | 280 | } |
281 | } | 281 | } |
282 | "#####, | 282 | "#####, |
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 e5920b6f6..722f207e2 100644 --- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs +++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs | |||
@@ -151,7 +151,7 @@ fn add_missing_impl_members_inner( | |||
151 | ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), | 151 | ast::ImplItem::FnDef(def) => ast::ImplItem::FnDef(add_body(def)), |
152 | _ => it, | 152 | _ => it, |
153 | }) | 153 | }) |
154 | .map(|it| edit::strip_attrs_and_docs(&it)); | 154 | .map(|it| edit::remove_attrs_and_docs(&it)); |
155 | let new_impl_item_list = impl_item_list.append_items(items); | 155 | let new_impl_item_list = impl_item_list.append_items(items); |
156 | let cursor_position = { | 156 | let cursor_position = { |
157 | let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); | 157 | let first_new_item = new_impl_item_list.impl_items().nth(n_existing_items).unwrap(); |
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index bb280f633..99682e023 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs | |||
@@ -17,6 +17,7 @@ use crate::{ | |||
17 | utils::insert_use_statement, | 17 | utils::insert_use_statement, |
18 | AssistId, | 18 | AssistId, |
19 | }; | 19 | }; |
20 | use either::Either; | ||
20 | 21 | ||
21 | // Assist: auto_import | 22 | // Assist: auto_import |
22 | // | 23 | // |
@@ -58,6 +59,7 @@ pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> { | |||
58 | group.finish() | 59 | group.finish() |
59 | } | 60 | } |
60 | 61 | ||
62 | #[derive(Debug)] | ||
61 | struct AutoImportAssets { | 63 | struct AutoImportAssets { |
62 | import_candidate: ImportCandidate, | 64 | import_candidate: ImportCandidate, |
63 | module_with_name_to_import: Module, | 65 | module_with_name_to_import: Module, |
@@ -127,14 +129,14 @@ impl AutoImportAssets { | |||
127 | ImportsLocator::new(db) | 129 | ImportsLocator::new(db) |
128 | .find_imports(&self.get_search_query()) | 130 | .find_imports(&self.get_search_query()) |
129 | .into_iter() | 131 | .into_iter() |
130 | .filter_map(|module_def| match &self.import_candidate { | 132 | .filter_map(|candidate| match &self.import_candidate { |
131 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { | 133 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { |
132 | let located_assoc_item = match module_def { | 134 | let located_assoc_item = match candidate { |
133 | ModuleDef::Function(located_function) => located_function | 135 | Either::Left(ModuleDef::Function(located_function)) => located_function |
134 | .as_assoc_item(db) | 136 | .as_assoc_item(db) |
135 | .map(|assoc| assoc.container(db)) | 137 | .map(|assoc| assoc.container(db)) |
136 | .and_then(Self::assoc_to_trait), | 138 | .and_then(Self::assoc_to_trait), |
137 | ModuleDef::Const(located_const) => located_const | 139 | Either::Left(ModuleDef::Const(located_const)) => located_const |
138 | .as_assoc_item(db) | 140 | .as_assoc_item(db) |
139 | .map(|assoc| assoc.container(db)) | 141 | .map(|assoc| assoc.container(db)) |
140 | .and_then(Self::assoc_to_trait), | 142 | .and_then(Self::assoc_to_trait), |
@@ -153,10 +155,11 @@ impl AutoImportAssets { | |||
153 | |_, assoc| Self::assoc_to_trait(assoc.container(db)), | 155 | |_, assoc| Self::assoc_to_trait(assoc.container(db)), |
154 | ) | 156 | ) |
155 | .map(ModuleDef::from) | 157 | .map(ModuleDef::from) |
158 | .map(Either::Left) | ||
156 | } | 159 | } |
157 | ImportCandidate::TraitMethod(function_callee, _) => { | 160 | ImportCandidate::TraitMethod(function_callee, _) => { |
158 | let located_assoc_item = | 161 | let located_assoc_item = |
159 | if let ModuleDef::Function(located_function) = module_def { | 162 | if let Either::Left(ModuleDef::Function(located_function)) = candidate { |
160 | located_function | 163 | located_function |
161 | .as_assoc_item(db) | 164 | .as_assoc_item(db) |
162 | .map(|assoc| assoc.container(db)) | 165 | .map(|assoc| assoc.container(db)) |
@@ -179,10 +182,18 @@ impl AutoImportAssets { | |||
179 | }, | 182 | }, |
180 | ) | 183 | ) |
181 | .map(ModuleDef::from) | 184 | .map(ModuleDef::from) |
185 | .map(Either::Left) | ||
186 | } | ||
187 | _ => Some(candidate), | ||
188 | }) | ||
189 | .filter_map(|candidate| match candidate { | ||
190 | Either::Left(module_def) => { | ||
191 | self.module_with_name_to_import.find_use_path(db, module_def) | ||
192 | } | ||
193 | Either::Right(macro_def) => { | ||
194 | self.module_with_name_to_import.find_use_path(db, macro_def) | ||
182 | } | 195 | } |
183 | _ => Some(module_def), | ||
184 | }) | 196 | }) |
185 | .filter_map(|module_def| self.module_with_name_to_import.find_use_path(db, module_def)) | ||
186 | .filter(|use_path| !use_path.segments.is_empty()) | 197 | .filter(|use_path| !use_path.segments.is_empty()) |
187 | .take(20) | 198 | .take(20) |
188 | .collect::<BTreeSet<_>>() | 199 | .collect::<BTreeSet<_>>() |
@@ -440,6 +451,30 @@ mod tests { | |||
440 | } | 451 | } |
441 | 452 | ||
442 | #[test] | 453 | #[test] |
454 | fn macro_import() { | ||
455 | check_assist( | ||
456 | auto_import, | ||
457 | r" | ||
458 | //- /lib.rs crate:crate_with_macro | ||
459 | #[macro_export] | ||
460 | macro_rules! foo { | ||
461 | () => () | ||
462 | } | ||
463 | |||
464 | //- /main.rs crate:main deps:crate_with_macro | ||
465 | fn main() { | ||
466 | foo<|> | ||
467 | }", | ||
468 | r"use crate_with_macro::foo; | ||
469 | |||
470 | fn main() { | ||
471 | foo<|> | ||
472 | } | ||
473 | ", | ||
474 | ); | ||
475 | } | ||
476 | |||
477 | #[test] | ||
443 | fn auto_import_target() { | 478 | fn auto_import_target() { |
444 | check_assist_target( | 479 | check_assist_target( |
445 | auto_import, | 480 | auto_import, |
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index fbd6a3ec3..add82e5b1 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -2,7 +2,8 @@ | |||
2 | 2 | ||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use hir::{Adt, HasSource, Semantics}; | 5 | use hir::{Adt, HasSource, ModuleDef, Semantics}; |
6 | use itertools::Itertools; | ||
6 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
7 | 8 | ||
8 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{Assist, AssistCtx, AssistId}; |
@@ -29,8 +30,8 @@ use ast::{MatchArm, Pat}; | |||
29 | // | 30 | // |
30 | // fn handle(action: Action) { | 31 | // fn handle(action: Action) { |
31 | // match action { | 32 | // match action { |
32 | // Action::Move { distance } => (), | 33 | // Action::Move { distance } => {} |
33 | // Action::Stop => (), | 34 | // Action::Stop => {} |
34 | // } | 35 | // } |
35 | // } | 36 | // } |
36 | // ``` | 37 | // ``` |
@@ -39,13 +40,6 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
39 | let match_arm_list = match_expr.match_arm_list()?; | 40 | let match_arm_list = match_expr.match_arm_list()?; |
40 | 41 | ||
41 | let expr = match_expr.expr()?; | 42 | let expr = match_expr.expr()?; |
42 | let enum_def = resolve_enum_def(&ctx.sema, &expr)?; | ||
43 | let module = ctx.sema.scope(expr.syntax()).module()?; | ||
44 | |||
45 | let variants = enum_def.variants(ctx.db); | ||
46 | if variants.is_empty() { | ||
47 | return None; | ||
48 | } | ||
49 | 43 | ||
50 | let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); | 44 | let mut arms: Vec<MatchArm> = match_arm_list.arms().collect(); |
51 | if arms.len() == 1 { | 45 | if arms.len() == 1 { |
@@ -54,13 +48,49 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
54 | } | 48 | } |
55 | } | 49 | } |
56 | 50 | ||
57 | let db = ctx.db; | 51 | let module = ctx.sema.scope(expr.syntax()).module()?; |
58 | let missing_arms: Vec<MatchArm> = variants | 52 | |
59 | .into_iter() | 53 | let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) { |
60 | .filter_map(|variant| build_pat(db, module, variant)) | 54 | let variants = enum_def.variants(ctx.db); |
61 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | 55 | |
62 | .map(|pat| make::match_arm(iter::once(pat), make::expr_unit())) | 56 | variants |
63 | .collect(); | 57 | .into_iter() |
58 | .filter_map(|variant| build_pat(ctx.db, module, variant)) | ||
59 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | ||
60 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | ||
61 | .collect() | ||
62 | } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) { | ||
63 | // Partial fill not currently supported for tuple of enums. | ||
64 | if !arms.is_empty() { | ||
65 | return None; | ||
66 | } | ||
67 | |||
68 | // We do not currently support filling match arms for a tuple | ||
69 | // containing a single enum. | ||
70 | if enum_defs.len() < 2 { | ||
71 | return None; | ||
72 | } | ||
73 | |||
74 | // When calculating the match arms for a tuple of enums, we want | ||
75 | // to create a match arm for each possible combination of enum | ||
76 | // values. The `multi_cartesian_product` method transforms | ||
77 | // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)> | ||
78 | // where each tuple represents a proposed match arm. | ||
79 | enum_defs | ||
80 | .into_iter() | ||
81 | .map(|enum_def| enum_def.variants(ctx.db)) | ||
82 | .multi_cartesian_product() | ||
83 | .map(|variants| { | ||
84 | let patterns = | ||
85 | variants.into_iter().filter_map(|variant| build_pat(ctx.db, module, variant)); | ||
86 | ast::Pat::from(make::tuple_pat(patterns)) | ||
87 | }) | ||
88 | .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat)) | ||
89 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | ||
90 | .collect() | ||
91 | } else { | ||
92 | return None; | ||
93 | }; | ||
64 | 94 | ||
65 | if missing_arms.is_empty() { | 95 | if missing_arms.is_empty() { |
66 | return None; | 96 | return None; |
@@ -104,8 +134,27 @@ fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option< | |||
104 | }) | 134 | }) |
105 | } | 135 | } |
106 | 136 | ||
137 | fn resolve_tuple_of_enum_def( | ||
138 | sema: &Semantics<RootDatabase>, | ||
139 | expr: &ast::Expr, | ||
140 | ) -> Option<Vec<hir::Enum>> { | ||
141 | sema.type_of_expr(&expr)? | ||
142 | .tuple_fields(sema.db) | ||
143 | .iter() | ||
144 | .map(|ty| { | ||
145 | ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { | ||
146 | Some(Adt::Enum(e)) => Some(e), | ||
147 | // For now we only handle expansion for a tuple of enums. Here | ||
148 | // we map non-enum items to None and rely on `collect` to | ||
149 | // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. | ||
150 | _ => None, | ||
151 | }) | ||
152 | }) | ||
153 | .collect() | ||
154 | } | ||
155 | |||
107 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { | 156 | fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> { |
108 | let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); | 157 | let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?); |
109 | 158 | ||
110 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | 159 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though |
111 | let pat: ast::Pat = match var.source(db).value.kind() { | 160 | let pat: ast::Pat = match var.source(db).value.kind() { |
@@ -143,8 +192,23 @@ mod tests { | |||
143 | fn main() { | 192 | fn main() { |
144 | match A::As<|> { | 193 | match A::As<|> { |
145 | A::As, | 194 | A::As, |
146 | A::Bs{x,y:Some(_)} => (), | 195 | A::Bs{x,y:Some(_)} => {} |
147 | A::Cs(_, Some(_)) => (), | 196 | A::Cs(_, Some(_)) => {} |
197 | } | ||
198 | } | ||
199 | "#, | ||
200 | ); | ||
201 | } | ||
202 | |||
203 | #[test] | ||
204 | fn tuple_of_non_enum() { | ||
205 | // for now this case is not handled, although it potentially could be | ||
206 | // in the future | ||
207 | check_assist_not_applicable( | ||
208 | fill_match_arms, | ||
209 | r#" | ||
210 | fn main() { | ||
211 | match (0, false)<|> { | ||
148 | } | 212 | } |
149 | } | 213 | } |
150 | "#, | 214 | "#, |
@@ -163,8 +227,8 @@ mod tests { | |||
163 | } | 227 | } |
164 | fn main() { | 228 | fn main() { |
165 | match A::As<|> { | 229 | match A::As<|> { |
166 | A::Bs{x,y:Some(_)} => (), | 230 | A::Bs{x,y:Some(_)} => {} |
167 | A::Cs(_, Some(_)) => (), | 231 | A::Cs(_, Some(_)) => {} |
168 | } | 232 | } |
169 | } | 233 | } |
170 | "#, | 234 | "#, |
@@ -176,9 +240,9 @@ mod tests { | |||
176 | } | 240 | } |
177 | fn main() { | 241 | fn main() { |
178 | match <|>A::As { | 242 | match <|>A::As { |
179 | A::Bs{x,y:Some(_)} => (), | 243 | A::Bs{x,y:Some(_)} => {} |
180 | A::Cs(_, Some(_)) => (), | 244 | A::Cs(_, Some(_)) => {} |
181 | A::As => (), | 245 | A::As => {} |
182 | } | 246 | } |
183 | } | 247 | } |
184 | "#, | 248 | "#, |
@@ -197,7 +261,7 @@ mod tests { | |||
197 | } | 261 | } |
198 | fn main() { | 262 | fn main() { |
199 | match A::As<|> { | 263 | match A::As<|> { |
200 | A::Cs(_) | A::Bs => (), | 264 | A::Cs(_) | A::Bs => {} |
201 | } | 265 | } |
202 | } | 266 | } |
203 | "#, | 267 | "#, |
@@ -209,8 +273,8 @@ mod tests { | |||
209 | } | 273 | } |
210 | fn main() { | 274 | fn main() { |
211 | match <|>A::As { | 275 | match <|>A::As { |
212 | A::Cs(_) | A::Bs => (), | 276 | A::Cs(_) | A::Bs => {} |
213 | A::As => (), | 277 | A::As => {} |
214 | } | 278 | } |
215 | } | 279 | } |
216 | "#, | 280 | "#, |
@@ -235,8 +299,8 @@ mod tests { | |||
235 | } | 299 | } |
236 | fn main() { | 300 | fn main() { |
237 | match A::As<|> { | 301 | match A::As<|> { |
238 | A::Bs if 0 < 1 => (), | 302 | A::Bs if 0 < 1 => {} |
239 | A::Ds(_value) => (), | 303 | A::Ds(_value) => { let x = 1; } |
240 | A::Es(B::Xs) => (), | 304 | A::Es(B::Xs) => (), |
241 | } | 305 | } |
242 | } | 306 | } |
@@ -255,11 +319,11 @@ mod tests { | |||
255 | } | 319 | } |
256 | fn main() { | 320 | fn main() { |
257 | match <|>A::As { | 321 | match <|>A::As { |
258 | A::Bs if 0 < 1 => (), | 322 | A::Bs if 0 < 1 => {} |
259 | A::Ds(_value) => (), | 323 | A::Ds(_value) => { let x = 1; } |
260 | A::Es(B::Xs) => (), | 324 | A::Es(B::Xs) => (), |
261 | A::As => (), | 325 | A::As => {} |
262 | A::Cs => (), | 326 | A::Cs => {} |
263 | } | 327 | } |
264 | } | 328 | } |
265 | "#, | 329 | "#, |
@@ -296,11 +360,174 @@ mod tests { | |||
296 | fn main() { | 360 | fn main() { |
297 | let a = A::As; | 361 | let a = A::As; |
298 | match <|>a { | 362 | match <|>a { |
299 | A::As => (), | 363 | A::As => {} |
300 | A::Bs => (), | 364 | A::Bs => {} |
301 | A::Cs(_) => (), | 365 | A::Cs(_) => {} |
302 | A::Ds(_, _) => (), | 366 | A::Ds(_, _) => {} |
303 | A::Es { x, y } => (), | 367 | A::Es { x, y } => {} |
368 | } | ||
369 | } | ||
370 | "#, | ||
371 | ); | ||
372 | } | ||
373 | |||
374 | #[test] | ||
375 | fn fill_match_arms_tuple_of_enum() { | ||
376 | check_assist( | ||
377 | fill_match_arms, | ||
378 | r#" | ||
379 | enum A { | ||
380 | One, | ||
381 | Two, | ||
382 | } | ||
383 | enum B { | ||
384 | One, | ||
385 | Two, | ||
386 | } | ||
387 | |||
388 | fn main() { | ||
389 | let a = A::One; | ||
390 | let b = B::One; | ||
391 | match (a<|>, b) {} | ||
392 | } | ||
393 | "#, | ||
394 | r#" | ||
395 | enum A { | ||
396 | One, | ||
397 | Two, | ||
398 | } | ||
399 | enum B { | ||
400 | One, | ||
401 | Two, | ||
402 | } | ||
403 | |||
404 | fn main() { | ||
405 | let a = A::One; | ||
406 | let b = B::One; | ||
407 | match <|>(a, b) { | ||
408 | (A::One, B::One) => {} | ||
409 | (A::One, B::Two) => {} | ||
410 | (A::Two, B::One) => {} | ||
411 | (A::Two, B::Two) => {} | ||
412 | } | ||
413 | } | ||
414 | "#, | ||
415 | ); | ||
416 | } | ||
417 | |||
418 | #[test] | ||
419 | fn fill_match_arms_tuple_of_enum_ref() { | ||
420 | check_assist( | ||
421 | fill_match_arms, | ||
422 | r#" | ||
423 | enum A { | ||
424 | One, | ||
425 | Two, | ||
426 | } | ||
427 | enum B { | ||
428 | One, | ||
429 | Two, | ||
430 | } | ||
431 | |||
432 | fn main() { | ||
433 | let a = A::One; | ||
434 | let b = B::One; | ||
435 | match (&a<|>, &b) {} | ||
436 | } | ||
437 | "#, | ||
438 | r#" | ||
439 | enum A { | ||
440 | One, | ||
441 | Two, | ||
442 | } | ||
443 | enum B { | ||
444 | One, | ||
445 | Two, | ||
446 | } | ||
447 | |||
448 | fn main() { | ||
449 | let a = A::One; | ||
450 | let b = B::One; | ||
451 | match <|>(&a, &b) { | ||
452 | (A::One, B::One) => {} | ||
453 | (A::One, B::Two) => {} | ||
454 | (A::Two, B::One) => {} | ||
455 | (A::Two, B::Two) => {} | ||
456 | } | ||
457 | } | ||
458 | "#, | ||
459 | ); | ||
460 | } | ||
461 | |||
462 | #[test] | ||
463 | fn fill_match_arms_tuple_of_enum_partial() { | ||
464 | check_assist_not_applicable( | ||
465 | fill_match_arms, | ||
466 | r#" | ||
467 | enum A { | ||
468 | One, | ||
469 | Two, | ||
470 | } | ||
471 | enum B { | ||
472 | One, | ||
473 | Two, | ||
474 | } | ||
475 | |||
476 | fn main() { | ||
477 | let a = A::One; | ||
478 | let b = B::One; | ||
479 | match (a<|>, b) { | ||
480 | (A::Two, B::One) => {} | ||
481 | } | ||
482 | } | ||
483 | "#, | ||
484 | ); | ||
485 | } | ||
486 | |||
487 | #[test] | ||
488 | fn fill_match_arms_tuple_of_enum_not_applicable() { | ||
489 | check_assist_not_applicable( | ||
490 | fill_match_arms, | ||
491 | r#" | ||
492 | enum A { | ||
493 | One, | ||
494 | Two, | ||
495 | } | ||
496 | enum B { | ||
497 | One, | ||
498 | Two, | ||
499 | } | ||
500 | |||
501 | fn main() { | ||
502 | let a = A::One; | ||
503 | let b = B::One; | ||
504 | match (a<|>, b) { | ||
505 | (A::Two, B::One) => {} | ||
506 | (A::One, B::One) => {} | ||
507 | (A::One, B::Two) => {} | ||
508 | (A::Two, B::Two) => {} | ||
509 | } | ||
510 | } | ||
511 | "#, | ||
512 | ); | ||
513 | } | ||
514 | |||
515 | #[test] | ||
516 | fn fill_match_arms_single_element_tuple_of_enum() { | ||
517 | // For now we don't hande the case of a single element tuple, but | ||
518 | // we could handle this in the future if `make::tuple_pat` allowed | ||
519 | // creating a tuple with a single pattern. | ||
520 | check_assist_not_applicable( | ||
521 | fill_match_arms, | ||
522 | r#" | ||
523 | enum A { | ||
524 | One, | ||
525 | Two, | ||
526 | } | ||
527 | |||
528 | fn main() { | ||
529 | let a = A::One; | ||
530 | match (a<|>, ) { | ||
304 | } | 531 | } |
305 | } | 532 | } |
306 | "#, | 533 | "#, |
@@ -328,7 +555,7 @@ mod tests { | |||
328 | 555 | ||
329 | fn foo(a: &A) { | 556 | fn foo(a: &A) { |
330 | match <|>a { | 557 | match <|>a { |
331 | A::As => (), | 558 | A::As => {} |
332 | } | 559 | } |
333 | } | 560 | } |
334 | "#, | 561 | "#, |
@@ -353,7 +580,7 @@ mod tests { | |||
353 | 580 | ||
354 | fn foo(a: &mut A) { | 581 | fn foo(a: &mut A) { |
355 | match <|>a { | 582 | match <|>a { |
356 | A::Es { x, y } => (), | 583 | A::Es { x, y } => {} |
357 | } | 584 | } |
358 | } | 585 | } |
359 | "#, | 586 | "#, |
@@ -384,7 +611,7 @@ mod tests { | |||
384 | 611 | ||
385 | fn main() { | 612 | fn main() { |
386 | match E::X { | 613 | match E::X { |
387 | <|>_ => {}, | 614 | <|>_ => {} |
388 | } | 615 | } |
389 | } | 616 | } |
390 | "#, | 617 | "#, |
@@ -393,8 +620,8 @@ mod tests { | |||
393 | 620 | ||
394 | fn main() { | 621 | fn main() { |
395 | match <|>E::X { | 622 | match <|>E::X { |
396 | E::X => (), | 623 | E::X => {} |
397 | E::Y => (), | 624 | E::Y => {} |
398 | } | 625 | } |
399 | } | 626 | } |
400 | "#, | 627 | "#, |
@@ -421,8 +648,8 @@ mod tests { | |||
421 | 648 | ||
422 | fn main() { | 649 | fn main() { |
423 | match <|>X { | 650 | match <|>X { |
424 | X => (), | 651 | X => {} |
425 | foo::E::Y => (), | 652 | foo::E::Y => {} |
426 | } | 653 | } |
427 | } | 654 | } |
428 | "#, | 655 | "#, |
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs index 3a2665d17..4c5716868 100644 --- a/crates/ra_assists/src/handlers/invert_if.rs +++ b/crates/ra_assists/src/handlers/invert_if.rs | |||
@@ -33,6 +33,11 @@ pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> { | |||
33 | return None; | 33 | return None; |
34 | } | 34 | } |
35 | 35 | ||
36 | // This assist should not apply for if-let. | ||
37 | if expr.condition()?.pat().is_some() { | ||
38 | return None; | ||
39 | } | ||
40 | |||
36 | let cond = expr.condition()?.expr()?; | 41 | let cond = expr.condition()?.expr()?; |
37 | let then_node = expr.then_branch()?.syntax().clone(); | 42 | let then_node = expr.then_branch()?.syntax().clone(); |
38 | 43 | ||
@@ -90,4 +95,12 @@ mod tests { | |||
90 | fn invert_if_doesnt_apply_with_cursor_not_on_if() { | 95 | fn invert_if_doesnt_apply_with_cursor_not_on_if() { |
91 | check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") | 96 | check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }") |
92 | } | 97 | } |
98 | |||
99 | #[test] | ||
100 | fn invert_if_doesnt_apply_with_if_let() { | ||
101 | check_assist_not_applicable( | ||
102 | invert_if, | ||
103 | "fn f() { i<|>f let Some(_) = Some(1) { 1 } else { 0 } }", | ||
104 | ) | ||
105 | } | ||
93 | } | 106 | } |
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs index 89bc975bd..9c57d1e30 100644 --- a/crates/ra_assists/src/handlers/merge_imports.rs +++ b/crates/ra_assists/src/handlers/merge_imports.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | use std::iter::successors; | 1 | use std::iter::successors; |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | algo::neighbor, | 4 | algo::{neighbor, SyntaxRewriter}, |
5 | ast::{self, edit::AstNodeEdit, make}, | 5 | ast::{self, edit::AstNodeEdit, make}, |
6 | AstNode, AstToken, Direction, InsertPosition, SyntaxElement, TextRange, T, | 6 | AstNode, Direction, InsertPosition, SyntaxElement, T, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{Assist, AssistCtx, AssistId}; |
@@ -22,9 +22,10 @@ use crate::{Assist, AssistCtx, AssistId}; | |||
22 | // ``` | 22 | // ``` |
23 | pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { | 23 | pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { |
24 | let tree: ast::UseTree = ctx.find_node_at_offset()?; | 24 | let tree: ast::UseTree = ctx.find_node_at_offset()?; |
25 | let (new_tree, to_delete) = if let Some(use_item) = | 25 | let mut rewriter = SyntaxRewriter::default(); |
26 | tree.syntax().parent().and_then(ast::UseItem::cast) | 26 | let mut offset = ctx.frange.range.start(); |
27 | { | 27 | |
28 | if let Some(use_item) = tree.syntax().parent().and_then(ast::UseItem::cast) { | ||
28 | let (merged, to_delete) = next_prev() | 29 | let (merged, to_delete) = next_prev() |
29 | .filter_map(|dir| neighbor(&use_item, dir)) | 30 | .filter_map(|dir| neighbor(&use_item, dir)) |
30 | .filter_map(|it| Some((it.clone(), it.use_tree()?))) | 31 | .filter_map(|it| Some((it.clone(), it.use_tree()?))) |
@@ -32,42 +33,28 @@ pub(crate) fn merge_imports(ctx: AssistCtx) -> Option<Assist> { | |||
32 | Some((try_merge_trees(&tree, &use_tree)?, use_item.clone())) | 33 | Some((try_merge_trees(&tree, &use_tree)?, use_item.clone())) |
33 | })?; | 34 | })?; |
34 | 35 | ||
35 | let mut range = to_delete.syntax().text_range(); | 36 | rewriter.replace_ast(&tree, &merged); |
36 | let next_ws = to_delete | 37 | rewriter += to_delete.remove(); |
37 | .syntax() | 38 | |
38 | .next_sibling_or_token() | 39 | if to_delete.syntax().text_range().end() < offset { |
39 | .and_then(|it| it.into_token()) | 40 | offset -= to_delete.syntax().text_range().len(); |
40 | .and_then(ast::Whitespace::cast); | ||
41 | if let Some(ws) = next_ws { | ||
42 | range = range.extend_to(&ws.syntax().text_range()) | ||
43 | } | 41 | } |
44 | (merged, range) | ||
45 | } else { | 42 | } else { |
46 | let (merged, to_delete) = next_prev() | 43 | let (merged, to_delete) = next_prev() |
47 | .filter_map(|dir| neighbor(&tree, dir)) | 44 | .filter_map(|dir| neighbor(&tree, dir)) |
48 | .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; | 45 | .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?; |
49 | 46 | ||
50 | let mut range = to_delete.syntax().text_range(); | 47 | rewriter.replace_ast(&tree, &merged); |
51 | if let Some((dir, nb)) = next_prev().find_map(|dir| Some((dir, neighbor(&to_delete, dir)?))) | 48 | rewriter += to_delete.remove(); |
52 | { | 49 | |
53 | let nb_range = nb.syntax().text_range(); | 50 | if to_delete.syntax().text_range().end() < offset { |
54 | if dir == Direction::Prev { | 51 | offset -= to_delete.syntax().text_range().len(); |
55 | range = TextRange::from_to(nb_range.end(), range.end()); | ||
56 | } else { | ||
57 | range = TextRange::from_to(range.start(), nb_range.start()); | ||
58 | } | ||
59 | } | 52 | } |
60 | (merged, range) | ||
61 | }; | 53 | }; |
62 | 54 | ||
63 | let mut offset = ctx.frange.range.start(); | ||
64 | ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { | 55 | ctx.add_assist(AssistId("merge_imports"), "Merge imports", |edit| { |
65 | edit.replace_ast(tree, new_tree); | 56 | edit.rewrite(rewriter); |
66 | edit.delete(to_delete); | 57 | // FIXME: we only need because our diff is imprecise |
67 | |||
68 | if to_delete.end() <= offset { | ||
69 | offset -= to_delete.len(); | ||
70 | } | ||
71 | edit.set_cursor(offset); | 58 | edit.set_cursor(offset); |
72 | }) | 59 | }) |
73 | } | 60 | } |
@@ -156,7 +143,7 @@ use std::fmt::Debug; | |||
156 | use std::fmt<|>::Display; | 143 | use std::fmt<|>::Display; |
157 | ", | 144 | ", |
158 | r" | 145 | r" |
159 | use std::fmt<|>::{Display, Debug}; | 146 | use std::fmt:<|>:{Display, Debug}; |
160 | ", | 147 | ", |
161 | ); | 148 | ); |
162 | } | 149 | } |
@@ -178,7 +165,57 @@ use std::{fmt<|>::{Debug, Display}}; | |||
178 | use std::{fmt::Debug, fmt<|>::Display}; | 165 | use std::{fmt::Debug, fmt<|>::Display}; |
179 | ", | 166 | ", |
180 | r" | 167 | r" |
181 | use std::{fmt<|>::{Display, Debug}}; | 168 | use std::{fmt::<|>{Display, Debug}}; |
169 | ", | ||
170 | ); | ||
171 | } | ||
172 | |||
173 | #[test] | ||
174 | fn removes_just_enough_whitespace() { | ||
175 | check_assist( | ||
176 | merge_imports, | ||
177 | r" | ||
178 | use foo<|>::bar; | ||
179 | use foo::baz; | ||
180 | |||
181 | /// Doc comment | ||
182 | ", | ||
183 | r" | ||
184 | use foo<|>::{bar, baz}; | ||
185 | |||
186 | /// Doc comment | ||
187 | ", | ||
188 | ); | ||
189 | } | ||
190 | |||
191 | #[test] | ||
192 | fn works_with_trailing_comma() { | ||
193 | check_assist( | ||
194 | merge_imports, | ||
195 | r" | ||
196 | use { | ||
197 | foo<|>::bar, | ||
198 | foo::baz, | ||
199 | }; | ||
200 | ", | ||
201 | r" | ||
202 | use { | ||
203 | foo<|>::{bar, baz}, | ||
204 | }; | ||
205 | ", | ||
206 | ); | ||
207 | check_assist( | ||
208 | merge_imports, | ||
209 | r" | ||
210 | use { | ||
211 | foo::baz, | ||
212 | foo<|>::bar, | ||
213 | }; | ||
214 | ", | ||
215 | r" | ||
216 | use { | ||
217 | foo::{bar<|>, baz}, | ||
218 | }; | ||
182 | ", | 219 | ", |
183 | ); | 220 | ); |
184 | } | 221 | } |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index b8704ea7d..bcc9b3f10 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -165,7 +165,6 @@ mod helpers { | |||
165 | 165 | ||
166 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; | 166 | use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt}; |
167 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 167 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; |
168 | use ra_syntax::TextRange; | ||
169 | use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; | 168 | use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset}; |
170 | 169 | ||
171 | use crate::{AssistCtx, AssistHandler}; | 170 | use crate::{AssistCtx, AssistHandler}; |
@@ -175,8 +174,7 @@ mod helpers { | |||
175 | let (mut db, file_id) = RootDatabase::with_single_file(text); | 174 | let (mut db, file_id) = RootDatabase::with_single_file(text); |
176 | // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, | 175 | // FIXME: ideally, this should be done by the above `RootDatabase::with_single_file`, |
177 | // but it looks like this might need specialization? :( | 176 | // but it looks like this might need specialization? :( |
178 | let local_roots = vec![db.file_source_root(file_id)]; | 177 | db.set_local_roots(Arc::new(vec![db.file_source_root(file_id)])); |
179 | db.set_local_roots(Arc::new(local_roots)); | ||
180 | (db, file_id) | 178 | (db, file_id) |
181 | } | 179 | } |
182 | 180 | ||
@@ -206,11 +204,24 @@ mod helpers { | |||
206 | } | 204 | } |
207 | 205 | ||
208 | fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { | 206 | fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) { |
209 | let (range_or_offset, before) = extract_range_or_offset(before); | 207 | let (text_without_caret, file_with_caret_id, range_or_offset, db) = |
210 | let range: TextRange = range_or_offset.into(); | 208 | if before.contains("//-") { |
209 | let (mut db, position) = RootDatabase::with_position(before); | ||
210 | db.set_local_roots(Arc::new(vec![db.file_source_root(position.file_id)])); | ||
211 | ( | ||
212 | db.file_text(position.file_id).as_ref().to_owned(), | ||
213 | position.file_id, | ||
214 | RangeOrOffset::Offset(position.offset), | ||
215 | db, | ||
216 | ) | ||
217 | } else { | ||
218 | let (range_or_offset, text_without_caret) = extract_range_or_offset(before); | ||
219 | let (db, file_id) = with_single_file(&text_without_caret); | ||
220 | (text_without_caret, file_id, range_or_offset, db) | ||
221 | }; | ||
222 | |||
223 | let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() }; | ||
211 | 224 | ||
212 | let (db, file_id) = with_single_file(&before); | ||
213 | let frange = FileRange { file_id, range }; | ||
214 | let sema = Semantics::new(&db); | 225 | let sema = Semantics::new(&db); |
215 | let assist_ctx = AssistCtx::new(&sema, frange, true); | 226 | let assist_ctx = AssistCtx::new(&sema, frange, true); |
216 | 227 | ||
@@ -218,7 +229,7 @@ mod helpers { | |||
218 | (Some(assist), ExpectedResult::After(after)) => { | 229 | (Some(assist), ExpectedResult::After(after)) => { |
219 | let action = assist.0[0].action.clone().unwrap(); | 230 | let action = assist.0[0].action.clone().unwrap(); |
220 | 231 | ||
221 | let mut actual = action.edit.apply(&before); | 232 | let mut actual = action.edit.apply(&text_without_caret); |
222 | match action.cursor_position { | 233 | match action.cursor_position { |
223 | None => { | 234 | None => { |
224 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { | 235 | if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset { |
@@ -237,7 +248,7 @@ mod helpers { | |||
237 | (Some(assist), ExpectedResult::Target(target)) => { | 248 | (Some(assist), ExpectedResult::Target(target)) => { |
238 | let action = assist.0[0].action.clone().unwrap(); | 249 | let action = assist.0[0].action.clone().unwrap(); |
239 | let range = action.target.expect("expected target on action"); | 250 | let range = action.target.expect("expected target on action"); |
240 | assert_eq_text!(&before[range], target); | 251 | assert_eq_text!(&text_without_caret[range], target); |
241 | } | 252 | } |
242 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), | 253 | (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"), |
243 | (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { | 254 | (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => { |