diff options
-rw-r--r-- | crates/ra_assists/src/handlers/fill_match_arms.rs | 73 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/edit.rs | 126 |
2 files changed, 164 insertions, 35 deletions
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index add82e5b1..5f279d25a 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -7,7 +7,7 @@ use itertools::Itertools; | |||
7 | use ra_ide_db::RootDatabase; | 7 | use ra_ide_db::RootDatabase; |
8 | 8 | ||
9 | use crate::{Assist, AssistCtx, AssistId}; | 9 | use crate::{Assist, AssistCtx, AssistId}; |
10 | use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; | 10 | use ra_syntax::ast::{self, make, AstNode, NameOwner}; |
11 | 11 | ||
12 | use ast::{MatchArm, Pat}; | 12 | use ast::{MatchArm, Pat}; |
13 | 13 | ||
@@ -97,10 +97,8 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> { | |||
97 | } | 97 | } |
98 | 98 | ||
99 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { | 99 | ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| { |
100 | arms.extend(missing_arms); | 100 | let new_arm_list = |
101 | 101 | match_arm_list.remove_placeholder().append_arms(missing_arms.into_iter()); | |
102 | let indent_level = IndentLevel::from_node(match_arm_list.syntax()); | ||
103 | let new_arm_list = indent_level.increase_indent(make::match_arm_list(arms)); | ||
104 | 102 | ||
105 | edit.target(match_expr.syntax().text_range()); | 103 | edit.target(match_expr.syntax().text_range()); |
106 | edit.set_cursor(expr.syntax().text_range().start()); | 104 | edit.set_cursor(expr.syntax().text_range().start()); |
@@ -655,4 +653,69 @@ mod tests { | |||
655 | "#, | 653 | "#, |
656 | ); | 654 | ); |
657 | } | 655 | } |
656 | |||
657 | #[test] | ||
658 | fn fill_match_arms_preserves_comments() { | ||
659 | check_assist( | ||
660 | fill_match_arms, | ||
661 | r#" | ||
662 | enum A { | ||
663 | One, | ||
664 | Two, | ||
665 | } | ||
666 | fn foo(a: A) { | ||
667 | match a { | ||
668 | // foo bar baz<|> | ||
669 | A::One => {} | ||
670 | // This is where the rest should be | ||
671 | } | ||
672 | } | ||
673 | "#, | ||
674 | r#" | ||
675 | enum A { | ||
676 | One, | ||
677 | Two, | ||
678 | } | ||
679 | fn foo(a: A) { | ||
680 | match <|>a { | ||
681 | // foo bar baz | ||
682 | A::One => {} | ||
683 | // This is where the rest should be | ||
684 | A::Two => {} | ||
685 | } | ||
686 | } | ||
687 | "#, | ||
688 | ); | ||
689 | } | ||
690 | |||
691 | #[test] | ||
692 | fn fill_match_arms_preserves_comments_empty() { | ||
693 | check_assist( | ||
694 | fill_match_arms, | ||
695 | r#" | ||
696 | enum A { | ||
697 | One, | ||
698 | Two, | ||
699 | } | ||
700 | fn foo(a: A) { | ||
701 | match a { | ||
702 | // foo bar baz<|> | ||
703 | } | ||
704 | } | ||
705 | "#, | ||
706 | r#" | ||
707 | enum A { | ||
708 | One, | ||
709 | Two, | ||
710 | } | ||
711 | fn foo(a: A) { | ||
712 | match <|>a { | ||
713 | // foo bar baz | ||
714 | A::One => {} | ||
715 | A::Two => {} | ||
716 | } | ||
717 | } | ||
718 | "#, | ||
719 | ); | ||
720 | } | ||
658 | } | 721 | } |
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 2304e00cf..baf9a1b4b 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -46,12 +46,44 @@ impl ast::FnDef { | |||
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
49 | fn make_multiline<N>(node: N) -> N | ||
50 | where | ||
51 | N: AstNode + Clone, | ||
52 | { | ||
53 | let l_curly = match node.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
54 | Some(it) => it, | ||
55 | None => return node, | ||
56 | }; | ||
57 | let sibling = match l_curly.next_sibling_or_token() { | ||
58 | Some(it) => it, | ||
59 | None => return node, | ||
60 | }; | ||
61 | let existing_ws = match sibling.as_token() { | ||
62 | None => None, | ||
63 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
64 | Some(ws) => { | ||
65 | if ws.text().contains('\n') { | ||
66 | return node; | ||
67 | } | ||
68 | Some(ws.clone()) | ||
69 | } | ||
70 | }; | ||
71 | |||
72 | let indent = leading_indent(node.syntax()).unwrap_or_default(); | ||
73 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
74 | let to_insert = iter::once(ws.ws().into()); | ||
75 | match existing_ws { | ||
76 | None => node.insert_children(InsertPosition::After(l_curly), to_insert), | ||
77 | Some(ws) => node.replace_children(single_node(ws), to_insert), | ||
78 | } | ||
79 | } | ||
80 | |||
49 | impl ast::ItemList { | 81 | impl ast::ItemList { |
50 | #[must_use] | 82 | #[must_use] |
51 | pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { | 83 | pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList { |
52 | let mut res = self.clone(); | 84 | let mut res = self.clone(); |
53 | if !self.syntax().text().contains_char('\n') { | 85 | if !self.syntax().text().contains_char('\n') { |
54 | res = res.make_multiline(); | 86 | res = make_multiline(res); |
55 | } | 87 | } |
56 | items.for_each(|it| res = res.append_item(it)); | 88 | items.for_each(|it| res = res.append_item(it)); |
57 | res | 89 | res |
@@ -81,35 +113,6 @@ impl ast::ItemList { | |||
81 | fn l_curly(&self) -> Option<SyntaxElement> { | 113 | fn l_curly(&self) -> Option<SyntaxElement> { |
82 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) | 114 | self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) |
83 | } | 115 | } |
84 | |||
85 | fn make_multiline(&self) -> ast::ItemList { | ||
86 | let l_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['{']) { | ||
87 | Some(it) => it, | ||
88 | None => return self.clone(), | ||
89 | }; | ||
90 | let sibling = match l_curly.next_sibling_or_token() { | ||
91 | Some(it) => it, | ||
92 | None => return self.clone(), | ||
93 | }; | ||
94 | let existing_ws = match sibling.as_token() { | ||
95 | None => None, | ||
96 | Some(tok) if tok.kind() != WHITESPACE => None, | ||
97 | Some(ws) => { | ||
98 | if ws.text().contains('\n') { | ||
99 | return self.clone(); | ||
100 | } | ||
101 | Some(ws.clone()) | ||
102 | } | ||
103 | }; | ||
104 | |||
105 | let indent = leading_indent(self.syntax()).unwrap_or_default(); | ||
106 | let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); | ||
107 | let to_insert = iter::once(ws.ws().into()); | ||
108 | match existing_ws { | ||
109 | None => self.insert_children(InsertPosition::After(l_curly), to_insert), | ||
110 | Some(ws) => self.replace_children(single_node(ws), to_insert), | ||
111 | } | ||
112 | } | ||
113 | } | 116 | } |
114 | 117 | ||
115 | impl ast::RecordFieldList { | 118 | impl ast::RecordFieldList { |
@@ -334,6 +337,69 @@ impl ast::UseTree { | |||
334 | } | 337 | } |
335 | } | 338 | } |
336 | 339 | ||
340 | impl ast::MatchArmList { | ||
341 | #[must_use] | ||
342 | pub fn append_arms(&self, items: impl Iterator<Item = ast::MatchArm>) -> ast::MatchArmList { | ||
343 | let mut res = self.clone(); | ||
344 | res = res.strip_if_only_whitespace(); | ||
345 | if !res.syntax().text().contains_char('\n') { | ||
346 | res = make_multiline(res); | ||
347 | } | ||
348 | items.for_each(|it| res = res.append_arm(it)); | ||
349 | res | ||
350 | } | ||
351 | |||
352 | fn strip_if_only_whitespace(&self) -> ast::MatchArmList { | ||
353 | let mut iter = self.syntax().children_with_tokens().skip_while(|it| it.kind() != T!['{']); | ||
354 | iter.next(); // Eat the curly | ||
355 | let mut inner = iter.take_while(|it| it.kind() != T!['}']); | ||
356 | if !inner.clone().all(|it| it.kind() == WHITESPACE) { | ||
357 | return self.clone(); | ||
358 | } | ||
359 | let start = match inner.next() { | ||
360 | Some(s) => s, | ||
361 | None => return self.clone(), | ||
362 | }; | ||
363 | let end = match inner.last() { | ||
364 | Some(s) => s, | ||
365 | None => start.clone(), | ||
366 | }; | ||
367 | self.replace_children(start..=end, &mut iter::empty()) | ||
368 | } | ||
369 | |||
370 | #[must_use] | ||
371 | pub fn remove_placeholder(&self) -> ast::MatchArmList { | ||
372 | let placeholder = self.arms().find(|arm| { | ||
373 | if let Some(ast::Pat::PlaceholderPat(_)) = arm.pat() { | ||
374 | return true; | ||
375 | } | ||
376 | false | ||
377 | }); | ||
378 | if let Some(placeholder) = placeholder { | ||
379 | let s: SyntaxElement = placeholder.syntax().clone().into(); | ||
380 | let e = s.clone(); | ||
381 | self.replace_children(s..=e, &mut iter::empty()) | ||
382 | } else { | ||
383 | self.clone() | ||
384 | } | ||
385 | } | ||
386 | |||
387 | #[must_use] | ||
388 | pub fn append_arm(&self, item: ast::MatchArm) -> ast::MatchArmList { | ||
389 | let r_curly = match self.syntax().children_with_tokens().find(|it| it.kind() == T!['}']) { | ||
390 | Some(t) => t, | ||
391 | None => return self.clone(), | ||
392 | }; | ||
393 | let position = InsertPosition::Before(r_curly.into()); | ||
394 | let arm_ws = tokens::WsBuilder::new(" "); | ||
395 | let match_indent = &leading_indent(self.syntax()).unwrap_or_default(); | ||
396 | let match_ws = tokens::WsBuilder::new(&format!("\n{}", match_indent)); | ||
397 | let to_insert: ArrayVec<[SyntaxElement; 3]> = | ||
398 | [arm_ws.ws().into(), item.syntax().clone().into(), match_ws.ws().into()].into(); | ||
399 | self.insert_children(position, to_insert) | ||
400 | } | ||
401 | } | ||
402 | |||
337 | #[must_use] | 403 | #[must_use] |
338 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { | 404 | pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N { |
339 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() | 405 | N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap() |