aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/assists.rs4
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html82
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs224
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs70
4 files changed, 324 insertions, 56 deletions
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(
37 file_id: FileId, 37 file_id: FileId,
38 assist_label: &AssistLabel, 38 assist_label: &AssistLabel,
39) -> SourceChange { 39) -> SourceChange {
40 let file_id = match action.file {
41 ra_assists::AssistFile::TargetFile(it) => it,
42 _ => file_id,
43 };
40 let file_edit = SourceFileEdit { file_id, edit: action.edit }; 44 let file_edit = SourceFileEdit { file_id, edit: action.edit };
41 SourceChange::source_file_edit(assist_label.label.clone(), file_edit) 45 SourceChange::source_file_edit(assist_label.label.clone(), file_edit)
42 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id })) 46 .with_cursor_opt(action.cursor_position.map(|offset| FilePosition { offset, file_id }))
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..433f2e0c5
--- /dev/null
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -0,0 +1,82 @@
1
2<style>
3body { margin: 0; }
4pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
5
6.lifetime { color: #DFAF8F; font-style: italic; }
7.comment { color: #7F9F7F; }
8.struct, .enum { color: #7CB8BB; }
9.enum_variant { color: #BDE0F3; }
10.string_literal { color: #CC9393; }
11.field { color: #94BFF3; }
12.function { color: #93E0E3; }
13.parameter { color: #94BFF3; }
14.text { color: #DCDCCC; }
15.type { color: #7CB8BB; }
16.builtin_type { color: #8CD0D3; }
17.type_param { color: #DFAF8F; }
18.attribute { color: #94BFF3; }
19.numeric_literal { color: #BFEBBF; }
20.macro { color: #94BFF3; }
21.module { color: #AFD8AF; }
22.variable { color: #DCDCCC; }
23.mutable { text-decoration: underline; }
24
25.keyword { color: #F0DFAF; font-weight: bold; }
26.keyword.unsafe { color: #BC8383; font-weight: bold; }
27.control { font-style: italic; }
28</style>
29<pre><code><span class="macro">macro_rules!</span> println {
30 ($($arg:tt)*) =&gt; ({
31 $<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
32 })
33}
34#[rustc_builtin_macro]
35<span class="macro">macro_rules!</span> format_args_nl {
36 ($fmt:expr) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
37 ($fmt:expr, $($args:tt)*) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
38}
39
40<span class="keyword">fn</span> <span class="function declaration">main</span>() {
41 <span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
42 <span class="macro">println!</span>(<span class="string_literal">"Hello"</span>); <span class="comment">// =&gt; "Hello"</span>
43 <span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); <span class="comment">// =&gt; "Hello, world!"</span>
44 <span class="macro">println!</span>(<span class="string_literal">"The number is </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>); <span class="comment">// =&gt; "The number is 1"</span>
45 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">?</span><span class="attribute">}</span><span class="string_literal">"</span>, (<span class="numeric_literal">3</span>, <span class="numeric_literal">4</span>)); <span class="comment">// =&gt; "(3, 4)"</span>
46 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">value</span><span class="attribute">}</span><span class="string_literal">"</span>, value=<span class="numeric_literal">4</span>); <span class="comment">// =&gt; "4"</span>
47 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "1 2"</span>
48 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">4</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">42</span>); <span class="comment">// =&gt; "0042" with leading zerosV</span>
49 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1 1 2"</span>
50 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">argument</span><span class="attribute">}</span><span class="string_literal">"</span>, argument = <span class="string_literal">"test"</span>); <span class="comment">// =&gt; "test"</span>
51 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">1</span>, name = <span class="numeric_literal">2</span>); <span class="comment">// =&gt; "2 1"</span>
52 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">a</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">c</span><span class="attribute">}</span><span class="string_literal"> </span><span class="attribute">{</span><span class="variable">b</span><span class="attribute">}</span><span class="string_literal">"</span>, a=<span class="string_literal">"a"</span>, b=<span class="char_literal">'b'</span>, c=<span class="numeric_literal">3</span>); <span class="comment">// =&gt; "a 3 b"</span>
53 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
54 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>);
55 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>);
56 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="variable">width</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>, width = <span class="numeric_literal">5</span>);
57 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
58 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">-</span><span class="attribute">&lt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
59 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">^</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
60 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"x"</span>);
61 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">+</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
62 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="variable">x</span><span class="string_literal">}!"</span>, <span class="numeric_literal">27</span>);
63 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">5</span>);
64 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">:</span><span class="numeric_literal">0</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">!"</span>, -<span class="numeric_literal">5</span>);
65 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">#</span><span class="numeric_literal">0</span><span class="numeric_literal">10</span><span class="variable">x</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="numeric_literal">27</span>);
66 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">5</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
67 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">1</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">0</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="numeric_literal">5</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">0.01</span>);
68 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="numeric_literal">0</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="numeric_literal">1</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
69 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
70 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="numeric_literal">2</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, <span class="numeric_literal">5</span>, <span class="numeric_literal">0.01</span>);
71 <span class="macro">println!</span>(<span class="string_literal">"Hello </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal"> is </span><span class="attribute">{</span><span class="variable">number</span><span class="attribute">:</span><span class="attribute">.</span><span class="variable">prec</span><span class="attribute">$</span><span class="attribute">}</span><span class="string_literal">"</span>, <span class="string_literal">"x"</span>, prec = <span class="numeric_literal">5</span>, number = <span class="numeric_literal">0.01</span>);
72 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 fractional digits"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="numeric_literal">1234.56</span>);
73 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
74 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">, `</span><span class="attribute">{</span><span class="variable">name</span><span class="attribute">:</span><span class="attribute">&gt;</span><span class="numeric_literal">8</span><span class="attribute">.</span><span class="attribute">*</span><span class="attribute">}</span><span class="string_literal">` has 3 right-aligned characters"</span>, <span class="string_literal">"Hello"</span>, <span class="numeric_literal">3</span>, name=<span class="string_literal">"1234.56"</span>);
75 <span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
76 <span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
77
78 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="attribute">{</span><span class="attribute">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
79
80 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">\x41</span><span class="attribute">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
81 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="attribute">{</span><span class="variable">ничоси</span><span class="attribute">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
82}</code></pre> \ 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 d9912155b..c0728bfb1 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::ActiveParameter, Analysis, FileId}; 22use crate::{call_info::ActiveParameter, 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
@@ -31,6 +32,81 @@ pub struct HighlightedRange {
31 pub binding_hash: Option<u64>, 32 pub binding_hash: Option<u64>,
32} 33}
33 34
35#[derive(Debug)]
36struct HighlightedRangeStack {
37 stack: Vec<Vec<HighlightedRange>>,
38}
39
40/// We use a stack to implement the flattening logic for the highlighted
41/// syntax ranges.
42impl HighlightedRangeStack {
43 fn new() -> Self {
44 Self { stack: vec![Vec::new()] }
45 }
46
47 fn push(&mut self) {
48 self.stack.push(Vec::new());
49 }
50
51 /// Flattens the highlighted ranges.
52 ///
53 /// For example `#[cfg(feature = "foo")]` contains the nested ranges:
54 /// 1) parent-range: Attribute [0, 23)
55 /// 2) child-range: String [16, 21)
56 ///
57 /// The following code implements the flattening, for our example this results to:
58 /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
59 fn pop(&mut self) {
60 let children = self.stack.pop().unwrap();
61 let prev = self.stack.last_mut().unwrap();
62 let needs_flattening = !children.is_empty()
63 && !prev.is_empty()
64 && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
65 if !needs_flattening {
66 prev.extend(children);
67 } else {
68 let mut parent = prev.pop().unwrap();
69 for ele in children {
70 assert!(ele.range.is_subrange(&parent.range));
71 let mut cloned = parent.clone();
72 parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
73 cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
74 if !parent.range.is_empty() {
75 prev.push(parent);
76 }
77 prev.push(ele);
78 parent = cloned;
79 }
80 if !parent.range.is_empty() {
81 prev.push(parent);
82 }
83 }
84 }
85
86 fn add(&mut self, range: HighlightedRange) {
87 self.stack
88 .last_mut()
89 .expect("during DFS traversal, the stack must not be empty")
90 .push(range)
91 }
92
93 fn flattened(mut self) -> Vec<HighlightedRange> {
94 assert_eq!(
95 self.stack.len(),
96 1,
97 "after DFS traversal, the stack should only contain a single element"
98 );
99 let mut res = self.stack.pop().unwrap();
100 res.sort_by_key(|range| range.range.start());
101 // Check that ranges are sorted and disjoint
102 assert!(res
103 .iter()
104 .zip(res.iter().skip(1))
105 .all(|(left, right)| left.range.end() <= right.range.start()));
106 res
107 }
108}
109
34pub(crate) fn highlight( 110pub(crate) fn highlight(
35 db: &RootDatabase, 111 db: &RootDatabase,
36 file_id: FileId, 112 file_id: FileId,
@@ -57,52 +133,18 @@ pub(crate) fn highlight(
57 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 133 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
58 // We use a stack for the DFS traversal below. 134 // We use a stack for the DFS traversal below.
59 // When we leave a node, the we use it to flatten the highlighted ranges. 135 // When we leave a node, the we use it to flatten the highlighted ranges.
60 let mut res: Vec<Vec<HighlightedRange>> = vec![Vec::new()]; 136 let mut stack = HighlightedRangeStack::new();
61 137
62 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;
63 140
64 // 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.
65 // If in macro, expand it first and highlight the expanded code. 142 // If in macro, expand it first and highlight the expanded code.
66 for event in root.preorder_with_tokens() { 143 for event in root.preorder_with_tokens() {
67 match &event { 144 match &event {
68 WalkEvent::Enter(_) => res.push(Vec::new()), 145 WalkEvent::Enter(_) => stack.push(),
69 WalkEvent::Leave(_) => { 146 WalkEvent::Leave(_) => stack.pop(),
70 /* Flattens the highlighted ranges.
71 *
72 * For example `#[cfg(feature = "foo")]` contains the nested ranges:
73 * 1) parent-range: Attribute [0, 23)
74 * 2) child-range: String [16, 21)
75 *
76 * The following code implements the flattening, for our example this results to:
77 * `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]`
78 */
79 let children = res.pop().unwrap();
80 let prev = res.last_mut().unwrap();
81 let needs_flattening = !children.is_empty()
82 && !prev.is_empty()
83 && children.first().unwrap().range.is_subrange(&prev.last().unwrap().range);
84 if !needs_flattening {
85 prev.extend(children);
86 } else {
87 let mut parent = prev.pop().unwrap();
88 for ele in children {
89 assert!(ele.range.is_subrange(&parent.range));
90 let mut cloned = parent.clone();
91 parent.range = TextRange::from_to(parent.range.start(), ele.range.start());
92 cloned.range = TextRange::from_to(ele.range.end(), cloned.range.end());
93 if !parent.range.is_empty() {
94 prev.push(parent);
95 }
96 prev.push(ele);
97 parent = cloned;
98 }
99 if !parent.range.is_empty() {
100 prev.push(parent);
101 }
102 }
103 }
104 }; 147 };
105 let current = res.last_mut().expect("during DFS traversal, the stack must not be empty");
106 148
107 let event_range = match &event { 149 let event_range = match &event {
108 WalkEvent::Enter(it) => it.text_range(), 150 WalkEvent::Enter(it) => it.text_range(),
@@ -119,7 +161,7 @@ pub(crate) fn highlight(
119 WalkEvent::Enter(Some(mc)) => { 161 WalkEvent::Enter(Some(mc)) => {
120 current_macro_call = Some(mc.clone()); 162 current_macro_call = Some(mc.clone());
121 if let Some(range) = macro_call_range(&mc) { 163 if let Some(range) = macro_call_range(&mc) {
122 current.push(HighlightedRange { 164 stack.add(HighlightedRange {
123 range, 165 range,
124 highlight: HighlightTag::Macro.into(), 166 highlight: HighlightTag::Macro.into(),
125 binding_hash: None, 167 binding_hash: None,
@@ -130,6 +172,7 @@ pub(crate) fn highlight(
130 WalkEvent::Leave(Some(mc)) => { 172 WalkEvent::Leave(Some(mc)) => {
131 assert!(current_macro_call == Some(mc)); 173 assert!(current_macro_call == Some(mc));
132 current_macro_call = None; 174 current_macro_call = None;
175 format_string = None;
133 continue; 176 continue;
134 } 177 }
135 _ => (), 178 _ => (),
@@ -150,6 +193,30 @@ pub(crate) fn highlight(
150 }; 193 };
151 let token = sema.descend_into_macros(token.clone()); 194 let token = sema.descend_into_macros(token.clone());
152 let parent = token.parent(); 195 let parent = token.parent();
196
197 // Check if macro takes a format string and remember 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
153 // We only care Name and Name_ref 220 // We only care Name and Name_ref
154 match (token.kind(), parent.kind()) { 221 match (token.kind(), parent.kind()) {
155 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), 222 (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -161,27 +228,72 @@ pub(crate) fn highlight(
161 228
162 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { 229 if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) {
163 let expanded = element_to_highlight.as_token().unwrap().clone(); 230 let expanded = element_to_highlight.as_token().unwrap().clone();
164 if highlight_injection(current, &sema, token, expanded).is_some() { 231 if highlight_injection(&mut stack, &sema, token, expanded).is_some() {
165 continue; 232 continue;
166 } 233 }
167 } 234 }
168 235
236 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
237
169 if let Some((highlight, binding_hash)) = 238 if let Some((highlight, binding_hash)) =
170 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight) 239 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone())
171 { 240 {
172 current.push(HighlightedRange { range, highlight, binding_hash }); 241 stack.add(HighlightedRange { range, highlight, binding_hash });
242 if let Some(string) =
243 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
244 {
245 stack.push();
246 if is_format_string {
247 string.lex_format_specifier(|piece_range, kind| {
248 if let Some(highlight) = highlight_format_specifier(kind) {
249 stack.add(HighlightedRange {
250 range: piece_range + range.start(),
251 highlight: highlight.into(),
252 binding_hash: None,
253 });
254 }
255 });
256 }
257 stack.pop();
258 } else if let Some(string) =
259 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
260 {
261 stack.push();
262 if is_format_string {
263 string.lex_format_specifier(|piece_range, kind| {
264 if let Some(highlight) = highlight_format_specifier(kind) {
265 stack.add(HighlightedRange {
266 range: piece_range + range.start(),
267 highlight: highlight.into(),
268 binding_hash: None,
269 });
270 }
271 });
272 }
273 stack.pop();
274 }
173 } 275 }
174 } 276 }
175 277
176 assert_eq!(res.len(), 1, "after DFS traversal, the stack should only contain a single element"); 278 stack.flattened()
177 let mut res = res.pop().unwrap(); 279}
178 res.sort_by_key(|range| range.range.start()); 280
179 // Check that ranges are sorted and disjoint 281fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> {
180 assert!(res 282 Some(match kind {
181 .iter() 283 FormatSpecifier::Open
182 .zip(res.iter().skip(1)) 284 | FormatSpecifier::Close
183 .all(|(left, right)| left.range.end() <= right.range.start())); 285 | FormatSpecifier::Colon
184 res 286 | FormatSpecifier::Fill
287 | FormatSpecifier::Align
288 | FormatSpecifier::Sign
289 | FormatSpecifier::NumberSign
290 | FormatSpecifier::DollarSign
291 | FormatSpecifier::Dot
292 | FormatSpecifier::Asterisk
293 | FormatSpecifier::QuestionMark => HighlightTag::Attribute,
294 FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral,
295 FormatSpecifier::Identifier => HighlightTag::Local,
296 })
185} 297}
186 298
187fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { 299fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
@@ -359,7 +471,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
359} 471}
360 472
361fn highlight_injection( 473fn highlight_injection(
362 acc: &mut Vec<HighlightedRange>, 474 acc: &mut HighlightedRangeStack,
363 sema: &Semantics<RootDatabase>, 475 sema: &Semantics<RootDatabase>,
364 literal: ast::RawString, 476 literal: ast::RawString,
365 expanded: SyntaxToken, 477 expanded: SyntaxToken,
@@ -372,7 +484,7 @@ fn highlight_injection(
372 let (analysis, tmp_file_id) = Analysis::from_single_file(value); 484 let (analysis, tmp_file_id) = Analysis::from_single_file(value);
373 485
374 if let Some(range) = literal.open_quote_text_range() { 486 if let Some(range) = literal.open_quote_text_range() {
375 acc.push(HighlightedRange { 487 acc.add(HighlightedRange {
376 range, 488 range,
377 highlight: HighlightTag::StringLiteral.into(), 489 highlight: HighlightTag::StringLiteral.into(),
378 binding_hash: None, 490 binding_hash: None,
@@ -382,12 +494,12 @@ fn highlight_injection(
382 for mut h in analysis.highlight(tmp_file_id).unwrap() { 494 for mut h in analysis.highlight(tmp_file_id).unwrap() {
383 if let Some(r) = literal.map_range_up(h.range) { 495 if let Some(r) = literal.map_range_up(h.range) {
384 h.range = r; 496 h.range = r;
385 acc.push(h) 497 acc.add(h)
386 } 498 }
387 } 499 }
388 500
389 if let Some(range) = literal.close_quote_text_range() { 501 if let Some(range) = literal.close_quote_text_range() {
390 acc.push(HighlightedRange { 502 acc.add(HighlightedRange {
391 range, 503 range,
392 highlight: HighlightTag::StringLiteral.into(), 504 highlight: HighlightTag::StringLiteral.into(),
393 binding_hash: None, 505 binding_hash: None,
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 73611e23a..a9aae957f 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -168,3 +168,73 @@ macro_rules! test {}
168 ); 168 );
169 let _ = analysis.highlight(file_id).unwrap(); 169 let _ = analysis.highlight(file_id).unwrap();
170} 170}
171
172#[test]
173fn test_string_highlighting() {
174 // The format string detection is based on macro-expansion,
175 // thus, we have to copy the macro definition from `std`
176 let (analysis, file_id) = single_file(
177 r#"
178macro_rules! println {
179 ($($arg:tt)*) => ({
180 $crate::io::_print($crate::format_args_nl!($($arg)*));
181 })
182}
183#[rustc_builtin_macro]
184macro_rules! format_args_nl {
185 ($fmt:expr) => {{ /* compiler built-in */ }};
186 ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
187}
188
189fn main() {
190 // from https://doc.rust-lang.org/std/fmt/index.html
191 println!("Hello"); // => "Hello"
192 println!("Hello, {}!", "world"); // => "Hello, world!"
193 println!("The number is {}", 1); // => "The number is 1"
194 println!("{:?}", (3, 4)); // => "(3, 4)"
195 println!("{value}", value=4); // => "4"
196 println!("{} {}", 1, 2); // => "1 2"
197 println!("{:04}", 42); // => "0042" with leading zerosV
198 println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
199 println!("{argument}", argument = "test"); // => "test"
200 println!("{name} {}", 1, name = 2); // => "2 1"
201 println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
202 println!("Hello {:5}!", "x");
203 println!("Hello {:1$}!", "x", 5);
204 println!("Hello {1:0$}!", 5, "x");
205 println!("Hello {:width$}!", "x", width = 5);
206 println!("Hello {:<5}!", "x");
207 println!("Hello {:-<5}!", "x");
208 println!("Hello {:^5}!", "x");
209 println!("Hello {:>5}!", "x");
210 println!("Hello {:+}!", 5);
211 println!("{:#x}!", 27);
212 println!("Hello {:05}!", 5);
213 println!("Hello {:05}!", -5);
214 println!("{:#010x}!", 27);
215 println!("Hello {0} is {1:.5}", "x", 0.01);
216 println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
217 println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
218 println!("Hello {} is {:.*}", "x", 5, 0.01);
219 println!("Hello {} is {2:.*}", "x", 5, 0.01);
220 println!("Hello {} is {number:.prec$}", "x", prec = 5, number = 0.01);
221 println!("{}, `{name:.*}` has 3 fractional digits", "Hello", 3, name=1234.56);
222 println!("{}, `{name:.*}` has 3 characters", "Hello", 3, name="1234.56");
223 println!("{}, `{name:>8.*}` has 3 right-aligned characters", "Hello", 3, name="1234.56");
224 println!("Hello {{}}");
225 println!("{{ Hello");
226
227 println!(r"Hello, {}!", "world");
228
229 println!("{\x41}", A = 92);
230 println!("{ничоси}", ничоси = 92);
231}"#
232 .trim(),
233 );
234
235 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
236 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
237 let expected_html = &read_text(&dst_file);
238 fs::write(dst_file, &actual_html).unwrap();
239 assert_eq_text!(expected_html, actual_html);
240}