diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index e36e0e570458..13f137b27ab2 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -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; diff --git a/crates/ide-completion/src/completions/macro_def.rs b/crates/ide-completion/src/completions/macro_def.rs new file mode 100644 index 000000000000..2c8e7a2e62cc --- /dev/null +++ b/crates/ide-completion/src/completions/macro_def.rs @@ -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", +]; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 9deaaf663127..3cd6699bddb8 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -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 syntax::{ AstNode, Edition, SmolStr, @@ -385,6 +385,7 @@ pub(crate) enum CompletionAnalysis<'db> { fake_attribute_under_caret: Option, extern_crate: Option, }, + 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; } diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index 77a94403abb9..9bb445dc4078 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -4,7 +4,7 @@ use std::iter; use base_db::salsa; use hir::{ExpandResult, InFile, Semantics, Type, TypeInfo, Variant}; use ide_db::{RootDatabase, active_parameter::ActiveParameter}; -use itertools::Either; +use itertools::{Either, Itertools}; use stdx::always; use syntax::{ AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, @@ -512,6 +512,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; } diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index a70a1138d2f4..2a666c0af563 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -255,6 +255,9 @@ pub fn completions( extern_crate.as_ref(), ); } + CompletionAnalysis::MacroSegment => { + completions::macro_def::complete_macro_segment(acc, ctx); + } CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (), } } diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs index 84ddff8f617a..cd210b03aa05 100644 --- a/crates/ide-completion/src/tests/special.rs +++ b/crates/ide-completion/src/tests/special.rs @@ -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![""], diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index cefd8fd49676..e4e33ff9f469 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -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 { @@ -503,3 +505,33 @@ pub fn macro_call_for_string_token(string: &ast::String) -> Option { 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) + }) +}