diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-01-09 20:08:41 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-01-09 20:08:41 +0000 |
commit | bcb9ae18de1fa405eb2add56409ae264267607bb (patch) | |
tree | 36a2142b0898a347e052bf5721896c90ef0f9fee | |
parent | 927657432e77bc971f2b853dec7b98b909351541 (diff) | |
parent | eeceff317964af160fb785578d2d5666d9c3efe7 (diff) |
Merge #7223
7223: Refactor highlighting r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/ide/src/syntax_highlighting.rs | 560 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/highlight.rs | 517 | ||||
-rw-r--r-- | crates/ide/src/syntax_highlighting/inject.rs | 2 |
3 files changed, 551 insertions, 528 deletions
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 079248511..b82e3775e 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -3,30 +3,29 @@ pub(crate) mod tags; | |||
3 | mod highlights; | 3 | mod highlights; |
4 | mod injector; | 4 | mod injector; |
5 | 5 | ||
6 | mod highlight; | ||
6 | mod format; | 7 | mod format; |
7 | mod inject; | ||
8 | mod macro_rules; | 8 | mod macro_rules; |
9 | mod inject; | ||
9 | 10 | ||
10 | mod html; | 11 | mod html; |
11 | #[cfg(test)] | 12 | #[cfg(test)] |
12 | mod tests; | 13 | mod tests; |
13 | 14 | ||
14 | use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; | 15 | use hir::{Name, Semantics}; |
15 | use ide_db::{ | 16 | use ide_db::RootDatabase; |
16 | defs::{Definition, NameClass, NameRefClass}, | ||
17 | RootDatabase, | ||
18 | }; | ||
19 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
20 | use syntax::{ | 18 | use syntax::{ |
21 | ast::{self, HasFormatSpecifier}, | 19 | ast::{self, HasFormatSpecifier}, |
22 | AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, | 20 | AstNode, AstToken, Direction, NodeOrToken, |
23 | SyntaxKind::{self, *}, | 21 | SyntaxKind::*, |
24 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 22 | SyntaxNode, TextRange, WalkEvent, T, |
25 | }; | 23 | }; |
26 | 24 | ||
27 | use crate::{ | 25 | use crate::{ |
28 | syntax_highlighting::{ | 26 | syntax_highlighting::{ |
29 | format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, | 27 | format::FormatStringHighlighter, highlights::Highlights, |
28 | macro_rules::MacroRulesHighlighter, tags::Highlight, | ||
30 | }, | 29 | }, |
31 | FileId, HlMod, HlTag, SymbolKind, | 30 | FileId, HlMod, HlTag, SymbolKind, |
32 | }; | 31 | }; |
@@ -73,8 +72,19 @@ pub(crate) fn highlight( | |||
73 | } | 72 | } |
74 | }; | 73 | }; |
75 | 74 | ||
76 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | ||
77 | let mut hl = highlights::Highlights::new(range_to_highlight); | 75 | let mut hl = highlights::Highlights::new(range_to_highlight); |
76 | traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); | ||
77 | hl.to_vec() | ||
78 | } | ||
79 | |||
80 | fn traverse( | ||
81 | hl: &mut Highlights, | ||
82 | sema: &Semantics<RootDatabase>, | ||
83 | root: &SyntaxNode, | ||
84 | range_to_highlight: TextRange, | ||
85 | syntactic_name_ref_highlighting: bool, | ||
86 | ) { | ||
87 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | ||
78 | 88 | ||
79 | let mut current_macro_call: Option<ast::MacroCall> = None; | 89 | let mut current_macro_call: Option<ast::MacroCall> = None; |
80 | let mut current_macro_rules: Option<ast::MacroRules> = None; | 90 | let mut current_macro_rules: Option<ast::MacroRules> = None; |
@@ -128,24 +138,24 @@ pub(crate) fn highlight( | |||
128 | } | 138 | } |
129 | _ => (), | 139 | _ => (), |
130 | } | 140 | } |
131 | |||
132 | match &event { | 141 | match &event { |
133 | // Check for Rust code in documentation | ||
134 | WalkEvent::Leave(NodeOrToken::Node(node)) => { | ||
135 | if ast::Attr::can_cast(node.kind()) { | ||
136 | inside_attribute = false | ||
137 | } | ||
138 | inject::doc_comment(&mut hl, node); | ||
139 | } | ||
140 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { | 142 | WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { |
141 | inside_attribute = true | 143 | inside_attribute = true |
142 | } | 144 | } |
145 | WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { | ||
146 | inside_attribute = false | ||
147 | } | ||
143 | _ => (), | 148 | _ => (), |
144 | } | 149 | } |
145 | 150 | ||
146 | let element = match event { | 151 | let element = match event { |
147 | WalkEvent::Enter(it) => it, | 152 | WalkEvent::Enter(it) => it, |
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(it) => { |
154 | if let Some(node) = it.as_node() { | ||
155 | inject::doc_comment(hl, node); | ||
156 | } | ||
157 | continue; | ||
158 | } | ||
149 | }; | 159 | }; |
150 | 160 | ||
151 | let range = element.text_range(); | 161 | let range = element.text_range(); |
@@ -179,13 +189,13 @@ pub(crate) fn highlight( | |||
179 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { | 189 | if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { |
180 | if token.is_raw() { | 190 | if token.is_raw() { |
181 | let expanded = element_to_highlight.as_token().unwrap().clone(); | 191 | let expanded = element_to_highlight.as_token().unwrap().clone(); |
182 | if inject::ra_fixture(&mut hl, &sema, token, expanded).is_some() { | 192 | if inject::ra_fixture(hl, &sema, token, expanded).is_some() { |
183 | continue; | 193 | continue; |
184 | } | 194 | } |
185 | } | 195 | } |
186 | } | 196 | } |
187 | 197 | ||
188 | if let Some((mut highlight, binding_hash)) = highlight_element( | 198 | if let Some((mut highlight, binding_hash)) = highlight::element( |
189 | &sema, | 199 | &sema, |
190 | &mut bindings_shadow_count, | 200 | &mut bindings_shadow_count, |
191 | syntactic_name_ref_highlighting, | 201 | syntactic_name_ref_highlighting, |
@@ -202,7 +212,7 @@ pub(crate) fn highlight( | |||
202 | if let Some(string) = | 212 | if let Some(string) = |
203 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 213 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
204 | { | 214 | { |
205 | format_string_highlighter.highlight_format_string(&mut hl, &string, range); | 215 | format_string_highlighter.highlight_format_string(hl, &string, range); |
206 | // Highlight escape sequences | 216 | // Highlight escape sequences |
207 | if let Some(char_ranges) = string.char_ranges() { | 217 | if let Some(char_ranges) = string.char_ranges() { |
208 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { | 218 | for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { |
@@ -218,8 +228,6 @@ pub(crate) fn highlight( | |||
218 | } | 228 | } |
219 | } | 229 | } |
220 | } | 230 | } |
221 | |||
222 | hl.to_vec() | ||
223 | } | 231 | } |
224 | 232 | ||
225 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 233 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
@@ -237,505 +245,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
237 | 245 | ||
238 | Some(TextRange::new(range_start, range_end)) | 246 | Some(TextRange::new(range_start, range_end)) |
239 | } | 247 | } |
240 | |||
241 | /// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. | ||
242 | fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool { | ||
243 | while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) { | ||
244 | if parent.kind() != *kind { | ||
245 | return false; | ||
246 | } | ||
247 | |||
248 | // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value | ||
249 | // in the same pattern is unstable: rust-lang/rust#68354. | ||
250 | node = node.parent().unwrap().into(); | ||
251 | kinds = rest; | ||
252 | } | ||
253 | |||
254 | // Only true if we matched all expected kinds | ||
255 | kinds.len() == 0 | ||
256 | } | ||
257 | |||
258 | fn is_consumed_lvalue( | ||
259 | node: NodeOrToken<SyntaxNode, SyntaxToken>, | ||
260 | local: &Local, | ||
261 | db: &RootDatabase, | ||
262 | ) -> bool { | ||
263 | // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. | ||
264 | parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) | ||
265 | } | ||
266 | |||
267 | fn highlight_element( | ||
268 | sema: &Semantics<RootDatabase>, | ||
269 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | ||
270 | syntactic_name_ref_highlighting: bool, | ||
271 | element: SyntaxElement, | ||
272 | ) -> Option<(Highlight, Option<u64>)> { | ||
273 | let db = sema.db; | ||
274 | let mut binding_hash = None; | ||
275 | let highlight: Highlight = match element.kind() { | ||
276 | FN => { | ||
277 | bindings_shadow_count.clear(); | ||
278 | return None; | ||
279 | } | ||
280 | |||
281 | // Highlight definitions depending on the "type" of the definition. | ||
282 | NAME => { | ||
283 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | ||
284 | let name_kind = NameClass::classify(sema, &name); | ||
285 | |||
286 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | ||
287 | if let Some(name) = local.name(db) { | ||
288 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); | ||
289 | *shadow_count += 1; | ||
290 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
291 | } | ||
292 | }; | ||
293 | |||
294 | match name_kind { | ||
295 | Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(), | ||
296 | Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, | ||
297 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | ||
298 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { | ||
299 | let mut h = HlTag::Symbol(SymbolKind::Field).into(); | ||
300 | if let Definition::Field(field) = field_ref { | ||
301 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
302 | h |= HlMod::Unsafe; | ||
303 | } | ||
304 | } | ||
305 | |||
306 | h | ||
307 | } | ||
308 | None => highlight_name_by_syntax(name) | HlMod::Definition, | ||
309 | } | ||
310 | } | ||
311 | |||
312 | // Highlight references like the definitions they resolve to | ||
313 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { | ||
314 | // even though we track whether we are in an attribute or not we still need this special case | ||
315 | // as otherwise we would emit unresolved references for name refs inside attributes | ||
316 | Highlight::from(HlTag::Symbol(SymbolKind::Function)) | ||
317 | } | ||
318 | NAME_REF => { | ||
319 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | ||
320 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | ||
321 | match NameRefClass::classify(sema, &name_ref) { | ||
322 | Some(name_kind) => match name_kind { | ||
323 | NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), | ||
324 | NameRefClass::Definition(def) => { | ||
325 | if let Definition::Local(local) = &def { | ||
326 | if let Some(name) = local.name(db) { | ||
327 | let shadow_count = | ||
328 | bindings_shadow_count.entry(name.clone()).or_default(); | ||
329 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
330 | } | ||
331 | }; | ||
332 | |||
333 | let mut h = highlight_def(db, def); | ||
334 | |||
335 | if let Definition::Local(local) = &def { | ||
336 | if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { | ||
337 | h |= HlMod::Consuming; | ||
338 | } | ||
339 | } | ||
340 | |||
341 | if let Some(parent) = name_ref.syntax().parent() { | ||
342 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | ||
343 | if let Definition::Field(field) = def { | ||
344 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
345 | h |= HlMod::Unsafe; | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | |||
351 | h | ||
352 | } | ||
353 | NameRefClass::FieldShorthand { .. } => { | ||
354 | HlTag::Symbol(SymbolKind::Field).into() | ||
355 | } | ||
356 | }, | ||
357 | None if syntactic_name_ref_highlighting => { | ||
358 | highlight_name_ref_by_syntax(name_ref, sema) | ||
359 | } | ||
360 | None => HlTag::UnresolvedReference.into(), | ||
361 | } | ||
362 | }) | ||
363 | } | ||
364 | |||
365 | // Simple token-based highlighting | ||
366 | COMMENT => { | ||
367 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
368 | let h = HlTag::Comment; | ||
369 | match comment.kind().doc { | ||
370 | Some(_) => h | HlMod::Documentation, | ||
371 | None => h.into(), | ||
372 | } | ||
373 | } | ||
374 | STRING | BYTE_STRING => HlTag::StringLiteral.into(), | ||
375 | ATTR => HlTag::Attribute.into(), | ||
376 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), | ||
377 | BYTE => HlTag::ByteLiteral.into(), | ||
378 | CHAR => HlTag::CharLiteral.into(), | ||
379 | QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow, | ||
380 | LIFETIME => { | ||
381 | let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); | ||
382 | |||
383 | match NameClass::classify_lifetime(sema, &lifetime) { | ||
384 | Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, | ||
385 | None => match NameRefClass::classify_lifetime(sema, &lifetime) { | ||
386 | Some(NameRefClass::Definition(def)) => highlight_def(db, def), | ||
387 | _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), | ||
388 | }, | ||
389 | _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition, | ||
390 | } | ||
391 | } | ||
392 | p if p.is_punct() => match p { | ||
393 | T![&] => { | ||
394 | let h = HlTag::Operator.into(); | ||
395 | let is_unsafe = element | ||
396 | .parent() | ||
397 | .and_then(ast::RefExpr::cast) | ||
398 | .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) | ||
399 | .unwrap_or(false); | ||
400 | if is_unsafe { | ||
401 | h | HlMod::Unsafe | ||
402 | } else { | ||
403 | h | ||
404 | } | ||
405 | } | ||
406 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(), | ||
407 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
408 | HlTag::Symbol(SymbolKind::Macro).into() | ||
409 | } | ||
410 | T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { | ||
411 | HlTag::BuiltinType.into() | ||
412 | } | ||
413 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { | ||
414 | HlTag::Keyword.into() | ||
415 | } | ||
416 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
417 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
418 | |||
419 | let expr = prefix_expr.expr()?; | ||
420 | let ty = sema.type_of_expr(&expr)?; | ||
421 | if ty.is_raw_ptr() { | ||
422 | HlTag::Operator | HlMod::Unsafe | ||
423 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { | ||
424 | HlTag::Operator.into() | ||
425 | } else { | ||
426 | HlTag::Punctuation.into() | ||
427 | } | ||
428 | } | ||
429 | T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
430 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
431 | |||
432 | let expr = prefix_expr.expr()?; | ||
433 | match expr { | ||
434 | ast::Expr::Literal(_) => HlTag::NumericLiteral, | ||
435 | _ => HlTag::Operator, | ||
436 | } | ||
437 | .into() | ||
438 | } | ||
439 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
440 | HlTag::Operator.into() | ||
441 | } | ||
442 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(), | ||
443 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { | ||
444 | HlTag::Operator.into() | ||
445 | } | ||
446 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(), | ||
447 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(), | ||
448 | _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(), | ||
449 | _ => HlTag::Punctuation.into(), | ||
450 | }, | ||
451 | |||
452 | k if k.is_keyword() => { | ||
453 | let h = Highlight::new(HlTag::Keyword); | ||
454 | match k { | ||
455 | T![break] | ||
456 | | T![continue] | ||
457 | | T![else] | ||
458 | | T![if] | ||
459 | | T![loop] | ||
460 | | T![match] | ||
461 | | T![return] | ||
462 | | T![while] | ||
463 | | T![in] => h | HlMod::ControlFlow, | ||
464 | T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, | ||
465 | T![unsafe] => h | HlMod::Unsafe, | ||
466 | T![true] | T![false] => HlTag::BoolLiteral.into(), | ||
467 | T![self] => { | ||
468 | let self_param_is_mut = element | ||
469 | .parent() | ||
470 | .and_then(ast::SelfParam::cast) | ||
471 | .and_then(|p| p.mut_token()) | ||
472 | .is_some(); | ||
473 | let self_path = &element | ||
474 | .parent() | ||
475 | .as_ref() | ||
476 | .and_then(SyntaxNode::parent) | ||
477 | .and_then(ast::Path::cast) | ||
478 | .and_then(|p| sema.resolve_path(&p)); | ||
479 | let mut h = HlTag::Symbol(SymbolKind::SelfParam).into(); | ||
480 | if self_param_is_mut | ||
481 | || matches!(self_path, | ||
482 | Some(hir::PathResolution::Local(local)) | ||
483 | if local.is_self(db) | ||
484 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) | ||
485 | ) | ||
486 | { | ||
487 | h |= HlMod::Mutable | ||
488 | } | ||
489 | |||
490 | if let Some(hir::PathResolution::Local(local)) = self_path { | ||
491 | if is_consumed_lvalue(element, &local, db) { | ||
492 | h |= HlMod::Consuming; | ||
493 | } | ||
494 | } | ||
495 | |||
496 | h | ||
497 | } | ||
498 | T![ref] => element | ||
499 | .parent() | ||
500 | .and_then(ast::IdentPat::cast) | ||
501 | .and_then(|ident_pat| { | ||
502 | if sema.is_unsafe_ident_pat(&ident_pat) { | ||
503 | Some(HlMod::Unsafe) | ||
504 | } else { | ||
505 | None | ||
506 | } | ||
507 | }) | ||
508 | .map(|modifier| h | modifier) | ||
509 | .unwrap_or(h), | ||
510 | _ => h, | ||
511 | } | ||
512 | } | ||
513 | |||
514 | _ => return None, | ||
515 | }; | ||
516 | |||
517 | return Some((highlight, binding_hash)); | ||
518 | |||
519 | fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 { | ||
520 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | ||
521 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | ||
522 | |||
523 | let mut hasher = DefaultHasher::new(); | ||
524 | x.hash(&mut hasher); | ||
525 | hasher.finish() | ||
526 | } | ||
527 | |||
528 | hash((name, shadow_count)) | ||
529 | } | ||
530 | } | ||
531 | |||
532 | fn is_child_of_impl(element: &SyntaxElement) -> bool { | ||
533 | match element.parent() { | ||
534 | Some(e) => e.kind() == IMPL, | ||
535 | _ => false, | ||
536 | } | ||
537 | } | ||
538 | |||
539 | fn highlight_func_by_name_ref( | ||
540 | sema: &Semantics<RootDatabase>, | ||
541 | name_ref: &ast::NameRef, | ||
542 | ) -> Option<Highlight> { | ||
543 | let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; | ||
544 | highlight_method_call(sema, &method_call) | ||
545 | } | ||
546 | |||
547 | fn highlight_method_call( | ||
548 | sema: &Semantics<RootDatabase>, | ||
549 | method_call: &ast::MethodCallExpr, | ||
550 | ) -> Option<Highlight> { | ||
551 | let func = sema.resolve_method_call(&method_call)?; | ||
552 | let mut h = HlTag::Symbol(SymbolKind::Function).into(); | ||
553 | h |= HlMod::Associated; | ||
554 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { | ||
555 | h |= HlMod::Unsafe; | ||
556 | } | ||
557 | if let Some(self_param) = func.self_param(sema.db) { | ||
558 | match self_param.access(sema.db) { | ||
559 | hir::Access::Shared => (), | ||
560 | hir::Access::Exclusive => h |= HlMod::Mutable, | ||
561 | hir::Access::Owned => { | ||
562 | if let Some(receiver_ty) = | ||
563 | method_call.receiver().and_then(|it| sema.type_of_expr(&it)) | ||
564 | { | ||
565 | if !receiver_ty.is_copy(sema.db) { | ||
566 | h |= HlMod::Consuming | ||
567 | } | ||
568 | } | ||
569 | } | ||
570 | } | ||
571 | } | ||
572 | Some(h) | ||
573 | } | ||
574 | |||
575 | fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | ||
576 | match def { | ||
577 | Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro), | ||
578 | Definition::Field(_) => HlTag::Symbol(SymbolKind::Field), | ||
579 | Definition::ModuleDef(def) => match def { | ||
580 | hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), | ||
581 | hir::ModuleDef::Function(func) => { | ||
582 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); | ||
583 | if func.as_assoc_item(db).is_some() { | ||
584 | h |= HlMod::Associated; | ||
585 | if func.self_param(db).is_none() { | ||
586 | h |= HlMod::Static | ||
587 | } | ||
588 | } | ||
589 | if func.is_unsafe(db) { | ||
590 | h |= HlMod::Unsafe; | ||
591 | } | ||
592 | return h; | ||
593 | } | ||
594 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct), | ||
595 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum), | ||
596 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union), | ||
597 | hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), | ||
598 | hir::ModuleDef::Const(konst) => { | ||
599 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); | ||
600 | if konst.as_assoc_item(db).is_some() { | ||
601 | h |= HlMod::Associated | ||
602 | } | ||
603 | return h; | ||
604 | } | ||
605 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), | ||
606 | hir::ModuleDef::TypeAlias(type_) => { | ||
607 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); | ||
608 | if type_.as_assoc_item(db).is_some() { | ||
609 | h |= HlMod::Associated | ||
610 | } | ||
611 | return h; | ||
612 | } | ||
613 | hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType, | ||
614 | hir::ModuleDef::Static(s) => { | ||
615 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static)); | ||
616 | if s.is_mut(db) { | ||
617 | h |= HlMod::Mutable; | ||
618 | h |= HlMod::Unsafe; | ||
619 | } | ||
620 | return h; | ||
621 | } | ||
622 | }, | ||
623 | Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl), | ||
624 | Definition::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam), | ||
625 | Definition::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam), | ||
626 | Definition::Local(local) => { | ||
627 | let tag = if local.is_param(db) { | ||
628 | HlTag::Symbol(SymbolKind::ValueParam) | ||
629 | } else { | ||
630 | HlTag::Symbol(SymbolKind::Local) | ||
631 | }; | ||
632 | let mut h = Highlight::new(tag); | ||
633 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | ||
634 | h |= HlMod::Mutable; | ||
635 | } | ||
636 | if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { | ||
637 | h |= HlMod::Callable; | ||
638 | } | ||
639 | return h; | ||
640 | } | ||
641 | Definition::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam), | ||
642 | Definition::Label(_) => HlTag::Symbol(SymbolKind::Label), | ||
643 | } | ||
644 | .into() | ||
645 | } | ||
646 | |||
647 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | ||
648 | let default = HlTag::UnresolvedReference; | ||
649 | |||
650 | let parent = match name.syntax().parent() { | ||
651 | Some(it) => it, | ||
652 | _ => return default.into(), | ||
653 | }; | ||
654 | |||
655 | let tag = match parent.kind() { | ||
656 | STRUCT => HlTag::Symbol(SymbolKind::Struct), | ||
657 | ENUM => HlTag::Symbol(SymbolKind::Enum), | ||
658 | VARIANT => HlTag::Symbol(SymbolKind::Variant), | ||
659 | UNION => HlTag::Symbol(SymbolKind::Union), | ||
660 | TRAIT => HlTag::Symbol(SymbolKind::Trait), | ||
661 | TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias), | ||
662 | TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam), | ||
663 | RECORD_FIELD => HlTag::Symbol(SymbolKind::Field), | ||
664 | MODULE => HlTag::Symbol(SymbolKind::Module), | ||
665 | FN => HlTag::Symbol(SymbolKind::Function), | ||
666 | CONST => HlTag::Symbol(SymbolKind::Const), | ||
667 | STATIC => HlTag::Symbol(SymbolKind::Static), | ||
668 | IDENT_PAT => HlTag::Symbol(SymbolKind::Local), | ||
669 | _ => default, | ||
670 | }; | ||
671 | |||
672 | tag.into() | ||
673 | } | ||
674 | |||
675 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { | ||
676 | let default = HlTag::UnresolvedReference; | ||
677 | |||
678 | let parent = match name.syntax().parent() { | ||
679 | Some(it) => it, | ||
680 | _ => return default.into(), | ||
681 | }; | ||
682 | |||
683 | match parent.kind() { | ||
684 | METHOD_CALL_EXPR => { | ||
685 | return ast::MethodCallExpr::cast(parent) | ||
686 | .and_then(|method_call| highlight_method_call(sema, &method_call)) | ||
687 | .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into()); | ||
688 | } | ||
689 | FIELD_EXPR => { | ||
690 | let h = HlTag::Symbol(SymbolKind::Field); | ||
691 | let is_union = ast::FieldExpr::cast(parent) | ||
692 | .and_then(|field_expr| { | ||
693 | let field = sema.resolve_field(&field_expr)?; | ||
694 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
695 | true | ||
696 | } else { | ||
697 | false | ||
698 | }) | ||
699 | }) | ||
700 | .unwrap_or(false); | ||
701 | if is_union { | ||
702 | h | HlMod::Unsafe | ||
703 | } else { | ||
704 | h.into() | ||
705 | } | ||
706 | } | ||
707 | PATH_SEGMENT => { | ||
708 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
709 | Some(it) => it, | ||
710 | _ => return default.into(), | ||
711 | }; | ||
712 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
713 | Some(it) => it, | ||
714 | _ => { | ||
715 | // within path, decide whether it is module or adt by checking for uppercase name | ||
716 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
717 | HlTag::Symbol(SymbolKind::Struct) | ||
718 | } else { | ||
719 | HlTag::Symbol(SymbolKind::Module) | ||
720 | } | ||
721 | .into(); | ||
722 | } | ||
723 | }; | ||
724 | let parent = match expr.syntax().parent() { | ||
725 | Some(it) => it, | ||
726 | None => return default.into(), | ||
727 | }; | ||
728 | |||
729 | match parent.kind() { | ||
730 | CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(), | ||
731 | _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
732 | HlTag::Symbol(SymbolKind::Struct) | ||
733 | } else { | ||
734 | HlTag::Symbol(SymbolKind::Const) | ||
735 | } | ||
736 | .into(), | ||
737 | } | ||
738 | } | ||
739 | _ => default.into(), | ||
740 | } | ||
741 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs new file mode 100644 index 000000000..1a88975d2 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/highlight.rs | |||
@@ -0,0 +1,517 @@ | |||
1 | //! Computes color for a single element. | ||
2 | |||
3 | use hir::{AsAssocItem, Semantics, VariantDef}; | ||
4 | use ide_db::{ | ||
5 | defs::{Definition, NameClass, NameRefClass}, | ||
6 | RootDatabase, | ||
7 | }; | ||
8 | use rustc_hash::FxHashMap; | ||
9 | use syntax::{ | ||
10 | ast, AstNode, AstToken, NodeOrToken, SyntaxElement, | ||
11 | SyntaxKind::{self, *}, | ||
12 | SyntaxNode, SyntaxToken, T, | ||
13 | }; | ||
14 | |||
15 | use crate::{Highlight, HlMod, HlTag, SymbolKind}; | ||
16 | |||
17 | pub(super) fn element( | ||
18 | sema: &Semantics<RootDatabase>, | ||
19 | bindings_shadow_count: &mut FxHashMap<hir::Name, u32>, | ||
20 | syntactic_name_ref_highlighting: bool, | ||
21 | element: SyntaxElement, | ||
22 | ) -> Option<(Highlight, Option<u64>)> { | ||
23 | let db = sema.db; | ||
24 | let mut binding_hash = None; | ||
25 | let highlight: Highlight = match element.kind() { | ||
26 | FN => { | ||
27 | bindings_shadow_count.clear(); | ||
28 | return None; | ||
29 | } | ||
30 | |||
31 | // Highlight definitions depending on the "type" of the definition. | ||
32 | NAME => { | ||
33 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | ||
34 | let name_kind = NameClass::classify(sema, &name); | ||
35 | |||
36 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | ||
37 | if let Some(name) = local.name(db) { | ||
38 | let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); | ||
39 | *shadow_count += 1; | ||
40 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
41 | } | ||
42 | }; | ||
43 | |||
44 | match name_kind { | ||
45 | Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(), | ||
46 | Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, | ||
47 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | ||
48 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { | ||
49 | let mut h = HlTag::Symbol(SymbolKind::Field).into(); | ||
50 | if let Definition::Field(field) = field_ref { | ||
51 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
52 | h |= HlMod::Unsafe; | ||
53 | } | ||
54 | } | ||
55 | |||
56 | h | ||
57 | } | ||
58 | None => highlight_name_by_syntax(name) | HlMod::Definition, | ||
59 | } | ||
60 | } | ||
61 | |||
62 | // Highlight references like the definitions they resolve to | ||
63 | NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { | ||
64 | // even though we track whether we are in an attribute or not we still need this special case | ||
65 | // as otherwise we would emit unresolved references for name refs inside attributes | ||
66 | Highlight::from(HlTag::Symbol(SymbolKind::Function)) | ||
67 | } | ||
68 | NAME_REF => { | ||
69 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | ||
70 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | ||
71 | match NameRefClass::classify(sema, &name_ref) { | ||
72 | Some(name_kind) => match name_kind { | ||
73 | NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), | ||
74 | NameRefClass::Definition(def) => { | ||
75 | if let Definition::Local(local) = &def { | ||
76 | if let Some(name) = local.name(db) { | ||
77 | let shadow_count = | ||
78 | bindings_shadow_count.entry(name.clone()).or_default(); | ||
79 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | ||
80 | } | ||
81 | }; | ||
82 | |||
83 | let mut h = highlight_def(db, def); | ||
84 | |||
85 | if let Definition::Local(local) = &def { | ||
86 | if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { | ||
87 | h |= HlMod::Consuming; | ||
88 | } | ||
89 | } | ||
90 | |||
91 | if let Some(parent) = name_ref.syntax().parent() { | ||
92 | if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { | ||
93 | if let Definition::Field(field) = def { | ||
94 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
95 | h |= HlMod::Unsafe; | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | } | ||
100 | |||
101 | h | ||
102 | } | ||
103 | NameRefClass::FieldShorthand { .. } => { | ||
104 | HlTag::Symbol(SymbolKind::Field).into() | ||
105 | } | ||
106 | }, | ||
107 | None if syntactic_name_ref_highlighting => { | ||
108 | highlight_name_ref_by_syntax(name_ref, sema) | ||
109 | } | ||
110 | None => HlTag::UnresolvedReference.into(), | ||
111 | } | ||
112 | }) | ||
113 | } | ||
114 | |||
115 | // Simple token-based highlighting | ||
116 | COMMENT => { | ||
117 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
118 | let h = HlTag::Comment; | ||
119 | match comment.kind().doc { | ||
120 | Some(_) => h | HlMod::Documentation, | ||
121 | None => h.into(), | ||
122 | } | ||
123 | } | ||
124 | STRING | BYTE_STRING => HlTag::StringLiteral.into(), | ||
125 | ATTR => HlTag::Attribute.into(), | ||
126 | INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), | ||
127 | BYTE => HlTag::ByteLiteral.into(), | ||
128 | CHAR => HlTag::CharLiteral.into(), | ||
129 | QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow, | ||
130 | LIFETIME => { | ||
131 | let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); | ||
132 | |||
133 | match NameClass::classify_lifetime(sema, &lifetime) { | ||
134 | Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, | ||
135 | None => match NameRefClass::classify_lifetime(sema, &lifetime) { | ||
136 | Some(NameRefClass::Definition(def)) => highlight_def(db, def), | ||
137 | _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), | ||
138 | }, | ||
139 | _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition, | ||
140 | } | ||
141 | } | ||
142 | p if p.is_punct() => match p { | ||
143 | T![&] => { | ||
144 | let h = HlTag::Operator.into(); | ||
145 | let is_unsafe = element | ||
146 | .parent() | ||
147 | .and_then(ast::RefExpr::cast) | ||
148 | .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) | ||
149 | .unwrap_or(false); | ||
150 | if is_unsafe { | ||
151 | h | HlMod::Unsafe | ||
152 | } else { | ||
153 | h | ||
154 | } | ||
155 | } | ||
156 | T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(), | ||
157 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
158 | HlTag::Symbol(SymbolKind::Macro).into() | ||
159 | } | ||
160 | T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { | ||
161 | HlTag::BuiltinType.into() | ||
162 | } | ||
163 | T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { | ||
164 | HlTag::Keyword.into() | ||
165 | } | ||
166 | T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
167 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
168 | |||
169 | let expr = prefix_expr.expr()?; | ||
170 | let ty = sema.type_of_expr(&expr)?; | ||
171 | if ty.is_raw_ptr() { | ||
172 | HlTag::Operator | HlMod::Unsafe | ||
173 | } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { | ||
174 | HlTag::Operator.into() | ||
175 | } else { | ||
176 | HlTag::Punctuation.into() | ||
177 | } | ||
178 | } | ||
179 | T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
180 | let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; | ||
181 | |||
182 | let expr = prefix_expr.expr()?; | ||
183 | match expr { | ||
184 | ast::Expr::Literal(_) => HlTag::NumericLiteral, | ||
185 | _ => HlTag::Operator, | ||
186 | } | ||
187 | .into() | ||
188 | } | ||
189 | _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { | ||
190 | HlTag::Operator.into() | ||
191 | } | ||
192 | _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(), | ||
193 | _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { | ||
194 | HlTag::Operator.into() | ||
195 | } | ||
196 | _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(), | ||
197 | _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(), | ||
198 | _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(), | ||
199 | _ => HlTag::Punctuation.into(), | ||
200 | }, | ||
201 | |||
202 | k if k.is_keyword() => { | ||
203 | let h = Highlight::new(HlTag::Keyword); | ||
204 | match k { | ||
205 | T![break] | ||
206 | | T![continue] | ||
207 | | T![else] | ||
208 | | T![if] | ||
209 | | T![loop] | ||
210 | | T![match] | ||
211 | | T![return] | ||
212 | | T![while] | ||
213 | | T![in] => h | HlMod::ControlFlow, | ||
214 | T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, | ||
215 | T![unsafe] => h | HlMod::Unsafe, | ||
216 | T![true] | T![false] => HlTag::BoolLiteral.into(), | ||
217 | T![self] => { | ||
218 | let self_param_is_mut = element | ||
219 | .parent() | ||
220 | .and_then(ast::SelfParam::cast) | ||
221 | .and_then(|p| p.mut_token()) | ||
222 | .is_some(); | ||
223 | let self_path = &element | ||
224 | .parent() | ||
225 | .as_ref() | ||
226 | .and_then(SyntaxNode::parent) | ||
227 | .and_then(ast::Path::cast) | ||
228 | .and_then(|p| sema.resolve_path(&p)); | ||
229 | let mut h = HlTag::Symbol(SymbolKind::SelfParam).into(); | ||
230 | if self_param_is_mut | ||
231 | || matches!(self_path, | ||
232 | Some(hir::PathResolution::Local(local)) | ||
233 | if local.is_self(db) | ||
234 | && (local.is_mut(db) || local.ty(db).is_mutable_reference()) | ||
235 | ) | ||
236 | { | ||
237 | h |= HlMod::Mutable | ||
238 | } | ||
239 | |||
240 | if let Some(hir::PathResolution::Local(local)) = self_path { | ||
241 | if is_consumed_lvalue(element, &local, db) { | ||
242 | h |= HlMod::Consuming; | ||
243 | } | ||
244 | } | ||
245 | |||
246 | h | ||
247 | } | ||
248 | T![ref] => element | ||
249 | .parent() | ||
250 | .and_then(ast::IdentPat::cast) | ||
251 | .and_then(|ident_pat| { | ||
252 | if sema.is_unsafe_ident_pat(&ident_pat) { | ||
253 | Some(HlMod::Unsafe) | ||
254 | } else { | ||
255 | None | ||
256 | } | ||
257 | }) | ||
258 | .map(|modifier| h | modifier) | ||
259 | .unwrap_or(h), | ||
260 | _ => h, | ||
261 | } | ||
262 | } | ||
263 | |||
264 | _ => return None, | ||
265 | }; | ||
266 | |||
267 | return Some((highlight, binding_hash)); | ||
268 | |||
269 | fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 { | ||
270 | fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 { | ||
271 | use std::{collections::hash_map::DefaultHasher, hash::Hasher}; | ||
272 | |||
273 | let mut hasher = DefaultHasher::new(); | ||
274 | x.hash(&mut hasher); | ||
275 | hasher.finish() | ||
276 | } | ||
277 | |||
278 | hash((name, shadow_count)) | ||
279 | } | ||
280 | } | ||
281 | |||
282 | fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { | ||
283 | match def { | ||
284 | Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro), | ||
285 | Definition::Field(_) => HlTag::Symbol(SymbolKind::Field), | ||
286 | Definition::ModuleDef(def) => match def { | ||
287 | hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), | ||
288 | hir::ModuleDef::Function(func) => { | ||
289 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); | ||
290 | if func.as_assoc_item(db).is_some() { | ||
291 | h |= HlMod::Associated; | ||
292 | if func.self_param(db).is_none() { | ||
293 | h |= HlMod::Static | ||
294 | } | ||
295 | } | ||
296 | if func.is_unsafe(db) { | ||
297 | h |= HlMod::Unsafe; | ||
298 | } | ||
299 | return h; | ||
300 | } | ||
301 | hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct), | ||
302 | hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum), | ||
303 | hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union), | ||
304 | hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), | ||
305 | hir::ModuleDef::Const(konst) => { | ||
306 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); | ||
307 | if konst.as_assoc_item(db).is_some() { | ||
308 | h |= HlMod::Associated | ||
309 | } | ||
310 | return h; | ||
311 | } | ||
312 | hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), | ||
313 | hir::ModuleDef::TypeAlias(type_) => { | ||
314 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); | ||
315 | if type_.as_assoc_item(db).is_some() { | ||
316 | h |= HlMod::Associated | ||
317 | } | ||
318 | return h; | ||
319 | } | ||
320 | hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType, | ||
321 | hir::ModuleDef::Static(s) => { | ||
322 | let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static)); | ||
323 | if s.is_mut(db) { | ||
324 | h |= HlMod::Mutable; | ||
325 | h |= HlMod::Unsafe; | ||
326 | } | ||
327 | return h; | ||
328 | } | ||
329 | }, | ||
330 | Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl), | ||
331 | Definition::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam), | ||
332 | Definition::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam), | ||
333 | Definition::Local(local) => { | ||
334 | let tag = if local.is_param(db) { | ||
335 | HlTag::Symbol(SymbolKind::ValueParam) | ||
336 | } else { | ||
337 | HlTag::Symbol(SymbolKind::Local) | ||
338 | }; | ||
339 | let mut h = Highlight::new(tag); | ||
340 | if local.is_mut(db) || local.ty(db).is_mutable_reference() { | ||
341 | h |= HlMod::Mutable; | ||
342 | } | ||
343 | if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { | ||
344 | h |= HlMod::Callable; | ||
345 | } | ||
346 | return h; | ||
347 | } | ||
348 | Definition::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam), | ||
349 | Definition::Label(_) => HlTag::Symbol(SymbolKind::Label), | ||
350 | } | ||
351 | .into() | ||
352 | } | ||
353 | |||
354 | fn highlight_func_by_name_ref( | ||
355 | sema: &Semantics<RootDatabase>, | ||
356 | name_ref: &ast::NameRef, | ||
357 | ) -> Option<Highlight> { | ||
358 | let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; | ||
359 | highlight_method_call(sema, &mc) | ||
360 | } | ||
361 | |||
362 | fn highlight_method_call( | ||
363 | sema: &Semantics<RootDatabase>, | ||
364 | method_call: &ast::MethodCallExpr, | ||
365 | ) -> Option<Highlight> { | ||
366 | let func = sema.resolve_method_call(&method_call)?; | ||
367 | let mut h = HlTag::Symbol(SymbolKind::Function).into(); | ||
368 | h |= HlMod::Associated; | ||
369 | if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { | ||
370 | h |= HlMod::Unsafe; | ||
371 | } | ||
372 | if let Some(self_param) = func.self_param(sema.db) { | ||
373 | match self_param.access(sema.db) { | ||
374 | hir::Access::Shared => (), | ||
375 | hir::Access::Exclusive => h |= HlMod::Mutable, | ||
376 | hir::Access::Owned => { | ||
377 | if let Some(receiver_ty) = | ||
378 | method_call.receiver().and_then(|it| sema.type_of_expr(&it)) | ||
379 | { | ||
380 | if !receiver_ty.is_copy(sema.db) { | ||
381 | h |= HlMod::Consuming | ||
382 | } | ||
383 | } | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | Some(h) | ||
388 | } | ||
389 | |||
390 | fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | ||
391 | let default = HlTag::UnresolvedReference; | ||
392 | |||
393 | let parent = match name.syntax().parent() { | ||
394 | Some(it) => it, | ||
395 | _ => return default.into(), | ||
396 | }; | ||
397 | |||
398 | let tag = match parent.kind() { | ||
399 | STRUCT => HlTag::Symbol(SymbolKind::Struct), | ||
400 | ENUM => HlTag::Symbol(SymbolKind::Enum), | ||
401 | VARIANT => HlTag::Symbol(SymbolKind::Variant), | ||
402 | UNION => HlTag::Symbol(SymbolKind::Union), | ||
403 | TRAIT => HlTag::Symbol(SymbolKind::Trait), | ||
404 | TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias), | ||
405 | TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam), | ||
406 | RECORD_FIELD => HlTag::Symbol(SymbolKind::Field), | ||
407 | MODULE => HlTag::Symbol(SymbolKind::Module), | ||
408 | FN => HlTag::Symbol(SymbolKind::Function), | ||
409 | CONST => HlTag::Symbol(SymbolKind::Const), | ||
410 | STATIC => HlTag::Symbol(SymbolKind::Static), | ||
411 | IDENT_PAT => HlTag::Symbol(SymbolKind::Local), | ||
412 | _ => default, | ||
413 | }; | ||
414 | |||
415 | tag.into() | ||
416 | } | ||
417 | |||
418 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { | ||
419 | let default = HlTag::UnresolvedReference; | ||
420 | |||
421 | let parent = match name.syntax().parent() { | ||
422 | Some(it) => it, | ||
423 | _ => return default.into(), | ||
424 | }; | ||
425 | |||
426 | match parent.kind() { | ||
427 | METHOD_CALL_EXPR => { | ||
428 | return ast::MethodCallExpr::cast(parent) | ||
429 | .and_then(|it| highlight_method_call(sema, &it)) | ||
430 | .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into()); | ||
431 | } | ||
432 | FIELD_EXPR => { | ||
433 | let h = HlTag::Symbol(SymbolKind::Field); | ||
434 | let is_union = ast::FieldExpr::cast(parent) | ||
435 | .and_then(|field_expr| { | ||
436 | let field = sema.resolve_field(&field_expr)?; | ||
437 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
438 | true | ||
439 | } else { | ||
440 | false | ||
441 | }) | ||
442 | }) | ||
443 | .unwrap_or(false); | ||
444 | if is_union { | ||
445 | h | HlMod::Unsafe | ||
446 | } else { | ||
447 | h.into() | ||
448 | } | ||
449 | } | ||
450 | PATH_SEGMENT => { | ||
451 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
452 | Some(it) => it, | ||
453 | _ => return default.into(), | ||
454 | }; | ||
455 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
456 | Some(it) => it, | ||
457 | _ => { | ||
458 | // within path, decide whether it is module or adt by checking for uppercase name | ||
459 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
460 | HlTag::Symbol(SymbolKind::Struct) | ||
461 | } else { | ||
462 | HlTag::Symbol(SymbolKind::Module) | ||
463 | } | ||
464 | .into(); | ||
465 | } | ||
466 | }; | ||
467 | let parent = match expr.syntax().parent() { | ||
468 | Some(it) => it, | ||
469 | None => return default.into(), | ||
470 | }; | ||
471 | |||
472 | match parent.kind() { | ||
473 | CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(), | ||
474 | _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
475 | HlTag::Symbol(SymbolKind::Struct) | ||
476 | } else { | ||
477 | HlTag::Symbol(SymbolKind::Const) | ||
478 | } | ||
479 | .into(), | ||
480 | } | ||
481 | } | ||
482 | _ => default.into(), | ||
483 | } | ||
484 | } | ||
485 | |||
486 | fn is_consumed_lvalue( | ||
487 | node: NodeOrToken<SyntaxNode, SyntaxToken>, | ||
488 | local: &hir::Local, | ||
489 | db: &RootDatabase, | ||
490 | ) -> bool { | ||
491 | // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. | ||
492 | parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) | ||
493 | } | ||
494 | |||
495 | /// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. | ||
496 | fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool { | ||
497 | while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) { | ||
498 | if parent.kind() != *kind { | ||
499 | return false; | ||
500 | } | ||
501 | |||
502 | // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value | ||
503 | // in the same pattern is unstable: rust-lang/rust#68354. | ||
504 | node = node.parent().unwrap().into(); | ||
505 | kinds = rest; | ||
506 | } | ||
507 | |||
508 | // Only true if we matched all expected kinds | ||
509 | kinds.len() == 0 | ||
510 | } | ||
511 | |||
512 | fn is_child_of_impl(element: &SyntaxElement) -> bool { | ||
513 | match element.parent() { | ||
514 | Some(e) => e.kind() == IMPL, | ||
515 | _ => false, | ||
516 | } | ||
517 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/inject.rs b/crates/ide/src/syntax_highlighting/inject.rs index 4647a72c2..281461493 100644 --- a/crates/ide/src/syntax_highlighting/inject.rs +++ b/crates/ide/src/syntax_highlighting/inject.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | //! Syntax highlighting injections such as highlighting of documentation tests. | 1 | //! "Recursive" Syntax highlighting for code in doctests and fixtures. |
2 | 2 | ||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::call_info::ActiveParameter; | 4 | use ide_db::call_info::ActiveParameter; |