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 --- Cargo.lock | 6 +- crates/ra_assists/src/lib.rs | 2 + crates/ra_assists/src/merge_match_arms.rs | 188 ++++++++++++++++++++++++++++++ crates/ra_syntax/Cargo.toml | 2 +- 4 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 crates/ra_assists/src/merge_match_arms.rs diff --git a/Cargo.lock b/Cargo.lock index fd4d876de..717b038f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1326,7 +1326,7 @@ dependencies = [ "ra_parser 0.1.0", "ra_rustc_lexer 0.1.0-pre.2 (registry+https://github.com/rust-lang/crates.io-index)", "ra_text_edit 0.1.0", - "rowan 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rowan 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "smol_str 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "test_utils 0.1.0", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1596,7 +1596,7 @@ dependencies = [ [[package]] name = "rowan" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2281,7 +2281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7790c7f1cc73d831d28dc5a7deb316a006e7848e6a7f467cdb10a0a9e0fb1c" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" "checksum ron 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "17f52a24414403f81528b67488cf8edc4eda977d3af1646bb6b106a600ead78f" -"checksum rowan 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a796c0517d6969224c42e9ef01356363b0a7c57d10ec986c9a600d075666a5ff" +"checksum rowan 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03e34c2e5f01d7fa4ab7e6a49da44f59fb38ffb61e6c9f714deb8e157274c2c7" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7540fc8b0c49f096ee9c961cda096467dce8084bec6bdca2fc83895fd9b28cb8" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 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