Skip to content

Commit 68750d8

Browse files
authored
Merge pull request #630 from tailwindlabs/next
Theme helper improvements, support `@config`, adopt `getVariants`, bump bundled modules
2 parents c9acd0d + fd5c1ef commit 68750d8

File tree

15 files changed

+1210
-870
lines changed

15 files changed

+1210
-870
lines changed

package-lock.json

Lines changed: 574 additions & 579 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/tailwindcss-language-server/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
},
3333
"devDependencies": {
3434
"@parcel/watcher": "2.0.3",
35-
"@tailwindcss/aspect-ratio": "0.4.0",
36-
"@tailwindcss/forms": "0.4.0",
37-
"@tailwindcss/line-clamp": "0.3.0",
38-
"@tailwindcss/typography": "0.5.0",
35+
"@tailwindcss/aspect-ratio": "0.4.2",
36+
"@tailwindcss/forms": "0.5.3",
37+
"@tailwindcss/line-clamp": "0.4.2",
38+
"@tailwindcss/typography": "0.5.7",
3939
"@types/debounce": "1.2.0",
4040
"@types/node": "14.14.34",
4141
"@types/vscode": "1.65.0",
@@ -63,7 +63,7 @@
6363
"resolve": "1.20.0",
6464
"rimraf": "3.0.2",
6565
"stack-trace": "0.0.10",
66-
"tailwindcss": "3.0.11",
66+
"tailwindcss": "3.1.8",
6767
"typescript": "4.6.4",
6868
"vscode-css-languageservice": "5.4.1",
6969
"vscode-languageserver": "8.0.2",

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: 142 additions & 80 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,6 +63,7 @@ import {
6063
FeatureFlags,
6164
Settings,
6265
ClassNames,
66+
Variant,
6367
} from 'tailwindcss-language-service/src/util/state'
6468
import {
6569
provideDiagnostics,
@@ -68,6 +72,7 @@ import {
6872
} from './lsp/diagnosticsProvider'
6973
import { doCodeActions } from 'tailwindcss-language-service/src/codeActions/codeActionProvider'
7074
import { getDocumentColors } from 'tailwindcss-language-service/src/documentColorProvider'
75+
import { getDocumentLinks } from 'tailwindcss-language-service/src/documentLinksProvider'
7176
import { debounce } from 'debounce'
7277
import { getModuleDependencies } from './util/getModuleDependencies'
7378
import 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

192199
type 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(/:merge\(([^)]+)\)/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(/:merge\(([^)]+)\)/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

12821336
async 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

Comments
 (0)