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.
This commit is contained in:
Leander Tentrup 2020-04-17 09:37:18 +02:00
parent 29a846464b
commit ac798e1f7c
4 changed files with 532 additions and 3 deletions

View File

@ -0,0 +1,77 @@
<style>
body { margin: 0; }
pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
.lifetime { color: #DFAF8F; font-style: italic; }
.comment { color: #7F9F7F; }
.struct, .enum { color: #7CB8BB; }
.enum_variant { color: #BDE0F3; }
.string_literal { color: #CC9393; }
.field { color: #94BFF3; }
.function { color: #93E0E3; }
.parameter { color: #94BFF3; }
.text { color: #DCDCCC; }
.type { color: #7CB8BB; }
.builtin_type { color: #8CD0D3; }
.type_param { color: #DFAF8F; }
.attribute { color: #94BFF3; }
.numeric_literal { color: #BFEBBF; }
.macro { color: #94BFF3; }
.module { color: #AFD8AF; }
.variable { color: #DCDCCC; }
.mutable { text-decoration: underline; }
.keyword { color: #F0DFAF; font-weight: bold; }
.keyword.unsafe { color: #BC8383; font-weight: bold; }
.control { font-style: italic; }
</style>
<pre><code><span class="macro">macro_rules!</span> println {
($($arg:tt)*) =&gt; ({
$<span class="keyword">crate</span>::io::_print($<span class="keyword">crate</span>::format_args_nl!($($arg)*));
})
}
#[rustc_builtin_macro]
<span class="macro">macro_rules!</span> format_args_nl {
($fmt:expr) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
($fmt:expr, $($args:tt)*) =&gt; {{ <span class="comment">/* compiler built-in */</span> }};
}
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
<span class="comment">// from https://doc.rust-lang.org/std/fmt/index.html</span>
<span class="macro">println!</span>(<span class="string_literal">"Hello"</span>); <span class="comment">// =&gt; "Hello"</span>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<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>);
<span class="macro">println!</span>(<span class="string_literal">"Hello {{}}"</span>);
<span class="macro">println!</span>(<span class="string_literal">"{{ Hello"</span>);
}</code></pre>

View File

@ -12,7 +12,7 @@
};
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 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 @@ fn flattened(mut self) -> Vec<HighlightedRange> {
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<ast::MacroCall> = None;
let mut format_string: Option<SyntaxElement> = 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();
}
}
}

View File

@ -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);
}

View File

@ -172,3 +172,327 @@ pub fn map_range_up(&self, range: TextRange) -> Option<TextRange> {
Some(range + contents_range.start())
}
}
#[derive(Debug)]
pub enum FormatSpecifier {
Open,
Close,
Integer,
Identifier,
Colon,
Fill,
Align,
Sign,
NumberSign,
Zero,
DollarSign,
Dot,
Asterisk,
QuestionMark,
}
pub trait HasFormatSpecifier: AstToken {
fn lex_format_specifier<F>(&self, callback: &mut F)
where
F: FnMut(TextRange, FormatSpecifier),
{
let src = self.text().as_str();
let initial_len = src.len();
let mut chars = src.chars();
while let Some(first_char) = chars.next() {
match first_char {
'{' => {
// Format specifier, see syntax at https://doc.rust-lang.org/std/fmt/index.html#syntax
if chars.clone().next() == Some('{') {
// Escaped format specifier, `{{`
chars.next();
continue;
}
let start = initial_len - chars.as_str().len() - first_char.len_utf8();
let end = initial_len - chars.as_str().len();
callback(
TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
FormatSpecifier::Open,
);
let next_char = if let Some(c) = chars.clone().next() {
c
} else {
break;
};
// check for integer/identifier
match next_char {
'0'..='9' => {
// integer
read_integer(&mut chars, initial_len, callback);
}
'a'..='z' | 'A'..='Z' | '_' => {
// identifier
read_identifier(&mut chars, initial_len, callback);
}
_ => {}
}
if chars.clone().next() == Some(':') {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Colon,
callback,
);
// check for fill/align
let mut cloned = chars.clone().take(2);
let first = cloned.next().unwrap_or_default();
let second = cloned.next().unwrap_or_default();
match second {
'<' | '^' | '>' => {
// alignment specifier, first char specifies fillment
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Fill,
callback,
);
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Align,
callback,
);
}
_ => match first {
'<' | '^' | '>' => {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Align,
callback,
);
}
_ => {}
},
}
// check for sign
match chars.clone().next().unwrap_or_default() {
'+' | '-' => {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Sign,
callback,
);
}
_ => {}
}
// check for `#`
if let Some('#') = chars.clone().next() {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::NumberSign,
callback,
);
}
// check for `0`
let mut cloned = chars.clone().take(2);
let first = cloned.next();
let second = cloned.next();
if first == Some('0') && second != Some('$') {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Zero,
callback,
);
}
// width
match chars.clone().next().unwrap_or_default() {
'0'..='9' => {
read_integer(&mut chars, initial_len, callback);
if chars.clone().next() == Some('$') {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::DollarSign,
callback,
);
}
}
'a'..='z' | 'A'..='Z' | '_' => {
read_identifier(&mut chars, initial_len, callback);
if chars.clone().next() != Some('$') {
continue;
}
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::DollarSign,
callback,
);
}
_ => {}
}
// precision
if chars.clone().next() == Some('.') {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Dot,
callback,
);
match chars.clone().next().unwrap_or_default() {
'*' => {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::Asterisk,
callback,
);
}
'0'..='9' => {
read_integer(&mut chars, initial_len, callback);
if chars.clone().next() == Some('$') {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::DollarSign,
callback,
);
}
}
'a'..='z' | 'A'..='Z' | '_' => {
read_identifier(&mut chars, initial_len, callback);
if chars.clone().next() != Some('$') {
continue;
}
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::DollarSign,
callback,
);
}
_ => {
continue;
}
}
}
// type
match chars.clone().next().unwrap_or_default() {
'?' => {
skip_char_and_emit(
&mut chars,
initial_len,
FormatSpecifier::QuestionMark,
callback,
);
}
'a'..='z' | 'A'..='Z' | '_' => {
read_identifier(&mut chars, initial_len, callback);
}
_ => {}
}
}
let mut cloned = chars.clone().take(2);
let first = cloned.next();
let second = cloned.next();
if first != Some('}') {
continue;
}
if second == Some('}') {
// Escaped format end specifier, `}}`
continue;
}
skip_char_and_emit(&mut chars, initial_len, FormatSpecifier::Close, callback);
}
_ => {
while let Some(next_char) = chars.clone().next() {
match next_char {
'{' => break,
_ => {}
}
chars.next();
}
}
};
}
fn skip_char_and_emit<F>(
chars: &mut std::str::Chars,
initial_len: usize,
emit: FormatSpecifier,
callback: &mut F,
) where
F: FnMut(TextRange, FormatSpecifier),
{
let start = initial_len - chars.as_str().len();
chars.next();
let end = initial_len - chars.as_str().len();
callback(
TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
emit,
);
}
fn read_integer<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
where
F: FnMut(TextRange, FormatSpecifier),
{
let start = initial_len - chars.as_str().len();
chars.next();
while let Some(next_char) = chars.clone().next() {
match next_char {
'0'..='9' => {
chars.next();
}
_ => {
break;
}
}
}
let end = initial_len - chars.as_str().len();
callback(
TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
FormatSpecifier::Integer,
);
}
fn read_identifier<F>(chars: &mut std::str::Chars, initial_len: usize, callback: &mut F)
where
F: FnMut(TextRange, FormatSpecifier),
{
let start = initial_len - chars.as_str().len();
chars.next();
while let Some(next_char) = chars.clone().next() {
match next_char {
'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {
chars.next();
}
_ => {
break;
}
}
}
let end = initial_len - chars.as_str().len();
callback(
TextRange::from_to(TextUnit::from_usize(start), TextUnit::from_usize(end)),
FormatSpecifier::Identifier,
);
}
}
}
impl HasFormatSpecifier for String {}
impl HasFormatSpecifier for RawString {}