Skip to content

Commit c64e6bb

Browse files
committed
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 ```
1 parent f6cf303 commit c64e6bb

File tree

7 files changed

+316
-4
lines changed

7 files changed

+316
-4
lines changed

crates/ide-completion/src/completions.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub(crate) mod format_string;
1313
pub(crate) mod item_list;
1414
pub(crate) mod keyword;
1515
pub(crate) mod lifetime;
16+
pub(crate) mod macro_def;
1617
pub(crate) mod mod_;
1718
pub(crate) mod pattern;
1819
pub(crate) mod postfix;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//! Completion for macro meta-variable segements
2+
3+
use ide_db::SymbolKind;
4+
5+
use crate::{CompletionItem, Completions, context::CompletionContext};
6+
7+
pub(crate) fn complete_macro_segment(acc: &mut Completions, ctx: &CompletionContext<'_>) {
8+
for &label in MACRO_SEGMENTS {
9+
let item =
10+
CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label, ctx.edition);
11+
item.add_to(acc, ctx.db);
12+
}
13+
}
14+
15+
const MACRO_SEGMENTS: &[&str] = &[
16+
"ident",
17+
"block",
18+
"stmt",
19+
"expr",
20+
"pat",
21+
"ty",
22+
"lifetime",
23+
"literal",
24+
"path",
25+
"meta",
26+
"tt",
27+
"item",
28+
"vis",
29+
"expr_2021",
30+
"pat_param",
31+
];

crates/ide-completion/src/context.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use hir::{
1313
};
1414
use ide_db::{
1515
FilePosition, FxHashMap, FxHashSet, RootDatabase, famous_defs::FamousDefs,
16-
helpers::is_editable_crate,
16+
helpers::is_editable_crate, syntax_helpers::node_ext::is_in_macro_matcher,
1717
};
1818
use syntax::{
1919
AstNode, Edition, SmolStr,
@@ -385,6 +385,7 @@ pub(crate) enum CompletionAnalysis<'db> {
385385
fake_attribute_under_caret: Option<ast::Attr>,
386386
extern_crate: Option<ast::ExternCrate>,
387387
},
388+
MacroSegment,
388389
}
389390

390391
/// Information about the field or method access we are completing.
@@ -729,7 +730,7 @@ impl<'db> CompletionContext<'db> {
729730
let prev_token = original_token.prev_token()?;
730731

731732
// only has a single colon
732-
if prev_token.kind() != T![:] {
733+
if prev_token.kind() != T![:] && !is_in_macro_matcher(&original_token) {
733734
return None;
734735
}
735736

crates/ide-completion/src/context/analysis.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::iter;
33

44
use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant};
55
use ide_db::{RootDatabase, active_parameter::ActiveParameter};
6-
use itertools::Either;
6+
use itertools::{Either, Itertools};
77
use stdx::always;
88
use syntax::{
99
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken,
@@ -505,6 +505,21 @@ fn analyze<'db>(
505505
colon_prefix,
506506
extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
507507
}
508+
} else if p.kind() == SyntaxKind::TOKEN_TREE
509+
&& p.ancestors().any(|it| ast::Macro::can_cast(it.kind()))
510+
{
511+
if let Some([_ident, colon, _name, dollar]) = fake_ident_token
512+
.siblings_with_tokens(Direction::Prev)
513+
.filter(|it| !it.kind().is_trivia())
514+
.take(4)
515+
.collect_array()
516+
&& dollar.kind() == T![$]
517+
&& colon.kind() == T![:]
518+
{
519+
CompletionAnalysis::MacroSegment
520+
} else {
521+
return None;
522+
}
508523
} else {
509524
return None;
510525
}

crates/ide-completion/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ pub fn completions(
255255
extern_crate.as_ref(),
256256
);
257257
}
258+
CompletionAnalysis::MacroSegment => {
259+
completions::macro_def::complete_macro_segment(acc, ctx);
260+
}
258261
CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
259262
}
260263
}

crates/ide-completion/src/tests/special.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,226 @@ fn foo() {}
481481
);
482482
}
483483

484+
#[test]
485+
fn completes_macro_segment() {
486+
check(
487+
r#"
488+
macro_rules! foo {
489+
($x:e$0) => ();
490+
}
491+
"#,
492+
expect![[r#"
493+
ba block
494+
ba expr
495+
ba expr_2021
496+
ba ident
497+
ba item
498+
ba lifetime
499+
ba literal
500+
ba meta
501+
ba pat
502+
ba pat_param
503+
ba path
504+
ba stmt
505+
ba tt
506+
ba ty
507+
ba vis
508+
"#]],
509+
);
510+
511+
check(
512+
r#"
513+
macro_rules! foo {
514+
($x:$0) => ();
515+
}
516+
"#,
517+
expect![[r#"
518+
ba block
519+
ba expr
520+
ba expr_2021
521+
ba ident
522+
ba item
523+
ba lifetime
524+
ba literal
525+
ba meta
526+
ba pat
527+
ba pat_param
528+
ba path
529+
ba stmt
530+
ba tt
531+
ba ty
532+
ba vis
533+
"#]],
534+
);
535+
536+
check(
537+
r#"
538+
macro_rules! foo {
539+
($($x:$0)*) => ();
540+
}
541+
"#,
542+
expect![[r#"
543+
ba block
544+
ba expr
545+
ba expr_2021
546+
ba ident
547+
ba item
548+
ba lifetime
549+
ba literal
550+
ba meta
551+
ba pat
552+
ba pat_param
553+
ba path
554+
ba stmt
555+
ba tt
556+
ba ty
557+
ba vis
558+
"#]],
559+
);
560+
561+
check(
562+
r#"
563+
macro foo {
564+
($($x:$0)*) => ();
565+
}
566+
"#,
567+
expect![[r#"
568+
ba block
569+
ba expr
570+
ba expr_2021
571+
ba ident
572+
ba item
573+
ba lifetime
574+
ba literal
575+
ba meta
576+
ba pat
577+
ba pat_param
578+
ba path
579+
ba stmt
580+
ba tt
581+
ba ty
582+
ba vis
583+
"#]],
584+
);
585+
586+
check(
587+
r#"
588+
macro foo($($x:$0)*) {
589+
xxx;
590+
}
591+
"#,
592+
expect![[r#"
593+
ba block
594+
ba expr
595+
ba expr_2021
596+
ba ident
597+
ba item
598+
ba lifetime
599+
ba literal
600+
ba meta
601+
ba pat
602+
ba pat_param
603+
ba path
604+
ba stmt
605+
ba tt
606+
ba ty
607+
ba vis
608+
"#]],
609+
);
610+
611+
check_edit(
612+
"expr",
613+
r#"
614+
macro foo($($x:$0)*) {
615+
xxx;
616+
}
617+
"#,
618+
r#"
619+
macro foo($($x:expr)*) {
620+
xxx;
621+
}
622+
"#,
623+
);
624+
625+
check(
626+
r#"
627+
macro_rules! foo {
628+
($fn : e$0) => ();
629+
}
630+
"#,
631+
expect![[r#"
632+
ba block
633+
ba expr
634+
ba expr_2021
635+
ba ident
636+
ba item
637+
ba lifetime
638+
ba literal
639+
ba meta
640+
ba pat
641+
ba pat_param
642+
ba path
643+
ba stmt
644+
ba tt
645+
ba ty
646+
ba vis
647+
"#]],
648+
);
649+
650+
check_edit(
651+
"expr",
652+
r#"
653+
macro foo($($x:ex$0)*) {
654+
xxx;
655+
}
656+
"#,
657+
r#"
658+
macro foo($($x:expr)*) {
659+
xxx;
660+
}
661+
"#,
662+
);
663+
}
664+
665+
#[test]
666+
fn completes_in_macro_body() {
667+
check(
668+
r#"
669+
macro_rules! foo {
670+
($x:expr) => ($y:$0);
671+
}
672+
"#,
673+
expect![[r#""#]],
674+
);
675+
676+
check(
677+
r#"
678+
macro_rules! foo {
679+
($x:expr) => ({$y:$0});
680+
}
681+
"#,
682+
expect![[r#""#]],
683+
);
684+
685+
check(
686+
r#"
687+
macro foo {
688+
($x:expr) => ($y:$0);
689+
}
690+
"#,
691+
expect![[r#""#]],
692+
);
693+
694+
check(
695+
r#"
696+
macro foo($x:expr) {
697+
$y:$0
698+
}
699+
"#,
700+
expect![[r#""#]],
701+
);
702+
}
703+
484704
#[test]
485705
fn function_mod_share_name() {
486706
check_no_kw(
@@ -942,6 +1162,15 @@ fn foo { crate::$0 }
9421162
check_with_trigger_character(
9431163
r#"
9441164
fn foo { crate:$0 }
1165+
"#,
1166+
Some(':'),
1167+
expect![""],
1168+
);
1169+
1170+
check_with_trigger_character(
1171+
r#"
1172+
macro_rules! bar { ($($x:tt)*) => ($($x)*); }
1173+
fn foo { bar!(crate:$0) }
9451174
"#,
9461175
Some(':'),
9471176
expect![""],

crates/ide-db/src/syntax_helpers/node_ext.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use itertools::Itertools;
55
use parser::T;
66
use span::Edition;
77
use syntax::{
8-
AstNode, AstToken, Preorder, RustLanguage, WalkEvent,
8+
AstNode, AstToken, Direction, Preorder, RustLanguage, SyntaxToken, WalkEvent,
9+
algo::non_trivia_sibling,
910
ast::{self, HasLoopBody, MacroCall, PathSegmentKind, VisibilityKind},
11+
syntax_editor::Element,
1012
};
1113

1214
pub fn expr_as_name_ref(expr: &ast::Expr) -> Option<ast::NameRef> {
@@ -503,3 +505,33 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option<MacroCall> {
503505
let macro_call = string.syntax().parent_ancestors().find_map(ast::MacroCall::cast)?;
504506
Some(macro_call)
505507
}
508+
509+
pub fn is_in_macro_matcher(token: &SyntaxToken) -> bool {
510+
let Some(macro_def) = token.parent_ancestors().find_map(ast::Macro::cast) else {
511+
return false;
512+
};
513+
let range = token.text_range();
514+
let Some(body) = (match macro_def {
515+
ast::Macro::MacroDef(macro_def) => {
516+
if let Some(args) = macro_def.args() {
517+
return args.syntax().text_range().contains_range(range);
518+
}
519+
macro_def.body()
520+
}
521+
ast::Macro::MacroRules(macro_rules) => macro_rules.token_tree(),
522+
}) else {
523+
return false;
524+
};
525+
if !body.syntax().text_range().contains_range(range) {
526+
return false;
527+
}
528+
body.token_trees_and_tokens().filter_map(|tt| tt.into_node()).any(|tt| {
529+
let Some(next) = non_trivia_sibling(tt.syntax().syntax_element(), Direction::Next) else {
530+
return false;
531+
};
532+
let Some(next_next) = next.next_sibling_or_token() else { return false };
533+
next.kind() == T![=]
534+
&& next_next.kind() == T![>]
535+
&& tt.syntax().text_range().contains_range(range)
536+
})
537+
}

0 commit comments

Comments
 (0)