1use omena_syntax::{StyleDialect, SyntaxKind};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum TemplatePlaceholderMode {
10 Brace,
11 AtomicIndexed,
12}
13
14pub trait DialectExtension {
15 fn dialect(&self) -> StyleDialect;
16
17 fn template_placeholder(&self) -> Option<TemplatePlaceholderMode> {
18 None
19 }
20
21 fn classify_variable_token(&self, text: &str) -> Option<SyntaxKind> {
22 match self.dialect() {
23 StyleDialect::Css => None,
24 StyleDialect::Scss | StyleDialect::Sass if text.starts_with('$') => {
25 Some(SyntaxKind::ScssVariable)
26 }
27 StyleDialect::Less if text.starts_with('@') => Some(SyntaxKind::LessVariable),
28 StyleDialect::Scss | StyleDialect::Sass | StyleDialect::Less => None,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct BuiltinDialectExtension {
35 dialect: StyleDialect,
36}
37
38impl BuiltinDialectExtension {
39 pub const fn new(dialect: StyleDialect) -> Self {
40 Self { dialect }
41 }
42}
43
44impl DialectExtension for BuiltinDialectExtension {
45 fn dialect(&self) -> StyleDialect {
46 self.dialect
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub(crate) struct AtRuleSpec {
52 pub(crate) node_kind: SyntaxKind,
53 pub(crate) block_kind: AtRuleBlockKind,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub(crate) enum AtRuleBlockKind {
58 GroupRuleList,
59 DeclarationList,
60 Keyframes,
61 Raw,
62}
63
64pub(crate) fn at_rule_spec(text: &str) -> Option<AtRuleSpec> {
65 let lowered = text.to_ascii_lowercase();
66 if at_rule_is_vendor_prefixed_keyframes(&lowered) {
67 return Some(AtRuleSpec {
68 node_kind: SyntaxKind::KeyframesRule,
69 block_kind: AtRuleBlockKind::Keyframes,
70 });
71 }
72 let (node_kind, block_kind) = match lowered.as_str() {
73 "@media" => (SyntaxKind::MediaRule, AtRuleBlockKind::GroupRuleList),
74 "@supports" => (SyntaxKind::SupportsRule, AtRuleBlockKind::GroupRuleList),
75 "@when" => (SyntaxKind::WhenRule, AtRuleBlockKind::GroupRuleList),
76 "@if" => (SyntaxKind::IfRule, AtRuleBlockKind::GroupRuleList),
77 "@else" => (SyntaxKind::ElseRule, AtRuleBlockKind::GroupRuleList),
78 "@container" => (SyntaxKind::ContainerRule, AtRuleBlockKind::GroupRuleList),
79 "@layer" => (SyntaxKind::LayerRule, AtRuleBlockKind::GroupRuleList),
80 "@scope" => (SyntaxKind::ScopeRule, AtRuleBlockKind::GroupRuleList),
81 "@starting-style" => (
82 SyntaxKind::StartingStyleRule,
83 AtRuleBlockKind::GroupRuleList,
84 ),
85 "@nest" => (SyntaxKind::NestRule, AtRuleBlockKind::DeclarationList),
86 "@keyframes" => (SyntaxKind::KeyframesRule, AtRuleBlockKind::Keyframes),
87 "@font-face" => (SyntaxKind::FontFaceRule, AtRuleBlockKind::DeclarationList),
88 "@page" => (SyntaxKind::PageRule, AtRuleBlockKind::DeclarationList),
89 "@property" => (SyntaxKind::PropertyRule, AtRuleBlockKind::DeclarationList),
90 "@function" => (SyntaxKind::FunctionRule, AtRuleBlockKind::DeclarationList),
91 "@counter-style" => (
92 SyntaxKind::CounterStyleRule,
93 AtRuleBlockKind::DeclarationList,
94 ),
95 "@font-palette-values" => (
96 SyntaxKind::FontPaletteValuesRule,
97 AtRuleBlockKind::DeclarationList,
98 ),
99 "@color-profile" => (
100 SyntaxKind::ColorProfileRule,
101 AtRuleBlockKind::DeclarationList,
102 ),
103 "@position-try" => (
104 SyntaxKind::PositionTryRule,
105 AtRuleBlockKind::DeclarationList,
106 ),
107 "@font-feature-values" => (
108 SyntaxKind::FontFeatureValuesRule,
109 AtRuleBlockKind::GroupRuleList,
110 ),
111 "@stylistic" => (
112 SyntaxKind::FontFeatureValuesStylisticRule,
113 AtRuleBlockKind::DeclarationList,
114 ),
115 "@styleset" => (
116 SyntaxKind::FontFeatureValuesStylesetRule,
117 AtRuleBlockKind::DeclarationList,
118 ),
119 "@character-variant" => (
120 SyntaxKind::FontFeatureValuesCharacterVariantRule,
121 AtRuleBlockKind::DeclarationList,
122 ),
123 "@swash" => (
124 SyntaxKind::FontFeatureValuesSwashRule,
125 AtRuleBlockKind::DeclarationList,
126 ),
127 "@ornaments" => (
128 SyntaxKind::FontFeatureValuesOrnamentsRule,
129 AtRuleBlockKind::DeclarationList,
130 ),
131 "@annotation" => (
132 SyntaxKind::FontFeatureValuesAnnotationRule,
133 AtRuleBlockKind::DeclarationList,
134 ),
135 "@historical-forms" => (
136 SyntaxKind::FontFeatureValuesHistoricalFormsRule,
137 AtRuleBlockKind::DeclarationList,
138 ),
139 "@view-transition" => (
140 SyntaxKind::ViewTransitionRule,
141 AtRuleBlockKind::DeclarationList,
142 ),
143 "@charset" => (SyntaxKind::CharsetRule, AtRuleBlockKind::Raw),
144 "@import" => (SyntaxKind::ImportRule, AtRuleBlockKind::Raw),
145 "@namespace" => (SyntaxKind::NamespaceRule, AtRuleBlockKind::Raw),
146 "@custom-media" => (SyntaxKind::CustomMediaRule, AtRuleBlockKind::Raw),
147 text if is_page_margin_at_rule(text) => {
148 (SyntaxKind::PageMarginRule, AtRuleBlockKind::DeclarationList)
149 }
150 _ => return None,
151 };
152 Some(AtRuleSpec {
153 node_kind,
154 block_kind,
155 })
156}
157
158fn at_rule_is_vendor_prefixed_keyframes(text: &str) -> bool {
159 let Some(rest) = text.strip_prefix("@-") else {
160 return false;
161 };
162 let Some((vendor, rule)) = rest.split_once('-') else {
163 return false;
164 };
165 !vendor.is_empty() && rule == "keyframes"
166}
167
168pub(crate) fn scss_at_rule_spec(text: &str) -> Option<AtRuleSpec> {
169 let (node_kind, block_kind) = match text {
170 "@use" => (SyntaxKind::ScssUseRule, AtRuleBlockKind::Raw),
171 "@forward" => (SyntaxKind::ScssForwardRule, AtRuleBlockKind::Raw),
172 "@mixin" => (
173 SyntaxKind::ScssMixinDeclaration,
174 AtRuleBlockKind::DeclarationList,
175 ),
176 "@include" => (
177 SyntaxKind::ScssIncludeRule,
178 AtRuleBlockKind::DeclarationList,
179 ),
180 "@function" => (
181 SyntaxKind::ScssFunctionDeclaration,
182 AtRuleBlockKind::DeclarationList,
183 ),
184 "@return" => (SyntaxKind::ScssReturnRule, AtRuleBlockKind::Raw),
185 "@extend" => (SyntaxKind::ScssExtendRule, AtRuleBlockKind::Raw),
186 "@if" => (SyntaxKind::ScssControlIf, AtRuleBlockKind::DeclarationList),
187 "@else" => (
188 SyntaxKind::ScssControlElse,
189 AtRuleBlockKind::DeclarationList,
190 ),
191 "@each" => (
192 SyntaxKind::ScssControlEach,
193 AtRuleBlockKind::DeclarationList,
194 ),
195 "@for" => (SyntaxKind::ScssControlFor, AtRuleBlockKind::DeclarationList),
196 "@while" => (
197 SyntaxKind::ScssControlWhile,
198 AtRuleBlockKind::DeclarationList,
199 ),
200 "@at-root" => (SyntaxKind::ScssAtRootRule, AtRuleBlockKind::DeclarationList),
201 "@error" => (SyntaxKind::ScssErrorRule, AtRuleBlockKind::Raw),
202 "@warn" => (SyntaxKind::ScssWarnRule, AtRuleBlockKind::Raw),
203 "@debug" => (SyntaxKind::ScssDebugRule, AtRuleBlockKind::Raw),
204 "@content" => (SyntaxKind::ScssContentRule, AtRuleBlockKind::Raw),
205 _ => return None,
206 };
207 Some(AtRuleSpec {
208 node_kind,
209 block_kind,
210 })
211}
212
213fn is_page_margin_at_rule(text: &str) -> bool {
214 matches!(
215 text,
216 "@top-left-corner"
217 | "@top-left"
218 | "@top-center"
219 | "@top-right"
220 | "@top-right-corner"
221 | "@bottom-left-corner"
222 | "@bottom-left"
223 | "@bottom-center"
224 | "@bottom-right"
225 | "@bottom-right-corner"
226 | "@left-top"
227 | "@left-middle"
228 | "@left-bottom"
229 | "@right-top"
230 | "@right-middle"
231 | "@right-bottom"
232 )
233}