aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/syntax_highlighting
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/syntax_highlighting')
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs78
-rw-r--r--crates/ide/src/syntax_highlighting/macro_rules.rs129
2 files changed, 207 insertions, 0 deletions
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
new file mode 100644
index 000000000..71bde24f0
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -0,0 +1,78 @@
1//! Syntax highlighting for format macro strings.
2use syntax::{
3 ast::{self, FormatSpecifier, HasFormatSpecifier},
4 AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange,
5};
6
7use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange};
8
9#[derive(Default)]
10pub(super) struct FormatStringHighlighter {
11 format_string: Option<SyntaxElement>,
12}
13
14impl 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
62fn 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}
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs
new file mode 100644
index 000000000..4462af47e
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/macro_rules.rs
@@ -0,0 +1,129 @@
1//! Syntax highlighting for macro_rules!.
2use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T};
3
4use crate::{HighlightTag, HighlightedRange};
5
6#[derive(Default)]
7pub(super) struct MacroRulesHighlighter {
8 state: Option<MacroMatcherParseState>,
9}
10
11impl MacroRulesHighlighter {
12 pub(super) fn init(&mut self) {
13 self.state = Some(MacroMatcherParseState::default());
14 }
15
16 pub(super) fn advance(&mut self, token: &SyntaxToken) {
17 if let Some(state) = self.state.as_mut() {
18 update_macro_rules_state(state, token);
19 }
20 }
21
22 pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> {
23 if let Some(state) = self.state.as_ref() {
24 if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
25 if let Some(range) = is_metavariable(element) {
26 return Some(HighlightedRange {
27 range,
28 highlight: HighlightTag::UnresolvedReference.into(),
29 binding_hash: None,
30 });
31 }
32 }
33 }
34 None
35 }
36}
37
38struct MacroMatcherParseState {
39 /// Opening and corresponding closing bracket of the matcher or expander of the current rule
40 paren_ty: Option<(SyntaxKind, SyntaxKind)>,
41 paren_level: usize,
42 rule_state: RuleState,
43 /// Whether we are inside the outer `{` `}` macro block that holds the rules
44 in_invoc_body: bool,
45}
46
47impl Default for MacroMatcherParseState {
48 fn default() -> Self {
49 MacroMatcherParseState {
50 paren_ty: None,
51 paren_level: 0,
52 in_invoc_body: false,
53 rule_state: RuleState::None,
54 }
55 }
56}
57
58#[derive(Copy, Clone, Debug, PartialEq)]
59enum RuleState {
60 Matcher,
61 Expander,
62 Between,
63 None,
64}
65
66impl RuleState {
67 fn transition(&mut self) {
68 *self = match self {
69 RuleState::Matcher => RuleState::Between,
70 RuleState::Expander => RuleState::None,
71 RuleState::Between => RuleState::Expander,
72 RuleState::None => RuleState::Matcher,
73 };
74 }
75}
76
77fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
78 if !state.in_invoc_body {
79 if tok.kind() == T!['{'] {
80 state.in_invoc_body = true;
81 }
82 return;
83 }
84
85 match state.paren_ty {
86 Some((open, close)) => {
87 if tok.kind() == open {
88 state.paren_level += 1;
89 } else if tok.kind() == close {
90 state.paren_level -= 1;
91 if state.paren_level == 0 {
92 state.rule_state.transition();
93 state.paren_ty = None;
94 }
95 }
96 }
97 None => {
98 match tok.kind() {
99 T!['('] => {
100 state.paren_ty = Some((T!['('], T![')']));
101 }
102 T!['{'] => {
103 state.paren_ty = Some((T!['{'], T!['}']));
104 }
105 T!['['] => {
106 state.paren_ty = Some((T!['['], T![']']));
107 }
108 _ => (),
109 }
110 if state.paren_ty.is_some() {
111 state.paren_level = 1;
112 state.rule_state.transition();
113 }
114 }
115 }
116}
117
118fn is_metavariable(element: SyntaxElement) -> Option<TextRange> {
119 let tok = element.as_token()?;
120 match tok.kind() {
121 kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {
122 if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) {
123 return Some(tok.text_range());
124 }
125 }
126 _ => (),
127 };
128 None
129}