Skip to content

Commit 1d72903

Browse files
Merge #2883
2883: Implement Syntax Highlight inside macro call r=matklad a=edwin0cheng Co-authored-by: Edwin Cheng <[email protected]>
2 parents 4f95064 + f320af4 commit 1d72903

File tree

4 files changed

+208
-111
lines changed

4 files changed

+208
-111
lines changed

crates/ra_ide/src/expand.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ pub(crate) fn descend_into_macros(
7979
let source_analyzer =
8080
hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None);
8181

82+
descend_into_macros_with_analyzer(db, &source_analyzer, src)
83+
}
84+
85+
pub(crate) fn descend_into_macros_with_analyzer(
86+
db: &RootDatabase,
87+
source_analyzer: &hir::SourceAnalyzer,
88+
src: InFile<SyntaxToken>,
89+
) -> InFile<SyntaxToken> {
8290
successors(Some(src), |token| {
8391
let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
8492
let tt = macro_call.token_tree()?;

crates/ra_ide/src/snapshots/highlighting.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@
3434
<span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;();
3535
}
3636

37+
<span class="macro">macro_rules</span><span class="macro">!</span> def_fn {
38+
($($tt:tt)*) =&gt; {$($tt)*}
39+
}
40+
41+
<span class="macro">def_fn</span><span class="macro">!</span>{
42+
<span class="keyword">fn</span> <span class="function">bar</span>() -&gt; <span class="type.builtin">u32</span> {
43+
<span class="literal.numeric">100</span>
44+
}
45+
}
46+
3747
<span class="comment">// comment</span>
3848
<span class="keyword">fn</span> <span class="function">main</span>() {
3949
<span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>);

crates/ra_ide/src/snapshots/rainbow_highlighting.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@
2424
.keyword\.control { color: #F0DFAF; font-weight: bold; }
2525
</style>
2626
<pre><code><span class="keyword">fn</span> <span class="function">main</span>() {
27-
<span class="keyword">let</span> <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>;
28-
<span class="keyword">let</span> <span class="variable" data-binding-hash="14702933417323009544" style="color: hsl(108,90%,49%);">x</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.to_string();
29-
<span class="keyword">let</span> <span class="variable" data-binding-hash="5443150872754369068" style="color: hsl(215,43%,43%);">y</span> = <span class="variable" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span>.to_string();
27+
<span class="keyword">let</span> <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span> = <span class="string">"hello"</span>;
28+
<span class="keyword">let</span> <span class="variable" data-binding-hash="4303609361109701698" style="color: hsl(242,75%,88%);">x</span> = <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span>.to_string();
29+
<span class="keyword">let</span> <span class="variable" data-binding-hash="13865792086344377029" style="color: hsl(340,64%,86%);">y</span> = <span class="variable" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span>.to_string();
3030

31-
<span class="keyword">let</span> <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span> = <span class="string">"other color please!"</span>;
32-
<span class="keyword">let</span> <span class="variable" data-binding-hash="2073121142529774969" style="color: hsl(320,43%,74%);">y</span> = <span class="variable" data-binding-hash="17358108296605513516" style="color: hsl(331,46%,60%);">x</span>.to_string();
31+
<span class="keyword">let</span> <span class="variable" data-binding-hash="7011301204224269512" style="color: hsl(198,45%,40%);">x</span> = <span class="string">"other color please!"</span>;
32+
<span class="keyword">let</span> <span class="variable" data-binding-hash="12461245066629867975" style="color: hsl(132,91%,68%);">y</span> = <span class="variable" data-binding-hash="7011301204224269512" style="color: hsl(198,45%,40%);">x</span>.to_string();
3333
}
3434

3535
<span class="keyword">fn</span> <span class="function">bar</span>() {
36-
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="8723171760279909834" style="color: hsl(307,91%,75%);">hello</span> = <span class="string">"hello"</span>;
36+
<span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable.mut" data-binding-hash="2217585909179791122" style="color: hsl(280,74%,48%);">hello</span> = <span class="string">"hello"</span>;
3737
}</code></pre>

crates/ra_ide/src/syntax_highlighting.rs

Lines changed: 184 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
//! FIXME: write short doc here
22
3-
use rustc_hash::{FxHashMap, FxHashSet};
3+
use rustc_hash::FxHashMap;
44

5-
use hir::{InFile, Name, SourceBinder};
5+
use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder};
66
use ra_db::SourceDatabase;
77
use ra_prof::profile;
8-
use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T};
8+
use ra_syntax::{
9+
ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, TextRange,
10+
WalkEvent, T,
11+
};
912

1013
use crate::{
1114
db::RootDatabase,
15+
expand::descend_into_macros_with_analyzer,
1216
references::{
1317
classify_name, classify_name_ref,
1418
NameKind::{self, *},
@@ -72,121 +76,186 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
7276
let parse = db.parse(file_id);
7377
let root = parse.tree().syntax().clone();
7478

75-
fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 {
76-
fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
77-
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
79+
let mut sb = SourceBinder::new(db);
80+
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
81+
let mut res = Vec::new();
82+
let analyzer = sb.analyze(InFile::new(file_id.into(), &root), None);
7883

79-
let mut hasher = DefaultHasher::new();
80-
x.hash(&mut hasher);
81-
hasher.finish()
84+
let mut in_macro_call = None;
85+
86+
for event in root.preorder_with_tokens() {
87+
match event {
88+
WalkEvent::Enter(node) => match node.kind() {
89+
MACRO_CALL => {
90+
in_macro_call = Some(node.clone());
91+
if let Some(range) = highlight_macro(InFile::new(file_id.into(), node)) {
92+
res.push(HighlightedRange { range, tag: tags::MACRO, binding_hash: None });
93+
}
94+
}
95+
_ if in_macro_call.is_some() => {
96+
if let Some(token) = node.as_token() {
97+
if let Some((tag, binding_hash)) = highlight_token_tree(
98+
db,
99+
&mut sb,
100+
&analyzer,
101+
&mut bindings_shadow_count,
102+
InFile::new(file_id.into(), token.clone()),
103+
) {
104+
res.push(HighlightedRange {
105+
range: node.text_range(),
106+
tag,
107+
binding_hash,
108+
});
109+
}
110+
}
111+
}
112+
_ => {
113+
if let Some((tag, binding_hash)) = highlight_node(
114+
db,
115+
&mut sb,
116+
&mut bindings_shadow_count,
117+
InFile::new(file_id.into(), node.clone()),
118+
) {
119+
res.push(HighlightedRange { range: node.text_range(), tag, binding_hash });
120+
}
121+
}
122+
},
123+
WalkEvent::Leave(node) => {
124+
if let Some(m) = in_macro_call.as_ref() {
125+
if *m == node {
126+
in_macro_call = None;
127+
}
128+
}
129+
}
82130
}
131+
}
83132

84-
hash((file_id, name, shadow_count))
133+
res
134+
}
135+
136+
fn highlight_macro(node: InFile<SyntaxElement>) -> Option<TextRange> {
137+
let macro_call = ast::MacroCall::cast(node.value.as_node()?.clone())?;
138+
let path = macro_call.path()?;
139+
let name_ref = path.segment()?.name_ref()?;
140+
141+
let range_start = name_ref.syntax().text_range().start();
142+
let mut range_end = name_ref.syntax().text_range().end();
143+
for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
144+
match sibling.kind() {
145+
T![!] | IDENT => range_end = sibling.text_range().end(),
146+
_ => (),
147+
}
85148
}
86149

87-
let mut sb = SourceBinder::new(db);
150+
Some(TextRange::from_to(range_start, range_end))
151+
}
88152

89-
// Visited nodes to handle highlighting priorities
90-
// FIXME: retain only ranges here
91-
let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default();
92-
let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
153+
fn highlight_token_tree(
154+
db: &RootDatabase,
155+
sb: &mut SourceBinder<RootDatabase>,
156+
analyzer: &SourceAnalyzer,
157+
bindings_shadow_count: &mut FxHashMap<Name, u32>,
158+
token: InFile<SyntaxToken>,
159+
) -> Option<(&'static str, Option<u64>)> {
160+
if token.value.parent().kind() != TOKEN_TREE {
161+
return None;
162+
}
163+
let token = descend_into_macros_with_analyzer(db, analyzer, token);
164+
let expanded = {
165+
let parent = token.value.parent();
166+
// We only care Name and Name_ref
167+
match (token.value.kind(), parent.kind()) {
168+
(IDENT, NAME) | (IDENT, NAME_REF) => token.with_value(parent.into()),
169+
_ => token.map(|it| it.into()),
170+
}
171+
};
93172

94-
let mut res = Vec::new();
95-
for node in root.descendants_with_tokens() {
96-
if highlighted.contains(&node) {
97-
continue;
173+
highlight_node(db, sb, bindings_shadow_count, expanded)
174+
}
175+
176+
fn highlight_node(
177+
db: &RootDatabase,
178+
sb: &mut SourceBinder<RootDatabase>,
179+
bindings_shadow_count: &mut FxHashMap<Name, u32>,
180+
node: InFile<SyntaxElement>,
181+
) -> Option<(&'static str, Option<u64>)> {
182+
let mut binding_hash = None;
183+
let tag = match node.value.kind() {
184+
FN_DEF => {
185+
bindings_shadow_count.clear();
186+
return None;
98187
}
99-
let mut binding_hash = None;
100-
let tag = match node.kind() {
101-
FN_DEF => {
102-
bindings_shadow_count.clear();
103-
continue;
104-
}
105-
COMMENT => tags::LITERAL_COMMENT,
106-
STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING,
107-
ATTR => tags::LITERAL_ATTRIBUTE,
108-
// Special-case field init shorthand
109-
NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD,
110-
NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => continue,
111-
NAME_REF => {
112-
let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
113-
let name_kind = classify_name_ref(&mut sb, InFile::new(file_id.into(), &name_ref))
114-
.map(|d| d.kind);
115-
match name_kind {
116-
Some(name_kind) => {
117-
if let Local(local) = &name_kind {
118-
if let Some(name) = local.name(db) {
119-
let shadow_count =
120-
bindings_shadow_count.entry(name.clone()).or_default();
121-
binding_hash =
122-
Some(calc_binding_hash(file_id, &name, *shadow_count))
123-
}
124-
};
125-
126-
highlight_name(db, name_kind)
127-
}
128-
_ => continue,
129-
}
130-
}
131-
NAME => {
132-
let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap();
133-
let name_kind =
134-
classify_name(&mut sb, InFile::new(file_id.into(), &name)).map(|d| d.kind);
135-
136-
if let Some(Local(local)) = &name_kind {
137-
if let Some(name) = local.name(db) {
138-
let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
139-
*shadow_count += 1;
140-
binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count))
141-
}
142-
};
143-
144-
match name_kind {
145-
Some(name_kind) => highlight_name(db, name_kind),
146-
None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() {
147-
STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE,
148-
TYPE_PARAM => tags::TYPE_PARAM,
149-
RECORD_FIELD_DEF => tags::FIELD,
150-
_ => tags::FUNCTION,
151-
}),
188+
COMMENT => tags::LITERAL_COMMENT,
189+
STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING,
190+
ATTR => tags::LITERAL_ATTRIBUTE,
191+
// Special-case field init shorthand
192+
NAME_REF if node.value.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD,
193+
NAME_REF if node.value.ancestors().any(|it| it.kind() == ATTR) => return None,
194+
NAME_REF => {
195+
let name_ref = node.value.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
196+
let name_kind = classify_name_ref(sb, node.with_value(&name_ref)).map(|d| d.kind);
197+
match name_kind {
198+
Some(name_kind) => {
199+
if let Local(local) = &name_kind {
200+
if let Some(name) = local.name(db) {
201+
let shadow_count =
202+
bindings_shadow_count.entry(name.clone()).or_default();
203+
binding_hash =
204+
Some(calc_binding_hash(node.file_id, &name, *shadow_count))
205+
}
206+
};
207+
208+
highlight_name(db, name_kind)
152209
}
210+
_ => return None,
153211
}
154-
INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC,
155-
BYTE => tags::LITERAL_BYTE,
156-
CHAR => tags::LITERAL_CHAR,
157-
LIFETIME => tags::TYPE_LIFETIME,
158-
T![unsafe] => tags::KEYWORD_UNSAFE,
159-
k if is_control_keyword(k) => tags::KEYWORD_CONTROL,
160-
k if k.is_keyword() => tags::KEYWORD,
161-
_ => {
162-
if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) {
163-
if let Some(path) = macro_call.path() {
164-
if let Some(segment) = path.segment() {
165-
if let Some(name_ref) = segment.name_ref() {
166-
highlighted.insert(name_ref.syntax().clone().into());
167-
let range_start = name_ref.syntax().text_range().start();
168-
let mut range_end = name_ref.syntax().text_range().end();
169-
for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
170-
match sibling.kind() {
171-
T![!] | IDENT => range_end = sibling.text_range().end(),
172-
_ => (),
173-
}
174-
}
175-
res.push(HighlightedRange {
176-
range: TextRange::from_to(range_start, range_end),
177-
tag: tags::MACRO,
178-
binding_hash: None,
179-
})
180-
}
181-
}
182-
}
212+
}
213+
NAME => {
214+
let name = node.value.as_node().cloned().and_then(ast::Name::cast).unwrap();
215+
let name_kind = classify_name(sb, node.with_value(&name)).map(|d| d.kind);
216+
217+
if let Some(Local(local)) = &name_kind {
218+
if let Some(name) = local.name(db) {
219+
let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
220+
*shadow_count += 1;
221+
binding_hash = Some(calc_binding_hash(node.file_id, &name, *shadow_count))
183222
}
184-
continue;
223+
};
224+
225+
match name_kind {
226+
Some(name_kind) => highlight_name(db, name_kind),
227+
None => name.syntax().parent().map_or(tags::FUNCTION, |x| match x.kind() {
228+
STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => tags::TYPE,
229+
TYPE_PARAM => tags::TYPE_PARAM,
230+
RECORD_FIELD_DEF => tags::FIELD,
231+
_ => tags::FUNCTION,
232+
}),
185233
}
186-
};
187-
res.push(HighlightedRange { range: node.text_range(), tag, binding_hash })
234+
}
235+
INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC,
236+
BYTE => tags::LITERAL_BYTE,
237+
CHAR => tags::LITERAL_CHAR,
238+
LIFETIME => tags::TYPE_LIFETIME,
239+
T![unsafe] => tags::KEYWORD_UNSAFE,
240+
k if is_control_keyword(k) => tags::KEYWORD_CONTROL,
241+
k if k.is_keyword() => tags::KEYWORD,
242+
243+
_ => return None,
244+
};
245+
246+
return Some((tag, binding_hash));
247+
248+
fn calc_binding_hash(file_id: HirFileId, name: &Name, shadow_count: u32) -> u64 {
249+
fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
250+
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
251+
252+
let mut hasher = DefaultHasher::new();
253+
x.hash(&mut hasher);
254+
hasher.finish()
255+
}
256+
257+
hash((file_id, name, shadow_count))
188258
}
189-
res
190259
}
191260

192261
pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
@@ -331,6 +400,16 @@ fn foo<T>() -> T {
331400
foo::<i32>();
332401
}
333402
403+
macro_rules! def_fn {
404+
($($tt:tt)*) => {$($tt)*}
405+
}
406+
407+
def_fn!{
408+
fn bar() -> u32 {
409+
100
410+
}
411+
}
412+
334413
// comment
335414
fn main() {
336415
println!("Hello, {}!", 92);

0 commit comments

Comments
 (0)