Skip to content

Commit 83dc22e

Browse files
bors[bot]matklad
andauthored
Merge #3019
3019: Better cursor placement when merging arms r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 1b1bca4 + f756d5d commit 83dc22e

File tree

1 file changed

+91
-36
lines changed

1 file changed

+91
-36
lines changed

crates/ra_assists/src/assists/merge_match_arms.rs

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
1+
use std::iter::successors;
2+
23
use hir::db::HirDatabase;
3-
use ra_syntax::ast::{AstNode, MatchArm};
4+
use ra_syntax::{
5+
ast::{self, AstNode},
6+
Direction, TextUnit,
7+
};
8+
9+
use crate::{Assist, AssistCtx, AssistId, TextRange};
410

511
// Assist: merge_match_arms
612
//
@@ -27,62 +33,80 @@ use ra_syntax::ast::{AstNode, MatchArm};
2733
// }
2834
// ```
2935
pub(crate) fn merge_match_arms(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
30-
let current_arm = ctx.find_node_at_offset::<MatchArm>()?;
31-
32-
// We check if the following match arm matches this one. We could, but don't,
33-
// compare to the previous match arm as well.
34-
let next = current_arm.syntax().next_sibling();
35-
let next_arm = MatchArm::cast(next?)?;
36-
36+
let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
3737
// Don't try to handle arms with guards for now - can add support for this later
38-
if current_arm.guard().is_some() || next_arm.guard().is_some() {
38+
if current_arm.guard().is_some() {
3939
return None;
4040
}
41-
4241
let current_expr = current_arm.expr()?;
43-
let next_expr = next_arm.expr()?;
42+
let current_text_range = current_arm.syntax().text_range();
4443

45-
// Check for match arm equality by comparing lengths and then string contents
46-
if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() {
47-
return None;
44+
enum CursorPos {
45+
InExpr(TextUnit),
46+
InPat(TextUnit),
4847
}
49-
if current_expr.syntax().text() != next_expr.syntax().text() {
48+
let cursor_pos = ctx.frange.range.start();
49+
let cursor_pos = if current_expr.syntax().text_range().contains(cursor_pos) {
50+
CursorPos::InExpr(current_text_range.end() - cursor_pos)
51+
} else {
52+
CursorPos::InPat(cursor_pos)
53+
};
54+
55+
// We check if the following match arms match this one. We could, but don't,
56+
// compare to the previous match arm as well.
57+
let arms_to_merge = successors(Some(current_arm), next_arm)
58+
.take_while(|arm| {
59+
if arm.guard().is_some() {
60+
return false;
61+
}
62+
match arm.expr() {
63+
Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
64+
None => false,
65+
}
66+
})
67+
.collect::<Vec<_>>();
68+
69+
if arms_to_merge.len() <= 1 {
5070
return None;
5171
}
5272

53-
let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
54-
5573
ctx.add_assist(AssistId("merge_match_arms"), "Merge match arms", |edit| {
56-
fn contains_placeholder(a: &MatchArm) -> bool {
57-
a.pats().any(|x| match x {
58-
ra_syntax::ast::Pat::PlaceholderPat(..) => true,
59-
_ => false,
60-
})
61-
}
62-
63-
let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
74+
let pats = if arms_to_merge.iter().any(contains_placeholder) {
6475
"_".into()
6576
} else {
66-
let ps: Vec<String> = current_arm
67-
.pats()
77+
arms_to_merge
78+
.iter()
79+
.flat_map(ast::MatchArm::pats)
6880
.map(|x| x.syntax().to_string())
69-
.chain(next_arm.pats().map(|x| x.syntax().to_string()))
70-
.collect();
71-
ps.join(" | ")
81+
.collect::<Vec<String>>()
82+
.join(" | ")
7283
};
7384

7485
let arm = format!("{} => {}", pats, current_expr.syntax().text());
75-
let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
7686

77-
let start = current_arm.syntax().text_range().start();
78-
let end = next_arm.syntax().text_range().end();
87+
let start = arms_to_merge.first().unwrap().syntax().text_range().start();
88+
let end = arms_to_merge.last().unwrap().syntax().text_range().end();
7989

80-
edit.target(current_arm.syntax().text_range());
90+
edit.target(current_text_range);
91+
edit.set_cursor(match cursor_pos {
92+
CursorPos::InExpr(back_offset) => start + TextUnit::from_usize(arm.len()) - back_offset,
93+
CursorPos::InPat(offset) => offset,
94+
});
8195
edit.replace(TextRange::from_to(start, end), arm);
82-
edit.set_cursor(start + offset);
8396
})
8497
}
8598

99+
fn contains_placeholder(a: &ast::MatchArm) -> bool {
100+
a.pats().any(|x| match x {
101+
ra_syntax::ast::Pat::PlaceholderPat(..) => true,
102+
_ => false,
103+
})
104+
}
105+
106+
fn next_arm(arm: &ast::MatchArm) -> Option<ast::MatchArm> {
107+
arm.syntax().siblings(Direction::Next).skip(1).find_map(ast::MatchArm::cast)
108+
}
109+
86110
#[cfg(test)]
87111
mod tests {
88112
use super::merge_match_arms;
@@ -184,6 +208,37 @@ mod tests {
184208
);
185209
}
186210

211+
#[test]
212+
fn merges_all_subsequent_arms() {
213+
check_assist(
214+
merge_match_arms,
215+
r#"
216+
enum X { A, B, C, D, E }
217+
218+
fn main() {
219+
match X::A {
220+
X::A<|> => 92,
221+
X::B => 92,
222+
X::C => 92,
223+
X::D => 62,
224+
_ => panic!(),
225+
}
226+
}
227+
"#,
228+
r#"
229+
enum X { A, B, C, D, E }
230+
231+
fn main() {
232+
match X::A {
233+
X::A<|> | X::B | X::C => 92,
234+
X::D => 62,
235+
_ => panic!(),
236+
}
237+
}
238+
"#,
239+
)
240+
}
241+
187242
#[test]
188243
fn merge_match_arms_rejects_guards() {
189244
check_assist_not_applicable(

0 commit comments

Comments
 (0)