@@ -36,13 +36,8 @@ import pkgUp from 'pkg-up'
3636import stackTrace from 'stack-trace'
3737import extractClassNames from './lib/extractClassNames'
3838import { 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'
4140import { Resolver } from './resolver'
42- import {
43- doComplete ,
44- resolveCompletionItem ,
45- } from '@tailwindcss/language-service/src/completionProvider'
4641import type {
4742 State ,
4843 FeatureFlags ,
@@ -52,17 +47,12 @@ import type {
5247 ClassEntry ,
5348} from '@tailwindcss/language-service/src/util/state'
5449import { 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'
5850import { debounce } from 'debounce'
5951import { getModuleDependencies } from './util/getModuleDependencies'
6052import assert from 'node:assert'
6153// import postcssLoadConfig from 'postcss-load-config'
6254import { bigSign } from '@tailwindcss/language-service/src/util/jit'
6355import { getColor } from '@tailwindcss/language-service/src/util/color'
64- import * as culori from 'culori'
65- import namedColors from 'color-name'
6656import tailwindPlugins from './lib/plugins'
6757import isExcluded from './util/isExcluded'
6858import { getFileFsPath } from './util/uri'
@@ -72,7 +62,6 @@ import {
7262 firstOptional ,
7363 withoutLogs ,
7464 clearRequireCache ,
75- withFallback ,
7665 isObject ,
7766 pathToFileURL ,
7867 changeAffectsFile ,
@@ -85,8 +74,7 @@ import { supportedFeatures } from '@tailwindcss/language-service/src/features'
8574import { loadDesignSystem } from './util/v4'
8675import { readCssFile } from './util/css'
8776import 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'
9078
9179function getConfigId ( configPath : string , configDependencies : string [ ] ) : string {
9280 return JSON . stringify (
@@ -234,36 +222,71 @@ export async function createProjectService(
234222 getDocumentSymbols : ( uri : string ) => {
235223 return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
236224 } ,
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 [ ] > {
238249 try {
239250 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 )
253253
254- if ( shouldRemove ) return null
254+ let dirents = await fs . promises . readdir ( filepath , { withFileTypes : true } )
255255
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'
259268
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
261284 } catch {
262285 return [ ]
263286 }
264287 } ,
265288 } ,
266- }
289+ } )
267290
268291 if ( projectConfig . configPath && projectConfig . config . source === 'js' ) {
269292 let deps = [ ]
@@ -1187,139 +1210,79 @@ export async function createProjectService(
11871210 } ,
11881211 onFileEvents,
11891212 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+ }
11991220 } ,
12001221 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+ }
12101229 } ,
12111230 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+ }
12211238 } ,
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+ }
12271245 } ,
12281246 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+ }
12371254 } ,
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 [ ]
12501262 }
1251-
1252- return getDocumentLinks ( state , document , resolveTarget )
12531263 } ,
12541264 provideDiagnostics : debounce (
1255- ( document : TextDocument ) => {
1256- if ( ! state . enabled ) return
1257- provideDiagnostics ( state , document )
1258- } ,
1265+ ( document ) => provideDiagnostics ( service , state , document ) ,
12591266 params . initializationOptions ?. testMode ? 0 : 500 ,
12601267 ) ,
1261- provideDiagnosticsForce : ( document : TextDocument ) => {
1262- if ( ! state . enabled ) return
1263- provideDiagnostics ( state , document )
1264- } ,
1268+ provideDiagnosticsForce : ( document ) => provideDiagnostics ( service , state , document ) ,
12651269 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+ }
12731277 } ,
12741278 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 [ ]
13101285 }
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 } ]` } ) )
13231286 } ,
13241287 sortClassLists ( classLists : string [ ] ) : string [ ] {
13251288 if ( ! state . jit ) {
0 commit comments