Skip to content

Commit e6113c0

Browse files
committed
Add merge_match_arm assist, bump rowan to 0.6.1
1 parent a5fe9f7 commit e6113c0

File tree

4 files changed

+194
-4
lines changed

4 files changed

+194
-4
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ mod flip_comma;
9393
mod flip_binexpr;
9494
mod change_visibility;
9595
mod fill_match_arms;
96+
mod merge_match_arms;
9697
mod introduce_variable;
9798
mod inline_local_variable;
9899
mod replace_if_let_with_match;
@@ -109,6 +110,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
109110
add_impl::add_impl,
110111
change_visibility::change_visibility,
111112
fill_match_arms::fill_match_arms,
113+
merge_match_arms::merge_match_arms,
112114
flip_comma::flip_comma,
113115
flip_binexpr::flip_binexpr,
114116
introduce_variable::introduce_variable,
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use crate::{Assist, AssistCtx, AssistId, TextRange, TextUnit};
2+
use hir::db::HirDatabase;
3+
use ra_syntax::ast::{AstNode, MatchArm};
4+
5+
pub(crate) fn merge_match_arms(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
6+
let current_arm = ctx.node_at_offset::<MatchArm>()?;
7+
8+
// We check if the following match arm matches this one. We could, but don't,
9+
// compare to the previous match arm as well.
10+
let next = current_arm.syntax().next_sibling();
11+
let next_arm = MatchArm::cast(next?.clone())?;
12+
13+
// Don't try to handle arms with guards for now - can add support for this later
14+
if current_arm.guard().is_some() || next_arm.guard().is_some() {
15+
return None;
16+
}
17+
18+
let current_expr = current_arm.expr()?;
19+
let next_expr = next_arm.expr()?;
20+
21+
// Check for match arm equality by comparing lengths and then string contents
22+
if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() {
23+
return None;
24+
}
25+
if current_expr.syntax().text() != next_expr.syntax().text() {
26+
return None;
27+
}
28+
29+
let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start();
30+
31+
ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| {
32+
fn contains_placeholder(a: &MatchArm) -> bool {
33+
a.pats().any(|x| match x.kind() {
34+
ra_syntax::ast::PatKind::PlaceholderPat(..) => true,
35+
_ => false,
36+
})
37+
}
38+
39+
let pats = if contains_placeholder(&current_arm) || contains_placeholder(&next_arm) {
40+
"_".into()
41+
} else {
42+
let ps: Vec<String> = current_arm
43+
.pats()
44+
.map(|x| x.syntax().to_string())
45+
.chain(next_arm.pats().map(|x| x.syntax().to_string()))
46+
.collect();
47+
ps.join(" | ")
48+
};
49+
50+
let arm = format!("{} => {}", pats, current_expr.syntax().text());
51+
let offset = TextUnit::from_usize(arm.len()) - cursor_to_end;
52+
53+
let start = current_arm.syntax().text_range().start();
54+
let end = next_arm.syntax().text_range().end();
55+
56+
edit.target(current_arm.syntax().text_range());
57+
edit.replace(TextRange::from_to(start, end), arm);
58+
edit.set_cursor(start + offset);
59+
});
60+
61+
ctx.build()
62+
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use super::merge_match_arms;
67+
use crate::helpers::{check_assist, check_assist_not_applicable};
68+
69+
#[test]
70+
fn merge_match_arms_single_patterns() {
71+
check_assist(
72+
merge_match_arms,
73+
r#"
74+
#[derive(Debug)]
75+
enum X { A, B, C }
76+
77+
fn main() {
78+
let x = X::A;
79+
let y = match x {
80+
X::A => { 1i32<|> }
81+
X::B => { 1i32 }
82+
X::C => { 2i32 }
83+
}
84+
}
85+
"#,
86+
r#"
87+
#[derive(Debug)]
88+
enum X { A, B, C }
89+
90+
fn main() {
91+
let x = X::A;
92+
let y = match x {
93+
X::A | X::B => { 1i32<|> }
94+
X::C => { 2i32 }
95+
}
96+
}
97+
"#,
98+
);
99+
}
100+
101+
#[test]
102+
fn merge_match_arms_multiple_patterns() {
103+
check_assist(
104+
merge_match_arms,
105+
r#"
106+
#[derive(Debug)]
107+
enum X { A, B, C, D, E }
108+
109+
fn main() {
110+
let x = X::A;
111+
let y = match x {
112+
X::A | X::B => {<|> 1i32 },
113+
X::C | X::D => { 1i32 },
114+
X::E => { 2i32 },
115+
}
116+
}
117+
"#,
118+
r#"
119+
#[derive(Debug)]
120+
enum X { A, B, C, D, E }
121+
122+
fn main() {
123+
let x = X::A;
124+
let y = match x {
125+
X::A | X::B | X::C | X::D => {<|> 1i32 },
126+
X::E => { 2i32 },
127+
}
128+
}
129+
"#,
130+
);
131+
}
132+
133+
#[test]
134+
fn merge_match_arms_placeholder_pattern() {
135+
check_assist(
136+
merge_match_arms,
137+
r#"
138+
#[derive(Debug)]
139+
enum X { A, B, C, D, E }
140+
141+
fn main() {
142+
let x = X::A;
143+
let y = match x {
144+
X::A => { 1i32 },
145+
X::B => { 2i<|>32 },
146+
_ => { 2i32 }
147+
}
148+
}
149+
"#,
150+
r#"
151+
#[derive(Debug)]
152+
enum X { A, B, C, D, E }
153+
154+
fn main() {
155+
let x = X::A;
156+
let y = match x {
157+
X::A => { 1i32 },
158+
_ => { 2i<|>32 }
159+
}
160+
}
161+
"#,
162+
);
163+
}
164+
165+
#[test]
166+
fn merge_match_arms_rejects_guards() {
167+
check_assist_not_applicable(
168+
merge_match_arms,
169+
r#"
170+
#[derive(Debug)]
171+
enum X {
172+
A(i32),
173+
B,
174+
C
175+
}
176+
177+
fn main() {
178+
let x = X::A;
179+
let y = match x {
180+
X::A(a) if a > 5 => { <|>1i32 },
181+
X::B => { 1i32 },
182+
X::C => { 2i32 }
183+
}
184+
}
185+
"#,
186+
);
187+
}
188+
}

crates/ra_syntax/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer"
1010
[dependencies]
1111
unicode-xid = "0.1.0"
1212
itertools = "0.8.0"
13-
rowan = "0.6.0"
13+
rowan = "0.6.1"
1414
ra_rustc_lexer = { version = "0.1.0-pre.2" }
1515

1616
# ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here

0 commit comments

Comments
 (0)