1
1
import { TextDocument , Range , Position } from 'vscode-languageserver'
2
- import { DocumentClassName , DocumentClassList } from './state'
2
+ import { DocumentClassName , DocumentClassList , State } from './state'
3
3
import lineColumn from 'line-column'
4
+ import { isCssContext } from './css'
5
+ import { isHtmlContext } from './html'
6
+ import { isWithinRange } from './isWithinRange'
7
+ import { isJsContext } from './js'
8
+ import { getClassAttributeLexer } from './lexers'
4
9
5
10
export function findAll ( re : RegExp , str : string ) : RegExpMatchArray [ ] {
6
11
let match : RegExpMatchArray
@@ -21,9 +26,10 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
21
26
22
27
export function findClassNamesInRange (
23
28
doc : TextDocument ,
24
- range : Range
29
+ range : Range ,
30
+ mode : 'html' | 'css'
25
31
) : DocumentClassName [ ] {
26
- const classLists = findClassListsInRange ( doc , range )
32
+ const classLists = findClassListsInRange ( doc , range , mode )
27
33
return [ ] . concat . apply (
28
34
[ ] ,
29
35
classLists . map ( ( { classList, range } ) => {
@@ -58,7 +64,7 @@ export function findClassNamesInRange(
58
64
)
59
65
}
60
66
61
- export function findClassListsInRange (
67
+ export function findClassListsInCssRange (
62
68
doc : TextDocument ,
63
69
range : Range
64
70
) : DocumentClassList [ ] {
@@ -87,7 +93,146 @@ export function findClassListsInRange(
87
93
} )
88
94
}
89
95
96
+ export function findClassListsInHtmlRange (
97
+ doc : TextDocument ,
98
+ range : Range
99
+ ) : DocumentClassList [ ] {
100
+ const text = doc . getText ( range )
101
+ const matches = findAll ( / [ \s : ] c l a s s (?: N a m e ) ? = [ ' " ` { ] / g, text )
102
+ const result : DocumentClassList [ ] = [ ]
103
+
104
+ matches . forEach ( ( match ) => {
105
+ const subtext = text . substr ( match . index + match [ 0 ] . length - 1 , 200 )
106
+
107
+ let lexer = getClassAttributeLexer ( )
108
+ lexer . reset ( subtext )
109
+
110
+ let classLists : { value : string ; offset : number } [ ] = [ ]
111
+ let token : moo . Token
112
+ let currentClassList : { value : string ; offset : number }
113
+
114
+ try {
115
+ for ( let token of lexer ) {
116
+ if ( token . type === 'classlist' ) {
117
+ if ( currentClassList ) {
118
+ currentClassList . value += token . value
119
+ } else {
120
+ currentClassList = {
121
+ value : token . value ,
122
+ offset : token . offset ,
123
+ }
124
+ }
125
+ } else {
126
+ if ( currentClassList ) {
127
+ classLists . push ( {
128
+ value : currentClassList . value ,
129
+ offset : currentClassList . offset ,
130
+ } )
131
+ }
132
+ currentClassList = undefined
133
+ }
134
+ }
135
+ } catch ( _ ) { }
136
+
137
+ if ( currentClassList ) {
138
+ classLists . push ( {
139
+ value : currentClassList . value ,
140
+ offset : currentClassList . offset ,
141
+ } )
142
+ }
143
+
144
+ result . push (
145
+ ...classLists
146
+ . map ( ( { value, offset } ) => {
147
+ if ( value . trim ( ) === '' ) {
148
+ return null
149
+ }
150
+
151
+ const before = value . match ( / ^ \s * / )
152
+ const beforeOffset = before === null ? 0 : before [ 0 ] . length
153
+ const after = value . match ( / \s * $ / )
154
+ const afterOffset = after === null ? 0 : - after [ 0 ] . length
155
+
156
+ const start = indexToPosition (
157
+ text ,
158
+ match . index + match [ 0 ] . length - 1 + offset + beforeOffset
159
+ )
160
+ const end = indexToPosition (
161
+ text ,
162
+ match . index +
163
+ match [ 0 ] . length -
164
+ 1 +
165
+ offset +
166
+ value . length +
167
+ afterOffset
168
+ )
169
+
170
+ return {
171
+ classList : value ,
172
+ range : {
173
+ start : {
174
+ line : range . start . line + start . line ,
175
+ character : range . start . character + start . character ,
176
+ } ,
177
+ end : {
178
+ line : range . start . line + end . line ,
179
+ character : range . start . character + end . character ,
180
+ } ,
181
+ } ,
182
+ }
183
+ } )
184
+ . filter ( ( x ) => x !== null )
185
+ )
186
+ } )
187
+
188
+ return result
189
+ }
190
+
191
+ export function findClassListsInRange (
192
+ doc : TextDocument ,
193
+ range : Range ,
194
+ mode : 'html' | 'css'
195
+ ) : DocumentClassList [ ] {
196
+ if ( mode === 'css' ) {
197
+ return findClassListsInCssRange ( doc , range )
198
+ }
199
+ return findClassListsInHtmlRange ( doc , range )
200
+ }
201
+
90
202
function indexToPosition ( str : string , index : number ) : Position {
91
203
const { line, col } = lineColumn ( str + '\n' , index )
92
204
return { line : line - 1 , character : col - 1 }
93
205
}
206
+
207
+ export function findClassNameAtPosition (
208
+ state : State ,
209
+ doc : TextDocument ,
210
+ position : Position
211
+ ) : DocumentClassName {
212
+ let classNames = [ ]
213
+ const searchRange = {
214
+ start : { line : Math . max ( position . line - 10 , 0 ) , character : 0 } ,
215
+ end : { line : position . line + 10 , character : 0 } ,
216
+ }
217
+
218
+ if ( isCssContext ( state , doc , position ) ) {
219
+ classNames = findClassNamesInRange ( doc , searchRange , 'css' )
220
+ } else if (
221
+ isHtmlContext ( state , doc , position ) ||
222
+ isJsContext ( state , doc , position )
223
+ ) {
224
+ classNames = findClassNamesInRange ( doc , searchRange , 'html' )
225
+ }
226
+
227
+ if ( classNames . length === 0 ) {
228
+ return null
229
+ }
230
+
231
+ const className = classNames . find ( ( { range } ) =>
232
+ isWithinRange ( position , range )
233
+ )
234
+
235
+ if ( ! className ) return null
236
+
237
+ return className
238
+ }
0 commit comments