From 1416413d69bfd11d2652349a7a6b20e76924de67 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 10 Oct 2020 23:13:16 +0200 Subject: Skip macro matcher fragment name semantic highlighting --- crates/ide/src/syntax_highlighting.rs | 118 +++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 3 deletions(-) (limited to 'crates/ide/src/syntax_highlighting.rs') diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index d9fc25d88..6aafd6fd5 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -68,7 +68,7 @@ pub(crate) fn highlight( // When we leave a node, the we use it to flatten the highlighted ranges. let mut stack = HighlightedRangeStack::new(); - let mut current_macro_call: Option = None; + let mut current_macro_call: Option<(ast::MacroCall, Option)> = None; let mut format_string: Option = None; // Walk all nodes, keeping track of whether we are inside a macro or not. @@ -92,7 +92,6 @@ pub(crate) fn highlight( // Track "inside macro" state match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { WalkEvent::Enter(Some(mc)) => { - current_macro_call = Some(mc.clone()); if let Some(range) = macro_call_range(&mc) { stack.add(HighlightedRange { range, @@ -100,7 +99,9 @@ pub(crate) fn highlight( binding_hash: None, }); } + let mut is_macro_rules = None; if let Some(name) = mc.is_macro_rules() { + is_macro_rules = Some(MacroMatcherParseState::new()); if let Some((highlight, binding_hash)) = highlight_element( &sema, &mut bindings_shadow_count, @@ -114,10 +115,11 @@ pub(crate) fn highlight( }); } } + current_macro_call = Some((mc.clone(), is_macro_rules)); continue; } WalkEvent::Leave(Some(mc)) => { - assert!(current_macro_call == Some(mc)); + assert!(current_macro_call.map(|it| it.0) == Some(mc)); current_macro_call = None; format_string = None; } @@ -146,6 +148,20 @@ pub(crate) fn highlight( WalkEvent::Leave(_) => continue, }; + // check if in matcher part of a macro_rules rule + if let Some((_, Some(ref mut state))) = current_macro_call { + if let Some(tok) = element.as_token() { + if matches!( + update_macro_rules_state(tok, state), + RuleState::Matcher | RuleState::Expander + ) { + if skip_metavariables(element.clone()) { + continue; + } + } + } + } + let range = element.text_range(); let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { @@ -918,3 +934,99 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics default.into(), } } + +struct MacroMatcherParseState { + /// Opening and corresponding closing bracket of the matcher or expander of the current rule + paren_ty: Option<(SyntaxKind, SyntaxKind)>, + paren_level: usize, + rule_state: RuleState, + /// Whether we are inside the outer `{` `}` macro block that holds the rules + in_invoc_body: bool, +} + +impl MacroMatcherParseState { + fn new() -> Self { + MacroMatcherParseState { + paren_ty: None, + paren_level: 0, + in_invoc_body: false, + rule_state: RuleState::None, + } + } +} + +#[derive(Copy, Clone, PartialEq)] +enum RuleState { + Matcher, + Expander, + Between, + None, +} + +impl RuleState { + fn transition(&mut self) { + *self = match self { + RuleState::Matcher => RuleState::Between, + RuleState::Expander => RuleState::None, + RuleState::Between => RuleState::Expander, + RuleState::None => RuleState::Matcher, + }; + } +} + +fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { + if !state.in_invoc_body { + if tok.kind() == T!['{'] { + state.in_invoc_body = true; + } + return state.rule_state; + } + + match state.paren_ty { + Some((open, close)) => { + if tok.kind() == open { + state.paren_level += 1; + } else if tok.kind() == close { + state.paren_level -= 1; + if state.paren_level == 0 { + let res = state.rule_state; + state.rule_state.transition(); + state.paren_ty = None; + return res; + } + } + } + None => { + match tok.kind() { + T!['('] => { + state.paren_ty = Some((T!['('], T![')'])); + } + T!['{'] => { + state.paren_ty = Some((T!['{'], T!['}'])); + } + T!['['] => { + state.paren_ty = Some((T!['['], T![']'])); + } + _ => (), + } + if state.paren_ty.is_some() { + state.paren_level = 1; + state.rule_state.transition(); + } + } + } + state.rule_state +} + +fn skip_metavariables(element: SyntaxElement) -> bool { + let tok = match element.as_token() { + Some(tok) => tok, + None => return false, + }; + let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); + match tok.kind() { + IDENT if is_fragment() => true, + kind if kind.is_keyword() && is_fragment() => true, + _ => false, + } +} -- cgit v1.2.3