From 29a846464b63259f5152d61a5520bffcc2cb8703 Mon Sep 17 00:00:00 2001 From: Leander Tentrup Date: Thu, 16 Apr 2020 14:29:58 +0200 Subject: Refactor flattening logic for highlighted syntax ranges --- crates/ra_ide/src/syntax_highlighting.rs | 139 +++++++++++++++++++------------ 1 file changed, 85 insertions(+), 54 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 7b15b82bd..e7d9bf696 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -31,6 +31,80 @@ pub struct HighlightedRange { pub binding_hash: Option, } +#[derive(Debug)] +struct HighlightedRangeStack { + stack: Vec>, +} + +/// We use a stack to implement the flattening logic for the highlighted +/// syntax ranges. +impl HighlightedRangeStack { + fn new() -> Self { + Self { stack: vec![Vec::new()] } + } + + fn push(&mut self) { + self.stack.push(Vec::new()); + } + + /// Flattens the highlighted ranges. + /// + /// For example `#[cfg(feature = "foo")]` contains the nested ranges: + /// 1) parent-range: Attribute [0, 23) + /// 2) child-range: String [16, 21) + /// + /// The following code implements the flattening, for our example this results to: + /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` + fn pop(&mut self) { + let children = self.stack.pop().unwrap(); + let prev = self.stack.last_mut().unwrap(); + let needs_flattening = !children.is_empty() + && !prev.is_empty() + && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range); + if !needs_flattening { + prev.extend(children); + } else { + let mut parent = prev.pop().unwrap(); + for ele in children { + assert!(ele.range.is_subrange(&parent.range)); + let mut cloned = parent.clone(); + parent.range = TextRange::from_to(parent.range.start(), ele.range.start()); + cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end()); + if !parent.range.is_empty() { + prev.push(parent); + } + prev.push(ele); + parent = cloned; + } + if !parent.range.is_empty() { + prev.push(parent); + } + } + } + + fn add(&mut self, range: HighlightedRange) { + self.stack + .last_mut() + .expect("during DFS traversal, the stack must not be empty") + .push(range) + } + + fn flattened(mut self) -> Vec { + assert_eq!( + self.stack.len(), + 1, + "after DFS traversal, the stack should only contain a single element" + ); + let res = self.stack.pop().unwrap(); + // Check that ranges are sorted and disjoint + assert!(res + .iter() + .zip(res.iter().skip(1)) + .all(|(left, right)| left.range.end() <= right.range.start())); + res + } +} + pub(crate) fn highlight( db: &RootDatabase, file_id: FileId, @@ -57,7 +131,7 @@ pub(crate) fn highlight( let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); // We use a stack for the DFS traversal below. // When we leave a node, the we use it to flatten the highlighted ranges. - let mut res: Vec> = vec![Vec::new()]; + let mut stack = HighlightedRangeStack::new(); let mut current_macro_call: Option = None; @@ -65,44 +139,9 @@ pub(crate) fn highlight( // If in macro, expand it first and highlight the expanded code. for event in root.preorder_with_tokens() { match &event { - WalkEvent::Enter(_) => res.push(Vec::new()), - WalkEvent::Leave(_) => { - /* Flattens the highlighted ranges. - * - * For example `#[cfg(feature = "foo")]` contains the nested ranges: - * 1) parent-range: Attribute [0, 23) - * 2) child-range: String [16, 21) - * - * The following code implements the flattening, for our example this results to: - * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` - */ - let children = res.pop().unwrap(); - let prev = res.last_mut().unwrap(); - let needs_flattening = !children.is_empty() - && !prev.is_empty() - && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range); - if !needs_flattening { - prev.extend(children); - } else { - let mut parent = prev.pop().unwrap(); - for ele in children { - assert!(ele.range.is_subrange(&parent.range)); - let mut cloned = parent.clone(); - parent.range = TextRange::from_to(parent.range.start(), ele.range.start()); - cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end()); - if !parent.range.is_empty() { - prev.push(parent); - } - prev.push(ele); - parent = cloned; - } - if !parent.range.is_empty() { - prev.push(parent); - } - } - } + WalkEvent::Enter(_) => stack.push(), + WalkEvent::Leave(_) => stack.pop(), }; - let current = res.last_mut().expect("during DFS traversal, the stack must not be empty"); let event_range = match &event { WalkEvent::Enter(it) => it.text_range(), @@ -119,7 +158,7 @@ pub(crate) fn highlight( WalkEvent::Enter(Some(mc)) => { current_macro_call = Some(mc.clone()); if let Some(range) = macro_call_range(&mc) { - current.push(HighlightedRange { + stack.add(HighlightedRange { range, highlight: HighlightTag::Macro.into(), binding_hash: None, @@ -161,7 +200,7 @@ pub(crate) fn highlight( if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { let expanded = element_to_highlight.as_token().unwrap().clone(); - if highlight_injection(current, &sema, token, expanded).is_some() { + if highlight_injection(&mut stack, &sema, token, expanded).is_some() { continue; } } @@ -169,19 +208,11 @@ pub(crate) fn highlight( if let Some((highlight, binding_hash)) = highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) { - current.push(HighlightedRange { range, highlight, binding_hash }); + stack.add(HighlightedRange { range, highlight, binding_hash }); } } - assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); - let mut res = res.pop().unwrap(); - res.sort_by_key(|range| range.range.start()); - // Check that ranges are sorted and disjoint - assert!(res - .iter() - .zip(res.iter().skip(1)) - .all(|(left, right)| left.range.end() <= right.range.start())); - res + stack.flattened() } fn macro_call_range(macro_call: &ast::MacroCall) -> Option { @@ -358,7 +389,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { } fn highlight_injection( - acc: &mut Vec, + acc: &mut HighlightedRangeStack, sema: &Semantics, literal: ast::RawString, expanded: SyntaxToken, @@ -373,7 +404,7 @@ fn highlight_injection( let (analysis, tmp_file_id) = Analysis::from_single_file(value); if let Some(range) = literal.open_quote_text_range() { - acc.push(HighlightedRange { + acc.add(HighlightedRange { range, highlight: HighlightTag::StringLiteral.into(), binding_hash: None, @@ -383,12 +414,12 @@ fn highlight_injection( for mut h in analysis.highlight(tmp_file_id).unwrap() { if let Some(r) = literal.map_range_up(h.range) { h.range = r; - acc.push(h) + acc.add(h) } } if let Some(range) = literal.close_quote_text_range() { - acc.push(HighlightedRange { + acc.add(HighlightedRange { range, highlight: HighlightTag::StringLiteral.into(), binding_hash: None, -- cgit v1.2.3 From ac798e1f7cfbc6d27c87bb28e3f1d5b6801796aa Mon Sep 17 00:00:00 2001 From: Leander Tentrup Date: Fri, 17 Apr 2020 09:37:18 +0200 Subject: Implement syntax highlighting for format strings Detailed changes: 1) Implement a lexer for string literals that divides the string in format specifier `{}` including the format specifier modifier. 2) Adapt syntax highlighting to add ranges for the detected sequences. 3) Add a test case for the format string syntax highlighting. --- crates/ra_ide/src/snapshots/highlight_strings.html | 77 ++++++++++++++++++++++ crates/ra_ide/src/syntax_highlighting.rs | 69 ++++++++++++++++++- crates/ra_ide/src/syntax_highlighting/tests.rs | 65 ++++++++++++++++++ 3 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 crates/ra_ide/src/snapshots/highlight_strings.html (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html new file mode 100644 index 000000000..d70627da0 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_strings.html @@ -0,0 +1,77 @@ + + +
macro_rules! println {
+    ($($arg:tt)*) => ({
+        $crate::io::_print($crate::format_args_nl!($($arg)*));
+    })
+}
+#[rustc_builtin_macro]
+macro_rules! format_args_nl {
+    ($fmt:expr) => {{ /* compiler built-in */ }};
+    ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
+}
+
+fn main() {
+    // from https://doc.rust-lang.org/std/fmt/index.html
+    println!("Hello");                 // => "Hello"
+    println!("Hello, {}!", "world");   // => "Hello, world!"
+    println!("The number is {}", 1);   // => "The number is 1"
+    println!("{:?}", (3, 4));          // => "(3, 4)"
+    println!("{value}", value=4);      // => "4"
+    println!("{} {}", 1, 2);           // => "1 2"
+    println!("{:04}", 42);             // => "0042" with leading zerosV
+    println!("{1} {} {0} {}", 1, 2);   // => "2 1 1 2"
+    println!("{argument}", argument = "test");   // => "test"
+    println!("{name} {}", 1, name = 2);          // => "2 1"
+    println!("{a} {c} {b}", a="a", b='b', c=3);  // => "a 3 b"
+    println!("Hello {:5}!", "x");
+    println!("Hello {:1$}!", "x", 5);
+    println!("Hello {1:0$}!", 5, "x");
+    println!("Hello {:width$}!", "x", width = 5);
+    println!("Hello {:<5}!", "x");
+    println!("Hello {:-<5}!", "x");
+    println!("Hello {:^5}!", "x");
+    println!("Hello {:>5}!", "x");
+    println!("Hello {:+}!", 5);
+    println!("{:#x}!", 27);
+    println!("Hello {:05}!", 5);
+    println!("Hello {:05}!", -5);
+    println!("{:#010x}!", 27);
+    println!("Hello {0} is {1:.5}", "x", 0.01);
+    println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
+    println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
+    println!("Hello {} is {:.*}",    "x", 5, 0.01);
+    println!("Hello {} is {2:.*}",   "x", 5, 0.01);
+    println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
+    println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
+    println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
+    println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
+    println!("Hello {{}}");
+    println!("{{ Hello");
+}
\ No newline at end of file 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::{ }; use ra_prof::profile; use ra_syntax::{ - ast::{self, HasQuotes, HasStringValue}, + ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, SyntaxToken, TextRange, WalkEvent, T, @@ -21,6 +21,7 @@ use rustc_hash::FxHashMap; use crate::{call_info::call_info_for_token, Analysis, FileId}; +use ast::FormatSpecifier; pub(crate) use html::highlight_as_html; pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; @@ -95,7 +96,8 @@ impl HighlightedRangeStack { 1, "after DFS traversal, the stack should only contain a single element" ); - let res = self.stack.pop().unwrap(); + let mut res = self.stack.pop().unwrap(); + res.sort_by_key(|range| range.range.start()); // Check that ranges are sorted and disjoint assert!(res .iter() @@ -134,6 +136,7 @@ pub(crate) fn highlight( let mut stack = HighlightedRangeStack::new(); let mut current_macro_call: Option = None; + let mut format_string: Option = None; // Walk all nodes, keeping track of whether we are inside a macro or not. // If in macro, expand it first and highlight the expanded code. @@ -169,6 +172,7 @@ pub(crate) fn highlight( WalkEvent::Leave(Some(mc)) => { assert!(current_macro_call == Some(mc)); current_macro_call = None; + format_string = None; continue; } _ => (), @@ -189,6 +193,30 @@ pub(crate) fn highlight( }; let token = sema.descend_into_macros(token.clone()); let parent = token.parent(); + + // Check if macro takes a format string and remeber it for highlighting later. + // The macros that accept a format string expand to a compiler builtin macros + // `format_args` and `format_args_nl`. + if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { + if let Some(name) = + fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) + { + match name.text().as_str() { + "format_args" | "format_args_nl" => { + format_string = parent + .children_with_tokens() + .filter(|t| t.kind() != WHITESPACE) + .nth(1) + .filter(|e| { + ast::String::can_cast(e.kind()) + || ast::RawString::can_cast(e.kind()) + }) + } + _ => {} + } + } + } + // We only care Name and Name_ref match (token.kind(), parent.kind()) { (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), @@ -205,10 +233,45 @@ pub(crate) fn highlight( } } + let is_format_string = + format_string.as_ref().map(|fs| fs == &element_to_highlight).unwrap_or_default(); + if let Some((highlight, binding_hash)) = - highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) + highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) { stack.add(HighlightedRange { range, highlight, binding_hash }); + if let Some(string) = + element_to_highlight.as_token().cloned().and_then(ast::String::cast) + { + stack.push(); + if is_format_string { + string.lex_format_specifier(&mut |piece_range, kind| { + let highlight = match kind { + FormatSpecifier::Open + | FormatSpecifier::Close + | FormatSpecifier::Colon + | FormatSpecifier::Fill + | FormatSpecifier::Align + | FormatSpecifier::Sign + | FormatSpecifier::NumberSign + | FormatSpecifier::DollarSign + | FormatSpecifier::Dot + | FormatSpecifier::Asterisk + | FormatSpecifier::QuestionMark => HighlightTag::Attribute, + FormatSpecifier::Integer | FormatSpecifier::Zero => { + HighlightTag::NumericLiteral + } + FormatSpecifier::Identifier => HighlightTag::Local, + }; + stack.add(HighlightedRange { + range: piece_range + range.start(), + highlight: highlight.into(), + binding_hash: None, + }); + }); + } + stack.pop(); + } } } diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 73611e23a..f198767ce 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs @@ -168,3 +168,68 @@ macro_rules! test {} ); let _ = analysis.highlight(file_id).unwrap(); } + +#[test] +fn test_string_highlighting() { + // The format string detection is based on macro-expansion, + // thus, we have to copy the macro definition from `std` + let (analysis, file_id) = single_file( + r#" +macro_rules! println { + ($($arg:tt)*) => ({ + $crate::io::_print($crate::format_args_nl!($($arg)*)); + }) +} +#[rustc_builtin_macro] +macro_rules! format_args_nl { + ($fmt:expr) => {{ /* compiler built-in */ }}; + ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }}; +} + +fn main() { + // from https://doc.rust-lang.org/std/fmt/index.html + println!("Hello"); // => "Hello" + println!("Hello, {}!", "world"); // => "Hello, world!" + println!("The number is {}", 1); // => "The number is 1" + println!("{:?}", (3, 4)); // => "(3, 4)" + println!("{value}", value=4); // => "4" + println!("{} {}", 1, 2); // => "1 2" + println!("{:04}", 42); // => "0042" with leading zerosV + println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2" + println!("{argument}", argument = "test"); // => "test" + println!("{name} {}", 1, name = 2); // => "2 1" + println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" + println!("Hello {:5}!", "x"); + println!("Hello {:1$}!", "x", 5); + println!("Hello {1:0$}!", 5, "x"); + println!("Hello {:width$}!", "x", width = 5); + println!("Hello {:<5}!", "x"); + println!("Hello {:-<5}!", "x"); + println!("Hello {:^5}!", "x"); + println!("Hello {:>5}!", "x"); + println!("Hello {:+}!", 5); + println!("{:#x}!", 27); + println!("Hello {:05}!", 5); + println!("Hello {:05}!", -5); + println!("{:#010x}!", 27); + println!("Hello {0} is {1:.5}", "x", 0.01); + println!("Hello {1} is {2:.0$}", 5, "x", 0.01); + println!("Hello {0} is {2:.1$}", "x", 5, 0.01); + println!("Hello {} is {:.*}", "x", 5, 0.01); + println!("Hello {} is {2:.*}", "x", 5, 0.01); + println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01); + println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56); + println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56"); + println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); + println!("Hello {{}}"); + println!("{{ Hello"); +}"# + .trim(), + ); + + let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); + let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); + let expected_html = &read_text(&dst_file); + fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); +} -- cgit v1.2.3 From ba8faf3efc7a3a373571f98569699bbe684779b3 Mon Sep 17 00:00:00 2001 From: Timo Freiberg Date: Sat, 18 Apr 2020 16:12:21 +0200 Subject: Add target file information to AssistAction --- crates/ra_ide/src/assists.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs index 40d56a4f7..2b5d11681 100644 --- a/crates/ra_ide/src/assists.rs +++ b/crates/ra_ide/src/assists.rs @@ -37,6 +37,10 @@ fn action_to_edit( file_id: FileId, assist_label: &AssistLabel, ) -> SourceChange { + let file_id = match action.file { + ra_assists::AssistFile::TargetFile(it) => it, + _ => file_id, + }; let file_edit = SourceFileEdit { file_id, edit: action.edit }; SourceChange::source_file_edit(assist_label.label.clone(), file_edit) .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) -- cgit v1.2.3 From b2829a52161bc414f3b361c06b66633a234bba16 Mon Sep 17 00:00:00 2001 From: Leander Tentrup Date: Wed, 22 Apr 2020 10:08:46 +0200 Subject: Apply suggestions from code review Co-Authored-By: bjorn3 --- crates/ra_ide/src/syntax_highlighting.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index e342ca9df..8ee3a78c6 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -194,7 +194,7 @@ pub(crate) fn highlight( let token = sema.descend_into_macros(token.clone()); let parent = token.parent(); - // Check if macro takes a format string and remeber it for highlighting later. + // Check if macro takes a format string and remember it for highlighting later. // The macros that accept a format string expand to a compiler builtin macros // `format_args` and `format_args_nl`. if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { @@ -233,8 +233,7 @@ pub(crate) fn highlight( } } - let is_format_string = - format_string.as_ref().map(|fs| fs == &element_to_highlight).unwrap_or_default(); + let is_format_string = format_string.as_ref() == Some(&element_to_highlight); if let Some((highlight, binding_hash)) = highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) @@ -245,7 +244,7 @@ pub(crate) fn highlight( { stack.push(); if is_format_string { - string.lex_format_specifier(&mut |piece_range, kind| { + string.lex_format_specifier(|piece_range, kind| { let highlight = match kind { FormatSpecifier::Open | FormatSpecifier::Close -- cgit v1.2.3 From 445052f6d426043b543033f3fa4594fc1a09d7fa Mon Sep 17 00:00:00 2001 From: Leander Tentrup Date: Wed, 22 Apr 2020 15:28:35 +0200 Subject: Adapt format specifier highlighting to support escaped squences and unicode identifiers --- crates/ra_ide/src/snapshots/highlight_strings.html | 5 ++ crates/ra_ide/src/syntax_highlighting.rs | 63 ++++++++++++++-------- crates/ra_ide/src/syntax_highlighting/tests.rs | 5 ++ 3 files changed, 51 insertions(+), 22 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index d70627da0..433f2e0c5 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html @@ -74,4 +74,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); println!("Hello {{}}"); println!("{{ Hello"); + + println!(r"Hello, {}!", "world"); + + println!("{\x41}", A = 92); + println!("{ничоси}", ничоси = 92); } \ No newline at end of file diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 8ee3a78c6..b5fd3390f 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -245,28 +245,29 @@ pub(crate) fn highlight( stack.push(); if is_format_string { string.lex_format_specifier(|piece_range, kind| { - let highlight = match kind { - FormatSpecifier::Open - | FormatSpecifier::Close - | FormatSpecifier::Colon - | FormatSpecifier::Fill - | FormatSpecifier::Align - | FormatSpecifier::Sign - | FormatSpecifier::NumberSign - | FormatSpecifier::DollarSign - | FormatSpecifier::Dot - | FormatSpecifier::Asterisk - | FormatSpecifier::QuestionMark => HighlightTag::Attribute, - FormatSpecifier::Integer | FormatSpecifier::Zero => { - HighlightTag::NumericLiteral - } - FormatSpecifier::Identifier => HighlightTag::Local, - }; - stack.add(HighlightedRange { - range: piece_range + range.start(), - highlight: highlight.into(), - binding_hash: None, - }); + if let Some(highlight) = highlight_format_specifier(kind) { + stack.add(HighlightedRange { + range: piece_range + range.start(), + highlight: highlight.into(), + binding_hash: None, + }); + } + }); + } + stack.pop(); + } else if let Some(string) = + element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) + { + stack.push(); + if is_format_string { + string.lex_format_specifier(|piece_range, kind| { + if let Some(highlight) = highlight_format_specifier(kind) { + stack.add(HighlightedRange { + range: piece_range + range.start(), + highlight: highlight.into(), + binding_hash: None, + }); + } }); } stack.pop(); @@ -277,6 +278,24 @@ pub(crate) fn highlight( stack.flattened() } +fn highlight_format_specifier(kind: FormatSpecifier) -> Option { + Some(match kind { + FormatSpecifier::Open + | FormatSpecifier::Close + | FormatSpecifier::Colon + | FormatSpecifier::Fill + | FormatSpecifier::Align + | FormatSpecifier::Sign + | FormatSpecifier::NumberSign + | FormatSpecifier::DollarSign + | FormatSpecifier::Dot + | FormatSpecifier::Asterisk + | FormatSpecifier::QuestionMark => HighlightTag::Attribute, + FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, + FormatSpecifier::Identifier => HighlightTag::Local, + }) +} + fn macro_call_range(macro_call: &ast::MacroCall) -> Option { let path = macro_call.path()?; let name_ref = path.segment()?.name_ref()?; diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index f198767ce..a9aae957f 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs @@ -223,6 +223,11 @@ fn main() { println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56"); println!("Hello {{}}"); println!("{{ Hello"); + + println!(r"Hello, {}!", "world"); + + println!("{\x41}", A = 92); + println!("{ничоси}", ничоси = 92); }"# .trim(), ); -- cgit v1.2.3