@@ -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,10 +63,12 @@ import {
60
63
FeatureFlags ,
61
64
Settings ,
62
65
ClassNames ,
66
+ Variant ,
63
67
} from 'tailwindcss-language-service/src/util/state'
64
68
import { provideDiagnostics } from './lsp/diagnosticsProvider'
65
69
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
66
70
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
71
+ import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
67
72
import { debounce } from 'debounce'
68
73
import { getModuleDependencies } from './util/getModuleDependencies'
69
74
import assert from 'assert'
@@ -108,6 +113,7 @@ const TRIGGER_CHARACTERS = [
108
113
// @apply and emmet-style
109
114
'.' ,
110
115
// config/theme helper
116
+ '(' ,
111
117
'[' ,
112
118
// JIT "important" prefix
113
119
'!' ,
@@ -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 = {
@@ -386,6 +393,27 @@ async function createProjectService(
386
393
getDocumentSymbols : ( uri : string ) => {
387
394
return connection . sendRequest ( '@/tailwindCSS/getDocumentSymbols' , { uri } )
388
395
} ,
396
+ async readDirectory ( document , directory ) {
397
+ try {
398
+ directory = path . resolve ( path . dirname ( getFileFsPath ( document . uri ) ) , directory )
399
+ let dirents = await fs . promises . readdir ( directory , { withFileTypes : true } )
400
+ let result : Array < [ string , { isDirectory : boolean } ] | null > = await Promise . all (
401
+ dirents . map ( async ( dirent ) => {
402
+ let isDirectory = dirent . isDirectory ( )
403
+ return ( await isExcluded (
404
+ state ,
405
+ document ,
406
+ path . join ( directory , dirent . name , isDirectory ? '/' : '' )
407
+ ) )
408
+ ? null
409
+ : [ dirent . name , { isDirectory } ]
410
+ } )
411
+ )
412
+ return result . filter ( ( item ) => item !== null )
413
+ } catch {
414
+ return [ ]
415
+ }
416
+ } ,
389
417
} ,
390
418
}
391
419
@@ -1100,6 +1128,14 @@ async function createProjectService(
1100
1128
return doCodeActions ( state , params )
1101
1129
} , null )
1102
1130
} ,
1131
+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ] {
1132
+ if ( ! state . enabled ) return null
1133
+ let document = documentService . getDocument ( params . textDocument . uri )
1134
+ if ( ! document ) return null
1135
+ return getDocumentLinks ( state , document , ( linkPath ) =>
1136
+ URI . file ( path . resolve ( path . dirname ( URI . parse ( document . uri ) . fsPath ) , linkPath ) ) . toString ( )
1137
+ )
1138
+ } ,
1103
1139
provideDiagnostics : debounce ( ( document : TextDocument ) => {
1104
1140
if ( ! state . enabled ) return
1105
1141
provideDiagnostics ( state , document )
@@ -1226,107 +1262,119 @@ function isAtRule(node: Node): node is AtRule {
1226
1262
return node . type === 'atrule'
1227
1263
}
1228
1264
1229
- function getVariants ( state : State ) : Record < string , string > {
1230
- if ( state . jit ) {
1231
- function escape ( className : string ) : string {
1232
- let node = state . modules . postcssSelectorParser . module . className ( )
1233
- node . value = className
1234
- return dlv ( node , 'raws.value' , node . value )
1235
- }
1265
+ function getVariants ( state : State ) : Array < Variant > {
1266
+ if ( state . jitContext ?. getVariants ) {
1267
+ return state . jitContext . getVariants ( )
1268
+ }
1236
1269
1237
- let result = { }
1270
+ if ( state . jit ) {
1271
+ let result : Array < Variant > = [ ]
1238
1272
// [name, [sort, fn]]
1239
1273
// [name, [[sort, fn]]]
1240
1274
Array . from ( state . jitContext . variantMap as Map < string , [ any , any ] > ) . forEach (
1241
1275
( [ variantName , variantFnOrFns ] ) => {
1242
- let fns = ( Array . isArray ( variantFnOrFns [ 0 ] ) ? variantFnOrFns : [ variantFnOrFns ] ) . map (
1243
- ( [ _sort , fn ] ) => fn
1244
- )
1276
+ result . push ( {
1277
+ name : variantName ,
1278
+ values : [ ] ,
1279
+ isArbitrary : false ,
1280
+ hasDash : true ,
1281
+ selectors : ( ) => {
1282
+ function escape ( className : string ) : string {
1283
+ let node = state . modules . postcssSelectorParser . module . className ( )
1284
+ node . value = className
1285
+ return dlv ( node , 'raws.value' , node . value )
1286
+ }
1245
1287
1246
- let placeholder = '__variant_placeholder__'
1288
+ let fns = ( Array . isArray ( variantFnOrFns [ 0 ] ) ? variantFnOrFns : [ variantFnOrFns ] ) . map (
1289
+ ( [ _sort , fn ] ) => fn
1290
+ )
1247
1291
1248
- let root = state . modules . postcss . module . root ( {
1249
- nodes : [
1250
- state . modules . postcss . module . rule ( {
1251
- selector : `.${ escape ( placeholder ) } ` ,
1252
- nodes : [ ] ,
1253
- } ) ,
1254
- ] ,
1255
- } )
1292
+ let placeholder = '__variant_placeholder__'
1256
1293
1257
- let classNameParser = state . modules . postcssSelectorParser . module ( ( selectors ) => {
1258
- return selectors . first . filter ( ( { type } ) => type === 'class' ) . pop ( ) . value
1259
- } )
1294
+ let root = state . modules . postcss . module . root ( {
1295
+ nodes : [
1296
+ state . modules . postcss . module . rule ( {
1297
+ selector : `.${ escape ( placeholder ) } ` ,
1298
+ nodes : [ ] ,
1299
+ } ) ,
1300
+ ] ,
1301
+ } )
1260
1302
1261
- function getClassNameFromSelector ( selector ) {
1262
- return classNameParser . transformSync ( selector )
1263
- }
1303
+ let classNameParser = state . modules . postcssSelectorParser . module ( ( selectors ) => {
1304
+ return selectors . first . filter ( ( { type } ) => type === 'class' ) . pop ( ) . value
1305
+ } )
1264
1306
1265
- function modifySelectors ( modifierFunction ) {
1266
- root . each ( ( rule ) => {
1267
- if ( rule . type !== 'rule' ) {
1268
- return
1307
+ function getClassNameFromSelector ( selector ) {
1308
+ return classNameParser . transformSync ( selector )
1269
1309
}
1270
1310
1271
- rule . selectors = rule . selectors . map ( ( selector ) => {
1272
- return modifierFunction ( {
1273
- get className ( ) {
1274
- return getClassNameFromSelector ( selector )
1275
- } ,
1276
- selector,
1311
+ function modifySelectors ( modifierFunction ) {
1312
+ root . each ( ( rule ) => {
1313
+ if ( rule . type !== 'rule' ) {
1314
+ return
1315
+ }
1316
+
1317
+ rule . selectors = rule . selectors . map ( ( selector ) => {
1318
+ return modifierFunction ( {
1319
+ get className ( ) {
1320
+ return getClassNameFromSelector ( selector )
1321
+ } ,
1322
+ selector,
1323
+ } )
1324
+ } )
1277
1325
} )
1278
- } )
1279
- } )
1280
- return root
1281
- }
1326
+ return root
1327
+ }
1282
1328
1283
- let definitions = [ ]
1284
-
1285
- for ( let fn of fns ) {
1286
- let definition : string
1287
- let container = root . clone ( )
1288
- let returnValue = withoutLogs ( ( ) =>
1289
- fn ( {
1290
- container,
1291
- separator : state . separator ,
1292
- modifySelectors,
1293
- format : ( def : string ) => {
1294
- definition = def . replace ( / : m e r g e \( ( [ ^ ) ] + ) \) / g, '$1' )
1295
- } ,
1296
- wrap : ( rule : Container ) => {
1297
- if ( isAtRule ( rule ) ) {
1298
- definition = `@${ rule . name } ${ rule . params } `
1299
- }
1300
- } ,
1301
- } )
1302
- )
1329
+ let definitions = [ ]
1330
+
1331
+ for ( let fn of fns ) {
1332
+ let definition : string
1333
+ let container = root . clone ( )
1334
+ let returnValue = withoutLogs ( ( ) =>
1335
+ fn ( {
1336
+ container,
1337
+ separator : state . separator ,
1338
+ modifySelectors,
1339
+ format : ( def : string ) => {
1340
+ definition = def . replace ( / : m e r g e \( ( [ ^ ) ] + ) \) / g, '$1' )
1341
+ } ,
1342
+ wrap : ( rule : Container ) => {
1343
+ if ( isAtRule ( rule ) ) {
1344
+ definition = `@${ rule . name } ${ rule . params } `
1345
+ }
1346
+ } ,
1347
+ } )
1348
+ )
1303
1349
1304
- if ( ! definition ) {
1305
- definition = returnValue
1306
- }
1350
+ if ( ! definition ) {
1351
+ definition = returnValue
1352
+ }
1307
1353
1308
- if ( definition ) {
1309
- definitions . push ( definition )
1310
- continue
1311
- }
1354
+ if ( definition ) {
1355
+ definitions . push ( definition )
1356
+ continue
1357
+ }
1312
1358
1313
- container . walkDecls ( ( decl ) => {
1314
- decl . remove ( )
1315
- } )
1359
+ container . walkDecls ( ( decl ) => {
1360
+ decl . remove ( )
1361
+ } )
1316
1362
1317
- definition = container
1318
- . toString ( )
1319
- . replace ( `.${ escape ( `${ variantName } :${ placeholder } ` ) } ` , '&' )
1320
- . replace ( / (?< ! \\ ) [ { } ] / g, '' )
1321
- . replace ( / \s * \n \s * / g, ' ' )
1322
- . trim ( )
1363
+ definition = container
1364
+ . toString ( )
1365
+ . replace ( `.${ escape ( `${ variantName } :${ placeholder } ` ) } ` , '&' )
1366
+ . replace ( / (?< ! \\ ) [ { } ] / g, '' )
1367
+ . replace ( / \s * \n \s * / g, ' ' )
1368
+ . trim ( )
1323
1369
1324
- if ( ! definition . includes ( placeholder ) ) {
1325
- definitions . push ( definition )
1326
- }
1327
- }
1370
+ if ( ! definition . includes ( placeholder ) ) {
1371
+ definitions . push ( definition )
1372
+ }
1373
+ }
1328
1374
1329
- result [ variantName ] = definitions . join ( ', ' ) || null
1375
+ return definitions
1376
+ } ,
1377
+ } )
1330
1378
}
1331
1379
)
1332
1380
@@ -1358,7 +1406,13 @@ function getVariants(state: State): Record<string, string> {
1358
1406
} )
1359
1407
} )
1360
1408
1361
- return variants . reduce ( ( obj , variant ) => ( { ...obj , [ variant ] : null } ) , { } )
1409
+ return variants . map ( ( variant ) => ( {
1410
+ name : variant ,
1411
+ values : [ ] ,
1412
+ isArbitrary : false ,
1413
+ hasDash : true ,
1414
+ selectors : ( ) => [ ] ,
1415
+ } ) )
1362
1416
}
1363
1417
1364
1418
async function getPlugins ( config : any ) {
@@ -1994,6 +2048,7 @@ class TW {
1994
2048
this . connection . onDocumentColor ( this . onDocumentColor . bind ( this ) )
1995
2049
this . connection . onColorPresentation ( this . onColorPresentation . bind ( this ) )
1996
2050
this . connection . onCodeAction ( this . onCodeAction . bind ( this ) )
2051
+ this . connection . onDocumentLinks ( this . onDocumentLinks . bind ( this ) )
1997
2052
}
1998
2053
1999
2054
private updateCapabilities ( ) {
@@ -2008,13 +2063,17 @@ class TW {
2008
2063
capabilities . add ( HoverRequest . type , { documentSelector : null } )
2009
2064
capabilities . add ( DocumentColorRequest . type , { documentSelector : null } )
2010
2065
capabilities . add ( CodeActionRequest . type , { documentSelector : null } )
2066
+ capabilities . add ( DocumentLinkRequest . type , { documentSelector : null } )
2011
2067
2012
2068
capabilities . add ( CompletionRequest . type , {
2013
2069
documentSelector : null ,
2014
2070
resolveProvider : true ,
2015
2071
triggerCharacters : [
2016
2072
...TRIGGER_CHARACTERS ,
2017
- ...projects . map ( ( project ) => project . state . separator ) . filter ( Boolean ) ,
2073
+ ...projects
2074
+ . map ( ( project ) => project . state . separator )
2075
+ . filter ( ( sep ) => typeof sep === 'string' )
2076
+ . map ( ( sep ) => sep . slice ( - 1 ) ) ,
2018
2077
] . filter ( Boolean ) ,
2019
2078
} )
2020
2079
@@ -2090,6 +2149,10 @@ class TW {
2090
2149
return this . getProject ( params . textDocument ) ?. onCodeAction ( params ) ?? null
2091
2150
}
2092
2151
2152
+ onDocumentLinks ( params : DocumentLinkParams ) : DocumentLink [ ] {
2153
+ return this . getProject ( params . textDocument ) ?. onDocumentLinks ( params ) ?? null
2154
+ }
2155
+
2093
2156
listen ( ) {
2094
2157
this . connection . listen ( )
2095
2158
}
@@ -2154,7 +2217,8 @@ function supportsDynamicRegistration(connection: Connection, params: InitializeP
2154
2217
params . capabilities . textDocument . hover ?. dynamicRegistration &&
2155
2218
params . capabilities . textDocument . colorProvider ?. dynamicRegistration &&
2156
2219
params . capabilities . textDocument . codeAction ?. dynamicRegistration &&
2157
- params . capabilities . textDocument . completion ?. dynamicRegistration
2220
+ params . capabilities . textDocument . completion ?. dynamicRegistration &&
2221
+ params . capabilities . textDocument . documentLink ?. dynamicRegistration
2158
2222
)
2159
2223
}
2160
2224
@@ -2179,6 +2243,7 @@ connection.onInitialize(async (params: InitializeParams): Promise<InitializeResu
2179
2243
hoverProvider : true ,
2180
2244
colorProvider : true ,
2181
2245
codeActionProvider : true ,
2246
+ documentLinkProvider : { } ,
2182
2247
completionProvider : {
2183
2248
resolveProvider : true ,
2184
2249
triggerCharacters : [ ...TRIGGER_CHARACTERS , ':' ] ,
0 commit comments