@@ -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 = [ ]
@@ -1193,139 +1216,79 @@ export async function createProjectService(
11931216 } ,
11941217 onFileEvents,
11951218 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+ }
12051226 } ,
12061227 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+ }
12161235 } ,
12171236 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+ }
12271244 } ,
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+ }
12331251 } ,
12341252 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+ }
12431260 } ,
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 [ ]
12561268 }
1257-
1258- return getDocumentLinks ( state , document , resolveTarget )
12591269 } ,
12601270 provideDiagnostics : debounce (
1261- ( document : TextDocument ) => {
1262- if ( ! state . enabled ) return
1263- provideDiagnostics ( state , document )
1264- } ,
1271+ ( document ) => provideDiagnostics ( service , state , document ) ,
12651272 params . initializationOptions ?. testMode ? 0 : 500 ,
12661273 ) ,
1267- provideDiagnosticsForce : ( document : TextDocument ) => {
1268- if ( ! state . enabled ) return
1269- provideDiagnostics ( state , document )
1270- } ,
1274+ provideDiagnosticsForce : ( document ) => provideDiagnostics ( service , state , document ) ,
12711275 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+ }
12791283 } ,
12801284 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 [ ]
13161291 }
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 } ]` } ) )
13291292 } ,
13301293 sortClassLists ( classLists : string [ ] ) : string [ ] {
13311294 if ( ! state . jit ) {
0 commit comments