@@ -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 = [ ]
@@ -1193,139 +1216,79 @@ export async function createProjectService(
1193
1216
} ,
1194
1217
onFileEvents,
1195
1218
async onHover ( params : TextDocumentPositionParams ) : Promise < Hover > {
1196
- return withFallback ( async ( ) => {
1197
- if ( ! state . enabled ) return null
1198
- let document = documentService . getDocument ( params . textDocument . uri )
1199
- if ( ! document ) return null
1200
- let settings = await state . editor . getConfiguration ( document . uri )
1201
- if ( ! settings . tailwindCSS . hovers ) return null
1202
- if ( await isExcluded ( state , document ) ) return null
1203
- return doHover ( state , document , params . position )
1204
- } , null )
1219
+ try {
1220
+ let doc = await service . open ( params . textDocument . uri )
1221
+ if ( ! doc ) return null
1222
+ return doc . hover ( params . position )
1223
+ } catch {
1224
+ return null
1225
+ }
1205
1226
} ,
1206
1227
async onCodeLens ( params : CodeLensParams ) : Promise < CodeLens [ ] > {
1207
- return withFallback ( async ( ) => {
1208
- if ( ! state . enabled ) return null
1209
- let document = documentService . getDocument ( params . textDocument . uri )
1210
- if ( ! document ) return null
1211
- let settings = await state . editor . getConfiguration ( document . uri )
1212
- if ( ! settings . tailwindCSS . codeLens ) return null
1213
- if ( await isExcluded ( state , document ) ) return null
1214
- return getCodeLens ( state , document )
1215
- } , null )
1228
+ try {
1229
+ let doc = await service . open ( params . textDocument . uri )
1230
+ if ( ! doc ) return null
1231
+ return doc . codeLenses ( )
1232
+ } catch {
1233
+ return [ ]
1234
+ }
1216
1235
} ,
1217
1236
async onCompletion ( params : CompletionParams ) : Promise < CompletionList > {
1218
- return withFallback ( async ( ) => {
1219
- if ( ! state . enabled ) return null
1220
- let document = documentService . getDocument ( params . textDocument . uri )
1221
- if ( ! document ) return null
1222
- let settings = await state . editor . getConfiguration ( document . uri )
1223
- if ( ! settings . tailwindCSS . suggestions ) return null
1224
- if ( await isExcluded ( state , document ) ) return null
1225
- return doComplete ( state , document , params . position , params . context )
1226
- } , null )
1237
+ try {
1238
+ let doc = await service . open ( params . textDocument . uri )
1239
+ if ( ! doc ) return null
1240
+ return doc . completions ( params . position )
1241
+ } catch {
1242
+ return null
1243
+ }
1227
1244
} ,
1228
- onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1229
- return withFallback ( ( ) => {
1230
- if ( ! state . enabled ) return null
1231
- return resolveCompletionItem ( state , item )
1232
- } , null )
1245
+ async onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1246
+ try {
1247
+ return await service . resolveCompletion ( item )
1248
+ } catch {
1249
+ return null
1250
+ }
1233
1251
} ,
1234
1252
async onCodeAction ( params : CodeActionParams ) : Promise < CodeAction [ ] > {
1235
- return withFallback ( async ( ) => {
1236
- if ( ! state . enabled ) return null
1237
- let document = documentService . getDocument ( params . textDocument . uri )
1238
- if ( ! document ) return null
1239
- let settings = await state . editor . getConfiguration ( document . uri )
1240
- if ( ! settings . tailwindCSS . codeActions ) return null
1241
- return doCodeActions ( state , params , document )
1242
- } , null )
1253
+ try {
1254
+ let doc = await service . open ( params . textDocument . uri )
1255
+ if ( ! doc ) return null
1256
+ return doc . codeActions ( params . range , params . context )
1257
+ } catch {
1258
+ return [ ]
1259
+ }
1243
1260
} ,
1244
- onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1245
- if ( ! state . enabled ) return null
1246
- let document = documentService . getDocument ( params . textDocument . uri )
1247
- if ( ! document ) return null
1248
-
1249
- let documentPath = URI . parse ( document . uri ) . fsPath
1250
- let baseDir = path . dirname ( documentPath )
1251
-
1252
- async function resolveTarget ( linkPath : string ) {
1253
- linkPath = ( await resolver . substituteId ( linkPath , baseDir ) ) ?? linkPath
1254
-
1255
- return URI . file ( path . resolve ( baseDir , linkPath ) ) . toString ( )
1261
+ async onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1262
+ try {
1263
+ let doc = await service . open ( params . textDocument . uri )
1264
+ if ( ! doc ) return null
1265
+ return doc . documentLinks ( )
1266
+ } catch {
1267
+ return [ ]
1256
1268
}
1257
-
1258
- return getDocumentLinks ( state , document , resolveTarget )
1259
1269
} ,
1260
1270
provideDiagnostics : debounce (
1261
- ( document : TextDocument ) => {
1262
- if ( ! state . enabled ) return
1263
- provideDiagnostics ( state , document )
1264
- } ,
1271
+ ( document ) => provideDiagnostics ( service , state , document ) ,
1265
1272
params . initializationOptions ?. testMode ? 0 : 500 ,
1266
1273
) ,
1267
- provideDiagnosticsForce : ( document : TextDocument ) => {
1268
- if ( ! state . enabled ) return
1269
- provideDiagnostics ( state , document )
1270
- } ,
1274
+ provideDiagnosticsForce : ( document ) => provideDiagnostics ( service , state , document ) ,
1271
1275
async onDocumentColor ( params : DocumentColorParams ) : Promise < ColorInformation [ ] > {
1272
- return withFallback ( async ( ) => {
1273
- if ( ! state . enabled ) return [ ]
1274
- let document = documentService . getDocument ( params . textDocument . uri )
1275
- if ( ! document ) return [ ]
1276
- if ( await isExcluded ( state , document ) ) return null
1277
- return getDocumentColors ( state , document )
1278
- } , null )
1276
+ try {
1277
+ let doc = await service . open ( params . textDocument . uri )
1278
+ if ( ! doc ) return null
1279
+ return doc . documentColors ( )
1280
+ } catch {
1281
+ return [ ]
1282
+ }
1279
1283
} ,
1280
1284
async onColorPresentation ( params : ColorPresentationParams ) : Promise < ColorPresentation [ ] > {
1281
- let document = documentService . getDocument ( params . textDocument . uri )
1282
- if ( ! document ) return [ ]
1283
- let className = document . getText ( params . range )
1284
- let match = className . match (
1285
- new RegExp ( `-\\[(${ colorNames . join ( '|' ) } |(?:(?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$` , 'i' ) ,
1286
- )
1287
- // let match = className.match(/-\[((?:#|rgba?\(|hsla?\()[^\]]+)\]$/i)
1288
- if ( match === null ) return [ ]
1289
-
1290
- let currentColor = match [ 1 ]
1291
-
1292
- let isNamedColor = colorNames . includes ( currentColor )
1293
-
1294
- let color : culori . Color = {
1295
- mode : 'rgb' ,
1296
- r : params . color . red ,
1297
- g : params . color . green ,
1298
- b : params . color . blue ,
1299
- alpha : params . color . alpha ,
1300
- }
1301
-
1302
- let hexValue = culori . formatHex8 ( color )
1303
-
1304
- if ( ! isNamedColor && ( currentColor . length === 4 || currentColor . length === 5 ) ) {
1305
- let [ , ...chars ] =
1306
- hexValue . match ( / ^ # ( [ a - f \d ] ) \1( [ a - f \d ] ) \2( [ a - f \d ] ) \3(?: ( [ a - f \d ] ) \4) ? $ / i) ?? [ ]
1307
- if ( chars . length ) {
1308
- hexValue = `#${ chars . filter ( Boolean ) . join ( '' ) } `
1309
- }
1310
- }
1311
-
1312
- if ( hexValue . length === 5 ) {
1313
- hexValue = hexValue . replace ( / f $ / , '' )
1314
- } else if ( hexValue . length === 9 ) {
1315
- hexValue = hexValue . replace ( / f f $ / , '' )
1285
+ try {
1286
+ let doc = await service . open ( params . textDocument . uri )
1287
+ if ( ! doc ) return null
1288
+ return doc . colorPresentation ( params . color , params . range )
1289
+ } catch {
1290
+ return [ ]
1316
1291
}
1317
-
1318
- let prefix = className . substr ( 0 , match . index )
1319
-
1320
- return [
1321
- hexValue ,
1322
- culori . formatRgb ( color ) . replace ( / / g, '' ) ,
1323
- culori
1324
- . formatHsl ( color )
1325
- . replace ( / / g, '' )
1326
- // round numbers
1327
- . replace ( / \d + \. \d + ( % ? ) / g, ( value , suffix ) => `${ Math . round ( parseFloat ( value ) ) } ${ suffix } ` ) ,
1328
- ] . map ( ( value ) => ( { label : `${ prefix } -[${ value } ]` } ) )
1329
1292
} ,
1330
1293
sortClassLists ( classLists : string [ ] ) : string [ ] {
1331
1294
if ( ! state . jit ) {
0 commit comments