@@ -27,6 +27,9 @@ import {
2727 FileChangeType ,
2828 Disposable ,
2929 TextDocumentIdentifier ,
30+ DocumentLinkRequest ,
31+ DocumentLinkParams ,
32+ DocumentLink ,
3033} from 'vscode-languageserver/node'
3134import { TextDocument } from 'vscode-languageserver-textdocument'
3235import { URI } from 'vscode-uri'
@@ -60,6 +63,7 @@ import {
6063 FeatureFlags ,
6164 Settings ,
6265 ClassNames ,
66+ Variant ,
6367} from 'tailwindcss-language-service/src/util/state'
6468import {
6569 provideDiagnostics ,
@@ -68,6 +72,7 @@ import {
6872} from './lsp/diagnosticsProvider'
6973import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
7074import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
75+ import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
7176import { debounce } from 'debounce'
7277import { getModuleDependencies } from './util/getModuleDependencies'
7378import assert from 'assert'
@@ -112,6 +117,7 @@ const TRIGGER_CHARACTERS = [
112117 // @apply and emmet-style
113118 '.' ,
114119 // config/theme helper
120+ '(' ,
115121 '[' ,
116122 // JIT "important" prefix
117123 '!' ,
@@ -187,6 +193,7 @@ interface ProjectService {
187193 onDocumentColor ( params : DocumentColorParams ) : Promise < ColorInformation [ ] >
188194 onColorPresentation ( params : ColorPresentationParams ) : Promise < ColorPresentation [ ] >
189195 onCodeAction ( params : CodeActionParams ) : Promise < CodeAction [ ] >
196+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ]
190197}
191198
192199type ProjectConfig = { folder : string ; configPath ?: string ; documentSelector ?: string [ ] }
@@ -298,6 +305,27 @@ async function createProjectService(
298305 getDocumentSymbols : ( uri : string ) => {
299306 return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
300307 } ,
308+ async readDirectory ( document , directory ) {
309+ try {
310+ directory = path . resolve ( path . dirname ( getFileFsPath ( document . uri ) ) , directory )
311+ let dirents = await fs . promises . readdir ( directory , { withFileTypes : true } )
312+ let result : Array < [ string , { isDirectory : boolean } ] | null > = await Promise . all (
313+ dirents . map ( async ( dirent ) => {
314+ let isDirectory = dirent . isDirectory ( )
315+ return ( await isExcluded (
316+ state ,
317+ document ,
318+ path . join ( directory , dirent . name , isDirectory ? '/' : '' )
319+ ) )
320+ ? null
321+ : [ dirent . name , { isDirectory } ]
322+ } )
323+ )
324+ return result . filter ( ( item ) => item !== null )
325+ } catch {
326+ return [ ]
327+ }
328+ } ,
301329 } ,
302330 }
303331
@@ -1027,6 +1055,14 @@ async function createProjectService(
10271055 if ( ! settings . tailwindCSS . codeActions ) return null
10281056 return doCodeActions ( state , params )
10291057 } ,
1058+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ] {
1059+ if ( ! state . enabled ) return null
1060+ let document = documentService . getDocument ( params . textDocument . uri )
1061+ if ( ! document ) return null
1062+ return getDocumentLinks ( state , document , ( linkPath ) =>
1063+ URI . file ( path . resolve ( path . dirname ( URI . parse ( document . uri ) . fsPath ) , linkPath ) ) . toString ( )
1064+ )
1065+ } ,
10301066 provideDiagnostics : debounce ( ( document : TextDocument ) => {
10311067 if ( ! state . enabled ) return
10321068 provideDiagnostics ( state , document )
@@ -1146,105 +1182,117 @@ function isAtRule(node: Node): node is AtRule {
11461182 return node . type === 'atrule'
11471183}
11481184
1149- function getVariants ( state : State ) : Record < string , string > {
1150- if ( state . jit ) {
1151- function escape ( className : string ) : string {
1152- let node = state . modules . postcssSelectorParser . module . className ( )
1153- node . value = className
1154- return dlv ( node , 'raws.value' , node . value )
1155- }
1185+ function getVariants ( state : State ) : Array < Variant > {
1186+ if ( state . jitContext ?. getVariants ) {
1187+ return state . jitContext . getVariants ( )
1188+ }
11561189
1157- let result = { }
1190+ if ( state . jit ) {
1191+ let result : Array < Variant > = [ ]
11581192 // [name, [sort, fn]]
11591193 // [name, [[sort, fn]]]
11601194 Array . from ( state . jitContext . variantMap as Map < string , [ any , any ] > ) . forEach (
11611195 ( [ variantName , variantFnOrFns ] ) => {
1162- let fns = ( Array . isArray ( variantFnOrFns [ 0 ] ) ? variantFnOrFns : [ variantFnOrFns ] ) . map (
1163- ( [ _sort , fn ] ) => fn
1164- )
1196+ result . push ( {
1197+ name : variantName ,
1198+ values : [ ] ,
1199+ isArbitrary : false ,
1200+ hasDash : true ,
1201+ selectors : ( ) => {
1202+ function escape ( className : string ) : string {
1203+ let node = state . modules . postcssSelectorParser . module . className ( )
1204+ node . value = className
1205+ return dlv ( node , 'raws.value' , node . value )
1206+ }
11651207
1166- let placeholder = '__variant_placeholder__'
1208+ let fns = ( Array . isArray ( variantFnOrFns [ 0 ] ) ? variantFnOrFns : [ variantFnOrFns ] ) . map (
1209+ ( [ _sort , fn ] ) => fn
1210+ )
11671211
1168- let root = state . modules . postcss . module . root ( {
1169- nodes : [
1170- state . modules . postcss . module . rule ( {
1171- selector : `.${ escape ( placeholder ) } ` ,
1172- nodes : [ ] ,
1173- } ) ,
1174- ] ,
1175- } )
1212+ let placeholder = '__variant_placeholder__'
11761213
1177- let classNameParser = state . modules . postcssSelectorParser . module ( ( selectors ) => {
1178- return selectors . first . filter ( ( { type } ) => type === 'class' ) . pop ( ) . value
1179- } )
1214+ let root = state . modules . postcss . module . root ( {
1215+ nodes : [
1216+ state . modules . postcss . module . rule ( {
1217+ selector : `.${ escape ( placeholder ) } ` ,
1218+ nodes : [ ] ,
1219+ } ) ,
1220+ ] ,
1221+ } )
11801222
1181- function getClassNameFromSelector ( selector ) {
1182- return classNameParser . transformSync ( selector )
1183- }
1223+ let classNameParser = state . modules . postcssSelectorParser . module ( ( selectors ) => {
1224+ return selectors . first . filter ( ( { type } ) => type === 'class' ) . pop ( ) . value
1225+ } )
1226+
1227+ function getClassNameFromSelector ( selector ) {
1228+ return classNameParser . transformSync ( selector )
1229+ }
1230+
1231+ function modifySelectors ( modifierFunction ) {
1232+ root . each ( ( rule ) => {
1233+ if ( rule . type !== 'rule' ) {
1234+ return
1235+ }
11841236
1185- function modifySelectors ( modifierFunction ) {
1186- root . each ( ( rule ) => {
1187- if ( rule . type !== 'rule' ) {
1188- return
1237+ rule . selectors = rule . selectors . map ( ( selector ) => {
1238+ return modifierFunction ( {
1239+ get className ( ) {
1240+ return getClassNameFromSelector ( selector )
1241+ } ,
1242+ selector,
1243+ } )
1244+ } )
1245+ } )
1246+ return root
11891247 }
11901248
1191- rule . selectors = rule . selectors . map ( ( selector ) => {
1192- return modifierFunction ( {
1193- get className ( ) {
1194- return getClassNameFromSelector ( selector )
1249+ let definitions = [ ]
1250+
1251+ for ( let fn of fns ) {
1252+ let definition : string
1253+ let container = root . clone ( )
1254+ let returnValue = fn ( {
1255+ container,
1256+ separator : state . separator ,
1257+ modifySelectors,
1258+ format : ( def : string ) => {
1259+ definition = def . replace ( / : m e r g e \( ( [ ^ ) ] + ) \) / g, '$1' )
1260+ } ,
1261+ wrap : ( rule : Container ) => {
1262+ if ( isAtRule ( rule ) ) {
1263+ definition = `@${ rule . name } ${ rule . params } `
1264+ }
11951265 } ,
1196- selector,
11971266 } )
1198- } )
1199- } )
1200- return root
1201- }
12021267
1203- let definitions = [ ]
1204-
1205- for ( let fn of fns ) {
1206- let definition : string
1207- let container = root . clone ( )
1208- let returnValue = fn ( {
1209- container,
1210- separator : state . separator ,
1211- modifySelectors,
1212- format : ( def : string ) => {
1213- definition = def . replace ( / : m e r g e \( ( [ ^ ) ] + ) \) / g, '$1' )
1214- } ,
1215- wrap : ( rule : Container ) => {
1216- if ( isAtRule ( rule ) ) {
1217- definition = `@${ rule . name } ${ rule . params } `
1268+ if ( ! definition ) {
1269+ definition = returnValue
12181270 }
1219- } ,
1220- } )
1221-
1222- if ( ! definition ) {
1223- definition = returnValue
1224- }
12251271
1226- if ( definition ) {
1227- definitions . push ( definition )
1228- continue
1229- }
1272+ if ( definition ) {
1273+ definitions . push ( definition )
1274+ continue
1275+ }
12301276
1231- container . walkDecls ( ( decl ) => {
1232- decl . remove ( )
1233- } )
1277+ container . walkDecls ( ( decl ) => {
1278+ decl . remove ( )
1279+ } )
12341280
1235- definition = container
1236- . toString ( )
1237- . replace ( `.${ escape ( `${ variantName } :${ placeholder } ` ) } ` , '&' )
1238- . replace ( / (?< ! \\ ) [ { } ] / g, '' )
1239- . replace ( / \s * \n \s * / g, ' ' )
1240- . trim ( )
1281+ definition = container
1282+ . toString ( )
1283+ . replace ( `.${ escape ( `${ variantName } :${ placeholder } ` ) } ` , '&' )
1284+ . replace ( / (?< ! \\ ) [ { } ] / g, '' )
1285+ . replace ( / \s * \n \s * / g, ' ' )
1286+ . trim ( )
12411287
1242- if ( ! definition . includes ( placeholder ) ) {
1243- definitions . push ( definition )
1244- }
1245- }
1288+ if ( ! definition . includes ( placeholder ) ) {
1289+ definitions . push ( definition )
1290+ }
1291+ }
12461292
1247- result [ variantName ] = definitions . join ( ', ' ) || null
1293+ return definitions
1294+ } ,
1295+ } )
12481296 }
12491297 )
12501298
@@ -1276,7 +1324,13 @@ function getVariants(state: State): Record<string, string> {
12761324 } )
12771325 } )
12781326
1279- return variants . reduce ( ( obj , variant ) => ( { ...obj , [ variant ] : null } ) , { } )
1327+ return variants . map ( ( variant ) => ( {
1328+ name : variant ,
1329+ values : [ ] ,
1330+ isArbitrary : false ,
1331+ hasDash : true ,
1332+ selectors : ( ) => [ ] ,
1333+ } ) )
12801334}
12811335
12821336async function getPlugins ( config : any ) {
@@ -1484,6 +1538,7 @@ class TW {
14841538 this . connection . onDocumentColor ( this . onDocumentColor . bind ( this ) )
14851539 this . connection . onColorPresentation ( this . onColorPresentation . bind ( this ) )
14861540 this . connection . onCodeAction ( this . onCodeAction . bind ( this ) )
1541+ this . connection . onDocumentLinks ( this . onDocumentLinks . bind ( this ) )
14871542 }
14881543
14891544 private updateCapabilities ( ) {
@@ -1498,6 +1553,7 @@ class TW {
14981553 capabilities . add ( HoverRequest . type , { documentSelector : null } )
14991554 capabilities . add ( DocumentColorRequest . type , { documentSelector : null } )
15001555 capabilities . add ( CodeActionRequest . type , { documentSelector : null } )
1556+ capabilities . add ( DocumentLinkRequest . type , { documentSelector : null } )
15011557
15021558 capabilities . add ( CompletionRequest . type , {
15031559 documentSelector : null ,
@@ -1563,6 +1619,10 @@ class TW {
15631619 return this . getProject ( params . textDocument ) ?. onCodeAction ( params ) ?? null
15641620 }
15651621
1622+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ] {
1623+ return this . getProject ( params . textDocument ) ?. onDocumentLinks ( params ) ?? null
1624+ }
1625+
15661626 listen ( ) {
15671627 this . connection . listen ( )
15681628 }
@@ -1604,7 +1664,8 @@ function supportsDynamicRegistration(connection: Connection, params: InitializeP
16041664 params . capabilities . textDocument . hover ?. dynamicRegistration &&
16051665 params . capabilities . textDocument . colorProvider ?. dynamicRegistration &&
16061666 params . capabilities . textDocument . codeAction ?. dynamicRegistration &&
1607- params . capabilities . textDocument . completion ?. dynamicRegistration
1667+ params . capabilities . textDocument . completion ?. dynamicRegistration &&
1668+ params . capabilities . textDocument . documentLink ?. dynamicRegistration
16081669 )
16091670}
16101671
@@ -1629,6 +1690,7 @@ connection.onInitialize(async (params: InitializeParams): Promise<InitializeResu
16291690 hoverProvider : true ,
16301691 colorProvider : true ,
16311692 codeActionProvider : true ,
1693+ documentLinkProvider : { } ,
16321694 completionProvider : {
16331695 resolveProvider : true ,
16341696 triggerCharacters : [ ...TRIGGER_CHARACTERS , ':' ] ,
0 commit comments