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