@@ -36,13 +36,8 @@ import pkgUp from 'pkg-up'
36
36
import stackTrace from 'stack-trace'
37
37
import extractClassNames from './lib/extractClassNames'
38
38
import { klona } from 'klona/full'
39
- import { doHover } from '@tailwindcss/language-service/src/hoverProvider'
40
- import { getCodeLens } from '@tailwindcss/language-service/src/codeLensProvider'
39
+ import { createLanguageService } from '@tailwindcss/language-service/src/service'
41
40
import { Resolver } from './resolver'
42
- import {
43
- doComplete ,
44
- resolveCompletionItem ,
45
- } from '@tailwindcss/language-service/src/completionProvider'
46
41
import type {
47
42
State ,
48
43
FeatureFlags ,
@@ -52,17 +47,12 @@ import type {
52
47
ClassEntry ,
53
48
} from '@tailwindcss/language-service/src/util/state'
54
49
import { provideDiagnostics } from './lsp/diagnosticsProvider'
55
- import { doCodeActions } from '@tailwindcss/language-service/src/codeActions/codeActionProvider'
56
- import { getDocumentColors } from '@tailwindcss/language-service/src/documentColorProvider'
57
- import { getDocumentLinks } from '@tailwindcss/language-service/src/documentLinksProvider'
58
50
import { debounce } from 'debounce'
59
51
import { getModuleDependencies } from './util/getModuleDependencies'
60
52
import assert from 'node:assert'
61
53
// import postcssLoadConfig from 'postcss-load-config'
62
54
import { bigSign } from '@tailwindcss/language-service/src/util/jit'
63
55
import { getColor } from '@tailwindcss/language-service/src/util/color'
64
- import * as culori from 'culori'
65
- import namedColors from 'color-name'
66
56
import tailwindPlugins from './lib/plugins'
67
57
import isExcluded from './util/isExcluded'
68
58
import { getFileFsPath } from './util/uri'
@@ -72,7 +62,6 @@ import {
72
62
firstOptional ,
73
63
withoutLogs ,
74
64
clearRequireCache ,
75
- withFallback ,
76
65
isObject ,
77
66
pathToFileURL ,
78
67
changeAffectsFile ,
@@ -85,8 +74,7 @@ import { supportedFeatures } from '@tailwindcss/language-service/src/features'
85
74
import { loadDesignSystem } from './util/v4'
86
75
import { readCssFile } from './util/css'
87
76
import type { DesignSystem } from '@tailwindcss/language-service/src/util/v4'
88
-
89
- const colorNames = Object . keys ( namedColors )
77
+ import type { File , FileType } from '@tailwindcss/language-service/src/fs'
90
78
91
79
function getConfigId ( configPath : string , configDependencies : string [ ] ) : string {
92
80
return JSON . stringify (
@@ -234,36 +222,71 @@ export async function createProjectService(
234
222
getDocumentSymbols : ( uri : string ) => {
235
223
return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
236
224
} ,
237
- async readDirectory ( document , directory ) {
225
+ async readDirectory ( ) {
226
+ // NOTE: This is overwritten in `createLanguageDocument`
227
+ throw new Error ( 'Not implemented' )
228
+ } ,
229
+ } ,
230
+ }
231
+
232
+ let service = createLanguageService ( {
233
+ state : ( ) => state ,
234
+ fs : {
235
+ async document ( uri : string ) {
236
+ return documentService . getDocument ( uri )
237
+ } ,
238
+ async resolve ( document : TextDocument , relativePath : string ) : Promise < string | null > {
239
+ let documentPath = URI . parse ( document . uri ) . fsPath
240
+ let baseDir = path . dirname ( documentPath )
241
+
242
+ let resolved = await resolver . substituteId ( relativePath , baseDir )
243
+ resolved ??= relativePath
244
+
245
+ return URI . file ( path . resolve ( baseDir , resolved ) ) . toString ( )
246
+ } ,
247
+
248
+ async readDirectory ( document : TextDocument , filepath : string ) : Promise < File [ ] > {
238
249
try {
239
250
let baseDir = path . dirname ( getFileFsPath ( document . uri ) )
240
- directory = await resolver . substituteId ( `${ directory } /` , baseDir )
241
- directory = path . resolve ( baseDir , directory )
242
-
243
- let dirents = await fs . promises . readdir ( directory , { withFileTypes : true } )
244
-
245
- let result : Array < [ string , { isDirectory : boolean } ] | null > = await Promise . all (
246
- dirents . map ( async ( dirent ) => {
247
- let isDirectory = dirent . isDirectory ( )
248
- let shouldRemove = await isExcluded (
249
- state ,
250
- document ,
251
- path . join ( directory , dirent . name , isDirectory ? '/' : '' ) ,
252
- )
251
+ filepath = await resolver . substituteId ( `${ filepath } /` , baseDir )
252
+ filepath = path . resolve ( baseDir , filepath )
253
253
254
- if ( shouldRemove ) return null
254
+ let dirents = await fs . promises . readdir ( filepath , { withFileTypes : true } )
255
255
256
- return [ dirent . name , { isDirectory } ]
257
- } ) ,
258
- )
256
+ let results : File [ ] = [ ]
257
+
258
+ for ( let dirent of dirents ) {
259
+ let isDirectory = dirent . isDirectory ( )
260
+ let shouldRemove = await isExcluded (
261
+ state ,
262
+ document ,
263
+ path . join ( filepath , dirent . name , isDirectory ? '/' : '' ) ,
264
+ )
265
+ if ( shouldRemove ) continue
266
+
267
+ let type : FileType = 'unknown'
259
268
260
- return result . filter ( ( item ) => item !== null )
269
+ if ( dirent . isFile ( ) ) {
270
+ type = 'file'
271
+ } else if ( dirent . isDirectory ( ) ) {
272
+ type = 'directory'
273
+ } else if ( dirent . isSymbolicLink ( ) ) {
274
+ type = 'symbolic-link'
275
+ }
276
+
277
+ results . push ( {
278
+ name : dirent . name ,
279
+ type,
280
+ } )
281
+ }
282
+
283
+ return results
261
284
} catch {
262
285
return [ ]
263
286
}
264
287
} ,
265
288
} ,
266
- }
289
+ } )
267
290
268
291
if ( projectConfig . configPath && projectConfig . config . source === 'js' ) {
269
292
let deps = [ ]
@@ -1187,139 +1210,79 @@ export async function createProjectService(
1187
1210
} ,
1188
1211
onFileEvents,
1189
1212
async onHover ( params : TextDocumentPositionParams ) : Promise < Hover > {
1190
- return withFallback ( async ( ) => {
1191
- if ( ! state . enabled ) return null
1192
- let document = documentService . getDocument ( params . textDocument . uri )
1193
- if ( ! document ) return null
1194
- let settings = await state . editor . getConfiguration ( document . uri )
1195
- if ( ! settings . tailwindCSS . hovers ) return null
1196
- if ( await isExcluded ( state , document ) ) return null
1197
- return doHover ( state , document , params . position )
1198
- } , null )
1213
+ try {
1214
+ let doc = await service . open ( params . textDocument . uri )
1215
+ if ( ! doc ) return null
1216
+ return doc . hover ( params . position )
1217
+ } catch {
1218
+ return null
1219
+ }
1199
1220
} ,
1200
1221
async onCodeLens ( params : CodeLensParams ) : Promise < CodeLens [ ] > {
1201
- return withFallback ( async ( ) => {
1202
- if ( ! state . enabled ) return null
1203
- let document = documentService . getDocument ( params . textDocument . uri )
1204
- if ( ! document ) return null
1205
- let settings = await state . editor . getConfiguration ( document . uri )
1206
- if ( ! settings . tailwindCSS . codeLens ) return null
1207
- if ( await isExcluded ( state , document ) ) return null
1208
- return getCodeLens ( state , document )
1209
- } , null )
1222
+ try {
1223
+ let doc = await service . open ( params . textDocument . uri )
1224
+ if ( ! doc ) return null
1225
+ return doc . codeLenses ( )
1226
+ } catch {
1227
+ return [ ]
1228
+ }
1210
1229
} ,
1211
1230
async onCompletion ( params : CompletionParams ) : Promise < CompletionList > {
1212
- return withFallback ( async ( ) => {
1213
- if ( ! state . enabled ) return null
1214
- let document = documentService . getDocument ( params . textDocument . uri )
1215
- if ( ! document ) return null
1216
- let settings = await state . editor . getConfiguration ( document . uri )
1217
- if ( ! settings . tailwindCSS . suggestions ) return null
1218
- if ( await isExcluded ( state , document ) ) return null
1219
- return doComplete ( state , document , params . position , params . context )
1220
- } , null )
1231
+ try {
1232
+ let doc = await service . open ( params . textDocument . uri )
1233
+ if ( ! doc ) return null
1234
+ return doc . completions ( params . position )
1235
+ } catch {
1236
+ return null
1237
+ }
1221
1238
} ,
1222
- onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1223
- return withFallback ( ( ) => {
1224
- if ( ! state . enabled ) return null
1225
- return resolveCompletionItem ( state , item )
1226
- } , null )
1239
+ async onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1240
+ try {
1241
+ return await service . resolveCompletion ( item )
1242
+ } catch {
1243
+ return null
1244
+ }
1227
1245
} ,
1228
1246
async onCodeAction ( params : CodeActionParams ) : Promise < CodeAction [ ] > {
1229
- return withFallback ( async ( ) => {
1230
- if ( ! state . enabled ) return null
1231
- let document = documentService . getDocument ( params . textDocument . uri )
1232
- if ( ! document ) return null
1233
- let settings = await state . editor . getConfiguration ( document . uri )
1234
- if ( ! settings . tailwindCSS . codeActions ) return null
1235
- return doCodeActions ( state , params , document )
1236
- } , null )
1247
+ try {
1248
+ let doc = await service . open ( params . textDocument . uri )
1249
+ if ( ! doc ) return null
1250
+ return doc . codeActions ( params . range , params . context )
1251
+ } catch {
1252
+ return [ ]
1253
+ }
1237
1254
} ,
1238
- onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1239
- if ( ! state . enabled ) return null
1240
- let document = documentService . getDocument ( params . textDocument . uri )
1241
- if ( ! document ) return null
1242
-
1243
- let documentPath = URI . parse ( document . uri ) . fsPath
1244
- let baseDir = path . dirname ( documentPath )
1245
-
1246
- async function resolveTarget ( linkPath : string ) {
1247
- linkPath = ( await resolver . substituteId ( linkPath , baseDir ) ) ?? linkPath
1248
-
1249
- return URI . file ( path . resolve ( baseDir , linkPath ) ) . toString ( )
1255
+ async onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1256
+ try {
1257
+ let doc = await service . open ( params . textDocument . uri )
1258
+ if ( ! doc ) return null
1259
+ return doc . documentLinks ( )
1260
+ } catch {
1261
+ return [ ]
1250
1262
}
1251
-
1252
- return getDocumentLinks ( state , document , resolveTarget )
1253
1263
} ,
1254
1264
provideDiagnostics : debounce (
1255
- ( document : TextDocument ) => {
1256
- if ( ! state . enabled ) return
1257
- provideDiagnostics ( state , document )
1258
- } ,
1265
+ ( document ) => provideDiagnostics ( service , state , document ) ,
1259
1266
params . initializationOptions ?. testMode ? 0 : 500 ,
1260
1267
) ,
1261
- provideDiagnosticsForce : ( document : TextDocument ) => {
1262
- if ( ! state . enabled ) return
1263
- provideDiagnostics ( state , document )
1264
- } ,
1268
+ provideDiagnosticsForce : ( document ) => provideDiagnostics ( service , state , document ) ,
1265
1269
async onDocumentColor ( params : DocumentColorParams ) : Promise < ColorInformation [ ] > {
1266
- return withFallback ( async ( ) => {
1267
- if ( ! state . enabled ) return [ ]
1268
- let document = documentService . getDocument ( params . textDocument . uri )
1269
- if ( ! document ) return [ ]
1270
- if ( await isExcluded ( state , document ) ) return null
1271
- return getDocumentColors ( state , document )
1272
- } , null )
1270
+ try {
1271
+ let doc = await service . open ( params . textDocument . uri )
1272
+ if ( ! doc ) return null
1273
+ return doc . documentColors ( )
1274
+ } catch {
1275
+ return [ ]
1276
+ }
1273
1277
} ,
1274
1278
async onColorPresentation ( params : ColorPresentationParams ) : Promise < ColorPresentation [ ] > {
1275
- let document = documentService . getDocument ( params . textDocument . uri )
1276
- if ( ! document ) return [ ]
1277
- let className = document . getText ( params . range )
1278
- let match = className . match (
1279
- new RegExp ( `-\\[(${ colorNames . join ( '|' ) } |(?:(?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$` , 'i' ) ,
1280
- )
1281
- // let match = className.match(/-\[((?:#|rgba?\(|hsla?\()[^\]]+)\]$/i)
1282
- if ( match === null ) return [ ]
1283
-
1284
- let currentColor = match [ 1 ]
1285
-
1286
- let isNamedColor = colorNames . includes ( currentColor )
1287
-
1288
- let color : culori . Color = {
1289
- mode : 'rgb' ,
1290
- r : params . color . red ,
1291
- g : params . color . green ,
1292
- b : params . color . blue ,
1293
- alpha : params . color . alpha ,
1294
- }
1295
-
1296
- let hexValue = culori . formatHex8 ( color )
1297
-
1298
- if ( ! isNamedColor && ( currentColor . length === 4 || currentColor . length === 5 ) ) {
1299
- let [ , ...chars ] =
1300
- hexValue . match ( / ^ # ( [ a - f \d ] ) \1( [ a - f \d ] ) \2( [ a - f \d ] ) \3(?: ( [ a - f \d ] ) \4) ? $ / i) ?? [ ]
1301
- if ( chars . length ) {
1302
- hexValue = `#${ chars . filter ( Boolean ) . join ( '' ) } `
1303
- }
1304
- }
1305
-
1306
- if ( hexValue . length === 5 ) {
1307
- hexValue = hexValue . replace ( / f $ / , '' )
1308
- } else if ( hexValue . length === 9 ) {
1309
- hexValue = hexValue . replace ( / f f $ / , '' )
1279
+ try {
1280
+ let doc = await service . open ( params . textDocument . uri )
1281
+ if ( ! doc ) return null
1282
+ return doc . colorPresentation ( params . color , params . range )
1283
+ } catch {
1284
+ return [ ]
1310
1285
}
1311
-
1312
- let prefix = className . substr ( 0 , match . index )
1313
-
1314
- return [
1315
- hexValue ,
1316
- culori . formatRgb ( color ) . replace ( / / g, '' ) ,
1317
- culori
1318
- . formatHsl ( color )
1319
- . replace ( / / g, '' )
1320
- // round numbers
1321
- . replace ( / \d + \. \d + ( % ? ) / g, ( value , suffix ) => `${ Math . round ( parseFloat ( value ) ) } ${ suffix } ` ) ,
1322
- ] . map ( ( value ) => ( { label : `${ prefix } -[${ value } ]` } ) )
1323
1286
} ,
1324
1287
sortClassLists ( classLists : string [ ] ) : string [ ] {
1325
1288
if ( ! state . jit ) {
0 commit comments