Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22222 from A4-Tacks/postfix-if-else-in-value
feat: support if-else in value on postfix completions
| -rw-r--r-- | crates/ide-completion/src/completions/postfix.rs | 87 | ||||
| -rw-r--r-- | crates/ide-completion/src/context/analysis.rs | 23 |
2 files changed, 86 insertions, 24 deletions
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs index 70d4a46e1f..8a754a6b27 100644 --- a/crates/ide-completion/src/completions/postfix.rs +++ b/crates/ide-completion/src/completions/postfix.rs @@ -150,6 +150,7 @@ pub(crate) fn complete_postfix( let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty); let is_in_cond = is_in_condition(&dot_receiver_including_refs); + let is_in_value = is_in_value(&dot_receiver_including_refs); if let Some(parent) = dot_receiver_including_refs.syntax().parent() { let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema); match &try_enum { @@ -244,12 +245,14 @@ pub(crate) fn complete_postfix( } if let Some(try_enum) = &try_enum { let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema); + let if_then_snip = + if is_in_value { "{\n $2\n} else {\n $0\n}" } else { "{\n $0\n}" }; match try_enum { TryEnum::Result => { postfix_snippet( "ifl", "if let Ok {}", - format!("if let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"), + format!("if let Ok({placeholder}) = {receiver_text} {if_then_snip}"), ) .add_to(acc, ctx.db); @@ -271,7 +274,7 @@ pub(crate) fn complete_postfix( postfix_snippet( "ifl", "if let Some {}", - format!("if let Some({placeholder}) = {receiver_text} {{\n $0\n}}"), + format!("if let Some({placeholder}) = {receiver_text} {if_then_snip}"), ) .add_to(acc, ctx.db); @@ -293,7 +296,9 @@ pub(crate) fn complete_postfix( } } } else if receiver_ty.is_bool() || receiver_ty.is_unknown() { - postfix_snippet("if", "if expr {}", format!("if {receiver_text} {{\n $0\n}}")) + let if_then_snip = + if is_in_value { "{\n $1\n} else {\n $0\n}" } else { "{\n $0\n}" }; + postfix_snippet("if", "if expr {}", format!("if {receiver_text} {if_then_snip}")) .add_to(acc, ctx.db); postfix_snippet( "while", @@ -563,6 +568,22 @@ pub(crate) fn is_in_condition(it: &ast::Expr) -> bool { .unwrap_or(false) } +pub(crate) fn is_in_value(it: &ast::Expr) -> bool { + let Some(node) = it.syntax().parent() else { return false }; + let kind = node.kind(); + ast::LetStmt::can_cast(kind) + || ast::ArgList::can_cast(kind) + || ast::ArrayExpr::can_cast(kind) + || ast::ParenExpr::can_cast(kind) + || ast::BreakExpr::can_cast(kind) + || ast::ReturnExpr::can_cast(kind) + || ast::PrefixExpr::can_cast(kind) + || ast::FormatArgsArg::can_cast(kind) + || ast::RecordExprField::can_cast(kind) + || ast::BinExpr::cast(node.clone()).is_some_and(|expr| expr.rhs().as_ref() == Some(it)) + || ast::IndexExpr::cast(node).is_some_and(|expr| expr.index().as_ref() == Some(it)) +} + #[cfg(test)] mod tests { use expect_test::expect; @@ -1178,6 +1199,66 @@ fn main() { } #[test] + fn postfix_completion_if_else_in_value() { + check_edit( + "if", + r#" +fn main() { + let s = cond.is_some().$0; +} +"#, + r#" +fn main() { + let s = if cond.is_some() { + $1 +} else { + $0 +}; +} +"#, + ); + + check_edit( + "ifl", + r#" +//- minicore: option +fn main() { + let cond = Some("x"); + let s = cond.$0; +} +"#, + r#" +fn main() { + let cond = Some("x"); + let s = if let Some(${1:cond}) = cond { + $2 +} else { + $0 +}; +} +"#, + ); + + check_edit( + "if", + r#" +fn main() { + 2 + true.$0; +} +"#, + r#" +fn main() { + 2 + if true { + $1 +} else { + $0 +}; +} +"#, + ); + } + + #[test] fn postfix_completion_for_unsafe() { postfix_completion_for_block("unsafe"); } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index c8068d6fc4..faeb97f93f 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -22,7 +22,7 @@ use syntax::{ }; use crate::{ - completions::postfix::is_in_condition, + completions::postfix::{is_in_condition, is_in_value}, context::{ AttrCtx, BreakableKind, COMPLETION_MARKER, CompletionAnalysis, DotAccess, DotAccessExprCtx, DotAccessKind, ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, @@ -1097,25 +1097,6 @@ fn classify_name_ref<'db>( .and_then(|next| next.first_token()) .is_some_and(|token| token.kind() == SyntaxKind::ELSE_KW) }; - let is_in_value = |it: &SyntaxNode| { - let Some(node) = it.parent() else { return false }; - let kind = node.kind(); - ast::LetStmt::can_cast(kind) - || ast::ArgList::can_cast(kind) - || ast::ArrayExpr::can_cast(kind) - || ast::ParenExpr::can_cast(kind) - || ast::BreakExpr::can_cast(kind) - || ast::ReturnExpr::can_cast(kind) - || ast::PrefixExpr::can_cast(kind) - || ast::FormatArgsArg::can_cast(kind) - || ast::RecordExprField::can_cast(kind) - || ast::BinExpr::cast(node.clone()) - .and_then(|expr| expr.rhs()) - .is_some_and(|expr| expr.syntax() == it) - || ast::IndexExpr::cast(node) - .and_then(|expr| expr.index()) - .is_some_and(|expr| expr.syntax() == it) - }; // We do not want to generate path completions when we are sandwiched between an item decl signature and its body. // ex. trait Foo $0 {} @@ -1429,7 +1410,7 @@ fn classify_name_ref<'db>( .find_map(ast::LetStmt::cast) .is_some_and(|it| it.semicolon_token().is_none()) || after_incomplete_let && incomplete_expr_stmt.unwrap_or(true) && !before_else_kw; - let in_value = is_in_value(it); + let in_value = is_in_value(&expr); let impl_ = fetch_immediate_impl_or_trait(sema, original_file, expr.syntax()) .and_then(Either::left); |