Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/syntax_highlighting.rs')
-rw-r--r--crates/ide/src/syntax_highlighting.rs455
1 files changed, 229 insertions, 226 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index 1853e3a340..83082496d5 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -7,7 +7,6 @@ mod escape;
mod format;
mod highlight;
mod inject;
-mod macro_;
mod html;
#[cfg(test)]
@@ -15,14 +14,17 @@ mod tests;
use std::ops::ControlFlow;
-use hir::{InRealFile, Name, Semantics};
-use ide_db::{FxHashMap, Ranker, RootDatabase, SymbolKind};
+use either::Either;
+use hir::{
+ DefWithBody, HirFileIdExt, InFile, InRealFile, MacroFileIdExt, MacroKind, Name, Semantics,
+};
+use ide_db::{FxHashMap, FxHashSet, Ranker, RootDatabase, SymbolKind};
use span::EditionedFileId;
use syntax::{
ast::{self, IsString},
AstNode, AstToken, NodeOrToken,
SyntaxKind::*,
- SyntaxNode, TextRange, WalkEvent, T,
+ SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
};
use crate::{
@@ -30,7 +32,6 @@ use crate::{
escape::{highlight_escape_byte, highlight_escape_char, highlight_escape_string},
format::highlight_format_string,
highlights::Highlights,
- macro_::MacroHighlighter,
tags::Highlight,
},
FileId, HlMod, HlOperator, HlPunct, HlTag,
@@ -221,7 +222,7 @@ pub(crate) fn highlight(
Some(it) => it.krate(),
None => return hl.to_vec(),
};
- traverse(&mut hl, &sema, config, file_id, &root, krate, range_to_highlight);
+ traverse(&mut hl, &sema, config, InRealFile::new(file_id, &root), krate, range_to_highlight);
hl.to_vec()
}
@@ -229,13 +230,11 @@ fn traverse(
hl: &mut Highlights,
sema: &Semantics<'_, RootDatabase>,
config: HighlightConfig,
- file_id: EditionedFileId,
- root: &SyntaxNode,
+ InRealFile { file_id, value: root }: InRealFile<&SyntaxNode>,
krate: hir::Crate,
range_to_highlight: TextRange,
) {
let is_unlinked = sema.file_to_module_def(file_id).is_none();
- let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
enum AttrOrDerive {
Attr(ast::Item),
@@ -250,20 +249,26 @@ fn traverse(
}
}
+ let empty = FxHashSet::default();
+
+ // FIXME: accommodate range highlighting
let mut tt_level = 0;
+ // FIXME: accommodate range highlighting
let mut attr_or_derive_item = None;
- let mut current_macro: Option<ast::Macro> = None;
- let mut macro_highlighter = MacroHighlighter::default();
// FIXME: these are not perfectly accurate, we determine them by the real file's syntax tree
// an attribute nested in a macro call will not emit `inside_attribute`
let mut inside_attribute = false;
- let mut inside_macro_call = false;
- let mut inside_proc_macro_call = false;
+
+ // FIXME: accommodate range highlighting
+ let mut body_stack: Vec<Option<DefWithBody>> = vec![];
+ let mut per_body_cache: FxHashMap<DefWithBody, (FxHashSet<_>, FxHashMap<Name, u32>)> =
+ FxHashMap::default();
// 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.preorder_with_tokens() {
+ let mut preorder = root.preorder_with_tokens();
+ while let Some(event) = preorder.next() {
use WalkEvent::{Enter, Leave};
let range = match &event {
@@ -275,16 +280,11 @@ fn traverse(
continue;
}
- // set macro and attribute highlighting states
match event.clone() {
- Enter(NodeOrToken::Node(node))
- if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
- {
+ Enter(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
tt_level += 1;
}
- Leave(NodeOrToken::Node(node))
- if current_macro.is_none() && ast::TokenTree::can_cast(node.kind()) =>
- {
+ Leave(NodeOrToken::Node(node)) if ast::TokenTree::can_cast(node.kind()) => {
tt_level -= 1;
}
Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
@@ -293,73 +293,68 @@ fn traverse(
Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => {
inside_attribute = false
}
-
Enter(NodeOrToken::Node(node)) => {
- if let Some(item) = ast::Item::cast(node.clone()) {
+ if let Some(item) = <Either<ast::Item, ast::Variant>>::cast(node.clone()) {
match item {
- ast::Item::MacroRules(mac) => {
- macro_highlighter.init();
- current_macro = Some(mac.into());
- continue;
- }
- ast::Item::MacroDef(mac) => {
- macro_highlighter.init();
- current_macro = Some(mac.into());
- continue;
- }
- ast::Item::Fn(_) | ast::Item::Const(_) | ast::Item::Static(_) => {
- bindings_shadow_count.clear()
- }
- ast::Item::MacroCall(ref macro_call) => {
- inside_macro_call = true;
- inside_proc_macro_call = sema.is_proc_macro_call(macro_call);
- }
- _ => (),
- }
-
- if attr_or_derive_item.is_none() {
- if sema.is_attr_macro_call(&item) {
- attr_or_derive_item = Some(AttrOrDerive::Attr(item));
- } else {
- let adt = match item {
- ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
- ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
- ast::Item::Union(it) => Some(ast::Adt::Union(it)),
- _ => None,
- };
- match adt {
- Some(adt) if sema.is_derive_annotated(&adt) => {
- attr_or_derive_item =
- Some(AttrOrDerive::Derive(ast::Item::from(adt)));
+ Either::Left(item) => {
+ match &item {
+ ast::Item::Fn(it) => {
+ body_stack.push(sema.to_def(it).map(Into::into))
+ }
+ ast::Item::Const(it) => {
+ body_stack.push(sema.to_def(it).map(Into::into))
+ }
+ ast::Item::Static(it) => {
+ body_stack.push(sema.to_def(it).map(Into::into))
}
_ => (),
}
+
+ if attr_or_derive_item.is_none() {
+ if sema.is_attr_macro_call(InFile::new(file_id.into(), &item)) {
+ attr_or_derive_item = Some(AttrOrDerive::Attr(item));
+ } else {
+ let adt = match item {
+ ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+ ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+ ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+ _ => None,
+ };
+ match adt {
+ Some(adt)
+ if sema.is_derive_annotated(InFile::new(
+ file_id.into(),
+ &adt,
+ )) =>
+ {
+ attr_or_derive_item =
+ Some(AttrOrDerive::Derive(ast::Item::from(adt)));
+ }
+ _ => (),
+ }
+ }
+ }
}
+ Either::Right(it) => body_stack.push(sema.to_def(&it).map(Into::into)),
}
}
}
- Leave(NodeOrToken::Node(node)) if ast::Item::can_cast(node.kind()) => {
+ Leave(NodeOrToken::Node(node))
+ if <Either<ast::Item, ast::Variant>>::can_cast(node.kind()) =>
+ {
match ast::Item::cast(node.clone()) {
- 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 attr_or_derive_item.as_ref().is_some_and(|it| *it.item() == item) =>
- {
- attr_or_derive_item = None;
- }
- Some(ast::Item::MacroCall(_)) => {
- inside_macro_call = false;
- inside_proc_macro_call = false;
+ Some(item) => {
+ if attr_or_derive_item.as_ref().is_some_and(|it| *it.item() == item) {
+ attr_or_derive_item = None;
+ }
+ if matches!(
+ item,
+ ast::Item::Fn(_) | ast::Item::Const(_) | ast::Item::Static(_)
+ ) {
+ body_stack.pop();
+ }
}
- _ => (),
+ None => _ = body_stack.pop(),
}
}
_ => (),
@@ -379,12 +374,6 @@ fn traverse(
}
};
- if current_macro.is_some() {
- if let Some(tok) = element.as_token() {
- macro_highlighter.advance(tok);
- }
- }
-
let element = match element.clone() {
NodeOrToken::Node(n) => match ast::NameLike::cast(n) {
Some(n) => NodeOrToken::Node(n),
@@ -392,7 +381,7 @@ fn traverse(
},
NodeOrToken::Token(t) => NodeOrToken::Token(t),
};
- let token = element.as_token().cloned();
+ let original_token = element.as_token().cloned();
// Descending tokens into macros is expensive even if no descending occurs, so make sure
// that we actually are in a position where descending is possible.
@@ -403,146 +392,73 @@ fn traverse(
None => false,
};
- let descended_element = if in_macro {
+ let (descended_element, current_body) = match element {
// Attempt to descend tokens into macro-calls.
- let res = match element {
- NodeOrToken::Token(token) if token.kind() != COMMENT => {
- let ranker = Ranker::from_token(&token);
-
- let mut t = None;
- let mut r = 0;
- sema.descend_into_macros_breakable(
- InRealFile::new(file_id, token.clone()),
- |tok, _ctx| {
- // FIXME: Consider checking ctx transparency for being opaque?
- let tok = tok.value;
- let my_rank = ranker.rank_token(&tok);
-
- if my_rank >= Ranker::MAX_RANK {
- // a rank of 0b1110 means that we have found a maximally interesting
- // token so stop early.
- t = Some(tok);
- return ControlFlow::Break(());
- }
-
- // r = r.max(my_rank);
- // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok));
- match &mut t {
- Some(prev) if r < my_rank => {
- *prev = tok;
- r = my_rank;
- }
- Some(_) => (),
- None => {
- r = my_rank;
- t = Some(tok)
- }
- }
- ControlFlow::Continue(())
- },
- );
-
- let token = t.unwrap_or(token);
- match token.parent().and_then(ast::NameLike::cast) {
- // Remap the token into the wrapping single token nodes
- Some(parent) => match (token.kind(), parent.syntax().kind()) {
- (T![self] | T![ident], NAME | NAME_REF) => NodeOrToken::Node(parent),
- (T![self] | T![super] | T![crate] | T![Self], NAME_REF) => {
- NodeOrToken::Node(parent)
- }
- (INT_NUMBER, NAME_REF) => NodeOrToken::Node(parent),
- (LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
- _ => NodeOrToken::Token(token),
- },
- None => NodeOrToken::Token(token),
- }
- }
- e => e,
- };
- res
- } else {
- element
- };
-
- // FIXME: do proper macro def highlighting https://github.com/rust-lang/rust-analyzer/issues/6232
- // Skip metavariables from being highlighted to prevent keyword highlighting in them
- if descended_element.as_token().and_then(|t| macro_highlighter.highlight(t)).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(descended_token)) = (token, descended_element.as_token()) {
- if ast::String::can_cast(token.kind()) && ast::String::can_cast(descended_token.kind())
- {
- let string = ast::String::cast(token);
- let string_to_highlight = ast::String::cast(descended_token.clone());
- if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
- if string.is_raw()
- && inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some()
- {
- continue;
- }
- highlight_format_string(
- hl,
- sema,
- krate,
- &string,
- &expanded_string,
- range,
- file_id.edition(),
- );
-
- if !string.is_raw() {
- highlight_escape_string(hl, &string, range.start());
+ NodeOrToken::Token(token) if in_macro => {
+ let descended = descend_token(sema, InRealFile::new(file_id, token));
+ let body = match &descended.value {
+ NodeOrToken::Node(n) => {
+ sema.body_for(InFile::new(descended.file_id, n.syntax()))
}
- }
- } else if ast::ByteString::can_cast(token.kind())
- && ast::ByteString::can_cast(descended_token.kind())
- {
- if let Some(byte_string) = ast::ByteString::cast(token) {
- if !byte_string.is_raw() {
- highlight_escape_string(hl, &byte_string, range.start());
+ NodeOrToken::Token(t) => {
+ t.parent().and_then(|it| sema.body_for(InFile::new(descended.file_id, &it)))
}
- }
- } else if ast::CString::can_cast(token.kind())
- && ast::CString::can_cast(descended_token.kind())
- {
- if let Some(c_string) = ast::CString::cast(token) {
- if !c_string.is_raw() {
- highlight_escape_string(hl, &c_string, range.start());
- }
- }
- } else if ast::Char::can_cast(token.kind())
- && ast::Char::can_cast(descended_token.kind())
- {
- let Some(char) = ast::Char::cast(token) else {
- continue;
- };
-
- highlight_escape_char(hl, &char, range.start())
- } else if ast::Byte::can_cast(token.kind())
- && ast::Byte::can_cast(descended_token.kind())
- {
- let Some(byte) = ast::Byte::cast(token) else {
- continue;
};
-
- highlight_escape_byte(hl, &byte, range.start())
+ (descended, body)
}
- }
-
- let element = match descended_element {
- NodeOrToken::Node(name_like) => highlight::name_like(
+ n => (InFile::new(file_id.into(), n), body_stack.last().copied().flatten()),
+ };
+ // string highlight injections
+ if let (Some(original_token), Some(descended_token)) =
+ (original_token, descended_element.value.as_token())
+ {
+ let control_flow = string_injections(
+ hl,
sema,
+ config,
+ file_id,
krate,
- &mut bindings_shadow_count,
- config.syntactic_name_ref_highlighting,
- name_like,
- file_id.edition(),
- ),
+ original_token,
+ descended_token,
+ );
+ if control_flow.is_break() {
+ continue;
+ }
+ }
+
+ let edition = descended_element.file_id.edition(sema.db);
+ let (unsafe_ops, bindings_shadow_count) = match current_body {
+ Some(current_body) => {
+ let (ops, bindings) = per_body_cache
+ .entry(current_body)
+ .or_insert_with(|| (sema.get_unsafe_ops(current_body), Default::default()));
+ (&*ops, Some(bindings))
+ }
+ None => (&empty, None),
+ };
+ let is_unsafe_node =
+ |node| unsafe_ops.contains(&InFile::new(descended_element.file_id, node));
+ let element = match descended_element.value {
+ NodeOrToken::Node(name_like) => {
+ let hl = highlight::name_like(
+ sema,
+ krate,
+ bindings_shadow_count,
+ &is_unsafe_node,
+ config.syntactic_name_ref_highlighting,
+ name_like,
+ edition,
+ );
+ if hl.is_some() && !in_macro {
+ // skip highlighting the contained token of our name-like node
+ // as that would potentially overwrite our result
+ preorder.skip_subtree();
+ }
+ hl
+ }
NodeOrToken::Token(token) => {
- highlight::token(sema, token, file_id.edition()).zip(Some(None))
+ highlight::token(sema, token, edition, &is_unsafe_node, tt_level > 0)
+ .zip(Some(None))
}
};
if let Some((mut highlight, binding_hash)) = element {
@@ -551,13 +467,6 @@ fn traverse(
// let the editor do its highlighting for these tokens instead
continue;
}
- if highlight.tag == HlTag::UnresolvedReference
- && matches!(attr_or_derive_item, Some(AttrOrDerive::Derive(_)) if inside_attribute)
- {
- // do not emit unresolved references in derive helpers if the token mapping maps to
- // something unresolvable. FIXME: There should be a way to prevent that
- continue;
- }
// apply config filtering
if !filter_by_config(&mut highlight, config) {
@@ -567,8 +476,9 @@ fn traverse(
if inside_attribute {
highlight |= HlMod::Attribute
}
- if inside_macro_call && tt_level > 0 {
- if inside_proc_macro_call {
+ if let Some(m) = descended_element.file_id.macro_file() {
+ if let MacroKind::ProcMacro | MacroKind::Attr | MacroKind::Derive = m.kind(sema.db)
+ {
highlight |= HlMod::ProcMacro
}
highlight |= HlMod::Macro
@@ -579,6 +489,99 @@ fn traverse(
}
}
+fn string_injections(
+ hl: &mut Highlights,
+ sema: &Semantics<'_, RootDatabase>,
+ config: HighlightConfig,
+ file_id: EditionedFileId,
+ krate: hir::Crate,
+ token: SyntaxToken,
+ descended_token: &SyntaxToken,
+) -> ControlFlow<()> {
+ if !matches!(token.kind(), STRING | BYTE_STRING | BYTE | CHAR | C_STRING) {
+ return ControlFlow::Continue(());
+ }
+ if let Some(string) = ast::String::cast(token.clone()) {
+ if let Some(descended_string) = ast::String::cast(descended_token.clone()) {
+ if string.is_raw()
+ && inject::ra_fixture(hl, sema, config, &string, &descended_string).is_some()
+ {
+ return ControlFlow::Break(());
+ }
+ highlight_format_string(hl, sema, krate, &string, &descended_string, file_id.edition());
+
+ if !string.is_raw() {
+ highlight_escape_string(hl, &string);
+ }
+ }
+ } else if let Some(byte_string) = ast::ByteString::cast(token.clone()) {
+ if !byte_string.is_raw() {
+ highlight_escape_string(hl, &byte_string);
+ }
+ } else if let Some(c_string) = ast::CString::cast(token.clone()) {
+ if !c_string.is_raw() {
+ highlight_escape_string(hl, &c_string);
+ }
+ } else if let Some(char) = ast::Char::cast(token.clone()) {
+ highlight_escape_char(hl, &char)
+ } else if let Some(byte) = ast::Byte::cast(token) {
+ highlight_escape_byte(hl, &byte)
+ }
+ ControlFlow::Continue(())
+}
+
+fn descend_token(
+ sema: &Semantics<'_, RootDatabase>,
+ token: InRealFile<SyntaxToken>,
+) -> InFile<NodeOrToken<ast::NameLike, SyntaxToken>> {
+ if token.value.kind() == COMMENT {
+ return token.map(NodeOrToken::Token).into();
+ }
+ let ranker = Ranker::from_token(&token.value);
+
+ let mut t = None;
+ let mut r = 0;
+ sema.descend_into_macros_breakable(token.clone(), |tok, _ctx| {
+ // FIXME: Consider checking ctx transparency for being opaque?
+ let my_rank = ranker.rank_token(&tok.value);
+
+ if my_rank >= Ranker::MAX_RANK {
+ // a rank of 0b1110 means that we have found a maximally interesting
+ // token so stop early.
+ t = Some(tok);
+ return ControlFlow::Break(());
+ }
+
+ // r = r.max(my_rank);
+ // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok));
+ match &mut t {
+ Some(prev) if r < my_rank => {
+ *prev = tok;
+ r = my_rank;
+ }
+ Some(_) => (),
+ None => {
+ r = my_rank;
+ t = Some(tok)
+ }
+ }
+ ControlFlow::Continue(())
+ });
+
+ let token = t.unwrap_or_else(|| token.into());
+ token.map(|token| match token.parent().and_then(ast::NameLike::cast) {
+ // Remap the token into the wrapping single token nodes
+ Some(parent) => match (token.kind(), parent.syntax().kind()) {
+ (T![ident] | T![self], NAME)
+ | (T![ident] | T![self] | T![super] | T![crate] | T![Self], NAME_REF)
+ | (INT_NUMBER, NAME_REF)
+ | (LIFETIME_IDENT, LIFETIME) => NodeOrToken::Node(parent),
+ _ => NodeOrToken::Token(token),
+ },
+ None => NodeOrToken::Token(token),
+ })
+}
+
fn filter_by_config(highlight: &mut Highlight, config: HighlightConfig) -> bool {
match &mut highlight.tag {
HlTag::StringLiteral if !config.strings => return false,
@@ -590,7 +593,7 @@ fn filter_by_config(highlight: &mut Highlight, config: HighlightConfig) -> bool
*tag = HlTag::Punctuation(HlPunct::Other);
}
}
- HlTag::Punctuation(_) if !config.punctuation => return false,
+ HlTag::Punctuation(_) if !config.punctuation && highlight.mods.is_empty() => return false,
tag @ HlTag::Punctuation(_) if !config.specialize_punctuation => {
*tag = HlTag::Punctuation(HlPunct::Other);
}