Add macro segment completion

Example
---
```rust
macro_rules! foo {
    ($($x:$0)*) => ();
}
```

**Completion items**:

```text
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
```
This commit is contained in:
A4-Tacks 2025-09-25 13:05:45 +08:00
parent 075b3beb5d
commit d2cc89ffc7
No known key found for this signature in database
GPG Key ID: DBD861323040663B
7 changed files with 316 additions and 4 deletions

View File

@ -13,6 +13,7 @@ pub(crate) mod format_string;
pub(crate) mod item_list;
pub(crate) mod keyword;
pub(crate) mod lifetime;
pub(crate) mod macro_def;
pub(crate) mod mod_;
pub(crate) mod pattern;
pub(crate) mod postfix;

View File

@ -0,0 +1,31 @@
//! Completion for macro meta-variable segments
use ide_db::SymbolKind;
use crate::{CompletionItem, Completions, context::CompletionContext};
pub(crate) fn complete_macro_segment(acc: &mut Completions, ctx: &CompletionContext<'_>) {
for &label in MACRO_SEGMENTS {
let item =
CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label, ctx.edition);
item.add_to(acc, ctx.db);
}
}
const MACRO_SEGMENTS: &[&str] = &[
"ident",
"block",
"stmt",
"expr",
"pat",
"ty",
"lifetime",
"literal",
"path",
"meta",
"tt",
"item",
"vis",
"expr_2021",
"pat_param",
];

View File

@ -13,7 +13,7 @@ use hir::{
};
use ide_db::{
FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs,
helpers::is_editable_crate,
helpers::is_editable_crate, syntax_helpers::node_ext::is_in_macro_matcher,
};
use itertools::Either;
use syntax::{
@ -389,6 +389,7 @@ pub(crate) enum CompletionAnalysis<'db> {
fake_attribute_under_caret: Option<ast::Attr>,
extern_crate: Option<ast::ExternCrate>,
},
MacroSegment,
}
/// Information about the field or method access we are completing.
@ -729,7 +730,7 @@ impl<'db> CompletionContext<'db> {
let prev_token = original_token.prev_token()?;
// only has a single colon
if prev_token.kind() != T![:] {
if prev_token.kind() != T![:] && !is_in_macro_matcher(&original_token) {
return None;
}

View File

@ -5,7 +5,7 @@ use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant};
use ide_db::{
RootDatabase, active_parameter::ActiveParameter, syntax_helpers::node_ext::find_loops,
};
use itertools::Either;
use itertools::{Either, Itertools};
use stdx::always;
use syntax::{
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
@ -510,6 +510,21 @@ fn analyze<'db>(
colon_prefix,
extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
}
} else if p.kind() == SyntaxKind::TOKEN_TREE
&& p.ancestors().any(|it| ast::Macro::can_cast(it.kind()))
{
if let Some([_ident, colon, _name, dollar]) = fake_ident_token
.siblings_with_tokens(Direction::Prev)
.filter(|it| !it.kind().is_trivia())
.take(4)
.collect_array()
&& dollar.kind() == T![$]
&& colon.kind() == T![:]
{
CompletionAnalysis::MacroSegment
} else {
return None;
}
} else {
return None;
}

View File

@ -263,6 +263,9 @@ pub fn completions(
extern_crate.as_ref(),
);
}
CompletionAnalysis::MacroSegment => {
completions::macro_def::complete_macro_segment(acc, ctx);
}
CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
}
}

View File

@ -481,6 +481,226 @@ fn foo() {}
);
}
#[test]
fn completes_macro_segment() {
check(
r#"
macro_rules! foo {
($x:e$0) => ();
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check(
r#"
macro_rules! foo {
($x:$0) => ();
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check(
r#"
macro_rules! foo {
($($x:$0)*) => ();
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check(
r#"
macro foo {
($($x:$0)*) => ();
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check(
r#"
macro foo($($x:$0)*) {
xxx;
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check_edit(
"expr",
r#"
macro foo($($x:$0)*) {
xxx;
}
"#,
r#"
macro foo($($x:expr)*) {
xxx;
}
"#,
);
check(
r#"
macro_rules! foo {
($fn : e$0) => ();
}
"#,
expect![[r#"
ba block
ba expr
ba expr_2021
ba ident
ba item
ba lifetime
ba literal
ba meta
ba pat
ba pat_param
ba path
ba stmt
ba tt
ba ty
ba vis
"#]],
);
check_edit(
"expr",
r#"
macro foo($($x:ex$0)*) {
xxx;
}
"#,
r#"
macro foo($($x:expr)*) {
xxx;
}
"#,
);
}
#[test]
fn completes_in_macro_body() {
check(
r#"
macro_rules! foo {
($x:expr) => ($y:$0);
}
"#,
expect![[r#""#]],
);
check(
r#"
macro_rules! foo {
($x:expr) => ({$y:$0});
}
"#,
expect![[r#""#]],
);
check(
r#"
macro foo {
($x:expr) => ($y:$0);
}
"#,
expect![[r#""#]],
);
check(
r#"
macro foo($x:expr) {
$y:$0
}
"#,
expect![[r#""#]],
);
}
#[test]
fn function_mod_share_name() {
check_no_kw(
@ -942,6 +1162,15 @@ fn foo { crate::$0 }
check_with_trigger_character(
r#"
fn foo { crate:$0 }
"#,
Some(':'),
expect![""],
);
check_with_trigger_character(
r#"
macro_rules! bar { ($($x:tt)*) => ($($x)*); }
fn foo { bar!(crate:$0) }
"#,
Some(':'),
expect![""],

View File

@ -5,8 +5,10 @@ use itertools::Itertools;
use parser::T;
use span::Edition;
use syntax::{
AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
AstNode, AstToken, Direction, Preorder, RustLanguage, SyntaxToken, WalkEvent,
algo::non_trivia_sibling,
ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
syntax_editor::Element,
};
pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
@ -542,3 +544,33 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
Some(macro_call)
}
pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool {
let Some(macro_def) = token.parent_ancestors().find_map(ast::Macro::cast) else {
return false;
};
let range = token.text_range();
let Some(body) = (match macro_def {
ast::Macro::MacroDef(macro_def) => {
if let Some(args) = macro_def.args() {
return args.syntax().text_range().contains_range(range);
}
macro_def.body()
}
ast::Macro::MacroRules(macro_rules) => macro_rules.token_tree(),
}) else {
return false;
};
if !body.syntax().text_range().contains_range(range) {
return false;
}
body.token_trees_and_tokens().filter_map(|tt| tt.into_node()).any(|tt| {
let Some(next) = non_trivia_sibling(tt.syntax().syntax_element(), Direction::Next) else {
return false;
};
let Some(next_next) = next.next_sibling_or_token() else { return false };
next.kind() == T![=]
&& next_next.kind() == T![>]
&& tt.syntax().text_range().contains_range(range)
})
}