diff --git a/packages/tailwindcss-language-server/src/projects.ts b/packages/tailwindcss-language-server/src/projects.ts
index fc15964f..0d923154 100644
--- a/packages/tailwindcss-language-server/src/projects.ts
+++ b/packages/tailwindcss-language-server/src/projects.ts
@@ -415,6 +415,7 @@ export async function createProjectService(
let pluginVersions: string | undefined
let browserslist: string[] | undefined
let resolveConfigFn: (config: any) => any
+ let transformThemeValueFn: (section: any) => (value: any) => any
let loadConfigFn: (path: string) => any
let featureFlags: FeatureFlags = { future: [], experimental: [] }
let applyComplexClasses: any
@@ -513,6 +514,13 @@ export async function createProjectService(
}
}
+ try {
+ let fn = require(resolveFrom(tailwindDir, './lib/util/transformThemeValue.js'))
+ transformThemeValueFn = fn.default ?? fn
+ } catch {
+ //
+ }
+
try {
loadConfigFn = require(resolveFrom(tailwindDir, './loadConfig.js'))
} catch {}
@@ -633,6 +641,7 @@ export async function createProjectService(
console.error(util.format(error))
tailwindcss = require('tailwindcss')
resolveConfigFn = require('tailwindcss/resolveConfig')
+ transformThemeValueFn = require('tailwindcss/lib/util/transformThemeValue').default
loadConfigFn = require('tailwindcss/loadConfig')
postcss = require('postcss')
tailwindcssVersion = require('tailwindcss/package.json').version
@@ -660,6 +669,7 @@ export async function createProjectService(
postcssSelectorParser: { module: postcssSelectorParser },
resolveConfig: { module: resolveConfigFn },
loadConfig: { module: loadConfigFn },
+ transformThemeValue: { module: transformThemeValueFn },
jit: jitModules,
}
state.browserslist = browserslist
diff --git a/packages/tailwindcss-language-server/tests/completions/completions.test.js b/packages/tailwindcss-language-server/tests/completions/completions.test.js
index 2e3de946..656841a5 100644
--- a/packages/tailwindcss-language-server/tests/completions/completions.test.js
+++ b/packages/tailwindcss-language-server/tests/completions/completions.test.js
@@ -222,11 +222,11 @@ withFixture('basic', (c) => {
expect(resolved).toEqual({
...item,
- detail: 'font-size: 0.875rem/* 14px */; line-height: 1.25rem/* 20px */;',
+ detail: 'font-size: 0.875rem /* 14px */; line-height: 1.25rem /* 20px */;',
documentation: {
kind: 'markdown',
value:
- '```css\n.text-sm {\n font-size: 0.875rem/* 14px */;\n line-height: 1.25rem/* 20px */;\n}\n```',
+ '```css\n.text-sm {\n font-size: 0.875rem /* 14px */;\n line-height: 1.25rem /* 20px */;\n}\n```',
},
})
})
@@ -254,11 +254,11 @@ withFixture('basic', (c) => {
expect(resolved).toEqual({
...item,
- detail: 'font-size: 0.875rem/* 8.75px */; line-height: 1.25rem/* 12.5px */;',
+ detail: 'font-size: 0.875rem /* 8.75px */; line-height: 1.25rem /* 12.5px */;',
documentation: {
kind: 'markdown',
value:
- '```css\n.text-sm {\n font-size: 0.875rem/* 8.75px */;\n line-height: 1.25rem/* 12.5px */;\n}\n```',
+ '```css\n.text-sm {\n font-size: 0.875rem /* 8.75px */;\n line-height: 1.25rem /* 12.5px */;\n}\n```',
},
})
})
@@ -521,11 +521,11 @@ withFixture('v4/basic', (c) => {
expect(resolved).toEqual({
...item,
- detail: 'font-size: 0.875rem/* 8.75px */; line-height: 1.25rem/* 12.5px */;',
+ detail: 'font-size: 0.875rem /* 8.75px */; line-height: 1.25rem /* 12.5px */;',
documentation: {
kind: 'markdown',
value:
- '```css\n.text-sm {\n font-size: 0.875rem/* 8.75px */;\n line-height: 1.25rem/* 12.5px */;\n}\n```',
+ '```css\n.text-sm {\n font-size: 0.875rem /* 8.75px */;\n line-height: 1.25rem /* 12.5px */;\n}\n```',
},
})
})
diff --git a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
index 591a3325..0bdccd13 100644
--- a/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
+++ b/packages/tailwindcss-language-server/tests/env/multi-config-content.test.js
@@ -13,7 +13,7 @@ withFixture('multi-config-content', (c) => {
contents: {
language: 'css',
value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity))/* #ff0000 */;\n}',
+ '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity)) /* #ff0000 */;\n}',
},
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
@@ -30,7 +30,7 @@ withFixture('multi-config-content', (c) => {
contents: {
language: 'css',
value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity))/* #0000ff */;\n}',
+ '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity)) /* #0000ff */;\n}',
},
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
diff --git a/packages/tailwindcss-language-server/tests/env/multi-config.test.js b/packages/tailwindcss-language-server/tests/env/multi-config.test.js
index 9d34eb62..5050314c 100644
--- a/packages/tailwindcss-language-server/tests/env/multi-config.test.js
+++ b/packages/tailwindcss-language-server/tests/env/multi-config.test.js
@@ -13,7 +13,7 @@ withFixture('multi-config', (c) => {
contents: {
language: 'css',
value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity))/* #ff0000 */;\n}',
+ '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(255 0 0 / var(--tw-bg-opacity)) /* #ff0000 */;\n}',
},
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
@@ -30,7 +30,7 @@ withFixture('multi-config', (c) => {
contents: {
language: 'css',
value:
- '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity))/* #0000ff */;\n}',
+ '.bg-foo {\n --tw-bg-opacity: 1;\n background-color: rgb(0 0 255 / var(--tw-bg-opacity)) /* #0000ff */;\n}',
},
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 18 } },
})
diff --git a/packages/tailwindcss-language-server/tests/hover/hover.test.js b/packages/tailwindcss-language-server/tests/hover/hover.test.js
index f1b847c3..0e8289c6 100644
--- a/packages/tailwindcss-language-server/tests/hover/hover.test.js
+++ b/packages/tailwindcss-language-server/tests/hover/hover.test.js
@@ -2,7 +2,7 @@ import { test } from 'vitest'
import { withFixture } from '../common'
withFixture('basic', (c) => {
- async function testHover(name, { text, lang, position, expected, expectedRange, settings }) {
+ async function testHover(name, { text, lang, position, exact = false, expected, expectedRange, settings }) {
test.concurrent(name, async ({ expect }) => {
let textDocument = await c.openDocument({ text, lang, settings })
let res = await c.sendRequest('textDocument/hover', {
@@ -10,17 +10,17 @@ withFixture('basic', (c) => {
position,
})
- expect(res).toEqual(
- expected
- ? {
- contents: {
- language: 'css',
- value: expected,
- },
- range: expectedRange,
- }
- : expected,
- )
+ if (!exact && expected) {
+ expected = {
+ contents: {
+ language: 'css',
+ value: expected,
+ },
+ range: expectedRange,
+ }
+ }
+
+ expect(res).toEqual(expected)
})
}
@@ -38,7 +38,7 @@ withFixture('basic', (c) => {
expected:
'.bg-red-500 {\n' +
' --tw-bg-opacity: 1;\n' +
- ' background-color: rgb(239 68 68 / var(--tw-bg-opacity))/* #ef4444 */;\n' +
+ ' background-color: rgb(239 68 68 / var(--tw-bg-opacity)) /* #ef4444 */;\n' +
'}',
expectedRange: {
start: { line: 0, character: 12 },
@@ -59,7 +59,7 @@ withFixture('basic', (c) => {
testHover('arbitrary value with theme function', {
text: '
',
position: { line: 0, character: 13 },
- expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem/* 16px */;\n' + '}',
+ expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}',
expectedRange: {
start: { line: 0, character: 12 },
end: { line: 0, character: 32 },
@@ -89,6 +89,28 @@ withFixture('basic', (c) => {
end: { line: 2, character: 18 },
},
})
+
+ testHover('showPixelEquivalents works with theme()', {
+ lang: 'tailwindcss',
+ text: `.foo { font-size: theme(fontSize.xl) }`,
+ position: { line: 0, character: 32 },
+
+ exact: true,
+ expected: {
+ contents: {
+ kind: 'markdown',
+ value: [
+ '```plaintext',
+ '1.25rem /* 20px */',
+ '```',
+ ].join('\n'),
+ },
+ range: {
+ start: { line: 0, character: 24 },
+ end: { line: 0, character: 35 },
+ }
+ },
+ })
})
withFixture('v4/basic', (c) => {
@@ -146,7 +168,7 @@ withFixture('v4/basic', (c) => {
// testHover('arbitrary value with theme function', {
// text: '
',
// position: { line: 0, character: 13 },
- // expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem/* 16px */;\n' + '}',
+ // expected: '.p-\\[theme\\(spacing\\.4\\)\\] {\n' + ' padding: 1rem /* 16px */;\n' + '}',
// expectedRange: {
// start: { line: 0, character: 12 },
// end: { line: 0, character: 32 },
diff --git a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
index 1f8941ae..c3e79308 100644
--- a/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
+++ b/packages/tailwindcss-language-service/src/diagnostics/getInvalidConfigPathDiagnostics.ts
@@ -23,9 +23,17 @@ export function validateConfigPath(
base: string[] = [],
): { isValid: true; value: any } | { isValid: false; reason: string; suggestions: string[] } {
let keys = Array.isArray(path) ? path : stringToPath(path)
- let value = dlv(state.config, [...base, ...keys])
+ let fullPath = [...base, ...keys]
+ let value = dlv(state.config, fullPath)
let suggestions: string[] = []
+ // This property may not exist in the state object because of compatability with Tailwind Play
+ let transformThemeValue = state.modules?.transformThemeValue?.module ?? ((_: any) => (value: any) => value)
+
+ if (fullPath[0] === 'theme' && fullPath[1]) {
+ value = transformThemeValue(fullPath[1])(value)
+ }
+
function findAlternativePath(): string[] {
let points = combinations('123456789'.substr(0, keys.length - 1)).map((x) =>
x.split('').map((x) => parseInt(x, 10)),
diff --git a/packages/tailwindcss-language-service/src/hoverProvider.ts b/packages/tailwindcss-language-service/src/hoverProvider.ts
index d3869337..6ed47ec5 100644
--- a/packages/tailwindcss-language-service/src/hoverProvider.ts
+++ b/packages/tailwindcss-language-service/src/hoverProvider.ts
@@ -10,6 +10,7 @@ import * as jit from './util/jit'
import { validateConfigPath } from './diagnostics/getInvalidConfigPathDiagnostics'
import { isWithinRange } from './util/isWithinRange'
import type { TextDocument } from 'vscode-languageserver-textdocument'
+import { addPixelEquivalentsToValue } from './util/pixelEquivalents'
export async function doHover(
state: State,
@@ -18,35 +19,42 @@ export async function doHover(
): Promise {
return (
(await provideClassNameHover(state, document, position)) ||
- provideCssHelperHover(state, document, position)
+ (await provideCssHelperHover(state, document, position))
)
}
-function provideCssHelperHover(state: State, document: TextDocument, position: Position): Hover {
+async function provideCssHelperHover(state: State, document: TextDocument, position: Position): Promise {
if (!isCssContext(state, document, position)) {
return null
}
+ const settings = await state.editor.getConfiguration(document.uri)
+
let helperFns = findHelperFunctionsInRange(document, {
start: { line: position.line, character: 0 },
end: { line: position.line + 1, character: 0 },
})
for (let helperFn of helperFns) {
- if (isWithinRange(position, helperFn.ranges.path)) {
- let validated = validateConfigPath(
- state,
- helperFn.path,
- helperFn.helper === 'theme' ? ['theme'] : [],
- )
- let value = validated.isValid ? stringifyConfigValue(validated.value) : null
- if (value === null) {
- return null
- }
- return {
- contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
- range: helperFn.ranges.path,
- }
+ if (!isWithinRange(position, helperFn.ranges.path)) continue
+
+ let validated = validateConfigPath(
+ state,
+ helperFn.path,
+ helperFn.helper === 'theme' ? ['theme'] : [],
+ )
+
+ // This property may not exist in the state object because of compatability with Tailwind Play
+ let value = validated.isValid ? stringifyConfigValue(validated.value) : null
+ if (value === null) return null
+
+ if (settings.tailwindCSS.showPixelEquivalents) {
+ value = addPixelEquivalentsToValue(value, settings.tailwindCSS.rootFontSize)
+ }
+
+ return {
+ contents: { kind: 'markdown', value: ['```plaintext', value, '```'].join('\n') },
+ range: helperFn.ranges.path,
}
}
diff --git a/packages/tailwindcss-language-service/src/util/comments.ts b/packages/tailwindcss-language-service/src/util/comments.ts
index fef04267..1a989643 100644
--- a/packages/tailwindcss-language-service/src/util/comments.ts
+++ b/packages/tailwindcss-language-service/src/util/comments.ts
@@ -5,7 +5,7 @@ export function applyComments(str: string, comments: Comment[]): string {
for (let comment of comments) {
let index = comment.index + offset
- let commentStr = `/* ${comment.value} */`
+ let commentStr = ` /* ${comment.value} */`
str = str.slice(0, index) + commentStr + str.slice(index)
offset += commentStr.length
}
diff --git a/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
index b562c33d..1901923c 100644
--- a/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
+++ b/packages/tailwindcss-language-service/src/util/pixelEquivalents.ts
@@ -20,7 +20,7 @@ export function addPixelEquivalentsToValue(value: string, rootFontSize: number):
return false
}
- let commentStr = `/* ${parseFloat(unit.number) * rootFontSize}px */`
+ let commentStr = ` /* ${parseFloat(unit.number) * rootFontSize}px */`
value = value.slice(0, node.sourceEndIndex) + commentStr + value.slice(node.sourceEndIndex)
return false
diff --git a/packages/tailwindcss-language-service/src/util/state.ts b/packages/tailwindcss-language-service/src/util/state.ts
index 3f94f259..abe8863c 100644
--- a/packages/tailwindcss-language-service/src/util/state.ts
+++ b/packages/tailwindcss-language-service/src/util/state.ts
@@ -107,6 +107,7 @@ export interface State {
postcss?: { version: string; module: Postcss }
postcssSelectorParser?: { module: any }
resolveConfig?: { module: any }
+ transformThemeValue?: { module: any }
loadConfig?: { module: any }
jit?: {
generateRules: { module: any }
diff --git a/packages/tailwindcss-language-service/src/util/stringify.ts b/packages/tailwindcss-language-service/src/util/stringify.ts
index eaa70c2e..725d224d 100644
--- a/packages/tailwindcss-language-service/src/util/stringify.ts
+++ b/packages/tailwindcss-language-service/src/util/stringify.ts
@@ -11,6 +11,7 @@ import { addEquivalents } from './equivalents'
export function stringifyConfigValue(x: any): string {
if (isObject(x)) return `${Object.keys(x).length} values`
if (typeof x === 'function') return 'ƒ'
+ if (typeof x === 'string') return x
return stringifyObject(x, {
inlineCharacterLimit: Infinity,
singleQuotes: false,
diff --git a/packages/vscode-tailwindcss/CHANGELOG.md b/packages/vscode-tailwindcss/CHANGELOG.md
index 8773adba..75602d67 100644
--- a/packages/vscode-tailwindcss/CHANGELOG.md
+++ b/packages/vscode-tailwindcss/CHANGELOG.md
@@ -6,6 +6,7 @@
- Support Astro's `class:list` attribute by default (#890)
- Fix hovers and CSS conflict detection in Vue `