From eeceff317964af160fb785578d2d5666d9c3efe7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 9 Jan 2021 23:07:32 +0300 Subject: Refactor highlighting --- crates/ide/src/syntax_highlighting.rs | 560 ++-------------------------------- 1 file changed, 33 insertions(+), 527 deletions(-) (limited to 'crates/ide/src/syntax_highlighting.rs') 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; mod highlights; mod injector; +mod highlight; mod format; -mod inject; mod macro_rules; +mod inject; mod html; #[cfg(test)] mod tests; -use hir::{AsAssocItem, Local, Name, Semantics, VariantDef}; -use ide_db::{ - defs::{Definition, NameClass, NameRefClass}, - RootDatabase, -}; +use hir::{Name, Semantics}; +use ide_db::RootDatabase; use rustc_hash::FxHashMap; use syntax::{ ast::{self, HasFormatSpecifier}, - AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, - SyntaxKind::{self, *}, - SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, + AstNode, AstToken, Direction, NodeOrToken, + SyntaxKind::*, + SyntaxNode, TextRange, WalkEvent, T, }; use crate::{ syntax_highlighting::{ - format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter, tags::Highlight, + format::FormatStringHighlighter, highlights::Highlights, + macro_rules::MacroRulesHighlighter, tags::Highlight, }, FileId, HlMod, HlTag, SymbolKind, }; @@ -73,8 +72,19 @@ pub(crate) fn highlight( } }; - let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); let mut hl = highlights::Highlights::new(range_to_highlight); + traverse(&mut hl, &sema, &root, range_to_highlight, syntactic_name_ref_highlighting); + hl.to_vec() +} + +fn traverse( + hl: &mut Highlights, + sema: &Semantics, + root: &SyntaxNode, + range_to_highlight: TextRange, + syntactic_name_ref_highlighting: bool, +) { + let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); let mut current_macro_call: Option = None; let mut current_macro_rules: Option = None; @@ -128,24 +138,24 @@ pub(crate) fn highlight( } _ => (), } - match &event { - // Check for Rust code in documentation - WalkEvent::Leave(NodeOrToken::Node(node)) => { - if ast::Attr::can_cast(node.kind()) { - inside_attribute = false - } - inject::doc_comment(&mut hl, node); - } WalkEvent::Enter(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { inside_attribute = true } + WalkEvent::Leave(NodeOrToken::Node(node)) if ast::Attr::can_cast(node.kind()) => { + inside_attribute = false + } _ => (), } let element = match event { WalkEvent::Enter(it) => it, - WalkEvent::Leave(_) => continue, + WalkEvent::Leave(it) => { + if let Some(node) = it.as_node() { + inject::doc_comment(hl, node); + } + continue; + } }; let range = element.text_range(); @@ -179,13 +189,13 @@ pub(crate) fn highlight( if let Some(token) = element.as_token().cloned().and_then(ast::String::cast) { if token.is_raw() { let expanded = element_to_highlight.as_token().unwrap().clone(); - if inject::ra_fixture(&mut hl, &sema, token, expanded).is_some() { + if inject::ra_fixture(hl, &sema, token, expanded).is_some() { continue; } } } - if let Some((mut highlight, binding_hash)) = highlight_element( + if let Some((mut highlight, binding_hash)) = highlight::element( &sema, &mut bindings_shadow_count, syntactic_name_ref_highlighting, @@ -202,7 +212,7 @@ pub(crate) fn highlight( if let Some(string) = element_to_highlight.as_token().cloned().and_then(ast::String::cast) { - format_string_highlighter.highlight_format_string(&mut hl, &string, range); + format_string_highlighter.highlight_format_string(hl, &string, range); // Highlight escape sequences if let Some(char_ranges) = string.char_ranges() { for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) { @@ -218,8 +228,6 @@ pub(crate) fn highlight( } } } - - hl.to_vec() } fn macro_call_range(macro_call: &ast::MacroCall) -> Option { @@ -237,505 +245,3 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option { Some(TextRange::new(range_start, range_end)) } - -/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. -fn parents_match(mut node: NodeOrToken, mut kinds: &[SyntaxKind]) -> bool { - while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) { - if parent.kind() != *kind { - return false; - } - - // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value - // in the same pattern is unstable: rust-lang/rust#68354. - node = node.parent().unwrap().into(); - kinds = rest; - } - - // Only true if we matched all expected kinds - kinds.len() == 0 -} - -fn is_consumed_lvalue( - node: NodeOrToken, - local: &Local, - db: &RootDatabase, -) -> bool { - // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. - parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) -} - -fn highlight_element( - sema: &Semantics, - bindings_shadow_count: &mut FxHashMap, - syntactic_name_ref_highlighting: bool, - element: SyntaxElement, -) -> Option<(Highlight, Option)> { - let db = sema.db; - let mut binding_hash = None; - let highlight: Highlight = match element.kind() { - FN => { - bindings_shadow_count.clear(); - return None; - } - - // Highlight definitions depending on the "type" of the definition. - NAME => { - let name = element.into_node().and_then(ast::Name::cast).unwrap(); - let name_kind = NameClass::classify(sema, &name); - - if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { - if let Some(name) = local.name(db) { - let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); - *shadow_count += 1; - binding_hash = Some(calc_binding_hash(&name, *shadow_count)) - } - }; - - match name_kind { - Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(), - Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, - Some(NameClass::ConstReference(def)) => highlight_def(db, def), - Some(NameClass::PatFieldShorthand { field_ref, .. }) => { - let mut h = HlTag::Symbol(SymbolKind::Field).into(); - if let Definition::Field(field) = field_ref { - if let VariantDef::Union(_) = field.parent_def(db) { - h |= HlMod::Unsafe; - } - } - - h - } - None => highlight_name_by_syntax(name) | HlMod::Definition, - } - } - - // Highlight references like the definitions they resolve to - NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { - // even though we track whether we are in an attribute or not we still need this special case - // as otherwise we would emit unresolved references for name refs inside attributes - Highlight::from(HlTag::Symbol(SymbolKind::Function)) - } - NAME_REF => { - let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); - highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { - match NameRefClass::classify(sema, &name_ref) { - Some(name_kind) => match name_kind { - NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), - NameRefClass::Definition(def) => { - if let Definition::Local(local) = &def { - if let Some(name) = local.name(db) { - let shadow_count = - bindings_shadow_count.entry(name.clone()).or_default(); - binding_hash = Some(calc_binding_hash(&name, *shadow_count)) - } - }; - - let mut h = highlight_def(db, def); - - if let Definition::Local(local) = &def { - if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) { - h |= HlMod::Consuming; - } - } - - if let Some(parent) = name_ref.syntax().parent() { - if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { - if let Definition::Field(field) = def { - if let VariantDef::Union(_) = field.parent_def(db) { - h |= HlMod::Unsafe; - } - } - } - } - - h - } - NameRefClass::FieldShorthand { .. } => { - HlTag::Symbol(SymbolKind::Field).into() - } - }, - None if syntactic_name_ref_highlighting => { - highlight_name_ref_by_syntax(name_ref, sema) - } - None => HlTag::UnresolvedReference.into(), - } - }) - } - - // Simple token-based highlighting - COMMENT => { - let comment = element.into_token().and_then(ast::Comment::cast)?; - let h = HlTag::Comment; - match comment.kind().doc { - Some(_) => h | HlMod::Documentation, - None => h.into(), - } - } - STRING | BYTE_STRING => HlTag::StringLiteral.into(), - ATTR => HlTag::Attribute.into(), - INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), - BYTE => HlTag::ByteLiteral.into(), - CHAR => HlTag::CharLiteral.into(), - QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow, - LIFETIME => { - let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); - - match NameClass::classify_lifetime(sema, &lifetime) { - Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, - None => match NameRefClass::classify_lifetime(sema, &lifetime) { - Some(NameRefClass::Definition(def)) => highlight_def(db, def), - _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), - }, - _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition, - } - } - p if p.is_punct() => match p { - T![&] => { - let h = HlTag::Operator.into(); - let is_unsafe = element - .parent() - .and_then(ast::RefExpr::cast) - .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) - .unwrap_or(false); - if is_unsafe { - h | HlMod::Unsafe - } else { - h - } - } - T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(), - T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { - HlTag::Symbol(SymbolKind::Macro).into() - } - T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => { - HlTag::BuiltinType.into() - } - T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => { - HlTag::Keyword.into() - } - T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { - let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; - - let expr = prefix_expr.expr()?; - let ty = sema.type_of_expr(&expr)?; - if ty.is_raw_ptr() { - HlTag::Operator | HlMod::Unsafe - } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { - HlTag::Operator.into() - } else { - HlTag::Punctuation.into() - } - } - T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { - let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; - - let expr = prefix_expr.expr()?; - match expr { - ast::Expr::Literal(_) => HlTag::NumericLiteral, - _ => HlTag::Operator, - } - .into() - } - _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { - HlTag::Operator.into() - } - _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(), - _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { - HlTag::Operator.into() - } - _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(), - _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(), - _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(), - _ => HlTag::Punctuation.into(), - }, - - k if k.is_keyword() => { - let h = Highlight::new(HlTag::Keyword); - match k { - T![break] - | T![continue] - | T![else] - | T![if] - | T![loop] - | T![match] - | T![return] - | T![while] - | T![in] => h | HlMod::ControlFlow, - T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, - T![unsafe] => h | HlMod::Unsafe, - T![true] | T![false] => HlTag::BoolLiteral.into(), - T![self] => { - let self_param_is_mut = element - .parent() - .and_then(ast::SelfParam::cast) - .and_then(|p| p.mut_token()) - .is_some(); - let self_path = &element - .parent() - .as_ref() - .and_then(SyntaxNode::parent) - .and_then(ast::Path::cast) - .and_then(|p| sema.resolve_path(&p)); - let mut h = HlTag::Symbol(SymbolKind::SelfParam).into(); - if self_param_is_mut - || matches!(self_path, - Some(hir::PathResolution::Local(local)) - if local.is_self(db) - && (local.is_mut(db) || local.ty(db).is_mutable_reference()) - ) - { - h |= HlMod::Mutable - } - - if let Some(hir::PathResolution::Local(local)) = self_path { - if is_consumed_lvalue(element, &local, db) { - h |= HlMod::Consuming; - } - } - - h - } - T![ref] => element - .parent() - .and_then(ast::IdentPat::cast) - .and_then(|ident_pat| { - if sema.is_unsafe_ident_pat(&ident_pat) { - Some(HlMod::Unsafe) - } else { - None - } - }) - .map(|modifier| h | modifier) - .unwrap_or(h), - _ => h, - } - } - - _ => return None, - }; - - return Some((highlight, binding_hash)); - - fn calc_binding_hash(name: &Name, shadow_count: u32) -> u64 { - fn hash(x: T) -> u64 { - use std::{collections::hash_map::DefaultHasher, hash::Hasher}; - - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - hasher.finish() - } - - hash((name, shadow_count)) - } -} - -fn is_child_of_impl(element: &SyntaxElement) -> bool { - match element.parent() { - Some(e) => e.kind() == IMPL, - _ => false, - } -} - -fn highlight_func_by_name_ref( - sema: &Semantics, - name_ref: &ast::NameRef, -) -> Option { - let method_call = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; - highlight_method_call(sema, &method_call) -} - -fn highlight_method_call( - sema: &Semantics, - method_call: &ast::MethodCallExpr, -) -> Option { - let func = sema.resolve_method_call(&method_call)?; - let mut h = HlTag::Symbol(SymbolKind::Function).into(); - h |= HlMod::Associated; - if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { - h |= HlMod::Unsafe; - } - if let Some(self_param) = func.self_param(sema.db) { - match self_param.access(sema.db) { - hir::Access::Shared => (), - hir::Access::Exclusive => h |= HlMod::Mutable, - hir::Access::Owned => { - if let Some(receiver_ty) = - method_call.receiver().and_then(|it| sema.type_of_expr(&it)) - { - if !receiver_ty.is_copy(sema.db) { - h |= HlMod::Consuming - } - } - } - } - } - Some(h) -} - -fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { - match def { - Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro), - Definition::Field(_) => HlTag::Symbol(SymbolKind::Field), - Definition::ModuleDef(def) => match def { - hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module), - hir::ModuleDef::Function(func) => { - let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); - if func.as_assoc_item(db).is_some() { - h |= HlMod::Associated; - if func.self_param(db).is_none() { - h |= HlMod::Static - } - } - if func.is_unsafe(db) { - h |= HlMod::Unsafe; - } - return h; - } - hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct), - hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum), - hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union), - hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant), - hir::ModuleDef::Const(konst) => { - let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)); - if konst.as_assoc_item(db).is_some() { - h |= HlMod::Associated - } - return h; - } - hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait), - hir::ModuleDef::TypeAlias(type_) => { - let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); - if type_.as_assoc_item(db).is_some() { - h |= HlMod::Associated - } - return h; - } - hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType, - hir::ModuleDef::Static(s) => { - let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static)); - if s.is_mut(db) { - h |= HlMod::Mutable; - h |= HlMod::Unsafe; - } - return h; - } - }, - Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl), - Definition::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam), - Definition::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam), - Definition::Local(local) => { - let tag = if local.is_param(db) { - HlTag::Symbol(SymbolKind::ValueParam) - } else { - HlTag::Symbol(SymbolKind::Local) - }; - let mut h = Highlight::new(tag); - if local.is_mut(db) || local.ty(db).is_mutable_reference() { - h |= HlMod::Mutable; - } - if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) { - h |= HlMod::Callable; - } - return h; - } - Definition::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam), - Definition::Label(_) => HlTag::Symbol(SymbolKind::Label), - } - .into() -} - -fn highlight_name_by_syntax(name: ast::Name) -> Highlight { - let default = HlTag::UnresolvedReference; - - let parent = match name.syntax().parent() { - Some(it) => it, - _ => return default.into(), - }; - - let tag = match parent.kind() { - STRUCT => HlTag::Symbol(SymbolKind::Struct), - ENUM => HlTag::Symbol(SymbolKind::Enum), - VARIANT => HlTag::Symbol(SymbolKind::Variant), - UNION => HlTag::Symbol(SymbolKind::Union), - TRAIT => HlTag::Symbol(SymbolKind::Trait), - TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias), - TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam), - RECORD_FIELD => HlTag::Symbol(SymbolKind::Field), - MODULE => HlTag::Symbol(SymbolKind::Module), - FN => HlTag::Symbol(SymbolKind::Function), - CONST => HlTag::Symbol(SymbolKind::Const), - STATIC => HlTag::Symbol(SymbolKind::Static), - IDENT_PAT => HlTag::Symbol(SymbolKind::Local), - _ => default, - }; - - tag.into() -} - -fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics) -> Highlight { - let default = HlTag::UnresolvedReference; - - let parent = match name.syntax().parent() { - Some(it) => it, - _ => return default.into(), - }; - - match parent.kind() { - METHOD_CALL_EXPR => { - return ast::MethodCallExpr::cast(parent) - .and_then(|method_call| highlight_method_call(sema, &method_call)) - .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into()); - } - FIELD_EXPR => { - let h = HlTag::Symbol(SymbolKind::Field); - let is_union = ast::FieldExpr::cast(parent) - .and_then(|field_expr| { - let field = sema.resolve_field(&field_expr)?; - Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { - true - } else { - false - }) - }) - .unwrap_or(false); - if is_union { - h | HlMod::Unsafe - } else { - h.into() - } - } - PATH_SEGMENT => { - let path = match parent.parent().and_then(ast::Path::cast) { - Some(it) => it, - _ => return default.into(), - }; - let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { - Some(it) => it, - _ => { - // within path, decide whether it is module or adt by checking for uppercase name - return if name.text().chars().next().unwrap_or_default().is_uppercase() { - HlTag::Symbol(SymbolKind::Struct) - } else { - HlTag::Symbol(SymbolKind::Module) - } - .into(); - } - }; - let parent = match expr.syntax().parent() { - Some(it) => it, - None => return default.into(), - }; - - match parent.kind() { - CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(), - _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { - HlTag::Symbol(SymbolKind::Struct) - } else { - HlTag::Symbol(SymbolKind::Const) - } - .into(), - } - } - _ => default.into(), - } -} -- cgit v1.2.3