aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorLeander Tentrup <[email protected]>2020-04-17 08:37:18 +0100
committerLeander Tentrup <[email protected]>2020-04-20 10:19:15 +0100
commitac798e1f7cfbc6d27c87bb28e3f1d5b6801796aa (patch)
tree958ccd22ae5e75047b8189f69cf5878eeb9611ea /crates
parent29a846464b63259f5152d61a5520bffcc2cb8703 (diff)
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.
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html77
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs69
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs65
-rw-r--r--crates/ra_syntax/src/ast/tokens.rs324
4 files changed, 532 insertions, 3 deletions
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 @@
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}</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 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
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 {}
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 .trim(),
228 );
229
230 let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html");
231 let actual_html = &analysis.highlight_as_html(file_id, false).unwrap();
232 let expected_html = &read_text(&dst_file);
233 fs::write(dst_file, &actual_html).unwrap();
234 assert_eq_text!(expected_html, actual_html);
235}
diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs
index e8320b57e..ec3b4e553 100644
--- a/crates/ra_syntax/src/ast/tokens.rs
+++ b/crates/ra_syntax/src/ast/tokens.rs
@@ -172,3 +172,327 @@ impl RawString {
172 Some(range + contents_range.start()) 172 Some(range + contents_range.start())
173 } 173 }
174} 174}
175
176#[derive(Debug)]
177pub enum FormatSpecifier {
178 Open,
179 Close,
180 Integer,
181 Identifier,
182 Colon,
183 Fill,
184 Align,
185 Sign,
186 NumberSign,
187 Zero,
188 DollarSign,
189 Dot,
190 Asterisk,
191 QuestionMark,
192}
193
194pub trait HasFormatSpecifier: AstToken {
195 fn lex_format_specifier<F>(&self, callback: &mut F)
196 where
197 F: FnMut(TextRange, FormatSpecifier),
198 {
199 let src = self.text().as_str();
200 let initial_len = src.len();
201 let mut chars = src.chars();
202
203 while let Some(first_char) = chars.next() {
204 match first_char {
205 '{' => {
206 // Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
207 if chars.clone().next() == Some('{') {
208 // Escaped format specifier, `{{`
209 chars.next();
210 continue;
211 }
212
213 let start = initial_len - chars.as_str().len() - first_char.len_utf8();
214 let end = initial_len - chars.as_str().len();
215 callback(
216 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
217 FormatSpecifier::Open,
218 );
219
220 let next_char = if let Some(c) = chars.clone().next() {
221 c
222 } else {
223 break;
224 };
225
226 // check for integer/identifier
227 match next_char {
228 '0'..='9' => {
229 // integer
230 read_integer(&mut chars, initial_len, callback);
231 }
232 'a'..='z' | 'A'..='Z' | '_' => {
233 // identifier
234 read_identifier(&mut chars, initial_len, callback);
235 }
236 _ => {}
237 }
238
239 if chars.clone().next() == Some(':') {
240 skip_char_and_emit(
241 &mut chars,
242 initial_len,
243 FormatSpecifier::Colon,
244 callback,
245 );
246
247 // check for fill/align
248 let mut cloned = chars.clone().take(2);
249 let first = cloned.next().unwrap_or_default();
250 let second = cloned.next().unwrap_or_default();
251 match second {
252 '<' | '^' | '>' => {
253 // alignment specifier, first char specifies fillment
254 skip_char_and_emit(
255 &mut chars,
256 initial_len,
257 FormatSpecifier::Fill,
258 callback,
259 );
260 skip_char_and_emit(
261 &mut chars,
262 initial_len,
263 FormatSpecifier::Align,
264 callback,
265 );
266 }
267 _ => match first {
268 '<' | '^' | '>' => {
269 skip_char_and_emit(
270 &mut chars,
271 initial_len,
272 FormatSpecifier::Align,
273 callback,
274 );
275 }
276 _ => {}
277 },
278 }
279
280 // check for sign
281 match chars.clone().next().unwrap_or_default() {
282 '+' | '-' => {
283 skip_char_and_emit(
284 &mut chars,
285 initial_len,
286 FormatSpecifier::Sign,
287 callback,
288 );
289 }
290 _ => {}
291 }
292
293 // check for `#`
294 if let Some('#') = chars.clone().next() {
295 skip_char_and_emit(
296 &mut chars,
297 initial_len,
298 FormatSpecifier::NumberSign,
299 callback,
300 );
301 }
302
303 // check for `0`
304 let mut cloned = chars.clone().take(2);
305 let first = cloned.next();
306 let second = cloned.next();
307
308 if first == Some('0') && second != Some('$') {
309 skip_char_and_emit(
310 &mut chars,
311 initial_len,
312 FormatSpecifier::Zero,
313 callback,
314 );
315 }
316
317 // width
318 match chars.clone().next().unwrap_or_default() {
319 '0'..='9' => {
320 read_integer(&mut chars, initial_len, callback);
321 if chars.clone().next() == Some('$') {
322 skip_char_and_emit(
323 &mut chars,
324 initial_len,
325 FormatSpecifier::DollarSign,
326 callback,
327 );
328 }
329 }
330 'a'..='z' | 'A'..='Z' | '_' => {
331 read_identifier(&mut chars, initial_len, callback);
332 if chars.clone().next() != Some('$') {
333 continue;
334 }
335 skip_char_and_emit(
336 &mut chars,
337 initial_len,
338 FormatSpecifier::DollarSign,
339 callback,
340 );
341 }
342 _ => {}
343 }
344
345 // precision
346 if chars.clone().next() == Some('.') {
347 skip_char_and_emit(
348 &mut chars,
349 initial_len,
350 FormatSpecifier::Dot,
351 callback,
352 );
353
354 match chars.clone().next().unwrap_or_default() {
355 '*' => {
356 skip_char_and_emit(
357 &mut chars,
358 initial_len,
359 FormatSpecifier::Asterisk,
360 callback,
361 );
362 }
363 '0'..='9' => {
364 read_integer(&mut chars, initial_len, callback);
365 if chars.clone().next() == Some('$') {
366 skip_char_and_emit(
367 &mut chars,
368 initial_len,
369 FormatSpecifier::DollarSign,
370 callback,
371 );
372 }
373 }
374 'a'..='z' | 'A'..='Z' | '_' => {
375 read_identifier(&mut chars, initial_len, callback);
376 if chars.clone().next() != Some('$') {
377 continue;
378 }
379 skip_char_and_emit(
380 &mut chars,
381 initial_len,
382 FormatSpecifier::DollarSign,
383 callback,
384 );
385 }
386 _ => {
387 continue;
388 }
389 }
390 }
391
392 // type
393 match chars.clone().next().unwrap_or_default() {
394 '?' => {
395 skip_char_and_emit(
396 &mut chars,
397 initial_len,
398 FormatSpecifier::QuestionMark,
399 callback,
400 );
401 }
402 'a'..='z' | 'A'..='Z' | '_' => {
403 read_identifier(&mut chars, initial_len, callback);
404 }
405 _ => {}
406 }
407 }
408
409 let mut cloned = chars.clone().take(2);
410 let first = cloned.next();
411 let second = cloned.next();
412 if first != Some('}') {
413 continue;
414 }
415 if second == Some('}') {
416 // Escaped format end specifier, `}}`
417 continue;
418 }
419 skip_char_and_emit(&mut chars, initial_len, FormatSpecifier::Close, callback);
420 }
421 _ => {
422 while let Some(next_char) = chars.clone().next() {
423 match next_char {
424 '{' => break,
425 _ => {}
426 }
427 chars.next();
428 }
429 }
430 };
431 }
432
433 fn skip_char_and_emit<F>(
434 chars: &mut std::str::Chars,
435 initial_len: usize,
436 emit: FormatSpecifier,
437 callback: &mut F,
438 ) where
439 F: FnMut(TextRange, FormatSpecifier),
440 {
441 let start = initial_len - chars.as_str().len();
442 chars.next();
443 let end = initial_len - chars.as_str().len();
444 callback(
445 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
446 emit,
447 );
448 }
449
450 fn read_integer<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
451 where
452 F: FnMut(TextRange, FormatSpecifier),
453 {
454 let start = initial_len - chars.as_str().len();
455 chars.next();
456 while let Some(next_char) = chars.clone().next() {
457 match next_char {
458 '0'..='9' => {
459 chars.next();
460 }
461 _ => {
462 break;
463 }
464 }
465 }
466 let end = initial_len - chars.as_str().len();
467 callback(
468 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
469 FormatSpecifier::Integer,
470 );
471 }
472 fn read_identifier<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
473 where
474 F: FnMut(TextRange, FormatSpecifier),
475 {
476 let start = initial_len - chars.as_str().len();
477 chars.next();
478 while let Some(next_char) = chars.clone().next() {
479 match next_char {
480 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {
481 chars.next();
482 }
483 _ => {
484 break;
485 }
486 }
487 }
488 let end = initial_len - chars.as_str().len();
489 callback(
490 TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
491 FormatSpecifier::Identifier,
492 );
493 }
494 }
495}
496
497impl HasFormatSpecifier for String {}
498impl HasFormatSpecifier for RawString {}