diff options
Diffstat (limited to 'crates/ide/src/syntax_highlighting')
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 78 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/injection.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/macro_rules.rs | 129 |
3 files changed, 209 insertions, 4 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. | ||
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 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/injection.rs b/crates/ide/src/syntax_highlighting/injection.rs index 43f4e6fea..acd91b26c 100644 --- a/crates/ide/src/syntax_highlighting/injection.rs +++ b/crates/ide/src/syntax_highlighting/injection.rs | |||
@@ -3,14 +3,12 @@ | |||
3 | use std::{collections::BTreeMap, convert::TryFrom}; | 3 | use std::{collections::BTreeMap, convert::TryFrom}; |
4 | 4 | ||
5 | use ast::{HasQuotes, HasStringValue}; | 5 | use ast::{HasQuotes, HasStringValue}; |
6 | use call_info::ActiveParameter; | ||
6 | use hir::Semantics; | 7 | use hir::Semantics; |
7 | use itertools::Itertools; | 8 | use itertools::Itertools; |
8 | use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 9 | use syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
9 | 10 | ||
10 | use crate::{ | 11 | use crate::{Analysis, Highlight, HighlightModifier, HighlightTag, HighlightedRange, RootDatabase}; |
11 | call_info::ActiveParameter, Analysis, Highlight, HighlightModifier, HighlightTag, | ||
12 | HighlightedRange, RootDatabase, | ||
13 | }; | ||
14 | 12 | ||
15 | use super::HighlightedRangeStack; | 13 | use super::HighlightedRangeStack; |
16 | 14 | ||
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!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub(super) struct MacroRulesHighlighter { | ||
8 | state: Option<MacroMatcherParseState>, | ||
9 | } | ||
10 | |||
11 | impl 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 | |||
38 | struct 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 | |||
47 | impl 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)] | ||
59 | enum RuleState { | ||
60 | Matcher, | ||
61 | Expander, | ||
62 | Between, | ||
63 | None, | ||
64 | } | ||
65 | |||
66 | impl 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 | |||
77 | fn 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 | |||
118 | fn 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 | } | ||