use crate::{AssistCtx, Assist};
-pub(crate) fn introduce_variable<'a>(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+pub(crate) fn introduce_variable(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
let node = ctx.covering_node();
if !valid_covering_node(node) {
return None;
if indent.kind() != WHITESPACE {
return None;
}
- ctx.build("introduce variable", move |edit| {
+ ctx.add_action("introduce variable", move |edit| {
let mut buf = String::new();
let cursor_offset = if wrap_in_block {
edit.replace(expr.syntax().range(), buf);
} else {
buf.push_str(";");
- indent.text().push_to(&mut buf);
+
+ // We want to maintain the indent level,
+ // but we do not want to duplicate possible
+ // extra newlines in the indent block
+ for chunk in indent.text().chunks() {
+ if chunk.starts_with("\r\n") {
+ buf.push_str("\r\n");
+ buf.push_str(chunk.trim_start_matches("\r\n"));
+ } else if chunk.starts_with("\n") {
+ buf.push_str("\n");
+ buf.push_str(chunk.trim_start_matches("\n"));
+ } else {
+ buf.push_str(chunk);
+ }
+ }
+
+ edit.target(expr.syntax().range());
edit.replace(expr.syntax().range(), "var_name".to_string());
edit.insert(anchor_stmt.range().start(), buf);
if wrap_in_block {
}
}
edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
- })
+ });
+
+ ctx.build()
}
fn valid_covering_node(node: &SyntaxNode) -> bool {
node.kind() != COMMENT
}
-/// Check wether the node is a valid expression which can be extracted to a variable.
+/// Check whether the node is a valid expression which can be extracted to a variable.
/// In general that's true for any expression, but in some cases that would produce invalid code.
fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
- return match node.kind() {
+ match node.kind() {
PATH_EXPR => None,
BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
_ => ast::Expr::cast(node),
- };
+ }
}
/// Returns the syntax node which will follow the freshly introduced var
/// and a boolean indicating whether we have to wrap it within a { } block
/// to produce correct code.
/// It can be a statement, the last in a block expression or a wanna be block
-/// expression like a lamba or match arm.
+/// expression like a lambda or match arm.
fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
expr.syntax().ancestors().find_map(|node| {
if ast::Stmt::cast(node).is_some() {
return Some((node, false));
}
- if let Some(expr) = node
- .parent()
- .and_then(ast::Block::cast)
- .and_then(|it| it.expr())
- {
+ if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
if expr.syntax() == node {
return Some((node, false));
}
#[cfg(test)]
mod tests {
use super::*;
- use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
+ use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target};
#[test]
fn test_introduce_var_simple() {
",
"
fn foo() -> u32 {
+ let <|>var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+ }
+
+ #[test]
+ fn test_introduce_var_does_not_add_extra_whitespace() {
+ check_assist(
+ introduce_variable,
+ "
+fn foo() -> u32 {
+
+
+ r<|>eturn 2 + 2;
+}
+",
+ "
+fn foo() -> u32 {
+
+
+ let <|>var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+
+ check_assist(
+ introduce_variable,
+ "
+fn foo() -> u32 {
+
+ r<|>eturn 2 + 2;
+}
+",
+ "
+fn foo() -> u32 {
+
+ let <|>var_name = 2 + 2;
+ return var_name;
+}
+",
+ );
+
+ check_assist(
+ introduce_variable,
+ "
+fn foo() -> u32 {
+ let foo = 1;
+
+ // bar
+
+
+ r<|>eturn 2 + 2;
+}
+",
+ "
+fn foo() -> u32 {
+ let foo = 1;
+
+ // bar
+
+
let <|>var_name = 2 + 2;
return var_name;
}
",
);
}
+
+ // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
+ #[test]
+ fn introduce_var_target() {
+ check_assist_target(
+ introduce_variable,
+ "
+fn foo() -> u32 {
+ r<|>eturn 2 + 2;
+}
+",
+ "2 + 2",
+ );
+
+ check_assist_range_target(
+ introduce_variable,
+ "
+fn main() {
+ let x = true;
+ let tuple = match x {
+ true => (<|>2 + 2<|>, true)
+ _ => (0, false)
+ };
+}
+",
+ "2 + 2",
+ );
+ }
}