@@ -27,6 +27,9 @@ import {
27
27
FileChangeType ,
28
28
Disposable ,
29
29
TextDocumentIdentifier ,
30
+ DocumentLinkRequest ,
31
+ DocumentLinkParams ,
32
+ DocumentLink ,
30
33
} from 'vscode-languageserver/node'
31
34
import { TextDocument } from 'vscode-languageserver-textdocument'
32
35
import { URI } from 'vscode-uri'
@@ -60,6 +63,7 @@ import {
60
63
FeatureFlags ,
61
64
Settings ,
62
65
ClassNames ,
66
+ Variant ,
63
67
} from 'tailwindcss-language-service/src/util/state'
64
68
import {
65
69
provideDiagnostics ,
@@ -68,6 +72,7 @@ import {
68
72
} from './lsp/diagnosticsProvider'
69
73
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
70
74
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
75
+ import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
71
76
import { debounce } from 'debounce'
72
77
import { getModuleDependencies } from './util/getModuleDependencies'
73
78
import assert from 'assert'
@@ -112,6 +117,7 @@ const TRIGGER_CHARACTERS = [
112
117
// @apply and emmet-style
113
118
'.' ,
114
119
// config/theme helper
120
+ '(' ,
115
121
'[' ,
116
122
// JIT "important" prefix
117
123
'!' ,
@@ -187,6 +193,7 @@ interface ProjectService {
187
193
onDocumentColor ( params : DocumentColorParams ) : Promise < ColorInformation [ ] >
188
194
onColorPresentation ( params : ColorPresentationParams ) : Promise < ColorPresentation [ ] >
189
195
onCodeAction ( params : CodeActionParams ) : Promise < CodeAction [ ] >
196
+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ]
190
197
}
191
198
192
199
type ProjectConfig = { folder : string ; configPath ?: string ; documentSelector ?: string [ ] }
@@ -298,6 +305,27 @@ async function createProjectService(
298
305
getDocumentSymbols : ( uri : string ) => {
299
306
return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
300
307
} ,
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
+ } ,
301
329
} ,
302
330
}
303
331
@@ -1027,6 +1055,14 @@ async function createProjectService(
1027
1055
if ( ! settings . tailwindCSS . codeActions ) return null
1028
1056
return doCodeActions ( state , params )
1029
1057
} ,
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
+ } ,
1030
1066
provideDiagnostics : debounce ( ( document : TextDocument ) => {
1031
1067
if ( ! state . enabled ) return
1032
1068
provideDiagnostics ( state , document )
@@ -1146,105 +1182,117 @@ function isAtRule(node: Node): node is AtRule {
1146
1182
return node . type === 'atrule'
1147
1183
}
1148
1184
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
+ }
1156
1189
1157
- let result = { }
1190
+ if ( state . jit ) {
1191
+ let result : Array < Variant > = [ ]
1158
1192
// [name, [sort, fn]]
1159
1193
// [name, [[sort, fn]]]
1160
1194
Array . from ( state . jitContext . variantMap as Map < string , [ any , any ] > ) . forEach (
1161
1195
( [ 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
+ }
1165
1207
1166
- let placeholder = '__variant_placeholder__'
1208
+ let fns = ( Array . isArray ( variantFnOrFns [ 0 ] ) ? variantFnOrFns : [ variantFnOrFns ] ) . map (
1209
+ ( [ _sort , fn ] ) => fn
1210
+ )
1167
1211
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__'
1176
1213
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
+ } )
1180
1222
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
+ }
1184
1236
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
1189
1247
}
1190
1248
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
+ }
1195
1265
} ,
1196
- selector,
1197
1266
} )
1198
- } )
1199
- } )
1200
- return root
1201
- }
1202
1267
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
1218
1270
}
1219
- } ,
1220
- } )
1221
-
1222
- if ( ! definition ) {
1223
- definition = returnValue
1224
- }
1225
1271
1226
- if ( definition ) {
1227
- definitions . push ( definition )
1228
- continue
1229
- }
1272
+ if ( definition ) {
1273
+ definitions . push ( definition )
1274
+ continue
1275
+ }
1230
1276
1231
- container . walkDecls ( ( decl ) => {
1232
- decl . remove ( )
1233
- } )
1277
+ container . walkDecls ( ( decl ) => {
1278
+ decl . remove ( )
1279
+ } )
1234
1280
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 ( )
1241
1287
1242
- if ( ! definition . includes ( placeholder ) ) {
1243
- definitions . push ( definition )
1244
- }
1245
- }
1288
+ if ( ! definition . includes ( placeholder ) ) {
1289
+ definitions . push ( definition )
1290
+ }
1291
+ }
1246
1292
1247
- result [ variantName ] = definitions . join ( ', ' ) || null
1293
+ return definitions
1294
+ } ,
1295
+ } )
1248
1296
}
1249
1297
)
1250
1298
@@ -1276,7 +1324,13 @@ function getVariants(state: State): Record<string, string> {
1276
1324
} )
1277
1325
} )
1278
1326
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
+ } ) )
1280
1334
}
1281
1335
1282
1336
async function getPlugins ( config : any ) {
@@ -1484,6 +1538,7 @@ class TW {
1484
1538
this . connection . onDocumentColor ( this . onDocumentColor . bind ( this ) )
1485
1539
this . connection . onColorPresentation ( this . onColorPresentation . bind ( this ) )
1486
1540
this . connection . onCodeAction ( this . onCodeAction . bind ( this ) )
1541
+ this . connection . onDocumentLinks ( this . onDocumentLinks . bind ( this ) )
1487
1542
}
1488
1543
1489
1544
private updateCapabilities ( ) {
@@ -1498,6 +1553,7 @@ class TW {
1498
1553
capabilities . add ( HoverRequest . type , { documentSelector : null } )
1499
1554
capabilities . add ( DocumentColorRequest . type , { documentSelector : null } )
1500
1555
capabilities . add ( CodeActionRequest . type , { documentSelector : null } )
1556
+ capabilities . add ( DocumentLinkRequest . type , { documentSelector : null } )
1501
1557
1502
1558
capabilities . add ( CompletionRequest . type , {
1503
1559
documentSelector : null ,
@@ -1563,6 +1619,10 @@ class TW {
1563
1619
return this . getProject ( params . textDocument ) ?. onCodeAction ( params ) ?? null
1564
1620
}
1565
1621
1622
+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ] {
1623
+ return this . getProject ( params . textDocument ) ?. onDocumentLinks ( params ) ?? null
1624
+ }
1625
+
1566
1626
listen ( ) {
1567
1627
this . connection . listen ( )
1568
1628
}
@@ -1604,7 +1664,8 @@ function supportsDynamicRegistration(connection: Connection, params: InitializeP
1604
1664
params . capabilities . textDocument . hover ?. dynamicRegistration &&
1605
1665
params . capabilities . textDocument . colorProvider ?. dynamicRegistration &&
1606
1666
params . capabilities . textDocument . codeAction ?. dynamicRegistration &&
1607
- params . capabilities . textDocument . completion ?. dynamicRegistration
1667
+ params . capabilities . textDocument . completion ?. dynamicRegistration &&
1668
+ params . capabilities . textDocument . documentLink ?. dynamicRegistration
1608
1669
)
1609
1670
}
1610
1671
@@ -1629,6 +1690,7 @@ connection.onInitialize(async (params: InitializeParams): Promise<InitializeResu
1629
1690
hoverProvider : true ,
1630
1691
colorProvider : true ,
1631
1692
codeActionProvider : true ,
1693
+ documentLinkProvider : { } ,
1632
1694
completionProvider : {
1633
1695
resolveProvider : true ,
1634
1696
triggerCharacters : [ ...TRIGGER_CHARACTERS , ':' ] ,
0 commit comments