@@ -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 { File , FileType } from '@tailwindcss/language-service/src/fs'
90
78
91
79
function getConfigId ( configPath : string , configDependencies : string [ ] ) : string {
92
80
return JSON . stringify (
@@ -233,36 +221,71 @@ export async function createProjectService(
233
221
getDocumentSymbols : ( uri : string ) => {
234
222
return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
235
223
} ,
236
- async readDirectory ( document , directory ) {
224
+ async readDirectory ( ) {
225
+ // NOTE: This is overwritten in `createLanguageDocument`
226
+ throw new Error ( 'Not implemented' )
227
+ } ,
228
+ } ,
229
+ }
230
+
231
+ let service = createLanguageService ( {
232
+ state : ( ) => state ,
233
+ fs : {
234
+ async document ( uri : string ) {
235
+ return documentService . getDocument ( uri )
236
+ } ,
237
+ async resolve ( document : TextDocument , relativePath : string ) : Promise < string | null > {
238
+ let documentPath = URI . parse ( document . uri ) . fsPath
239
+ let baseDir = path . dirname ( documentPath )
240
+
241
+ let resolved = await resolver . substituteId ( relativePath , baseDir )
242
+ resolved ??= relativePath
243
+
244
+ return URI . file ( path . resolve ( baseDir , resolved ) ) . toString ( )
245
+ } ,
246
+
247
+ async readDirectory ( document : TextDocument , filepath : string ) : Promise < File [ ] > {
237
248
try {
238
249
let baseDir = path . dirname ( getFileFsPath ( document . uri ) )
239
- directory = await resolver . substituteId ( `${ directory } /` , baseDir )
240
- directory = path . resolve ( baseDir , directory )
241
-
242
- let dirents = await fs . promises . readdir ( directory , { withFileTypes : true } )
243
-
244
- let result : Array < [ string , { isDirectory : boolean } ] | null > = await Promise . all (
245
- dirents . map ( async ( dirent ) => {
246
- let isDirectory = dirent . isDirectory ( )
247
- let shouldRemove = await isExcluded (
248
- state ,
249
- document ,
250
- path . join ( directory , dirent . name , isDirectory ? '/' : '' ) ,
251
- )
250
+ filepath = await resolver . substituteId ( `${ filepath } /` , baseDir )
251
+ filepath = path . resolve ( baseDir , filepath )
252
252
253
- if ( shouldRemove ) return null
253
+ let dirents = await fs . promises . readdir ( filepath , { withFileTypes : true } )
254
254
255
- return [ dirent . name , { isDirectory } ]
256
- } ) ,
257
- )
255
+ let results : File [ ] = [ ]
256
+
257
+ for ( let dirent of dirents ) {
258
+ let isDirectory = dirent . isDirectory ( )
259
+ let shouldRemove = await isExcluded (
260
+ state ,
261
+ document ,
262
+ path . join ( filepath , dirent . name , isDirectory ? '/' : '' ) ,
263
+ )
264
+ if ( shouldRemove ) continue
265
+
266
+ let type : FileType = 'unknown'
258
267
259
- return result . filter ( ( item ) => item !== null )
268
+ if ( dirent . isFile ( ) ) {
269
+ type = 'file'
270
+ } else if ( dirent . isDirectory ( ) ) {
271
+ type = 'directory'
272
+ } else if ( dirent . isSymbolicLink ( ) ) {
273
+ type = 'symbolic-link'
274
+ }
275
+
276
+ results . push ( {
277
+ name : dirent . name ,
278
+ type,
279
+ } )
280
+ }
281
+
282
+ return results
260
283
} catch {
261
284
return [ ]
262
285
}
263
286
} ,
264
287
} ,
265
- }
288
+ } )
266
289
267
290
if ( projectConfig . configPath && projectConfig . config . source === 'js' ) {
268
291
let deps = [ ]
@@ -1171,139 +1194,79 @@ export async function createProjectService(
1171
1194
} ,
1172
1195
onFileEvents,
1173
1196
async onHover ( params : TextDocumentPositionParams ) : Promise < Hover > {
1174
- return withFallback ( async ( ) => {
1175
- if ( ! state . enabled ) return null
1176
- let document = documentService . getDocument ( params . textDocument . uri )
1177
- if ( ! document ) return null
1178
- let settings = await state . editor . getConfiguration ( document . uri )
1179
- if ( ! settings . tailwindCSS . hovers ) return null
1180
- if ( await isExcluded ( state , document ) ) return null
1181
- return doHover ( state , document , params . position )
1182
- } , null )
1197
+ try {
1198
+ let doc = await service . open ( params . textDocument . uri )
1199
+ if ( ! doc ) return null
1200
+ return doc . hover ( params . position )
1201
+ } catch {
1202
+ return null
1203
+ }
1183
1204
} ,
1184
1205
async onCodeLens ( params : CodeLensParams ) : Promise < CodeLens [ ] > {
1185
- return withFallback ( async ( ) => {
1186
- if ( ! state . enabled ) return null
1187
- let document = documentService . getDocument ( params . textDocument . uri )
1188
- if ( ! document ) return null
1189
- let settings = await state . editor . getConfiguration ( document . uri )
1190
- if ( ! settings . tailwindCSS . codeLens ) return null
1191
- if ( await isExcluded ( state , document ) ) return null
1192
- return getCodeLens ( state , document )
1193
- } , null )
1206
+ try {
1207
+ let doc = await service . open ( params . textDocument . uri )
1208
+ if ( ! doc ) return null
1209
+ return doc . codeLenses ( )
1210
+ } catch {
1211
+ return [ ]
1212
+ }
1194
1213
} ,
1195
1214
async onCompletion ( params : CompletionParams ) : Promise < CompletionList > {
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 . suggestions ) return null
1202
- if ( await isExcluded ( state , document ) ) return null
1203
- return doComplete ( state , document , params . position , params . context )
1204
- } , null )
1215
+ try {
1216
+ let doc = await service . open ( params . textDocument . uri )
1217
+ if ( ! doc ) return null
1218
+ return doc . completions ( params . position )
1219
+ } catch {
1220
+ return null
1221
+ }
1205
1222
} ,
1206
- onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1207
- return withFallback ( ( ) => {
1208
- if ( ! state . enabled ) return null
1209
- return resolveCompletionItem ( state , item )
1210
- } , null )
1223
+ async onCompletionResolve ( item : CompletionItem ) : Promise < CompletionItem > {
1224
+ try {
1225
+ return await service . resolveCompletion ( item )
1226
+ } catch {
1227
+ return null
1228
+ }
1211
1229
} ,
1212
1230
async onCodeAction ( params : CodeActionParams ) : Promise < CodeAction [ ] > {
1213
- return withFallback ( async ( ) => {
1214
- if ( ! state . enabled ) return null
1215
- let document = documentService . getDocument ( params . textDocument . uri )
1216
- if ( ! document ) return null
1217
- let settings = await state . editor . getConfiguration ( document . uri )
1218
- if ( ! settings . tailwindCSS . codeActions ) return null
1219
- return doCodeActions ( state , params , document )
1220
- } , null )
1231
+ try {
1232
+ let doc = await service . open ( params . textDocument . uri )
1233
+ if ( ! doc ) return null
1234
+ return doc . codeActions ( params . range , params . context )
1235
+ } catch {
1236
+ return [ ]
1237
+ }
1221
1238
} ,
1222
- onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1223
- if ( ! state . enabled ) return null
1224
- let document = documentService . getDocument ( params . textDocument . uri )
1225
- if ( ! document ) return null
1226
-
1227
- let documentPath = URI . parse ( document . uri ) . fsPath
1228
- let baseDir = path . dirname ( documentPath )
1229
-
1230
- async function resolveTarget ( linkPath : string ) {
1231
- linkPath = ( await resolver . substituteId ( linkPath , baseDir ) ) ?? linkPath
1232
-
1233
- return URI . file ( path . resolve ( baseDir , linkPath ) ) . toString ( )
1239
+ async onDocumentLinks ( params : DocumentLinkParams ) : Promise < DocumentLink [ ] > {
1240
+ try {
1241
+ let doc = await service . open ( params . textDocument . uri )
1242
+ if ( ! doc ) return null
1243
+ return doc . documentLinks ( )
1244
+ } catch {
1245
+ return [ ]
1234
1246
}
1235
-
1236
- return getDocumentLinks ( state , document , resolveTarget )
1237
1247
} ,
1238
1248
provideDiagnostics : debounce (
1239
- ( document : TextDocument ) => {
1240
- if ( ! state . enabled ) return
1241
- provideDiagnostics ( state , document )
1242
- } ,
1249
+ ( document ) => provideDiagnostics ( service , state , document ) ,
1243
1250
params . initializationOptions ?. testMode ? 0 : 500 ,
1244
1251
) ,
1245
- provideDiagnosticsForce : ( document : TextDocument ) => {
1246
- if ( ! state . enabled ) return
1247
- provideDiagnostics ( state , document )
1248
- } ,
1252
+ provideDiagnosticsForce : ( document ) => provideDiagnostics ( service , state , document ) ,
1249
1253
async onDocumentColor ( params : DocumentColorParams ) : Promise < ColorInformation [ ] > {
1250
- return withFallback ( async ( ) => {
1251
- if ( ! state . enabled ) return [ ]
1252
- let document = documentService . getDocument ( params . textDocument . uri )
1253
- if ( ! document ) return [ ]
1254
- if ( await isExcluded ( state , document ) ) return null
1255
- return getDocumentColors ( state , document )
1256
- } , null )
1254
+ try {
1255
+ let doc = await service . open ( params . textDocument . uri )
1256
+ if ( ! doc ) return null
1257
+ return doc . documentColors ( )
1258
+ } catch {
1259
+ return [ ]
1260
+ }
1257
1261
} ,
1258
1262
async onColorPresentation ( params : ColorPresentationParams ) : Promise < ColorPresentation [ ] > {
1259
- let document = documentService . getDocument ( params . textDocument . uri )
1260
- if ( ! document ) return [ ]
1261
- let className = document . getText ( params . range )
1262
- let match = className . match (
1263
- new RegExp ( `-\\[(${ colorNames . join ( '|' ) } |(?:(?:#|rgba?\\(|hsla?\\())[^\\]]+)\\]$` , 'i' ) ,
1264
- )
1265
- // let match = className.match(/-\[((?:#|rgba?\(|hsla?\()[^\]]+)\]$/i)
1266
- if ( match === null ) return [ ]
1267
-
1268
- let currentColor = match [ 1 ]
1269
-
1270
- let isNamedColor = colorNames . includes ( currentColor )
1271
-
1272
- let color : culori . Color = {
1273
- mode : 'rgb' ,
1274
- r : params . color . red ,
1275
- g : params . color . green ,
1276
- b : params . color . blue ,
1277
- alpha : params . color . alpha ,
1278
- }
1279
-
1280
- let hexValue = culori . formatHex8 ( color )
1281
-
1282
- if ( ! isNamedColor && ( currentColor . length === 4 || currentColor . length === 5 ) ) {
1283
- let [ , ...chars ] =
1284
- hexValue . match ( / ^ # ( [ a - f \d ] ) \1( [ a - f \d ] ) \2( [ a - f \d ] ) \3(?: ( [ a - f \d ] ) \4) ? $ / i) ?? [ ]
1285
- if ( chars . length ) {
1286
- hexValue = `#${ chars . filter ( Boolean ) . join ( '' ) } `
1287
- }
1288
- }
1289
-
1290
- if ( hexValue . length === 5 ) {
1291
- hexValue = hexValue . replace ( / f $ / , '' )
1292
- } else if ( hexValue . length === 9 ) {
1293
- hexValue = hexValue . replace ( / f f $ / , '' )
1263
+ try {
1264
+ let doc = await service . open ( params . textDocument . uri )
1265
+ if ( ! doc ) return null
1266
+ return doc . colorPresentation ( params . color , params . range )
1267
+ } catch {
1268
+ return [ ]
1294
1269
}
1295
-
1296
- let prefix = className . substr ( 0 , match . index )
1297
-
1298
- return [
1299
- hexValue ,
1300
- culori . formatRgb ( color ) . replace ( / / g, '' ) ,
1301
- culori
1302
- . formatHsl ( color )
1303
- . replace ( / / g, '' )
1304
- // round numbers
1305
- . replace ( / \d + \. \d + ( % ? ) / g, ( value , suffix ) => `${ Math . round ( parseFloat ( value ) ) } ${ suffix } ` ) ,
1306
- ] . map ( ( value ) => ( { label : `${ prefix } -[${ value } ]` } ) )
1307
1270
} ,
1308
1271
sortClassLists ( classLists : string [ ] ) : string [ ] {
1309
1272
if ( ! state . jit ) {
0 commit comments