internal: Handle macro calls better in highlighting

This commit is contained in:
Lukas Wirth 2022-01-02 19:10:10 +01:00
parent 367cd5ce9b
commit 3a525c831f
11 changed files with 151 additions and 159 deletions

View file

@ -13,11 +13,11 @@ mod html;
mod tests;
use hir::{InFile, Name, Semantics};
use ide_db::{RootDatabase, SymbolKind};
use ide_db::RootDatabase;
use rustc_hash::FxHashMap;
use syntax::{
ast::{self, HasFormatSpecifier},
match_ast, AstNode, AstToken, Direction, NodeOrToken,
AstNode, AstToken, NodeOrToken,
SyntaxKind::*,
SyntaxNode, TextRange, WalkEvent, T,
};
@ -100,7 +100,8 @@ pub struct HlRange {
// colon:: Emitted for the `:` token.
// comma:: Emitted for the `,` token.
// dot:: Emitted for the `.` token.
// Semi:: Emitted for the `;` token.
// semi:: Emitted for the `;` token.
// macroBang:: Emitted for the `!` token in macro calls.
//
// //-
//
@ -209,107 +210,94 @@ fn traverse(
// Walk all nodes, keeping track of whether we are inside a macro or not.
// If in macro, expand it first and highlight the expanded code.
for event in root.value.preorder_with_tokens() {
let event_range = match &event {
let range = match &event {
WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
};
// Element outside of the viewport, no need to highlight
if range_to_highlight.intersect(event_range).is_none() {
if range_to_highlight.intersect(range).is_none() {
continue;
}
// set macro and attribute highlighting states
match event.clone() {
WalkEvent::Enter(NodeOrToken::Node(node)) => {
match_ast! {
match node {
ast::MacroCall(mcall) => {
if let Some(range) = macro_call_range(&mcall) {
hl.add(HlRange {
range,
highlight: HlTag::Symbol(SymbolKind::Macro).into(),
binding_hash: None,
});
}
current_macro_call = Some(mcall);
continue;
},
ast::Macro(mac) => {
macro_highlighter.init();
current_macro = Some(mac);
continue;
},
ast::Item(item) => {
if sema.is_attr_macro_call(&item) {
current_attr_call = Some(item);
}
},
ast::Attr(__) => inside_attribute = true,
_ => ()
}
WalkEvent::Enter(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
Some(ast::Item::MacroCall(mcall)) => {
current_macro_call = Some(mcall);
continue;
}
}
WalkEvent::Leave(NodeOrToken::Node(node)) => {
match_ast! {
match node {
ast::MacroCall(mcall) => {
assert_eq!(current_macro_call, Some(mcall));
current_macro_call = None;
},
ast::Macro(mac) => {
assert_eq!(current_macro, Some(mac));
current_macro = None;
macro_highlighter = MacroHighlighter::default();
},
ast::Item(item) => {
if current_attr_call == Some(item) {
current_attr_call = None;
}
},
ast::Attr(__) => inside_attribute = false,
_ => ()
}
Some(ast::Item::MacroRules(mac)) => {
macro_highlighter.init();
current_macro = Some(mac.into());
continue;
}
}
Some(ast::Item::MacroDef(mac)) => {
macro_highlighter.init();
current_macro = Some(mac.into());
continue;
}
Some(item) if sema.is_attr_macro_call(&item) => current_attr_call = Some(item),
None if ast::Attr::can_cast(node.kind()) => inside_attribute = true,
_ => (),
},
WalkEvent::Leave(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
Some(ast::Item::MacroCall(mcall)) => {
assert_eq!(current_macro_call, Some(mcall));
current_macro_call = None;
}
Some(ast::Item::MacroRules(mac)) => {
assert_eq!(current_macro, Some(mac.into()));
current_macro = None;
macro_highlighter = MacroHighlighter::default();
}
Some(ast::Item::MacroDef(mac)) => {
assert_eq!(current_macro, Some(mac.into()));
current_macro = None;
macro_highlighter = MacroHighlighter::default();
}
Some(item) if current_attr_call.as_ref().map_or(false, |it| *it == item) => {
current_attr_call = None
}
None if ast::Attr::can_cast(node.kind()) => inside_attribute = false,
_ => (),
},
_ => (),
}
let element = match event {
WalkEvent::Enter(it) => it,
WalkEvent::Leave(it) => {
if let Some(node) = it.as_node() {
inject::doc_comment(hl, sema, root.with_value(node));
}
WalkEvent::Leave(NodeOrToken::Token(_)) => continue,
WalkEvent::Leave(NodeOrToken::Node(node)) => {
inject::doc_comment(hl, sema, root.with_value(&node));
continue;
}
};
let range = element.text_range();
if current_macro.is_some() {
if let Some(tok) = element.as_token() {
macro_highlighter.advance(tok);
}
}
let descend_token = (current_macro_call.is_some() || current_attr_call.is_some())
&& element.kind() != COMMENT;
// only attempt to descend if we are inside a macro call or attribute
// as calling `descend_into_macros_single` gets rather expensive if done for every single token
let descend_token = current_macro_call.is_some() || current_attr_call.is_some();
let element_to_highlight = if descend_token {
// Inside a macro -- expand it first
let token = match element.clone().into_token() {
Some(it) if current_macro_call.is_some() => {
let not_in_tt = it.parent().map_or(true, |it| it.kind() != TOKEN_TREE);
if not_in_tt {
continue;
}
it
}
Some(it) => it,
_ => continue,
let token = match &element {
NodeOrToken::Node(_) => continue,
NodeOrToken::Token(tok) => tok.clone(),
};
let in_mcall_outside_tt = current_macro_call.is_some()
&& token.parent().as_ref().map(SyntaxNode::kind) != Some(TOKEN_TREE);
let token = match in_mcall_outside_tt {
// not in the macros token tree, don't attempt to descend
true => token,
false => sema.descend_into_macros_single(token),
};
let token = sema.descend_into_macros_single(token);
match token.parent() {
Some(parent) => {
// We only care Name and Name_ref
// Names and NameRefs have special semantics, use them instead of the tokens
// as otherwise we won't ever visit them
match (token.kind(), parent.kind()) {
(T![ident], NAME | NAME_REF) => parent.into(),
(T![self] | T![super] | T![crate], NAME_REF) => parent.into(),
@ -323,10 +311,14 @@ fn traverse(
element.clone()
};
if macro_highlighter.highlight(element_to_highlight.clone()).is_some() {
// FIXME: do proper macro def highlighting https://github.com/rust-analyzer/rust-analyzer/issues/6232
// Skip metavariables from being highlighted to prevent keyword highlighting in them
if macro_highlighter.highlight(&element_to_highlight).is_some() {
continue;
}
// string highlight injections, note this does not use the descended element as proc-macros
// can rewrite string literals which invalidates our indices
if let (Some(token), Some(token_to_highlight)) =
(element.into_token(), element_to_highlight.as_token())
{
@ -354,13 +346,15 @@ fn traverse(
}
}
if let Some((mut highlight, binding_hash)) = highlight::element(
// do the normal highlighting
let element = highlight::element(
sema,
krate,
&mut bindings_shadow_count,
syntactic_name_ref_highlighting,
element_to_highlight.clone(),
) {
element_to_highlight,
);
if let Some((mut highlight, binding_hash)) = element {
if inside_attribute {
highlight |= HlMod::Attribute
}
@ -369,18 +363,3 @@ fn traverse(
}
}
}
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
let path = macro_call.path()?;
let name_ref = path.segment()?.name_ref()?;
let range_start = name_ref.syntax().text_range().start();
let mut range_end = name_ref.syntax().text_range().end();
for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
if let T![!] | T![ident] = sibling.kind() {
range_end = sibling.text_range().end();
}
}
Some(TextRange::new(range_start, range_end))
}