11import { TextDocument , Range , Position } from 'vscode-languageserver'
2- import { DocumentClassName , DocumentClassList } from './state'
2+ import { DocumentClassName , DocumentClassList , State } from './state'
33import 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'
49
510export function findAll ( re : RegExp , str : string ) : RegExpMatchArray [ ] {
611 let match : RegExpMatchArray
@@ -21,9 +26,10 @@ export function findLast(re: RegExp, str: string): RegExpMatchArray {
2126
2227export function findClassNamesInRange (
2328 doc : TextDocument ,
24- range : Range
29+ range : Range ,
30+ mode : 'html' | 'css'
2531) : DocumentClassName [ ] {
26- const classLists = findClassListsInRange ( doc , range )
32+ const classLists = findClassListsInRange ( doc , range , mode )
2733 return [ ] . concat . apply (
2834 [ ] ,
2935 classLists . map ( ( { classList, range } ) => {
@@ -58,7 +64,7 @@ export function findClassNamesInRange(
5864 )
5965}
6066
61- export function findClassListsInRange (
67+ export function findClassListsInCssRange (
6268 doc : TextDocument ,
6369 range : Range
6470) : DocumentClassList [ ] {
@@ -87,7 +93,146 @@ export function findClassListsInRange(
8793 } )
8894}
8995
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+
90202function indexToPosition ( str : string , index : number ) : Position {
91203 const { line, col } = lineColumn ( str + '\n' , index )
92204 return { line : line - 1 , character : col - 1 }
93205}
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