From 96236a9be542be461550083373be3d0cb0bd8406 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 8 Jan 2019 14:21:29 +0300 Subject: assist to convert if-let to match --- crates/ra_editor/src/assists.rs | 23 ++++++ .../src/assists/replace_if_let_with_match.rs | 92 ++++++++++++++++++++++ crates/ra_editor/src/test_utils.rs | 5 +- crates/ra_syntax/src/ast.rs | 2 +- 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 crates/ra_editor/src/assists/replace_if_let_with_match.rs (limited to 'crates') diff --git a/crates/ra_editor/src/assists.rs b/crates/ra_editor/src/assists.rs index a320caabf..f839f6a7a 100644 --- a/crates/ra_editor/src/assists.rs +++ b/crates/ra_editor/src/assists.rs @@ -9,12 +9,15 @@ mod add_impl; mod introduce_variable; mod change_visibility; mod split_import; +mod replace_if_let_with_match; use ra_text_edit::{TextEdit, TextEditBuilder}; use ra_syntax::{ Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode, algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset}, + ast::{self, AstToken}, }; +use itertools::Itertools; use crate::find_node_at_offset; @@ -25,6 +28,7 @@ pub use self::{ introduce_variable::introduce_variable, change_visibility::change_visibility, split_import::split_import, + replace_if_let_with_match::replace_if_let_with_match, }; /// Return all the assists applicable at the given position. @@ -37,6 +41,7 @@ pub fn assists(file: &SourceFile, range: TextRange) -> Vec { introduce_variable, change_visibility, split_import, + replace_if_let_with_match, ] .iter() .filter_map(|&assist| ctx.clone().apply(assist)) @@ -160,6 +165,13 @@ impl AssistBuilder { fn replace(&mut self, range: TextRange, replace_with: impl Into) { self.edit.replace(range, replace_with.into()) } + fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into) { + let mut replace_with = replace_with.into(); + if let Some(indent) = calc_indent(node) { + replace_with = reindent(&replace_with, indent) + } + self.replace(node.range(), replace_with) + } #[allow(unused)] fn delete(&mut self, range: TextRange) { self.edit.delete(range) @@ -172,6 +184,17 @@ impl AssistBuilder { } } +fn calc_indent(node: &SyntaxNode) -> Option<&str> { + let prev = node.prev_sibling()?; + let ws_text = ast::Whitespace::cast(prev)?.text(); + ws_text.rfind('\n').map(|pos| &ws_text[pos + 1..]) +} + +fn reindent(text: &str, indent: &str) -> String { + let indent = format!("\n{}", indent); + text.lines().intersperse(&indent).collect() +} + #[cfg(test)] fn check_assist(assist: fn(AssistCtx) -> Option, before: &str, after: &str) { crate::test_utils::check_action(before, after, |file, off| { diff --git a/crates/ra_editor/src/assists/replace_if_let_with_match.rs b/crates/ra_editor/src/assists/replace_if_let_with_match.rs new file mode 100644 index 000000000..30c371480 --- /dev/null +++ b/crates/ra_editor/src/assists/replace_if_let_with_match.rs @@ -0,0 +1,92 @@ +use ra_syntax::{ + AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE}, + ast, +}; + +use crate::assists::{AssistCtx, Assist}; + +pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option { + let if_expr: &ast::IfExpr = ctx.node_at_offset()?; + let cond = if_expr.condition()?; + let pat = cond.pat()?; + let expr = cond.expr()?; + let then_block = if_expr.then_branch()?; + let else_block = if_expr.else_branch()?; + + ctx.build("replace with match", |edit| { + let match_expr = build_match_expr(expr, pat, then_block, else_block); + edit.replace_node_and_indent(if_expr.syntax(), match_expr); + edit.set_cursor(if_expr.syntax().range().start()) + }) +} + +fn build_match_expr( + expr: &ast::Expr, + pat1: &ast::Pat, + arm1: &ast::Block, + arm2: &ast::Block, +) -> String { + let mut buf = String::new(); + buf.push_str(&format!("match {} {{\n", expr.syntax().text())); + buf.push_str(&format!( + " {} => {}\n", + pat1.syntax().text(), + format_arm(arm1) + )); + buf.push_str(&format!(" _ => {}\n", format_arm(arm2))); + buf.push_str("}"); + buf +} + +fn format_arm(block: &ast::Block) -> String { + match extract_expression(block) { + None => block.syntax().text().to_string(), + Some(e) => format!("{},", e.syntax().text()), + } +} + +fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> { + let expr = block.expr()?; + let non_trivial_children = block.syntax().children().filter(|it| { + !(it == &expr.syntax() + || it.kind() == L_CURLY + || it.kind() == R_CURLY + || it.kind() == WHITESPACE) + }); + if non_trivial_children.count() > 0 { + return None; + } + Some(expr) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::assists::check_assist; + + #[test] + fn test_replace_if_let_with_match_unwraps_simple_expressions() { + check_assist( + replace_if_let_with_match, + " +impl VariantData { + pub fn is_struct(&self) -> bool { + if <|>let VariantData::Struct(..) = *self { + true + } else { + false + } + } +} ", + " +impl VariantData { + pub fn is_struct(&self) -> bool { + <|>match *self { + VariantData::Struct(..) => true, + _ => false, + } + } +} ", + ) + } +} diff --git a/crates/ra_editor/src/test_utils.rs b/crates/ra_editor/src/test_utils.rs index bf40c92c0..dc2470aa3 100644 --- a/crates/ra_editor/src/test_utils.rs +++ b/crates/ra_editor/src/test_utils.rs @@ -13,7 +13,10 @@ pub fn check_action Option>( let result = f(&file, before_cursor_pos).expect("code action is not applicable"); let actual = result.edit.apply(&before); let actual_cursor_pos = match result.cursor_position { - None => result.edit.apply_to_offset(before_cursor_pos).unwrap(), + None => result + .edit + .apply_to_offset(before_cursor_pos) + .expect("cursor position is affected by the edit"), Some(off) => off, }; let actual = add_cursor(&actual, actual_cursor_pos); diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 96879ae5a..d25b5642b 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -225,7 +225,7 @@ impl Whitespace { } pub fn has_newlines(&self) -> bool { - self.count_newlines_lazy().count() > 0 + self.text().contains('\n') } } -- cgit v1.2.3