Skip to content

Commit 3a38554

Browse files
bors[bot]Veykril
andauthored
Merge #6231
6231: Factor macro_rules and format-string highlighting out into submodules r=Veykril a=Veykril This moves `format`-like macro string highlighting and macro_rules highlight skipping out of the main module. Co-authored-by: Lukas Wirth <[email protected]>
2 parents b2205bd + bab29e6 commit 3a38554

File tree

3 files changed

+231
-190
lines changed

3 files changed

+231
-190
lines changed

crates/ide/src/syntax_highlighting.rs

Lines changed: 24 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
mod tags;
1+
mod format;
22
mod html;
33
mod injection;
4+
mod macro_rules;
5+
mod tags;
46
#[cfg(test)]
57
mod tests;
68

@@ -17,9 +19,11 @@ use syntax::{
1719
SyntaxNode, SyntaxToken, TextRange, WalkEvent, T,
1820
};
1921

20-
use crate::FileId;
22+
use crate::{
23+
syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter},
24+
FileId,
25+
};
2126

22-
use ast::FormatSpecifier;
2327
pub(crate) use html::highlight_as_html;
2428
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
2529

@@ -68,8 +72,9 @@ pub(crate) fn highlight(
6872
// When we leave a node, the we use it to flatten the highlighted ranges.
6973
let mut stack = HighlightedRangeStack::new();
7074

71-
let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None;
72-
let mut format_string: Option<SyntaxElement> = None;
75+
let mut current_macro_call: Option<ast::MacroCall> = None;
76+
let mut format_string_highlighter = FormatStringHighlighter::default();
77+
let mut macro_rules_highlighter = MacroRulesHighlighter::default();
7378

7479
// Walk all nodes, keeping track of whether we are inside a macro or not.
7580
// If in macro, expand it first and highlight the expanded code.
@@ -99,9 +104,8 @@ pub(crate) fn highlight(
99104
binding_hash: None,
100105
});
101106
}
102-
let mut is_macro_rules = None;
103107
if let Some(name) = mc.is_macro_rules() {
104-
is_macro_rules = Some(MacroMatcherParseState::new());
108+
macro_rules_highlighter.init();
105109
if let Some((highlight, binding_hash)) = highlight_element(
106110
&sema,
107111
&mut bindings_shadow_count,
@@ -115,13 +119,14 @@ pub(crate) fn highlight(
115119
});
116120
}
117121
}
118-
current_macro_call = Some((mc.clone(), is_macro_rules));
122+
current_macro_call = Some(mc.clone());
119123
continue;
120124
}
121125
WalkEvent::Leave(Some(mc)) => {
122-
assert!(current_macro_call.map(|it| it.0) == Some(mc));
126+
assert!(current_macro_call == Some(mc));
123127
current_macro_call = None;
124-
format_string = None;
128+
format_string_highlighter = FormatStringHighlighter::default();
129+
macro_rules_highlighter = MacroRulesHighlighter::default();
125130
}
126131
_ => (),
127132
}
@@ -148,20 +153,6 @@ pub(crate) fn highlight(
148153
WalkEvent::Leave(_) => continue,
149154
};
150155

151-
// check if in matcher part of a macro_rules rule
152-
if let Some((_, Some(ref mut state))) = current_macro_call {
153-
if let Some(tok) = element.as_token() {
154-
if matches!(
155-
update_macro_rules_state(tok, state),
156-
RuleState::Matcher | RuleState::Expander
157-
) {
158-
if skip_metavariables(element.clone()) {
159-
continue;
160-
}
161-
}
162-
}
163-
}
164-
165156
let range = element.text_range();
166157

167158
let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
@@ -173,29 +164,9 @@ pub(crate) fn highlight(
173164
let token = sema.descend_into_macros(token.clone());
174165
let parent = token.parent();
175166

176-
// Check if macro takes a format string and remember it for highlighting later.
177-
// The macros that accept a format string expand to a compiler builtin macros
178-
// `format_args` and `format_args_nl`.
179-
if let Some(name) = parent
180-
.parent()
181-
.and_then(ast::MacroCall::cast)
182-
.and_then(|mc| mc.path())
183-
.and_then(|p| p.segment())
184-
.and_then(|s| s.name_ref())
185-
{
186-
match name.text().as_str() {
187-
"format_args" | "format_args_nl" => {
188-
format_string = parent
189-
.children_with_tokens()
190-
.filter(|t| t.kind() != WHITESPACE)
191-
.nth(1)
192-
.filter(|e| {
193-
ast::String::can_cast(e.kind())
194-
|| ast::RawString::can_cast(e.kind())
195-
})
196-
}
197-
_ => {}
198-
}
167+
format_string_highlighter.check_for_format_string(&parent);
168+
if let Some(tok) = element.as_token() {
169+
macro_rules_highlighter.advance(tok);
199170
}
200171

201172
// We only care Name and Name_ref
@@ -214,31 +185,20 @@ pub(crate) fn highlight(
214185
}
215186
}
216187

217-
let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
218-
219188
if let Some((highlight, binding_hash)) = highlight_element(
220189
&sema,
221190
&mut bindings_shadow_count,
222191
syntactic_name_ref_highlighting,
223192
element_to_highlight.clone(),
224193
) {
225-
stack.add(HighlightedRange { range, highlight, binding_hash });
194+
if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() {
195+
stack.add(HighlightedRange { range, highlight, binding_hash });
196+
}
197+
226198
if let Some(string) =
227199
element_to_highlight.as_token().cloned().and_then(ast::String::cast)
228200
{
229-
if is_format_string {
230-
stack.push();
231-
string.lex_format_specifier(|piece_range, kind| {
232-
if let Some(highlight) = highlight_format_specifier(kind) {
233-
stack.add(HighlightedRange {
234-
range: piece_range + range.start(),
235-
highlight: highlight.into(),
236-
binding_hash: None,
237-
});
238-
}
239-
});
240-
stack.pop();
241-
}
201+
format_string_highlighter.highlight_format_string(&mut stack, &string, range);
242202
// Highlight escape sequences
243203
if let Some(char_ranges) = string.char_ranges() {
244204
stack.push();
@@ -256,19 +216,7 @@ pub(crate) fn highlight(
256216
} else if let Some(string) =
257217
element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
258218
{
259-
if is_format_string {
260-
stack.push();
261-
string.lex_format_specifier(|piece_range, kind| {
262-
if let Some(highlight) = highlight_format_specifier(kind) {
263-
stack.add(HighlightedRange {
264-
range: piece_range + range.start(),
265-
highlight: highlight.into(),
266-
binding_hash: None,
267-
});
268-
}
269-
});
270-
stack.pop();
271-
}
219+
format_string_highlighter.highlight_format_string(&mut stack, &string, range);
272220
}
273221
}
274222
}
@@ -436,24 +384,6 @@ impl HighlightedRangeStack {
436384
}
437385
}
438386

439-
fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
440-
Some(match kind {
441-
FormatSpecifier::Open
442-
| FormatSpecifier::Close
443-
| FormatSpecifier::Colon
444-
| FormatSpecifier::Fill
445-
| FormatSpecifier::Align
446-
| FormatSpecifier::Sign
447-
| FormatSpecifier::NumberSign
448-
| FormatSpecifier::DollarSign
449-
| FormatSpecifier::Dot
450-
| FormatSpecifier::Asterisk
451-
| FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier,
452-
FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
453-
FormatSpecifier::Identifier => HighlightTag::Local,
454-
})
455-
}
456-
457387
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
458388
let path = macro_call.path()?;
459389
let name_ref = path.segment()?.name_ref()?;
@@ -934,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
934864
_ => default.into(),
935865
}
936866
}
937-
938-
struct MacroMatcherParseState {
939-
/// Opening and corresponding closing bracket of the matcher or expander of the current rule
940-
paren_ty: Option<(SyntaxKind, SyntaxKind)>,
941-
paren_level: usize,
942-
rule_state: RuleState,
943-
/// Whether we are inside the outer `{` `}` macro block that holds the rules
944-
in_invoc_body: bool,
945-
}
946-
947-
impl MacroMatcherParseState {
948-
fn new() -> Self {
949-
MacroMatcherParseState {
950-
paren_ty: None,
951-
paren_level: 0,
952-
in_invoc_body: false,
953-
rule_state: RuleState::None,
954-
}
955-
}
956-
}
957-
958-
#[derive(Copy, Clone, PartialEq)]
959-
enum RuleState {
960-
Matcher,
961-
Expander,
962-
Between,
963-
None,
964-
}
965-
966-
impl RuleState {
967-
fn transition(&mut self) {
968-
*self = match self {
969-
RuleState::Matcher => RuleState::Between,
970-
RuleState::Expander => RuleState::None,
971-
RuleState::Between => RuleState::Expander,
972-
RuleState::None => RuleState::Matcher,
973-
};
974-
}
975-
}
976-
977-
fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState {
978-
if !state.in_invoc_body {
979-
if tok.kind() == T!['{'] {
980-
state.in_invoc_body = true;
981-
}
982-
return state.rule_state;
983-
}
984-
985-
match state.paren_ty {
986-
Some((open, close)) => {
987-
if tok.kind() == open {
988-
state.paren_level += 1;
989-
} else if tok.kind() == close {
990-
state.paren_level -= 1;
991-
if state.paren_level == 0 {
992-
let res = state.rule_state;
993-
state.rule_state.transition();
994-
state.paren_ty = None;
995-
return res;
996-
}
997-
}
998-
}
999-
None => {
1000-
match tok.kind() {
1001-
T!['('] => {
1002-
state.paren_ty = Some((T!['('], T![')']));
1003-
}
1004-
T!['{'] => {
1005-
state.paren_ty = Some((T!['{'], T!['}']));
1006-
}
1007-
T!['['] => {
1008-
state.paren_ty = Some((T!['['], T![']']));
1009-
}
1010-
_ => (),
1011-
}
1012-
if state.paren_ty.is_some() {
1013-
state.paren_level = 1;
1014-
state.rule_state.transition();
1015-
}
1016-
}
1017-
}
1018-
state.rule_state
1019-
}
1020-
1021-
fn skip_metavariables(element: SyntaxElement) -> bool {
1022-
let tok = match element.as_token() {
1023-
Some(tok) => tok,
1024-
None => return false,
1025-
};
1026-
let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]);
1027-
match tok.kind() {
1028-
IDENT if is_fragment() => true,
1029-
kind if kind.is_keyword() && is_fragment() => true,
1030-
_ => false,
1031-
}
1032-
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//! Syntax highlighting for format macro strings.
2+
use syntax::{
3+
ast::{self, FormatSpecifier, HasFormatSpecifier},
4+
AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
5+
};
6+
7+
use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange};
8+
9+
#[derive(Default)]
10+
pub(super) struct FormatStringHighlighter {
11+
format_string: Option<SyntaxElement>,
12+
}
13+
14+
impl FormatStringHighlighter {
15+
pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) {
16+
// Check if macro takes a format string and remember it for highlighting later.
17+
// The macros that accept a format string expand to a compiler builtin macros
18+
// `format_args` and `format_args_nl`.
19+
if let Some(name) = parent
20+
.parent()
21+
.and_then(ast::MacroCall::cast)
22+
.and_then(|mc| mc.path())
23+
.and_then(|p| p.segment())
24+
.and_then(|s| s.name_ref())
25+
{
26+
match name.text().as_str() {
27+
"format_args" | "format_args_nl" => {
28+
self.format_string = parent
29+
.children_with_tokens()
30+
.filter(|t| t.kind() != SyntaxKind::WHITESPACE)
31+
.nth(1)
32+
.filter(|e| {
33+
ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind())
34+
})
35+
}
36+
_ => {}
37+
}
38+
}
39+
}
40+
pub(super) fn highlight_format_string(
41+
&self,
42+
range_stack: &mut HighlightedRangeStack,
43+
string: &impl HasFormatSpecifier,
44+
range: TextRange,
45+
) {
46+
if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) {
47+
range_stack.push();
48+
string.lex_format_specifier(|piece_range, kind| {
49+
if let Some(highlight) = highlight_format_specifier(kind) {
50+
range_stack.add(HighlightedRange {
51+
range: piece_range + range.start(),
52+
highlight: highlight.into(),
53+
binding_hash: None,
54+
});
55+
}
56+
});
57+
range_stack.pop();
58+
}
59+
}
60+
}
61+
62+
fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
63+
Some(match kind {
64+
FormatSpecifier::Open
65+
| FormatSpecifier::Close
66+
| FormatSpecifier::Colon
67+
| FormatSpecifier::Fill
68+
| FormatSpecifier::Align
69+
| FormatSpecifier::Sign
70+
| FormatSpecifier::NumberSign
71+
| FormatSpecifier::DollarSign
72+
| FormatSpecifier::Dot
73+
| FormatSpecifier::Asterisk
74+
| FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier,
75+
FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
76+
FormatSpecifier::Identifier => HighlightTag::Local,
77+
})
78+
}

0 commit comments

Comments
 (0)