From e6113c09980043dd3d9a57eca8fef96c011a5da5 Mon Sep 17 00:00:00 2001 From: Phil Ellison Date: Mon, 29 Jul 2019 21:59:52 +0100 Subject: Add merge_match_arm assist, bump rowan to 0.6.1 --- crates/ra_assists/src/lib.rs | 2 + crates/ra_assists/src/merge_match_arms.rs | 188 ++++++++++++++++++++++++++++++ crates/ra_syntax/Cargo.toml | 2 +- 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 crates/ra_assists/src/merge_match_arms.rs (limited to 'crates') diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 0d848629d..03eec73ad 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -93,6 +93,7 @@ mod flip_comma; mod flip_binexpr; mod change_visibility; mod fill_match_arms; +mod merge_match_arms; mod introduce_variable; mod inline_local_variable; mod replace_if_let_with_match; @@ -109,6 +110,7 @@ fn all_assists() -> &'static [fn(AssistCtx) -> Option) -> Option { + let current_arm = ctx.node_at_offset::()?; + + // We check if the following match arm matches this one. We could, but don't, + // compare to the previous match arm as well. + let next = current_arm.syntax().next_sibling(); + let next_arm = MatchArm::cast(next?.clone())?; + + // Don't try to handle arms with guards for now - can add support for this later + if current_arm.guard().is_some() || next_arm.guard().is_some() { + return None; + } + + let current_expr = current_arm.expr()?; + let next_expr = next_arm.expr()?; + + // Check for match arm equality by comparing lengths and then string contents + if current_expr.syntax().text_range().len() != next_expr.syntax().text_range().len() { + return None; + } + if current_expr.syntax().text() != next_expr.syntax().text() { + return None; + } + + let cursor_to_end = current_arm.syntax().text_range().end() - ctx.frange.range.start(); + + ctx.add_action(AssistId("merge_match_arms"), "merge match arms", |edit| { + fn contains_placeholder(a: &MatchArm) -> bool { + a.pats().any(|x| match x.kind() { + ra_syntax::ast::PatKind::PlaceholderPat(..) => true, + _ => false, + }) + } + + let pats = if contains_placeholder(¤t_arm) || contains_placeholder(&next_arm) { + "_".into() + } else { + let ps: Vec = current_arm + .pats() + .map(|x| x.syntax().to_string()) + .chain(next_arm.pats().map(|x| x.syntax().to_string())) + .collect(); + ps.join(" | ") + }; + + let arm = format!("{} => {}", pats, current_expr.syntax().text()); + let offset = TextUnit::from_usize(arm.len()) - cursor_to_end; + + let start = current_arm.syntax().text_range().start(); + let end = next_arm.syntax().text_range().end(); + + edit.target(current_arm.syntax().text_range()); + edit.replace(TextRange::from_to(start, end), arm); + edit.set_cursor(start + offset); + }); + + ctx.build() +} + +#[cfg(test)] +mod tests { + use super::merge_match_arms; + use crate::helpers::{check_assist, check_assist_not_applicable}; + + #[test] + fn merge_match_arms_single_patterns() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32<|> } + X::B => { 1i32 } + X::C => { 2i32 } + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B => { 1i32<|> } + X::C => { 2i32 } + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_multiple_patterns() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B => {<|> 1i32 }, + X::C | X::D => { 1i32 }, + X::E => { 2i32 }, + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A | X::B | X::C | X::D => {<|> 1i32 }, + X::E => { 2i32 }, + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_placeholder_pattern() { + check_assist( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 }, + X::B => { 2i<|>32 }, + _ => { 2i32 } + } + } + "#, + r#" + #[derive(Debug)] + enum X { A, B, C, D, E } + + fn main() { + let x = X::A; + let y = match x { + X::A => { 1i32 }, + _ => { 2i<|>32 } + } + } + "#, + ); + } + + #[test] + fn merge_match_arms_rejects_guards() { + check_assist_not_applicable( + merge_match_arms, + r#" + #[derive(Debug)] + enum X { + A(i32), + B, + C + } + + fn main() { + let x = X::A; + let y = match x { + X::A(a) if a > 5 => { <|>1i32 }, + X::B => { 1i32 }, + X::C => { 2i32 } + } + } + "#, + ); + } +} diff --git a/crates/ra_syntax/Cargo.toml b/crates/ra_syntax/Cargo.toml index 40d63ef7a..bc1c88070 100644 --- a/crates/ra_syntax/Cargo.toml +++ b/crates/ra_syntax/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/rust-analyzer/rust-analyzer" [dependencies] unicode-xid = "0.1.0" itertools = "0.8.0" -rowan = "0.6.0" +rowan = "0.6.1" ra_rustc_lexer = { version = "0.1.0-pre.2" } # ideally, `serde` should be enabled by `ra_lsp_server`, but we enable it here -- cgit v1.2.3