From 8e58663c79792fb6909ba47193a007973bdaa0f1 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 27 Nov 2025 13:40:28 +0800 Subject: [PATCH] Fix complete after `extern`, add `crate` completion Example --- ```rust extern "C" $0 ``` **Before this PR** Can't be completion **After this PR** ```text kw async kw const kw enum kw fn kw impl kw impl for kw mod kw pub kw pub(crate) kw pub(super) kw static kw struct kw trait kw type kw union kw unsafe kw use ``` --- ```rust extern $0 ``` **Before this PR** Can't be completion **After this PR** ```rust extern crate $0; ``` --- .../src/completions/item_list.rs | 10 +- crates/ide-completion/src/context.rs | 2 + crates/ide-completion/src/context/analysis.rs | 1 + crates/ide-completion/src/tests/item_list.rs | 99 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index 6c001bd16bfe..39048e44001e 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -87,6 +87,9 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option let in_block = kind.is_none(); let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); + let no_abi_qualifiers = ctx.qualifier_ctx.abi_node.is_none(); + let has_extern_kw = + ctx.qualifier_ctx.abi_node.as_ref().is_some_and(|it| it.string_token().is_none()); let has_unsafe_kw = ctx.qualifier_ctx.unsafe_tok.is_some(); let has_async_kw = ctx.qualifier_ctx.async_tok.is_some(); let has_safe_kw = ctx.qualifier_ctx.safe_tok.is_some(); @@ -118,7 +121,7 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option } } - if !has_async_kw && no_vis_qualifiers && in_item_list { + if !has_async_kw && no_vis_qualifiers && no_abi_qualifiers && in_item_list { add_keyword("extern", "extern $0"); } @@ -159,11 +162,14 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option add_keyword("static", "static $1: $2;"); } else { if !in_inherent_impl { - if !in_trait { + if !in_trait && no_abi_qualifiers { add_keyword("extern", "extern $0"); } add_keyword("type", "type $0"); } + if has_extern_kw { + add_keyword("crate", "crate $0;"); + } add_keyword("fn", "fn $1($2) {\n $0\n}"); add_keyword("unsafe", "unsafe $0"); diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 31a9a74aa890..23318e1d1991 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -53,6 +53,7 @@ pub(crate) struct QualifierCtx { pub(crate) unsafe_tok: Option, pub(crate) safe_tok: Option, pub(crate) vis_node: Option, + pub(crate) abi_node: Option, } impl QualifierCtx { @@ -61,6 +62,7 @@ impl QualifierCtx { && self.unsafe_tok.is_none() && self.safe_tok.is_none() && self.vis_node.is_none() + && self.abi_node.is_none() } } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index e84c0653364d..da9464f48a12 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1628,6 +1628,7 @@ fn classify_name_ref<'db>( } } qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast); + qualifier_ctx.abi_node = error_node.children().find_map(ast::Abi::cast); } if let PathKind::Item { .. } = path_ctx.kind diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs index ac32649d4ffb..9afc8b49d6bc 100644 --- a/crates/ide-completion/src/tests/item_list.rs +++ b/crates/ide-completion/src/tests/item_list.rs @@ -176,6 +176,105 @@ fn after_visibility_unsafe() { ); } +#[test] +fn after_abi() { + check_with_base_items( + r#"extern "C" $0"#, + expect![[r#" + kw async + kw const + kw enum + kw fn + kw impl + kw impl for + kw mod + kw pub + kw pub(crate) + kw pub(super) + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); + check_with_base_items( + r#"extern "C" f$0"#, + expect![[r#" + kw async + kw const + kw enum + kw fn + kw impl + kw impl for + kw mod + kw pub + kw pub(crate) + kw pub(super) + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); +} + +#[test] +fn after_extern_token() { + check_with_base_items( + r#"extern $0"#, + expect![[r#" + kw async + kw const + kw crate + kw enum + kw fn + kw impl + kw impl for + kw mod + kw pub + kw pub(crate) + kw pub(super) + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); + check_with_base_items( + r#"extern cr$0"#, + expect![[r#" + kw async + kw const + kw crate + kw enum + kw fn + kw impl + kw impl for + kw mod + kw pub + kw pub(crate) + kw pub(super) + kw static + kw struct + kw trait + kw type + kw union + kw unsafe + kw use + "#]], + ); + check_edit("crate", "extern $0", "extern crate $0;"); +} + #[test] fn in_impl_assoc_item_list() { check_with_base_items(