diff options
author | Aleksey Kladov <[email protected]> | 2021-05-16 13:10:18 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-05-16 13:10:18 +0100 |
commit | e4a7b44e554e183fc66474bcbd8f7ace541c5536 (patch) | |
tree | e606c6fe7990c468ce58049b3074aa70542ce906 | |
parent | 4e142757e167ac16ce65ba1c743e131aba83cdc4 (diff) |
internal: use mutable trees when filling match arms
-rw-r--r-- | crates/ide_assists/src/handlers/fill_match_arms.rs | 28 | ||||
-rw-r--r-- | crates/syntax/src/ast/edit.rs | 105 | ||||
-rw-r--r-- | crates/syntax/src/ast/edit_in_place.rs | 90 |
3 files changed, 89 insertions, 134 deletions
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs index be927cc1c..f66a9b54b 100644 --- a/crates/ide_assists/src/handlers/fill_match_arms.rs +++ b/crates/ide_assists/src/handlers/fill_match_arms.rs | |||
@@ -71,6 +71,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
71 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) | 71 | .filter_map(|variant| build_pat(ctx.db(), module, variant)) |
72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) | 72 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
73 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 73 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
74 | .map(|it| it.clone_for_update()) | ||
74 | .collect::<Vec<_>>(); | 75 | .collect::<Vec<_>>(); |
75 | if Some(enum_def) | 76 | if Some(enum_def) |
76 | == FamousDefs(&ctx.sema, Some(module.krate())) | 77 | == FamousDefs(&ctx.sema, Some(module.krate())) |
@@ -99,6 +100,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
99 | }) | 100 | }) |
100 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) | 101 | .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) |
101 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) | 102 | .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) |
103 | .map(|it| it.clone_for_update()) | ||
102 | .collect() | 104 | .collect() |
103 | } else { | 105 | } else { |
104 | return None; | 106 | return None; |
@@ -114,10 +116,20 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
114 | "Fill match arms", | 116 | "Fill match arms", |
115 | target, | 117 | target, |
116 | |builder| { | 118 | |builder| { |
117 | let new_arm_list = match_arm_list.remove_placeholder(); | 119 | let new_match_arm_list = match_arm_list.clone_for_update(); |
118 | let n_old_arms = new_arm_list.arms().count(); | 120 | |
119 | let new_arm_list = new_arm_list.append_arms(missing_arms); | 121 | let catch_all_arm = new_match_arm_list |
120 | let first_new_arm = new_arm_list.arms().nth(n_old_arms); | 122 | .arms() |
123 | .find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_)))); | ||
124 | if let Some(arm) = catch_all_arm { | ||
125 | arm.remove() | ||
126 | } | ||
127 | let mut first_new_arm = None; | ||
128 | for arm in missing_arms { | ||
129 | first_new_arm.get_or_insert_with(|| arm.clone()); | ||
130 | new_match_arm_list.add_arm(arm); | ||
131 | } | ||
132 | |||
121 | let old_range = ctx.sema.original_range(match_arm_list.syntax()).range; | 133 | let old_range = ctx.sema.original_range(match_arm_list.syntax()).range; |
122 | match (first_new_arm, ctx.config.snippet_cap) { | 134 | match (first_new_arm, ctx.config.snippet_cap) { |
123 | (Some(first_new_arm), Some(cap)) => { | 135 | (Some(first_new_arm), Some(cap)) => { |
@@ -131,10 +143,10 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
131 | } | 143 | } |
132 | None => Cursor::Before(first_new_arm.syntax()), | 144 | None => Cursor::Before(first_new_arm.syntax()), |
133 | }; | 145 | }; |
134 | let snippet = render_snippet(cap, new_arm_list.syntax(), cursor); | 146 | let snippet = render_snippet(cap, new_match_arm_list.syntax(), cursor); |
135 | builder.replace_snippet(cap, old_range, snippet); | 147 | builder.replace_snippet(cap, old_range, snippet); |
136 | } | 148 | } |
137 | _ => builder.replace(old_range, new_arm_list.to_string()), | 149 | _ => builder.replace(old_range, new_match_arm_list.to_string()), |
138 | } | 150 | } |
139 | }, | 151 | }, |
140 | ) | 152 | ) |
@@ -919,8 +931,8 @@ fn main() { | |||
919 | match a { | 931 | match a { |
920 | // foo bar baz | 932 | // foo bar baz |
921 | A::One => {} | 933 | A::One => {} |
922 | // This is where the rest should be | ||
923 | $0A::Two => {} | 934 | $0A::Two => {} |
935 | // This is where the rest should be | ||
924 | } | 936 | } |
925 | } | 937 | } |
926 | "#, | 938 | "#, |
@@ -943,9 +955,9 @@ fn main() { | |||
943 | enum A { One, Two } | 955 | enum A { One, Two } |
944 | fn foo(a: A) { | 956 | fn foo(a: A) { |
945 | match a { | 957 | match a { |
946 | // foo bar baz | ||
947 | $0A::One => {} | 958 | $0A::One => {} |
948 | A::Two => {} | 959 | A::Two => {} |
960 | // foo bar baz | ||
949 | } | 961 | } |
950 | } | 962 | } |
951 | "#, | 963 | "#, |
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs index 4b5f5c571..5e6c1d44e 100644 --- a/crates/syntax/src/ast/edit.rs +++ b/crates/syntax/src/ast/edit.rs | |||
@@ -29,38 +29,6 @@ impl ast::BinExpr { | |||
29 | } | 29 | } |
30 | } | 30 | } |
31 | 31 | ||
32 | fn make_multiline<N>(node: N) -> N | ||
33 | where | ||
34 | N: AstNode + Clone, | ||
35 | { | ||
36 | let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
37 | Some(it) => it, | ||
38 | None => return node, | ||
39 | }; | ||
40 | let sibling = match l_curly.next_sibling_or_token() { | ||
41 | Some(it) => it, | ||
42 | None => return node, | ||
43 | }; | ||
44 | let existing_ws = match sibling.as_token() { | ||
45 | None => None, | ||
46 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
47 | Some(ws) => { | ||
48 | if ws.text().contains('\n') { | ||
49 | return node; | ||
50 | } | ||
51 | Some(ws.clone()) | ||
52 | } | ||
53 | }; | ||
54 | |||
55 | let indent = leading_indent(node.syntax()).unwrap_or_default(); | ||
56 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
57 | let to_insert = iter::once(ws.ws().into()); | ||
58 | match existing_ws { | ||
59 | None => node.insert_children(InsertPosition::After(l_curly), to_insert), | ||
60 | Some(ws) => node.replace_children(single_node(ws), to_insert), | ||
61 | } | ||
62 | } | ||
63 | |||
64 | impl ast::RecordExprFieldList { | 32 | impl ast::RecordExprFieldList { |
65 | #[must_use] | 33 | #[must_use] |
66 | pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { | 34 | pub fn append_field(&self, field: &ast::RecordExprField) -> ast::RecordExprFieldList { |
@@ -214,79 +182,6 @@ impl ast::UseTree { | |||
214 | } | 182 | } |
215 | } | 183 | } |
216 | 184 | ||
217 | impl ast::MatchArmList { | ||
218 | #[must_use] | ||
219 | pub fn append_arms(&self, items: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList { | ||
220 | let mut res = self.clone(); | ||
221 | res = res.strip_if_only_whitespace(); | ||
222 | if !res.syntax().text().contains_char('\n') { | ||
223 | res = make_multiline(res); | ||
224 | } | ||
225 | items.into_iter().for_each(|it| res = res.append_arm(it)); | ||
226 | res | ||
227 | } | ||
228 | |||
229 | fn strip_if_only_whitespace(&self) -> ast::MatchArmList { | ||
230 | let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']); | ||
231 | iter.next(); // Eat the curly | ||
232 | let mut inner = iter.take_while(|it| it.kind() != T!['}']); | ||
233 | if !inner.clone().all(|it| it.kind() == WHITESPACE) { | ||
234 | return self.clone(); | ||
235 | } | ||
236 | let start = match inner.next() { | ||
237 | Some(s) => s, | ||
238 | None => return self.clone(), | ||
239 | }; | ||
240 | let end = match inner.last() { | ||
241 | Some(s) => s, | ||
242 | None => start.clone(), | ||
243 | }; | ||
244 | self.replace_children(start..=end, &mut iter::empty()) | ||
245 | } | ||
246 | |||
247 | #[must_use] | ||
248 | pub fn remove_placeholder(&self) -> ast::MatchArmList { | ||
249 | let placeholder = | ||
250 | self.arms().find(|arm| matches!(arm.pat(), Some(ast::Pat::WildcardPat(_)))); | ||
251 | if let Some(placeholder) = placeholder { | ||
252 | self.remove_arm(&placeholder) | ||
253 | } else { | ||
254 | self.clone() | ||
255 | } | ||
256 | } | ||
257 | |||
258 | #[must_use] | ||
259 | fn remove_arm(&self, arm: &ast::MatchArm) -> ast::MatchArmList { | ||
260 | let start = arm.syntax().clone(); | ||
261 | let end = if let Some(comma) = start | ||
262 | .siblings_with_tokens(Direction::Next) | ||
263 | .skip(1) | ||
264 | .find(|it| !it.kind().is_trivia()) | ||
265 | .filter(|it| it.kind() == T![,]) | ||
266 | { | ||
267 | comma | ||
268 | } else { | ||
269 | start.clone().into() | ||
270 | }; | ||
271 | self.replace_children(start.into()..=end, None) | ||
272 | } | ||
273 | |||
274 | #[must_use] | ||
275 | pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList { | ||
276 | let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) { | ||
277 | Some(t) => t, | ||
278 | None => return self.clone(), | ||
279 | }; | ||
280 | let position = InsertPosition::Before(r_curly); | ||
281 | let arm_ws = tokens::WsBuilder::new(" "); | ||
282 | let match_indent = &leading_indent(self.syntax()).unwrap_or_default(); | ||
283 | let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent)); | ||
284 | let to_insert: ArrayVec<SyntaxElement, 3> = | ||
285 | [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into(); | ||
286 | self.insert_children(position, to_insert) | ||
287 | } | ||
288 | } | ||
289 | |||
290 | #[must_use] | 185 | #[must_use] |
291 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 186 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
292 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 187 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() |
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs index ca777d057..abab0269a 100644 --- a/crates/syntax/src/ast/edit_in_place.rs +++ b/crates/syntax/src/ast/edit_in_place.rs | |||
@@ -13,7 +13,7 @@ use crate::{ | |||
13 | make, GenericParamsOwner, | 13 | make, GenericParamsOwner, |
14 | }, | 14 | }, |
15 | ted::{self, Position}, | 15 | ted::{self, Position}, |
16 | AstNode, AstToken, Direction, | 16 | AstNode, AstToken, Direction, SyntaxNode, |
17 | }; | 17 | }; |
18 | 18 | ||
19 | use super::NameOwner; | 19 | use super::NameOwner; |
@@ -297,7 +297,7 @@ impl ast::AssocItemList { | |||
297 | ), | 297 | ), |
298 | None => match self.l_curly_token() { | 298 | None => match self.l_curly_token() { |
299 | Some(l_curly) => { | 299 | Some(l_curly) => { |
300 | self.normalize_ws_between_braces(); | 300 | normalize_ws_between_braces(self.syntax()); |
301 | (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n") | 301 | (IndentLevel::from_token(&l_curly) + 1, Position::after(&l_curly), "\n") |
302 | } | 302 | } |
303 | None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"), | 303 | None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"), |
@@ -309,25 +309,6 @@ impl ast::AssocItemList { | |||
309 | ]; | 309 | ]; |
310 | ted::insert_all(position, elements); | 310 | ted::insert_all(position, elements); |
311 | } | 311 | } |
312 | |||
313 | fn normalize_ws_between_braces(&self) -> Option<()> { | ||
314 | let l = self.l_curly_token()?; | ||
315 | let r = self.r_curly_token()?; | ||
316 | let indent = IndentLevel::from_node(self.syntax()); | ||
317 | |||
318 | match l.next_sibling_or_token() { | ||
319 | Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => { | ||
320 | if ws.next_sibling_or_token()?.into_token()? == r { | ||
321 | ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent))); | ||
322 | } | ||
323 | } | ||
324 | Some(ws) if ws.kind() == T!['}'] => { | ||
325 | ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent))); | ||
326 | } | ||
327 | _ => (), | ||
328 | } | ||
329 | Some(()) | ||
330 | } | ||
331 | } | 312 | } |
332 | 313 | ||
333 | impl ast::Fn { | 314 | impl ast::Fn { |
@@ -346,6 +327,73 @@ impl ast::Fn { | |||
346 | } | 327 | } |
347 | } | 328 | } |
348 | 329 | ||
330 | impl ast::MatchArm { | ||
331 | pub fn remove(&self) { | ||
332 | if let Some(sibling) = self.syntax().prev_sibling_or_token() { | ||
333 | if sibling.kind() == SyntaxKind::WHITESPACE { | ||
334 | ted::remove(sibling); | ||
335 | } | ||
336 | } | ||
337 | if let Some(sibling) = self.syntax().next_sibling_or_token() { | ||
338 | if sibling.kind() == T![,] { | ||
339 | ted::remove(sibling); | ||
340 | } | ||
341 | } | ||
342 | ted::remove(self.syntax()); | ||
343 | } | ||
344 | } | ||
345 | |||
346 | impl ast::MatchArmList { | ||
347 | pub fn add_arm(&self, arm: ast::MatchArm) { | ||
348 | normalize_ws_between_braces(self.syntax()); | ||
349 | let position = match self.arms().last() { | ||
350 | Some(last_arm) => { | ||
351 | let curly = last_arm | ||
352 | .syntax() | ||
353 | .siblings_with_tokens(Direction::Next) | ||
354 | .find(|it| it.kind() == T![,]); | ||
355 | Position::after(curly.unwrap_or_else(|| last_arm.syntax().clone().into())) | ||
356 | } | ||
357 | None => match self.l_curly_token() { | ||
358 | Some(it) => Position::after(it), | ||
359 | None => Position::last_child_of(self.syntax()), | ||
360 | }, | ||
361 | }; | ||
362 | let indent = IndentLevel::from_node(self.syntax()) + 1; | ||
363 | let elements = vec![ | ||
364 | make::tokens::whitespace(&format!("\n{}", indent)).into(), | ||
365 | arm.syntax().clone().into(), | ||
366 | ]; | ||
367 | ted::insert_all(position, elements); | ||
368 | } | ||
369 | } | ||
370 | |||
371 | fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> { | ||
372 | let l = node | ||
373 | .children_with_tokens() | ||
374 | .filter_map(|it| it.into_token()) | ||
375 | .find(|it| it.kind() == T!['{'])?; | ||
376 | let r = node | ||
377 | .children_with_tokens() | ||
378 | .filter_map(|it| it.into_token()) | ||
379 | .find(|it| it.kind() == T!['}'])?; | ||
380 | |||
381 | let indent = IndentLevel::from_node(node); | ||
382 | |||
383 | match l.next_sibling_or_token() { | ||
384 | Some(ws) if ws.kind() == SyntaxKind::WHITESPACE => { | ||
385 | if ws.next_sibling_or_token()?.into_token()? == r { | ||
386 | ted::replace(ws, make::tokens::whitespace(&format!("\n{}", indent))); | ||
387 | } | ||
388 | } | ||
389 | Some(ws) if ws.kind() == T!['}'] => { | ||
390 | ted::insert(Position::after(l), make::tokens::whitespace(&format!("\n{}", indent))); | ||
391 | } | ||
392 | _ => (), | ||
393 | } | ||
394 | Some(()) | ||
395 | } | ||
396 | |||
349 | #[cfg(test)] | 397 | #[cfg(test)] |
350 | mod tests { | 398 | mod tests { |
351 | use std::fmt; | 399 | use std::fmt; |