aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/syntax_highlighting.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs69
1 files changed, 66 insertions, 3 deletions
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index e7d9bf696..e342ca9df 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -12,7 +12,7 @@ use ra_ide_db::{
12}; 12};
13use ra_prof::profile; 13use ra_prof::profile;
14use ra_syntax::{ 14use ra_syntax::{
15 ast::{self, HasQuotes, HasStringValue}, 15 ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
16 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, 16 AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
17 SyntaxKind::*, 17 SyntaxKind::*,
18 SyntaxToken, TextRange, WalkEvent, T, 18 SyntaxToken, TextRange, WalkEvent, T,
@@ -21,6 +21,7 @@ use rustc_hash::FxHashMap;
21 21
22use crate::{call_info::call_info_for_token, Analysis, FileId}; 22use crate::{call_info::call_info_for_token, Analysis, FileId};
23 23
24use ast::FormatSpecifier;
24pub(crate) use html::highlight_as_html; 25pub(crate) use html::highlight_as_html;
25pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; 26pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
26 27
@@ -95,7 +96,8 @@ impl HighlightedRangeStack {
95 1, 96 1,
96 "after DFS traversal, the stack should only contain a single element" 97 "after DFS traversal, the stack should only contain a single element"
97 ); 98 );
98 let res = self.stack.pop().unwrap(); 99 let mut res = self.stack.pop().unwrap();
100 res.sort_by_key(|range| range.range.start());
99 // Check that ranges are sorted and disjoint 101 // Check that ranges are sorted and disjoint
100 assert!(res 102 assert!(res
101 .iter() 103 .iter()
@@ -134,6 +136,7 @@ pub(crate) fn highlight(
134 let mut stack = HighlightedRangeStack::new(); 136 let mut stack = HighlightedRangeStack::new();
135 137
136 let mut current_macro_call: Option<ast::MacroCall> = None; 138 let mut current_macro_call: Option<ast::MacroCall> = None;
139 let mut format_string: Option<SyntaxElement> = None;
137 140
138 // Walk all nodes, keeping track of whether we are inside a macro or not. 141 // Walk all nodes, keeping track of whether we are inside a macro or not.
139 // If in macro, expand it first and highlight the expanded code. 142 // If in macro, expand it first and highlight the expanded code.
@@ -169,6 +172,7 @@ pub(crate) fn highlight(
169 WalkEvent::Leave(Some(mc)) => { 172 WalkEvent::Leave(Some(mc)) => {
170 assert!(current_macro_call == Some(mc)); 173 assert!(current_macro_call == Some(mc));
171 current_macro_call = None; 174 current_macro_call = None;
175 format_string = None;
172 continue; 176 continue;
173 } 177 }
174 _ => (), 178 _ => (),
@@ -189,6 +193,30 @@ pub(crate) fn highlight(
189 }; 193 };
190 let token = sema.descend_into_macros(token.clone()); 194 let token = sema.descend_into_macros(token.clone());
191 let parent = token.parent(); 195 let parent = token.parent();
196
197 // Check if macro takes a format string and remeber it for highlighting later.
198 // The macros that accept a format string expand to a compiler builtin macros
199 // `format_args` and `format_args_nl`.
200 if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) {
201 if let Some(name) =
202 fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref())
203 {
204 match name.text().as_str() {
205 "format_args" | "format_args_nl" => {
206 format_string = parent
207 .children_with_tokens()
208 .filter(|t| t.kind() != WHITESPACE)
209 .nth(1)
210 .filter(|e| {
211 ast::String::can_cast(e.kind())
212 || ast::RawString::can_cast(e.kind())
213 })
214 }
215 _ => {}
216 }
217 }
218 }
219
192 // We only care Name and Name_ref 220 // We only care Name and Name_ref
193 match (token.kind(), parent.kind()) { 221 match (token.kind(), parent.kind()) {
194 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 222 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -205,10 +233,45 @@ pub(crate) fn highlight(
205 } 233 }
206 } 234 }
207 235
236 let is_format_string =
237 format_string.as_ref().map(|fs| fs == &element_to_highlight).unwrap_or_default();
238
208 if let Some((highlight, binding_hash)) = 239 if let Some((highlight, binding_hash)) =
209 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) 240 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
210 { 241 {
211 stack.add(HighlightedRange { range, highlight, binding_hash }); 242 stack.add(HighlightedRange { range, highlight, binding_hash });
243 if let Some(string) =
244 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
245 {
246 stack.push();
247 if is_format_string {
248 string.lex_format_specifier(&mut |piece_range, kind| {
249 let highlight = match kind {
250 FormatSpecifier::Open
251 | FormatSpecifier::Close
252 | FormatSpecifier::Colon
253 | FormatSpecifier::Fill
254 | FormatSpecifier::Align
255 | FormatSpecifier::Sign
256 | FormatSpecifier::NumberSign
257 | FormatSpecifier::DollarSign
258 | FormatSpecifier::Dot
259 | FormatSpecifier::Asterisk
260 | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
261 FormatSpecifier::Integer | FormatSpecifier::Zero => {
262 HighlightTag::NumericLiteral
263 }
264 FormatSpecifier::Identifier => HighlightTag::Local,
265 };
266 stack.add(HighlightedRange {
267 range: piece_range + range.start(),
268 highlight: highlight.into(),
269 binding_hash: None,
270 });
271 });
272 }
273 stack.pop();
274 }
212 } 275 }
213 } 276 }
214 277