@@ -2,6 +2,7 @@ import type { TextDocument } from 'vscode-languageserver-textdocument'
2
2
import type { DocumentClassList , Span , State } from '../util/state'
3
3
import type {
4
4
ScopeContext ,
5
+ ScopeAtRule ,
5
6
ScopeClassList ,
6
7
ScopeClassName ,
7
8
} from './scope'
@@ -109,3 +110,108 @@ async function* analyzeClassLists(
109
110
yield listScope
110
111
}
111
112
}
113
+
114
+ const AT_RULE = / (?< name > @ [ a - z - ] + ) \s * (?< params > [ ^ ; { ] * ) (?: [ ; { ] | $ ) / dg
115
+
116
+ interface AtRuleDescriptor {
117
+ name : Span
118
+ params : Span
119
+ body : Span | null
120
+ }
121
+
122
+ /**
123
+ * Find all class lists and classes within the given block of text
124
+ */
125
+ function * analyzeAtRules ( boundary : LanguageBoundary , slice : string ) : Iterable < ScopeAtRule > {
126
+ if ( boundary . type !== 'css' ) return
127
+
128
+ let rules : AtRuleDescriptor [ ] = [ ]
129
+
130
+ // Look for at-rules like `@apply` and `@screen`
131
+ // These _may not be complete_ but that's okay
132
+ // We just want to know that they're there and where they are
133
+ for ( let match of slice . matchAll ( AT_RULE ) ) {
134
+ rules . push ( {
135
+ name : match . indices ! . groups ! . name ,
136
+ params : match . indices ! . groups ! . params ,
137
+ body : null ,
138
+ } )
139
+ }
140
+
141
+ for ( let rule of rules ) {
142
+ // Scan forward from each at-rule to find the body
143
+ // This is a bit naive but it's good enough for now
144
+ let depth = 0
145
+ for ( let i = rule . params [ 1 ] ; i < slice . length ; ++ i ) {
146
+ if ( depth === 0 && slice [ i ] === ';' ) {
147
+ break
148
+ }
149
+
150
+ //
151
+ else if ( slice [ i ] === '{' ) {
152
+ depth += 1
153
+
154
+ if ( depth === 1 ) {
155
+ rule . body = [ i , i ]
156
+ }
157
+ }
158
+
159
+ //
160
+ else if ( slice [ i ] === '}' ) {
161
+ depth -= 1
162
+
163
+ if ( depth === 0 ) {
164
+ rule . body ! [ 1 ] = i
165
+ break
166
+ }
167
+ }
168
+ }
169
+
170
+ for ( let scope of analyzeAtRule ( boundary , slice , rule ) ) {
171
+ yield scope
172
+ }
173
+ }
174
+ }
175
+
176
+ function * analyzeAtRule (
177
+ boundary : LanguageBoundary ,
178
+ slice : string ,
179
+ desc : AtRuleDescriptor ,
180
+ ) : Iterable < ScopeAtRule > {
181
+ let name = slice . slice ( desc . name [ 0 ] , desc . name [ 1 ] )
182
+ let params = slice . slice ( desc . params [ 0 ] , desc . params [ 1 ] ) . trim ( )
183
+ let parts = segment ( params , ' ' )
184
+
185
+ // Offset spans so they're document-relative
186
+ let offset = boundary . span [ 0 ]
187
+ let overallSpan : Span = [ desc . name [ 0 ] , desc . body ? desc . body [ 1 ] : desc . params [ 1 ] ]
188
+ let nameSpan : Span = [ desc . name [ 0 ] , desc . name [ 1 ] ]
189
+ let paramsSpan : Span = [ desc . params [ 0 ] , desc . params [ 1 ] ]
190
+ let bodySpan : Span | null = desc . body ? [ desc . body [ 0 ] , desc . body [ 1 ] ] : null
191
+
192
+ overallSpan [ 0 ] += offset
193
+ overallSpan [ 1 ] += offset
194
+ nameSpan [ 0 ] += offset
195
+ nameSpan [ 1 ] += offset
196
+ paramsSpan [ 0 ] += offset
197
+ paramsSpan [ 1 ] += offset
198
+
199
+ if ( bodySpan ) {
200
+ bodySpan [ 0 ] += offset
201
+ bodySpan [ 1 ] += offset
202
+ }
203
+
204
+ let scope : ScopeAtRule = {
205
+ kind : 'css.at-rule' ,
206
+ children : [ ] ,
207
+
208
+ source : {
209
+ scope : overallSpan ,
210
+ name : nameSpan ,
211
+ params : paramsSpan ,
212
+ body : bodySpan ,
213
+ } ,
214
+ }
215
+
216
+ yield scope
217
+ }
0 commit comments