diff options
Diffstat (limited to 'crates/ra_ide/src/syntax_highlighting.rs')
-rw-r--r-- | crates/ra_ide/src/syntax_highlighting.rs | 289 |
1 files changed, 184 insertions, 105 deletions
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 | ||
3 | use rustc_hash::{FxHashMap, FxHashSet}; | 3 | use rustc_hash::FxHashMap; |
4 | 4 | ||
5 | use hir::{InFile, Name, SourceBinder}; | 5 | use hir::{HirFileId, InFile, Name, SourceAnalyzer, SourceBinder}; |
6 | use ra_db::SourceDatabase; | 6 | use ra_db::SourceDatabase; |
7 | use ra_prof::profile; | 7 | 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 | }; | ||
9 | 12 | ||
10 | use crate::{ | 13 | use 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 | |||
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 | } | ||
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 | 153 | fn 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; | 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; | ||
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 | ||
192 | pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { | 261 | pub(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 | ||
403 | macro_rules! def_fn { | ||
404 | ($($tt:tt)*) => {$($tt)*} | ||
405 | } | ||
406 | |||
407 | def_fn!{ | ||
408 | fn bar() -> u32 { | ||
409 | 100 | ||
410 | } | ||
411 | } | ||
412 | |||
334 | // comment | 413 | // comment |
335 | fn main() { | 414 | fn main() { |
336 | println!("Hello, {}!", 92); | 415 | println!("Hello, {}!", 92); |