Skip to main content

csskit_highlight/
css.rs

1use css_ast::{
2	CSSInt, Color, CssMetadata, Declaration, DeclarationValue, NodeId, NodeVisitor, PropertyRule, StyleRule,
3	ToChromashift, Visit, VisitNode, visitor,
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::FontFeatureValuesRule
45			| NodeId::FontFeatureValueTypeRule
46			| NodeId::FontPaletteValuesRule
47			| NodeId::ContainerRule
48			| NodeId::PageRule
49			| NodeId::LayerRule
50			| NodeId::MarginRule
51			| NodeId::WebkitKeyframesRule
52			| NodeId::DocumentRule
53			| NodeId::MozDocumentRule
54			| NodeId::PropertyRule
55			| NodeId::CounterStyleRule
56			| NodeId::NamespaceRule
57			| NodeId::ColorProfileRule
58			| NodeId::ScopeRule
59			| NodeId::CustomMediaRule
60			| NodeId::StartingStyleRule => Some(SemanticKind::AtKeyword),
61
62			// Value types
63			NodeId::Color => Some(SemanticKind::StyleValueColor),
64			NodeId::Url => Some(SemanticKind::StyleValueUrl),
65			NodeId::Length
66			| NodeId::LengthPercentage
67			| NodeId::Angle
68			| NodeId::Time
69			| NodeId::Flex
70			| NodeId::Percentage
71			| NodeId::Decibel => Some(SemanticKind::StyleValueDimension),
72
73			// Most nodes don't have a direct semantic kind mapping
74			_ => None,
75		}
76	}
77}
78
79impl From<&CssMetadata> for SemanticModifier {
80	fn from(metadata: &CssMetadata) -> Self {
81		use css_ast::NodeKinds;
82		let mut modifier = SemanticModifier::none();
83
84		if metadata.node_kinds.contains(NodeKinds::Unknown) {
85			modifier |= SemanticModifier::Unknown;
86		}
87		if metadata.node_kinds.contains(NodeKinds::Deprecated) {
88			modifier |= SemanticModifier::Deprecated;
89		}
90		if metadata.node_kinds.contains(NodeKinds::Experimental) {
91			modifier |= SemanticModifier::Experimental;
92		}
93		if metadata.node_kinds.contains(NodeKinds::NonStandard) {
94			modifier |= SemanticModifier::Vendor;
95		}
96		if metadata.node_kinds.contains(NodeKinds::Custom) {
97			modifier |= SemanticModifier::Custom;
98		}
99		if metadata.has_vendor_prefixes() {
100			modifier |= SemanticModifier::Vendor;
101		}
102
103		modifier
104	}
105}
106
107#[visitor]
108impl Visit for TokenHighlighter {
109	fn enter_node(&mut self, query: VisitNode) {
110		let node_id = query.node_id.unwrap();
111		let modifier = SemanticModifier::from(&query.self_metadata());
112		let kind = SemanticKind::from_node_id(node_id).unwrap_or(SemanticKind::None);
113
114		if !modifier.is_none() || kind != SemanticKind::None {
115			self.insert(query.span(), kind, modifier);
116		}
117	}
118
119	// Visit style rules to specifically mark the curlies as Punctuation
120	fn visit_style_rule<'a>(&mut self, rule: &StyleRule<'a>) {
121		self.insert(rule.rule.block.open_curly.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
122		if let Some(close) = rule.rule.block.close_curly {
123			self.insert(close.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
124		}
125	}
126
127	// Visit Declarations to mark the name and colon
128	fn enter_declaration<'a, T: DeclarationValue<'a, CssMetadata>>(
129		&mut self,
130		property: &Declaration<'a, T, CssMetadata>,
131		_query: css_ast::VisitNode,
132	) {
133		let metadata = property.metadata();
134		let modifier = SemanticModifier::from(&metadata);
135		self.insert(property.name.to_span(), SemanticKind::Declaration, modifier);
136		self.insert(property.colon.to_span(), SemanticKind::Punctuation, SemanticModifier::none());
137	}
138
139	// Visit PropertyRule to mark the AtKeyword and the Prelude
140	fn visit_property_rule<'a>(&mut self, property: &PropertyRule<'a>) {
141		self.insert(property.name.to_span(), SemanticKind::AtKeyword, SemanticModifier::none());
142		self.insert(property.prelude.to_span(), SemanticKind::Declaration, SemanticModifier::Custom);
143	}
144
145	// Visit Color nodes to decorate with the swatch
146	fn visit_color<'b>(&mut self, color: &Color<'b>) {
147		let metadata = color.metadata();
148		let modifier = SemanticModifier::from(&metadata);
149		if let Some(bg) = color.to_chromashift() {
150			let swatch = SemanticDecoration::BackgroundColor(bg.into());
151			self.insert_with_decoration(color.to_span(), SemanticKind::StyleValueColor, modifier, swatch);
152		} else {
153			self.insert(color.to_span(), SemanticKind::StyleValueColor, modifier);
154		}
155	}
156
157	// Special case: CSSInt doesn't implement QueryableNode (has visit(skip))
158	fn visit_css_int(&mut self, int: &CSSInt) {
159		self.insert(int.to_span(), SemanticKind::StyleValueNumber, SemanticModifier::none());
160	}
161}