Skip to content

Commit d2ea3f2

Browse files
bors[bot]mattyhall
andauthored
Merge #3761
3761: Append new match arms rather than replacing all of them r=matklad a=mattyhall This means we now retain comments when filling in match arms. This fixes #3687. This is my first contribution so apologies if it needs a rethink! I think in particular the way I find the position to append to and remove_if_only_whitespace are a little hairy. Co-authored-by: Matthew Hall <matthew@quickbeam.me.uk>
2 parents 3901198 + ddb9cc4 commit d2ea3f2

File tree

2 files changed

+164
-35
lines changed

2 files changed

+164
-35
lines changed

crates/ra_assists/src/handlers/fill_match_arms.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use itertools::Itertools;
77
use ra_ide_db::RootDatabase;
88

99
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};
1111

1212
use ast::{MatchArm, Pat};
1313

@@ -97,10 +97,8 @@ pub(crate) fn fill_match_arms(ctx: AssistCtx) -> Option<Assist> {
9797
}
9898

9999
ctx.add_assist(AssistId("fill_match_arms"), "Fill match arms", |edit| {
100-
arms.extend(missing_arms);
101-
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));
100+
let new_arm_list =
101+
match_arm_list.remove_placeholder().append_arms(missing_arms.into_iter());
104102

105103
edit.target(match_expr.syntax().text_range());
106104
edit.set_cursor(expr.syntax().text_range().start());
@@ -655,4 +653,69 @@ mod tests {
655653
"#,
656654
);
657655
}
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+
}
658721
}

crates/ra_syntax/src/ast/edit.rs

Lines changed: 96 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,44 @@ impl ast::FnDef {
4646
}
4747
}
4848

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+
4981
impl ast::ItemList {
5082
#[must_use]
5183
pub fn append_items(&self, items: impl Iterator<Item = ast::ImplItem>) -> ast::ItemList {
5284
let mut res = self.clone();
5385
if !self.syntax().text().contains_char('\n') {
54-
res = res.make_multiline();
86+
res = make_multiline(res);
5587
}
5688
items.for_each(|it| res = res.append_item(it));
5789
res
@@ -81,35 +113,6 @@ impl ast::ItemList {
81113
fn l_curly(&self) -> Option<SyntaxElement> {
82114
self.syntax().children_with_tokens().find(|it| it.kind() == T!['{'])
83115
}
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-
}
113116
}
114117

115118
impl ast::RecordFieldList {
@@ -334,6 +337,69 @@ impl ast::UseTree {
334337
}
335338
}
336339

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+
337403
#[must_use]
338404
pub fn remove_attrs_and_docs<N: ast::AttrsOwner>(node: &N) -> N {
339405
N::cast(remove_attrs_and_docs_inner(node.syntax().clone())).unwrap()

0 commit comments

Comments
 (0)