mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #454
454: If-let to match r=matklad a=matklad Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
d70ccebcf1
5 changed files with 129 additions and 14 deletions
|
@ -9,12 +9,15 @@ mod add_impl;
|
||||||
mod introduce_variable;
|
mod introduce_variable;
|
||||||
mod change_visibility;
|
mod change_visibility;
|
||||||
mod split_import;
|
mod split_import;
|
||||||
|
mod replace_if_let_with_match;
|
||||||
|
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
|
Direction, SyntaxNode, TextUnit, TextRange, SourceFile, AstNode,
|
||||||
algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset},
|
algo::{find_leaf_at_offset, find_covering_node, LeafAtOffset},
|
||||||
|
ast::{self, AstToken},
|
||||||
};
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::find_node_at_offset;
|
use crate::find_node_at_offset;
|
||||||
|
|
||||||
|
@ -25,6 +28,7 @@ pub use self::{
|
||||||
introduce_variable::introduce_variable,
|
introduce_variable::introduce_variable,
|
||||||
change_visibility::change_visibility,
|
change_visibility::change_visibility,
|
||||||
split_import::split_import,
|
split_import::split_import,
|
||||||
|
replace_if_let_with_match::replace_if_let_with_match,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Return all the assists applicable at the given position.
|
/// Return all the assists applicable at the given position.
|
||||||
|
@ -37,6 +41,7 @@ pub fn assists(file: &SourceFile, range: TextRange) -> Vec<LocalEdit> {
|
||||||
introduce_variable,
|
introduce_variable,
|
||||||
change_visibility,
|
change_visibility,
|
||||||
split_import,
|
split_import,
|
||||||
|
replace_if_let_with_match,
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&assist| ctx.clone().apply(assist))
|
.filter_map(|&assist| ctx.clone().apply(assist))
|
||||||
|
@ -160,6 +165,13 @@ impl AssistBuilder {
|
||||||
fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
|
||||||
self.edit.replace(range, replace_with.into())
|
self.edit.replace(range, replace_with.into())
|
||||||
}
|
}
|
||||||
|
fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) {
|
||||||
|
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)]
|
#[allow(unused)]
|
||||||
fn delete(&mut self, range: TextRange) {
|
fn delete(&mut self, range: TextRange) {
|
||||||
self.edit.delete(range)
|
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)]
|
#[cfg(test)]
|
||||||
fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
|
fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
|
||||||
crate::test_utils::check_action(before, after, |file, off| {
|
crate::test_utils::check_action(before, after, |file, off| {
|
||||||
|
|
92
crates/ra_editor/src/assists/replace_if_let_with_match.rs
Normal file
92
crates/ra_editor/src/assists/replace_if_let_with_match.rs
Normal file
|
@ -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<Assist> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,10 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
|
||||||
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
|
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
|
||||||
let actual = result.edit.apply(&before);
|
let actual = result.edit.apply(&before);
|
||||||
let actual_cursor_pos = match result.cursor_position {
|
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,
|
Some(off) => off,
|
||||||
};
|
};
|
||||||
let actual = add_cursor(&actual, actual_cursor_pos);
|
let actual = add_cursor(&actual, actual_cursor_pos);
|
||||||
|
|
|
@ -171,24 +171,21 @@ impl VariantData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_struct(&self) -> bool {
|
pub fn is_struct(&self) -> bool {
|
||||||
if let VariantData::Struct(..) = *self {
|
match self {
|
||||||
true
|
VariantData::Struct(..) => true,
|
||||||
} else {
|
_ => false,
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_tuple(&self) -> bool {
|
pub fn is_tuple(&self) -> bool {
|
||||||
if let VariantData::Tuple(..) = *self {
|
match self {
|
||||||
true
|
VariantData::Tuple(..) => true,
|
||||||
} else {
|
_ => false,
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn is_unit(&self) -> bool {
|
pub fn is_unit(&self) -> bool {
|
||||||
if let VariantData::Unit = *self {
|
match self {
|
||||||
true
|
VariantData::Unit => true,
|
||||||
} else {
|
_ => false,
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ impl Whitespace {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_newlines(&self) -> bool {
|
pub fn has_newlines(&self) -> bool {
|
||||||
self.count_newlines_lazy().count() > 0
|
self.text().contains('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue