csskit_highlight/
css.rs

1use css_ast::{
2	CSSInt, Color, CssMetadata, Declaration, DeclarationValue, NodeId, PropertyRule, QueryableNode, StyleRule,
3	ToChromashift, Visit,
4};
5use css_lexer::ToSpan;
6use css_parse::NodeWithMetadata;
7
8use crate::{SemanticDecoration, SemanticKind, SemanticModifier, TokenHighlighter};
9
10impl SemanticKind {
11	/// Maps a CSS AST NodeId to a SemanticKind, if one exists.
12	/// Returns None for nodes that don't have a direct semantic kind mapping.
13	pub fn from_node_id(node_id: NodeId) -> Option<Self> {
14		use css_ast::NodeId;
15		match node_id {
16			// Selector components - Tag and all its variants
17			NodeId::Tag
18			| NodeId::HtmlTag
19			| NodeId::HtmlNonConformingTag
20			| NodeId::HtmlNonStandardTag
21			| NodeId::SvgTag
22			| NodeId::MathmlTag
23			| NodeId::CustomElementTag
24			| NodeId::UnknownTag => Some(SemanticKind::Tag),
25			NodeId::Class => Some(SemanticKind::Class),
26			NodeId::Id => Some(SemanticKind::Id),
27			NodeId::PseudoClass
28			| NodeId::MozPseudoClass
29			| NodeId::MsPseudoClass
30			| NodeId::WebkitPseudoClass
31			| NodeId::OPseudoClass => Some(SemanticKind::PseudoClass),
32			NodeId::PseudoElement
33			| NodeId::MozPseudoElement
34			| NodeId::MsPseudoElement
35			| NodeId::WebkitPseudoElement
36			| NodeId::OPseudoElement => Some(SemanticKind::PseudoElement),
37			NodeId::Wildcard => Some(SemanticKind::Wildcard),
38
39			// At-rules
40			NodeId::MediaRule
41			| NodeId::KeyframesRule
42			| NodeId::SupportsRule
43			| NodeId::FontFaceRule
44			| NodeId::ContainerRule
45			| NodeId::PageRule
46			| NodeId::LayerRule
47			| NodeId::MarginRule
48			| NodeId::WebkitKeyframesRule
49			| NodeId::DocumentRule
50			| NodeId::MozDocumentRule
51			| NodeId::PropertyRule
52			| NodeId::CounterStyleRule
53			| NodeId::NamespaceRule
54			| NodeId::StartingStyleRule => Some(SemanticKind::AtKeyword),
55
56			// Value types
57			NodeId::Color => Some(SemanticKind::StyleValueColor),
58			NodeId::Url => Some(SemanticKind::StyleValueUrl),
59			NodeId::Length
60			| NodeId::LengthPercentage
61			| NodeId::Angle
62			| NodeId::Time
63			| NodeId::Flex
64			| NodeId::Percentage
65			| NodeId::Decibel => Some(SemanticKind::StyleValueDimension),
66
67			// Most nodes don't have a direct semantic kind mapping
68			_ => None,
69		}
70	}
71}
72
73impl From<&CssMetadata> for SemanticModifier {
74	fn from(metadata: &CssMetadata) -> Self {
75		use css_ast::NodeKinds;
76		let mut modifier = SemanticModifier::none();
77
78		if metadata.node_kinds.contains(NodeKinds::Unknown) {
79			modifier |= SemanticModifier::Unknown;
80		}
81		if metadata.node_kinds.contains(NodeKinds::Deprecated) {
82			modifier |= SemanticModifier::Deprecated;
83		}
84		if metadata.node_kinds.contains(NodeKinds::Experimental) {
85			modifier |= SemanticModifier::Experimental;
86		}
87		if metadata.node_kinds.contains(NodeKinds::NonStandard) {
88			modifier |= SemanticModifier::Vendor;
89		}
90		if metadata.node_kinds.contains(NodeKinds::Custom) {
91			modifier |= SemanticModifier::Custom;
92		}
93		if metadata.has_vendor_prefixes() {
94			modifier |= SemanticModifier::Vendor;
95		}
96
97		modifier
98	}
99}
100
101impl Visit for TokenHighlighter {
102	fn visit_queryable_node<T: QueryableNode>(&mut self, node: &T) {
103		let node_id = node.node_id();
104		let metadata = node.metadata();
105		let modifier = SemanticModifier::from(&metadata);
106		let kind = SemanticKind::from_node_id(node_id).unwrap_or(SemanticKind::None);
107
108		if !modifier.is_none() || kind != SemanticKind::None {
109			self.insert(node.to_span(), kind, modifier);
110		}
111	}
112
113	// Visit style rules to specifically mark the curlies as Punctuation
114	fn visit_style_rule<'a>(&mut self, rule: &StyleRule<'a>) {
115		self.insert(rule.rule.block.open_curly.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
116		if let Some(close) = rule.rule.block.close_curly {
117			self.insert(close.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
118		}
119	}
120
121	// Visit Declarations to mark the name and colon
122	fn visit_declaration<'a, T: DeclarationValue<'a, CssMetadata>>(
123		&mut self,
124		property: &Declaration<'a, T, CssMetadata>,
125	) {
126		let metadata = property.metadata();
127		let modifier = SemanticModifier::from(&metadata);
128		self.insert(property.name.to_span(), SemanticKind::Declaration, modifier);
129		self.insert(property.colon.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
130	}
131
132	// Visit PropertyRule to mark the AtKeyword and the Prelude
133	fn visit_property_rule<'a>(&mut self, property: &PropertyRule<'a>) {
134		self.insert(property.name.to_span(), SemanticKind::AtKeyword, SemanticModifier::none());
135		self.insert(property.prelude.to_span(), SemanticKind::Declaration, SemanticModifier::Custom);
136	}
137
138	// Visit Color nodes to decorate with the swatch
139	fn visit_color(&mut self, color: &Color) {
140		let metadata = color.metadata();
141		let modifier = SemanticModifier::from(&metadata);
142		if let Some(bg) = color.to_chromashift() {
143			let swatch = SemanticDecoration::BackgroundColor(bg.into());
144			self.insert_with_decoration(color.to_span(), SemanticKind::StyleValueColor, modifier, swatch);
145		} else {
146			self.insert(color.to_span(), SemanticKind::StyleValueColor, modifier);
147		}
148	}
149
150	// Special case: CSSInt doesn't implement QueryableNode (has visit(skip))
151	fn visit_css_int(&mut self, int: &CSSInt) {
152		self.insert(int.to_span(), SemanticKind::StyleValueNumber, SemanticModifier::none());
153	}
154}