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:
parent
29a846464b
commit
ac798e1f7c
77
crates/ra_ide/src/snapshots/highlight_strings.html
Normal file
77
crates/ra_ide/src/snapshots/highlight_strings.html
Normal 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)*) => ({
|
||||
$<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) => {{ <span class="comment">/* compiler built-in */</span> }};
|
||||
($fmt:expr, $($args:tt)*) => {{ <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">// => "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">// => "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">// => "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">// => "(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">// => "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">// => "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">// => "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">// => "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">// => "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">// => "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">// => "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"><</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="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">></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">></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>
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {}
|
||||
|
Loading…
Reference in New Issue
Block a user