diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index c2c50b16de76..72262883cc09 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -76,10 +76,22 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) - if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) { t.parent().and_then(ast::ExprStmt::cast)?.syntax().clone() } else { - let expr = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset()) + let node = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset()) .next() - .and_then(ast::Expr::cast)?; - expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone() + .filter(|node| { + // Don’t offer the assist if we encounter a node that is not one of the kinds + // we want. This prevents the assist from appearing when, while there is an + // enclosing expression, it is unlikely to be relevant. + let kind = node.kind(); + ast::Expr::can_cast(kind) + || kind == SyntaxKind::RECORD_EXPR_FIELD + || (kind == SyntaxKind::NAME_REF + && node + .parent() + .is_some_and(|p| p.kind() == SyntaxKind::RECORD_EXPR_FIELD)) + })?; + + node.ancestors().find_map(valid_target_expr(ctx))?.syntax().clone() } } else { match ctx.covering_element() { @@ -367,6 +379,11 @@ fn valid_target_expr(ctx: &AssistContext<'_, '_>) -> impl Fn(SyntaxNode) -> Opti let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?; like_const_value(ctx, path_resolution).then_some(path_expr.into()) } + SyntaxKind::RECORD_EXPR_FIELD => { + // If we are on `k` in `Struct { k: v }`, then extract `v`. + let record_field = ast::RecordExprField::cast(node)?; + record_field.expr() + } _ => ast::Expr::cast(node), } } @@ -952,6 +969,16 @@ fn foo() { check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#); } + #[test] + fn dont_extract_in_pattern_with_selection() { + check_assist_not_applicable(extract_variable, r#"fn foo() { [].map(|$0bar$0| bar + 1) } "#); + } + + #[test] + fn dont_extract_in_pattern_without_selection() { + check_assist_not_applicable(extract_variable, r#"fn foo() { [].map(|b$0ar| bar + 1) } "#); + } + #[test] fn extract_var_expr_stmt() { cov_mark::check!(test_extract_var_expr_stmt); @@ -1586,6 +1613,87 @@ struct S { foo: i32 } +fn main() { + let $0foo = 1 + 1; + S { foo } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { $0foo: 1 + 1,$0 } +} +"#, + r#" +struct S { + foo: i32 +} + +fn main() { + let $0foo = 1 + 1; + S { foo, } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field_name() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { f$0oo: 1 + 1 } +} +"#, + r#" +struct S { + foo: i32 +} + +fn main() { + let $0foo = 1 + 1; + S { foo } +} +"#, + "Extract into variable", + ) + } + + #[test] + fn extract_var_from_record_field_colon() { + check_assist_by_label( + extract_variable, + r#" +struct S { + foo: i32 +} + +fn main() { + S { foo $0: 1 + 1 } +} +"#, + r#" +struct S { + foo: i32 +} + fn main() { let $0foo = 1 + 1; S { foo }