diff options
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 83 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/format.rs | 82 |
2 files changed, 90 insertions, 75 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..f430006d7 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | mod tags; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod tags; | ||
4 | #[cfg(test)] | 5 | #[cfg(test)] |
5 | mod tests; | 6 | mod tests; |
6 | 7 | ||
@@ -17,9 +18,8 @@ use syntax::{ | |||
17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 18 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 19 | }; |
19 | 20 | ||
20 | use crate::FileId; | 21 | use crate::{syntax_highlighting::format::FormatStringHighlighter, FileId}; |
21 | 22 | ||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | 23 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
25 | 25 | ||
@@ -69,7 +69,7 @@ pub(crate) fn highlight( | |||
69 | let mut stack = HighlightedRangeStack::new(); | 69 | let mut stack = HighlightedRangeStack::new(); |
70 | 70 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; |
72 | let mut format_string: Option<SyntaxElement> = None; | 72 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
73 | 73 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 74 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 75 | // If in macro, expand it first and highlight the expanded code. |
@@ -121,7 +121,7 @@ pub(crate) fn highlight( | |||
121 | WalkEvent::Leave(Some(mc)) => { | 121 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); |
123 | current_macro_call = None; | 123 | current_macro_call = None; |
124 | format_string = None; | 124 | format_string_highlighter.reset(); |
125 | } | 125 | } |
126 | _ => (), | 126 | _ => (), |
127 | } | 127 | } |
@@ -173,30 +173,7 @@ pub(crate) fn highlight( | |||
173 | let token = sema.descend_into_macros(token.clone()); | 173 | let token = sema.descend_into_macros(token.clone()); |
174 | let parent = token.parent(); | 174 | let parent = token.parent(); |
175 | 175 | ||
176 | // Check if macro takes a format string and remember it for highlighting later. | 176 | format_string_highlighter.check_for_format_string(&parent); |
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 | } | ||
199 | } | ||
200 | 177 | ||
201 | // We only care Name and Name_ref | 178 | // We only care Name and Name_ref |
202 | match (token.kind(), parent.kind()) { | 179 | match (token.kind(), parent.kind()) { |
@@ -214,8 +191,6 @@ pub(crate) fn highlight( | |||
214 | } | 191 | } |
215 | } | 192 | } |
216 | 193 | ||
217 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
218 | |||
219 | if let Some((highlight, binding_hash)) = highlight_element( | 194 | if let Some((highlight, binding_hash)) = highlight_element( |
220 | &sema, | 195 | &sema, |
221 | &mut bindings_shadow_count, | 196 | &mut bindings_shadow_count, |
@@ -226,19 +201,7 @@ pub(crate) fn highlight( | |||
226 | if let Some(string) = | 201 | if let Some(string) = |
227 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 202 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
228 | { | 203 | { |
229 | if is_format_string { | 204 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
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 | } | ||
242 | // Highlight escape sequences | 205 | // Highlight escape sequences |
243 | if let Some(char_ranges) = string.char_ranges() { | 206 | if let Some(char_ranges) = string.char_ranges() { |
244 | stack.push(); | 207 | stack.push(); |
@@ -256,19 +219,7 @@ pub(crate) fn highlight( | |||
256 | } else if let Some(string) = | 219 | } else if let Some(string) = |
257 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 220 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
258 | { | 221 | { |
259 | if is_format_string { | 222 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
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 | } | ||
272 | } | 223 | } |
273 | } | 224 | } |
274 | } | 225 | } |
@@ -436,24 +387,6 @@ impl HighlightedRangeStack { | |||
436 | } | 387 | } |
437 | } | 388 | } |
438 | 389 | ||
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 | |||
457 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 390 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
458 | let path = macro_call.path()?; | 391 | let path = macro_call.path()?; |
459 | let name_ref = path.segment()?.name_ref()?; | 392 | let name_ref = path.segment()?.name_ref()?; |
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs new file mode 100644 index 000000000..3ab01295a --- /dev/null +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -0,0 +1,82 @@ | |||
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 reset(&mut self) { | ||
16 | self.format_string = None; | ||
17 | } | ||
18 | |||
19 | pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { | ||
20 | // Check if macro takes a format string and remember it for highlighting later. | ||
21 | // The macros that accept a format string expand to a compiler builtin macros | ||
22 | // `format_args` and `format_args_nl`. | ||
23 | if let Some(name) = parent | ||
24 | .parent() | ||
25 | .and_then(ast::MacroCall::cast) | ||
26 | .and_then(|mc| mc.path()) | ||
27 | .and_then(|p| p.segment()) | ||
28 | .and_then(|s| s.name_ref()) | ||
29 | { | ||
30 | match name.text().as_str() { | ||
31 | "format_args" | "format_args_nl" => { | ||
32 | self.format_string = parent | ||
33 | .children_with_tokens() | ||
34 | .filter(|t| t.kind() != SyntaxKind::WHITESPACE) | ||
35 | .nth(1) | ||
36 | .filter(|e| { | ||
37 | ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind()) | ||
38 | }) | ||
39 | } | ||
40 | _ => {} | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | pub(super) fn highlight_format_string( | ||
45 | &self, | ||
46 | range_stack: &mut HighlightedRangeStack, | ||
47 | string: &impl HasFormatSpecifier, | ||
48 | range: TextRange, | ||
49 | ) { | ||
50 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | ||
51 | range_stack.push(); | ||
52 | string.lex_format_specifier(|piece_range, kind| { | ||
53 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
54 | range_stack.add(HighlightedRange { | ||
55 | range: piece_range + range.start(), | ||
56 | highlight: highlight.into(), | ||
57 | binding_hash: None, | ||
58 | }); | ||
59 | } | ||
60 | }); | ||
61 | range_stack.pop(); | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
67 | Some(match kind { | ||
68 | FormatSpecifier::Open | ||
69 | | FormatSpecifier::Close | ||
70 | | FormatSpecifier::Colon | ||
71 | | FormatSpecifier::Fill | ||
72 | | FormatSpecifier::Align | ||
73 | | FormatSpecifier::Sign | ||
74 | | FormatSpecifier::NumberSign | ||
75 | | FormatSpecifier::DollarSign | ||
76 | | FormatSpecifier::Dot | ||
77 | | FormatSpecifier::Asterisk | ||
78 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
79 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
80 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
81 | }) | ||
82 | } | ||