|
1 | 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 | 6 | use ra_db::SourceDatabase;
|
7 | 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 | 13 | use crate::{
|
11 | 14 | db::RootDatabase,
|
| 15 | + expand::descend_into_macros_with_analyzer, |
12 | 16 | references::{
|
13 | 17 | classify_name, classify_name_ref,
|
14 | 18 | NameKind::{self, *},
|
@@ -72,121 +76,186 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec<HighlightedRa
|
72 | 76 | let parse = db.parse(file_id);
|
73 | 77 | let root = parse.tree().syntax().clone();
|
74 | 78 |
|
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); |
78 | 83 |
|
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 | + } |
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 |
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 | + }; |
93 | 172 |
|
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; |
98 | 187 | }
|
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) |
152 | 209 | }
|
| 210 | + _ => return None, |
153 | 211 | }
|
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)) |
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 |
| - }; |
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)) |
188 | 258 | }
|
189 |
| - res |
190 | 259 | }
|
191 | 260 |
|
192 | 261 | pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String {
|
@@ -331,6 +400,16 @@ fn foo<T>() -> T {
|
331 | 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 | 413 | // comment
|
335 | 414 | fn main() {
|
336 | 415 | println!("Hello, {}!", 92);
|
|
0 commit comments