diff --git a/crates/ide-assists/src/handlers/merge_match_arms.rs b/crates/ide-assists/src/handlers/merge_match_arms.rs index 08170f81b2..355c1c090e 100644 --- a/crates/ide-assists/src/handlers/merge_match_arms.rs +++ b/crates/ide-assists/src/handlers/merge_match_arms.rs @@ -2,12 +2,13 @@ use hir::Type; use ide_db::FxHashMap; use std::iter::successors; use syntax::{ - Direction, + Direction, SyntaxKind, algo::neighbor, - ast::{self, AstNode, HasName}, + ast::{self, AstNode, HasName, syntax_factory::SyntaxFactory}, + syntax_editor::Position, }; -use crate::{AssistContext, AssistId, Assists, TextRange}; +use crate::{AssistContext, AssistId, Assists, TextRange, utils}; // Assist: merge_match_arms // @@ -100,6 +101,90 @@ pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ) } +// Assist: merge_other_match_arms +// +// Merge other arms, can be used after fill match arms. +// +// ``` +// enum Action { A, B, C, D, E } +// +// fn handle(action: Action) { +// match action { +// A => todo!(), +// $0B => foo(), +// C$0 => bar(), +// D => todo!(), +// E => todo!(), +// } +// } +// ``` +// -> +// ``` +// enum Action { A, B, C, D, E } +// +// fn handle(action: Action) { +// match action { +// B => foo(), +// C => bar(), +// _ => todo!(), +// } +// } +// ``` +pub(crate) fn merge_other_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + if ctx.has_empty_selection() { + return None; + } + let arm_list = ctx.find_node_at_trimmed_offset::()?; + let (current_arms, other_arms) = arm_list + .arms() + .partition::, _>(|arm| utils::is_selected(arm, ctx.selection_trimmed(), false)); + let other_expr = other_arms.first()?.expr()?; + let current_first_arm = current_arms.first()?; + let current_last_arm = current_arms.last()?; + + for other_arm in &other_arms { + if other_arm.expr()?.syntax().text() != other_expr.syntax().text() { + return None; + } + } + if current_first_arm.expr()?.syntax().text_range().contains_range(ctx.selection_trimmed()) { + return None; + } + + let target = + current_first_arm.syntax().text_range().cover(current_last_arm.syntax().text_range()); + acc.add( + AssistId::refactor_rewrite("merge_other_match_arms"), + "Merge other arms to `_ => ...`", + target, + |builder| { + let mut edit = builder.make_editor(current_first_arm.syntax()); + let make = SyntaxFactory::with_mappings(); + + for other_arm in other_arms { + if let Some(prev) = other_arm.syntax().prev_sibling_or_token() + && prev.kind() == SyntaxKind::WHITESPACE + { + edit.delete(prev); + } + edit.delete(other_arm.syntax()); + } + let merged_arm = make.match_arm(make.wildcard_pat().into(), None, other_expr); + edit.insert(Position::after(current_last_arm.syntax()), merged_arm.syntax()); + + if let Some(prev) = current_first_arm.syntax().prev_sibling_or_token() + && prev.kind() == SyntaxKind::WHITESPACE + { + let ws = make.whitespace(&prev.to_string()); + edit.insert(Position::after(current_last_arm.syntax()), ws); + } + + edit.add_mappings(make.finish_with_mappings()); + builder.add_file_edits(ctx.vfs_file_id(), edit); + }, + ) +} + fn contains_placeholder(a: &ast::MatchArm) -> bool { matches!(a.pat(), Some(ast::Pat::WildcardPat(..))) } @@ -919,4 +1004,205 @@ fn func(binary: &[u8]) { "#, ) } + + #[test] + fn merge_other_match_arms_basic() { + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + $00 $0=> code(), + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + }; +} + "#, + r#" +fn func() { + match 3 { + 0 => code(), + _ => else_code(), + }; +} + "#, + ); + + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + $00 $0=> code(), + }; +} + "#, + r#" +fn func() { + match 3 { + 0 => code(), + _ => else_code(), + }; +} + "#, + ); + + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 1 => else_code(), + 2 => else_code(), + $00 $0=> code(), + 3 => else_code(), + }; +} + "#, + r#" +fn func() { + match 3 { + 0 => code(), + _ => else_code(), + }; +} + "#, + ); + } + + #[test] + fn merge_other_match_arms_multiple_arms() { + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + $0-2 => code1(), + -1 => code2(), + 0 $0=> code3(), + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + }; +} + "#, + r#" +fn func() { + match 3 { + -2 => code1(), + -1 => code2(), + 0 => code3(), + _ => else_code(), + }; +} + "#, + ); + + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + $0-2 => code1(), + -1 => code2(), + 0 $0=> code3(), + }; +} + "#, + r#" +fn func() { + match 3 { + -2 => code1(), + -1 => code2(), + 0 => code3(), + _ => else_code(), + }; +} + "#, + ); + + check_assist( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 1 => else_code(), + 2 => else_code(), + $0-2 => code1(), + -1 => code2(), + 0 $0=> code3(), + 3 => else_code(), + }; +} + "#, + r#" +fn func() { + match 3 { + -2 => code1(), + -1 => code2(), + 0 => code3(), + _ => else_code(), + }; +} + "#, + ); + } + + #[test] + fn merge_other_match_arms_not_selected() { + check_assist_not_applicable( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 0$0 => code(), + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + }; +} + "#, + ); + } + + #[test] + fn merge_other_match_arms_in_expr() { + check_assist_not_applicable( + merge_other_match_arms, + r#" +fn func() { + match 3 { + 0 => $0code$0(), + 1 => else_code(), + 2 => else_code(), + 3 => else_code(), + }; +} + "#, + ); + } + + #[test] + fn merge_other_match_arms_in_cannot_merge_other() { + check_assist_not_applicable( + merge_other_match_arms, + r#" +fn func() { + match 3 { + $00 =>$0 code(), + 1 => else_code1(), + 2 => else_code2(), + 3 => else_code3(), + }; +} + "#, + ); + } } diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 4b4aa94279..90d1322e11 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -327,6 +327,7 @@ mod handlers { invert_if::invert_if, merge_imports::merge_imports, merge_match_arms::merge_match_arms, + merge_match_arms::merge_other_match_arms, merge_nested_if::merge_nested_if, move_bounds::move_bounds_to_where_clause, move_const_to_impl::move_const_to_impl, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 160b31af0a..eb1be66954 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -2643,6 +2643,37 @@ fn main() { ) } +#[test] +fn doctest_merge_other_match_arms() { + check_doc_test( + "merge_other_match_arms", + r#####" +enum Action { A, B, C, D, E } + +fn handle(action: Action) { + match action { + A => todo!(), + $0B => foo(), + C$0 => bar(), + D => todo!(), + E => todo!(), + } +} +"#####, + r#####" +enum Action { A, B, C, D, E } + +fn handle(action: Action) { + match action { + B => foo(), + C => bar(), + _ => todo!(), + } +} +"#####, + ) +} + #[test] fn doctest_move_arm_cond_to_match_guard() { check_doc_test(