mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 13:25:09 +00:00
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
4 changed files with 532 additions and 3 deletions
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_ide_db::{
|
||||||
};
|
};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{self, HasQuotes, HasStringValue},
|
ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue},
|
||||||
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
|
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
SyntaxToken, TextRange, WalkEvent, T,
|
SyntaxToken, TextRange, WalkEvent, T,
|
||||||
|
@ -21,6 +21,7 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::{call_info::call_info_for_token, Analysis, FileId};
|
use crate::{call_info::call_info_for_token, Analysis, FileId};
|
||||||
|
|
||||||
|
use ast::FormatSpecifier;
|
||||||
pub(crate) use html::highlight_as_html;
|
pub(crate) use html::highlight_as_html;
|
||||||
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
|
pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag};
|
||||||
|
|
||||||
|
@ -95,7 +96,8 @@ impl HighlightedRangeStack {
|
||||||
1,
|
1,
|
||||||
"after DFS traversal, the stack should only contain a single element"
|
"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
|
// Check that ranges are sorted and disjoint
|
||||||
assert!(res
|
assert!(res
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -134,6 +136,7 @@ pub(crate) fn highlight(
|
||||||
let mut stack = HighlightedRangeStack::new();
|
let mut stack = HighlightedRangeStack::new();
|
||||||
|
|
||||||
let mut current_macro_call: Option<ast::MacroCall> = None;
|
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.
|
// 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.
|
// If in macro, expand it first and highlight the expanded code.
|
||||||
|
@ -169,6 +172,7 @@ pub(crate) fn highlight(
|
||||||
WalkEvent::Leave(Some(mc)) => {
|
WalkEvent::Leave(Some(mc)) => {
|
||||||
assert!(current_macro_call == Some(mc));
|
assert!(current_macro_call == Some(mc));
|
||||||
current_macro_call = None;
|
current_macro_call = None;
|
||||||
|
format_string = None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -189,6 +193,30 @@ pub(crate) fn highlight(
|
||||||
};
|
};
|
||||||
let token = sema.descend_into_macros(token.clone());
|
let token = sema.descend_into_macros(token.clone());
|
||||||
let parent = token.parent();
|
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
|
// We only care Name and Name_ref
|
||||||
match (token.kind(), parent.kind()) {
|
match (token.kind(), parent.kind()) {
|
||||||
(IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
|
(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)) =
|
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 });
|
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();
|
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 @@ impl RawString {
|
||||||
Some(range + contents_range.start())
|
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…
Add table
Add a link
Reference in a new issue