Skip to content

Commit f08c96f

Browse files
committed
Implement syntax highlighting for format strings
In the syntax highlighting logic, we search for format macro names from std and then do a regex-based search for the format modifiers `{}`.
1 parent cce49e2 commit f08c96f

File tree

6 files changed

+57
-4
lines changed

6 files changed

+57
-4
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_ide/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ itertools = "0.9.0"
1717
log = "0.4.8"
1818
rustc-hash = "1.1.0"
1919
rand = { version = "0.7.3", features = ["small_rng"] }
20+
regex = "1.3.6"
21+
lazy_static = "1.4.0"
2022

2123
stdx = { path = "../stdx" }
2224

crates/ra_ide/src/snapshots/highlight_injection.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
<span class="function">fixture</span>(<span class="string_literal">r#"</span>
3333
<span class="keyword">trait</span> <span class="trait declaration">Foo</span> {
3434
<span class="keyword">fn</span> <span class="function declaration">foo</span>() {
35-
<span class="macro">println!</span>(<span class="string_literal">"2 + 2 = {}"</span>, <span class="numeric_literal">4</span>);
35+
<span class="macro">println!</span>(<span class="string_literal">"2 + </span><span class="attribute">{}</span><span class="string_literal"> = </span><span class="attribute">{}</span><span class="string_literal">"</span>, <span class="numeric_literal">2</span>, <span class="numeric_literal">4</span>);
36+
<span class="macro">format!</span>(<span class="string_literal">"2 + </span><span class="attribute">{:?}</span><span class="string_literal"> }}= </span><span class="attribute">{}</span><span class="string_literal">"</span>, <span class="numeric_literal">2</span>, <span class="numeric_literal">4</span>);
37+
<span class="macro">print!</span>(<span class="string_literal">r"2 + {{ </span><span class="attribute">{:#?}</span><span class="string_literal"> = </span><span class="attribute">{}</span><span class="string_literal"> {{}}"</span>, <span class="numeric_literal">2</span>, <span class="numeric_literal">4</span>);
3638
}
3739
}<span class="string_literal">"#</span>
3840
);

crates/ra_ide/src/snapshots/highlighting.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848

4949
<span class="comment">// comment</span>
5050
<span class="keyword">fn</span> <span class="function declaration">main</span>() {
51-
<span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
51+
<span class="macro">println!</span>(<span class="string_literal">"Hello, </span><span class="attribute">{}</span><span class="string_literal">!"</span>, <span class="numeric_literal">92</span>);
5252

5353
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">vec</span> = Vec::new();
5454
<span class="keyword control">if</span> <span class="keyword">true</span> {

crates/ra_ide/src/syntax_highlighting.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod html;
66
mod tests;
77

88
use hir::{Name, Semantics};
9+
use lazy_static::lazy_static;
910
use ra_ide_db::{
1011
defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
1112
RootDatabase,
@@ -15,8 +16,9 @@ use ra_syntax::{
1516
ast::{self, HasQuotes, HasStringValue},
1617
AstNode, AstToken, Direction, NodeOrToken, SyntaxElement,
1718
SyntaxKind::*,
18-
SyntaxToken, TextRange, WalkEvent, T,
19+
SyntaxToken, TextRange, TextUnit, WalkEvent, T,
1920
};
21+
use regex::Regex;
2022
use rustc_hash::FxHashMap;
2123

2224
use crate::{call_info::call_info_for_token, Analysis, FileId};
@@ -134,6 +136,7 @@ pub(crate) fn highlight(
134136
let mut stack = HighlightedRangeStack::new();
135137

136138
let mut current_macro_call: Option<ast::MacroCall> = None;
139+
let mut format_string: Option<SyntaxElement> = None;
137140

138141
// Walk all nodes, keeping track of whether we are inside a macro or not.
139142
// If in macro, expand it first and highlight the expanded code.
@@ -169,6 +172,7 @@ pub(crate) fn highlight(
169172
WalkEvent::Leave(Some(mc)) => {
170173
assert!(current_macro_call == Some(mc));
171174
current_macro_call = None;
175+
format_string = None;
172176
continue;
173177
}
174178
_ => (),
@@ -189,6 +193,23 @@ pub(crate) fn highlight(
189193
};
190194
let token = sema.descend_into_macros(token.clone());
191195
let parent = token.parent();
196+
197+
// Check if macro takes a format string and remember the string
198+
if let Some(name) = current_macro_call
199+
.as_ref()
200+
.and_then(|m| m.path())
201+
.and_then(|p| p.segment())
202+
.and_then(|s| s.name_ref())
203+
{
204+
if ["format", "println", "print", "eprint", "eprintln", "write", "writeln"]
205+
.contains(&name.text().as_str())
206+
{
207+
format_string = parent.children_with_tokens().nth(1).filter(|e| {
208+
ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind())
209+
});
210+
}
211+
}
212+
192213
// We only care Name and Name_ref
193214
match (token.kind(), parent.kind()) {
194215
(IDENT, NAME) | (IDENT, NAME_REF) => parent.into(),
@@ -205,10 +226,34 @@ pub(crate) fn highlight(
205226
}
206227
}
207228

229+
let is_format_string =
230+
format_string.as_ref().map(|fs| fs == &element_to_highlight).unwrap_or_default();
231+
208232
if let Some((highlight, binding_hash)) =
209233
highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight)
210234
{
211235
stack.add(HighlightedRange { range, highlight, binding_hash });
236+
if is_format_string {
237+
stack.push();
238+
lazy_static! {
239+
static ref RE: Regex =
240+
Regex::new(r"[^\{](?P<format>\{(?:\}|[^\{].*?\}))[^\}]").unwrap();
241+
}
242+
for (start, end) in RE
243+
.captures_iter(&format_string.as_ref().unwrap().to_string())
244+
.flat_map(|matches| matches.name("format").map(|m| (m.start(), m.end())))
245+
{
246+
stack.add(HighlightedRange {
247+
range: TextRange::from_to(
248+
range.start() + TextUnit::from_usize(start),
249+
range.start() + TextUnit::from_usize(end),
250+
),
251+
highlight: HighlightTag::Attribute.into(),
252+
binding_hash: None,
253+
});
254+
}
255+
stack.pop();
256+
}
212257
}
213258
}
214259

crates/ra_ide/src/syntax_highlighting/tests.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ fn main() {
142142
fixture(r#"
143143
trait Foo {
144144
fn foo() {
145-
println!("2 + 2 = {}", 4);
145+
println!("2 + {} = {}", 2, 4);
146+
format!("2 + {:?} }}= {}", 2, 4);
147+
print!(r"2 + {{ {:#?} = {} {{}}", 2, 4);
146148
}
147149
}"#
148150
);

0 commit comments

Comments
 (0)