Skip to content

Commit fb62e76

Browse files
committed
Merge branch 'next' into config-resolution
2 parents a929bb6 + 875cb6f commit fb62e76

File tree

13 files changed

+660
-344
lines changed

13 files changed

+660
-344
lines changed

packages/tailwindcss-language-server/src/language/cssServer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,16 @@ connection.onCompletion(async ({ textDocument, position }, _token) =>
162162
{
163163
...item,
164164
label: 'theme()',
165+
filterText: 'theme',
165166
documentation: {
166167
kind: 'markdown',
167168
value:
168169
'Use the `theme()` function to access your Tailwind config values using dot notation.',
169170
},
171+
command: {
172+
title: '',
173+
command: 'editor.action.triggerSuggest',
174+
},
170175
textEdit: {
171176
...item.textEdit,
172177
newText: item.textEdit.newText.replace(/^calc\(/, 'theme('),
@@ -357,6 +362,7 @@ function createVirtualCssDocument(textDocument: TextDocument): TextDocument {
357362
/@media(\s+screen\s*\([^)]+\))/g,
358363
(_match, screen) => `@media (${MEDIA_MARKER})${' '.repeat(screen.length - 4)}`
359364
)
365+
.replace(/(?<=\b(?:theme|config)\([^)]*)[.[\]]/g, '_')
360366
)
361367
}
362368

@@ -387,7 +393,7 @@ async function validateTextDocument(textDocument: TextDocument): Promise<void> {
387393
.filter((diagnostic) => {
388394
if (
389395
diagnostic.code === 'unknownAtRules' &&
390-
/Unknown at rule @(tailwind|apply)/.test(diagnostic.message)
396+
/Unknown at rule @(tailwind|apply|config)/.test(diagnostic.message)
391397
) {
392398
return false
393399
}

packages/tailwindcss-language-server/src/server.ts

Lines changed: 149 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import {
2727
FileChangeType,
2828
Disposable,
2929
TextDocumentIdentifier,
30+
DocumentLinkRequest,
31+
DocumentLinkParams,
32+
DocumentLink,
3033
} from 'vscode-languageserver/node'
3134
import { TextDocument } from 'vscode-languageserver-textdocument'
3235
import { URI } from 'vscode-uri'
@@ -60,10 +63,12 @@ import {
6063
FeatureFlags,
6164
Settings,
6265
ClassNames,
66+
Variant,
6367
} from 'tailwindcss-language-service/src/util/state'
6468
import { provideDiagnostics } from './lsp/diagnosticsProvider'
6569
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
6670
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
71+
import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
6772
import { debounce } from 'debounce'
6873
import { getModuleDependencies } from './util/getModuleDependencies'
6974
import assert from 'assert'
@@ -108,6 +113,7 @@ const TRIGGER_CHARACTERS = [
108113
// @apply and emmet-style
109114
'.',
110115
// config/theme helper
116+
'(',
111117
'[',
112118
// JIT "important" prefix
113119
'!',
@@ -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

192199
type ProjectConfig = {
@@ -386,6 +393,27 @@ async function createProjectService(
386393
getDocumentSymbols: (uri: string) => {
387394
return connection.sendRequest('@/tailwindCSS/getDocumentSymbols', { uri })
388395
},
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+
},
389417
},
390418
}
391419

@@ -1100,6 +1128,14 @@ async function createProjectService(
11001128
return doCodeActions(state, params)
11011129
}, null)
11021130
},
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+
},
11031139
provideDiagnostics: debounce((document: TextDocument) => {
11041140
if (!state.enabled) return
11051141
provideDiagnostics(state, document)
@@ -1226,107 +1262,119 @@ function isAtRule(node: Node): node is AtRule {
12261262
return node.type === 'atrule'
12271263
}
12281264

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+
}
12361269

1237-
let result = {}
1270+
if (state.jit) {
1271+
let result: Array<Variant> = []
12381272
// [name, [sort, fn]]
12391273
// [name, [[sort, fn]]]
12401274
Array.from(state.jitContext.variantMap as Map<string, [any, any]>).forEach(
12411275
([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+
}
12451287

1246-
let placeholder = '__variant_placeholder__'
1288+
let fns = (Array.isArray(variantFnOrFns[0]) ? variantFnOrFns : [variantFnOrFns]).map(
1289+
([_sort, fn]) => fn
1290+
)
12471291

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__'
12561293

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+
})
12601302

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+
})
12641306

1265-
function modifySelectors(modifierFunction) {
1266-
root.each((rule) => {
1267-
if (rule.type !== 'rule') {
1268-
return
1307+
function getClassNameFromSelector(selector) {
1308+
return classNameParser.transformSync(selector)
12691309
}
12701310

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+
})
12771325
})
1278-
})
1279-
})
1280-
return root
1281-
}
1326+
return root
1327+
}
12821328

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(/:merge\(([^)]+)\)/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(/:merge\(([^)]+)\)/g, '$1')
1341+
},
1342+
wrap: (rule: Container) => {
1343+
if (isAtRule(rule)) {
1344+
definition = `@${rule.name} ${rule.params}`
1345+
}
1346+
},
1347+
})
1348+
)
13031349

1304-
if (!definition) {
1305-
definition = returnValue
1306-
}
1350+
if (!definition) {
1351+
definition = returnValue
1352+
}
13071353

1308-
if (definition) {
1309-
definitions.push(definition)
1310-
continue
1311-
}
1354+
if (definition) {
1355+
definitions.push(definition)
1356+
continue
1357+
}
13121358

1313-
container.walkDecls((decl) => {
1314-
decl.remove()
1315-
})
1359+
container.walkDecls((decl) => {
1360+
decl.remove()
1361+
})
13161362

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()
13231369

1324-
if (!definition.includes(placeholder)) {
1325-
definitions.push(definition)
1326-
}
1327-
}
1370+
if (!definition.includes(placeholder)) {
1371+
definitions.push(definition)
1372+
}
1373+
}
13281374

1329-
result[variantName] = definitions.join(', ') || null
1375+
return definitions
1376+
},
1377+
})
13301378
}
13311379
)
13321380

@@ -1358,7 +1406,13 @@ function getVariants(state: State): Record<string, string> {
13581406
})
13591407
})
13601408

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+
}))
13621416
}
13631417

13641418
async function getPlugins(config: any) {
@@ -1994,6 +2048,7 @@ class TW {
19942048
this.connection.onDocumentColor(this.onDocumentColor.bind(this))
19952049
this.connection.onColorPresentation(this.onColorPresentation.bind(this))
19962050
this.connection.onCodeAction(this.onCodeAction.bind(this))
2051+
this.connection.onDocumentLinks(this.onDocumentLinks.bind(this))
19972052
}
19982053

19992054
private updateCapabilities() {
@@ -2008,13 +2063,17 @@ class TW {
20082063
capabilities.add(HoverRequest.type, { documentSelector: null })
20092064
capabilities.add(DocumentColorRequest.type, { documentSelector: null })
20102065
capabilities.add(CodeActionRequest.type, { documentSelector: null })
2066+
capabilities.add(DocumentLinkRequest.type, { documentSelector: null })
20112067

20122068
capabilities.add(CompletionRequest.type, {
20132069
documentSelector: null,
20142070
resolveProvider: true,
20152071
triggerCharacters: [
20162072
...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)),
20182077
].filter(Boolean),
20192078
})
20202079

@@ -2090,6 +2149,10 @@ class TW {
20902149
return this.getProject(params.textDocument)?.onCodeAction(params) ?? null
20912150
}
20922151

2152+
onDocumentLinks(params: DocumentLinkParams): DocumentLink[] {
2153+
return this.getProject(params.textDocument)?.onDocumentLinks(params) ?? null
2154+
}
2155+
20932156
listen() {
20942157
this.connection.listen()
20952158
}
@@ -2154,7 +2217,8 @@ function supportsDynamicRegistration(connection: Connection, params: InitializeP
21542217
params.capabilities.textDocument.hover?.dynamicRegistration &&
21552218
params.capabilities.textDocument.colorProvider?.dynamicRegistration &&
21562219
params.capabilities.textDocument.codeAction?.dynamicRegistration &&
2157-
params.capabilities.textDocument.completion?.dynamicRegistration
2220+
params.capabilities.textDocument.completion?.dynamicRegistration &&
2221+
params.capabilities.textDocument.documentLink?.dynamicRegistration
21582222
)
21592223
}
21602224

@@ -2179,6 +2243,7 @@ connection.onInitialize(async (params: InitializeParams): Promise<InitializeResu
21792243
hoverProvider: true,
21802244
colorProvider: true,
21812245
codeActionProvider: true,
2246+
documentLinkProvider: {},
21822247
completionProvider: {
21832248
resolveProvider: true,
21842249
triggerCharacters: [...TRIGGER_CHARACTERS, ':'],

0 commit comments

Comments
 (0)