aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdwin Cheng <[email protected]>2020-01-20 16:06:47 +0000
committerEdwin Cheng <[email protected]>2020-01-20 16:06:47 +0000
commitf320af4d63302d2933b37794826f705f13caf8a0 (patch)
treef0f91857370da8b07f293c05d4f918b3197c1c35
parent648241ee930de08ba70b0b5c2172dfb3cc7a34c6 (diff)
Implement Syntax Highlight inside Macro
-rw-r--r--crates/ra_ide/src/expand.rs8
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html10
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html12
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs289
4 files changed, 208 insertions, 111 deletions
diff --git a/crates/ra_ide/src/expand.rs b/crates/ra_ide/src/expand.rs
index b82259a3d..831438c09 100644
--- a/crates/ra_ide/src/expand.rs
+++ b/crates/ra_ide/src/expand.rs
@@ -79,6 +79,14 @@ pub(crate) fn descend_into_macros(
79 let source_analyzer = 79 let source_analyzer =
80 hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None); 80 hir::SourceAnalyzer::new(db, src.with_value(src.value.parent()).as_ref(), None);
81 81
82 descend_into_macros_with_analyzer(db, &source_analyzer, src)
83}
84
85pub(crate) fn descend_into_macros_with_analyzer(
86 db: &RootDatabase,
87 source_analyzer: &hir::SourceAnalyzer,
88 src: InFile<SyntaxToken>,
89) -> InFile<SyntaxToken> {
82 successors(Some(src), |token| { 90 successors(Some(src), |token| {
83 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; 91 let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?;
84 let tt = macro_call.token_tree()?; 92 let tt = macro_call.token_tree()?;
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 1d130544f..1cc55e78b 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -34,6 +34,16 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
34 <span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;(); 34 <span class="function">foo</span>::&lt;<span class="type.builtin">i32</span>&gt;();
35} 35}
36 36
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
37<span class="comment">// comment</span> 47<span class="comment">// comment</span>
38<span class="keyword">fn</span> <span class="function">main</span>() { 48<span class="keyword">fn</span> <span class="function">main</span>() {
39 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>); 49 <span class="macro">println</span><span class="macro">!</span>(<span class="string">"Hello, {}!"</span>, <span class="literal.numeric">92</span>);
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index d90ee8540..918fd4b97 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -24,14 +24,14 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
24.keyword\.control { color: #F0DFAF; font-weight: bold; } 24.keyword\.control { color: #F0DFAF; font-weight: bold; }
25</style> 25</style>
26<pre><code><span class="keyword">fn</span> <span class="function">main</span>() { 26<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>; 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="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(); 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="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(); 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();
30 30
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>; 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="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(); 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();
33} 33}
34 34
35<span class="keyword">fn</span> <span class="function">bar</span>() { 35<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>;
37}</code></pre> \ No newline at end of file 37}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index 0411977b9..530b984fc 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -1,14 +1,18 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use rustc_hash::{FxHashMap, FxHashSet}; 3use rustc_hash::FxHashMap;
4 4
5use hir::{InFile, Name, SourceBinder}; 5use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder};
6use ra_db::SourceDatabase; 6use ra_db::SourceDatabase;
7use ra_prof::profile; 7use ra_prof::profile;
8use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T}; 8use ra_syntax::{
9 ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxToken, TextRange,
10 WalkEvent, T,
11};
9 12
10use crate::{ 13use crate::{
11 db::RootDatabase, 14 db::RootDatabase,
15 expand::descend_into_macros_with_analyzer,
12 references::{ 16 references::{
13 classify_name, classify_name_ref, 17 classify_name, classify_name_ref,
14 NameKind::{self, *}, 18 NameKind::{self, *},
@@ -72,121 +76,186 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
72 let parse = db.parse(file_id); 76 let parse = db.parse(file_id);
73 let root = parse.tree().syntax().clone(); 77 let root = parse.tree().syntax().clone();
74 78
75 fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 { 79 let mut sb = SourceBinder::new(db);
76 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { 80 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default();
77 use std::{collections::hash_map::DefaultHasher, hash::Hasher}; 81 let mut res = Vec::new();
82 let analyzer = sb.analyze(InFile::new(file_id.into(), &root), None);
78 83
79 let mut hasher = DefaultHasher::new(); 84 let mut in_macro_call = None;
80 x.hash(&mut hasher); 85
81 hasher.finish() 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 }
82 } 130 }
131 }
83 132
84 hash((file_id, name, shadow_count)) 133 res
134}
135
136fn 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 }
85 } 148 }
86 149
87 let mut sb = SourceBinder::new(db); 150 Some(TextRange::from_to(range_start, range_end))
151}
88 152
89 // Visited nodes to handle highlighting priorities 153fn highlight_token_tree(
90 // FIXME: retain only ranges here 154 db: &RootDatabase,
91 let mut highlighted: FxHashSet<SyntaxElement> = FxHashSet::default(); 155 sb: &mut SourceBinder<RootDatabase>,
92 let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); 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 };
93 172
94 let mut res = Vec::new(); 173 highlight_node(db, sb, bindings_shadow_count, expanded)
95 for node in root.descendants_with_tokens() { 174}
96 if highlighted.contains(&node) { 175
97 continue; 176fn 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;
98 } 187 }
99 let mut binding_hash = None; 188 COMMENT => tags::LITERAL_COMMENT,
100 let tag = match node.kind() { 189 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING,
101 FN_DEF => { 190 ATTR => tags::LITERAL_ATTRIBUTE,
102 bindings_shadow_count.clear(); 191 // Special-case field init shorthand
103 continue; 192 NAME_REF if node.value.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD,
104 } 193 NAME_REF if node.value.ancestors().any(|it| it.kind() == ATTR) => return None,
105 COMMENT => tags::LITERAL_COMMENT, 194 NAME_REF => {
106 STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => tags::LITERAL_STRING, 195 let name_ref = node.value.as_node().cloned().and_then(ast::NameRef::cast).unwrap();
107 ATTR => tags::LITERAL_ATTRIBUTE, 196 let name_kind = classify_name_ref(sb, node.with_value(&name_ref)).map(|d| d.kind);
108 // Special-case field init shorthand 197 match name_kind {
109 NAME_REF if node.parent().and_then(ast::RecordField::cast).is_some() => tags::FIELD, 198 Some(name_kind) => {
110 NAME_REF if node.ancestors().any(|it| it.kind() == ATTR) => continue, 199 if let Local(local) = &name_kind {
111 NAME_REF => { 200 if let Some(name) = local.name(db) {
112 let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); 201 let shadow_count =
113 let name_kind = classify_name_ref(&mut sb, InFile::new(file_id.into(), &name_ref)) 202 bindings_shadow_count.entry(name.clone()).or_default();
114 .map(|d| d.kind); 203 binding_hash =
115 match name_kind { 204 Some(calc_binding_hash(node.file_id, &name, *shadow_count))
116 Some(name_kind) => { 205 }
117 if let Local(local) = &name_kind { 206 };
118 if let Some(name) = local.name(db) { 207
119 let shadow_count = 208 highlight_name(db, name_kind)
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 }),
152 } 209 }
210 _ => return None,
153 } 211 }
154 INT_NUMBER | FLOAT_NUMBER => tags::LITERAL_NUMERIC, 212 }
155 BYTE => tags::LITERAL_BYTE, 213 NAME => {
156 CHAR => tags::LITERAL_CHAR, 214 let name = node.value.as_node().cloned().and_then(ast::Name::cast).unwrap();
157 LIFETIME => tags::TYPE_LIFETIME, 215 let name_kind = classify_name(sb, node.with_value(&name)).map(|d| d.kind);
158 T![unsafe] => tags::KEYWORD_UNSAFE, 216
159 k if is_control_keyword(k) => tags::KEYWORD_CONTROL, 217 if let Some(Local(local)) = &name_kind {
160 k if k.is_keyword() => tags::KEYWORD, 218 if let Some(name) = local.name(db) {
161 _ => { 219 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
162 if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) { 220 *shadow_count += 1;
163 if let Some(path) = macro_call.path() { 221 binding_hash = Some(calc_binding_hash(node.file_id, &name, *shadow_count))
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 }
183 } 222 }
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 }),
185 } 233 }
186 }; 234 }
187 res.push(HighlightedRange { range: node.text_range(), tag, binding_hash }) 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))
188 } 258 }
189 res
190} 259}
191 260
192pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { 261pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
@@ -331,6 +400,16 @@ fn foo<T>() -> T {
331 foo::<i32>(); 400 foo::<i32>();
332} 401}
333 402
403macro_rules! def_fn {
404 ($($tt:tt)*) => {$($tt)*}
405}
406
407def_fn!{
408 fn bar() -> u32 {
409 100
410 }
411}
412
334// comment 413// comment
335fn main() { 414fn main() {
336 println!("Hello, {}!", 92); 415 println!("Hello, {}!", 92);