Skip to content

Show color decorators for oklab and oklch colors #936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/tailwindcss-language-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@tailwindcss/line-clamp": "0.4.2",
"@tailwindcss/typography": "0.5.7",
"@types/color-name": "^1.1.3",
"@types/culori": "^2.1.0",
"@types/debounce": "1.2.0",
"@types/dlv": "^1.1.4",
"@types/find-up": "^4.0.0",
Expand All @@ -55,7 +56,7 @@
"bun-types": "^1.0.6",
"chokidar": "3.5.1",
"color-name": "1.1.4",
"culori": "0.20.1",
"culori": "^4.0.1",
"debounce": "1.2.0",
"deepmerge": "4.2.2",
"dlv": "1.1.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,23 @@ export async function loadDesignSystem(
Object.assign(design, {
compile(classes: string[]): (postcss.Root | null)[] {
let css = design.candidatesToCss(classes)
let errors: any[] = []

let roots = css.map((str) => {
if (str === null) return postcss.root()

try {
return postcss.parse(str.trimEnd())
} catch {
return null
} catch (err) {
errors.push(err)
return postcss.root()
}
})

if (errors.length > 0) {
console.error(JSON.stringify(errors))
}

return roots
},

Expand Down
43 changes: 36 additions & 7 deletions packages/tailwindcss-language-server/tests/colors/colors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,30 @@ withFixture('basic', (c) => {
})

testColors('arbitrary value and opacity modifier', {
text: '<div class="bg-[red]/[0.33]">',
text: '<div class="bg-[red]/[0.5]">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 27 } },
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 26 } },
color: {
red: 1,
green: 0,
blue: 0,
alpha: 0.33,
alpha: 0.5,
},
},
],
})

testColors('oklch colors are parsed', {
text: '<div class="bg-[oklch(60%_0.25_25)]">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 35 } },
color: {
alpha: 1,
red: 0.9475942429386454,
green: 0,
blue: 0.14005415620741646,
},
},
],
Expand Down Expand Up @@ -135,19 +150,33 @@ withFixture('v4/basic', (c) => {

/*
testColors('arbitrary value and opacity modifier', {
text: '<div class="bg-[red]/[0.33]">',
text: '<div class="bg-[red]/[0.5]">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 27 } },
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 26 } },
color: {
red: 1,
green: 0,
blue: 0,
// TODO: This is strange, it should be 0.33
alpha: 0.32941176470588235,
alpha: 0.5,
},
},
],
})
*/

testColors('oklch colors are parsed', {
text: '<div class="bg-[oklch(60%_0.25_25)]">',
expected: [
{
range: { start: { line: 0, character: 12 }, end: { line: 0, character: 35 } },
color: {
alpha: 1,
red: 0.9475942429386454,
green: 0,
blue: 0.14005415620741646,
},
},
],
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ withFixture('basic', (c) => {
{ label: 'bg-[hsl(0,100%,50%)]' },
])
})

test.concurrent('arbitrary oklch color', async ({ expect }) => {
let textDocument = await c.openDocument({ text: '<div class="bg-[oklch(44.05%_0.16_303)]">' })
let res = await c.sendRequest('textDocument/colorPresentation', {
color: { red: 1, green: 0, blue: 0, alpha: 1 },
textDocument,
range: {
start: { line: 0, character: 12 },
end: { line: 0, character: 39 },
},
})

expect(res).toEqual([])
})
})

withFixture('v4/basic', (c) => {
Expand Down Expand Up @@ -211,4 +225,18 @@ withFixture('v4/basic', (c) => {
{ label: 'bg-[hsl(0,100%,50%)]' },
])
})

test.concurrent('arbitrary oklch color', async ({ expect }) => {
let textDocument = await c.openDocument({ text: '<div class="bg-[oklch(44.05%_0.16_303)]">' })
let res = await c.sendRequest('textDocument/colorPresentation', {
color: { red: 1, green: 0, blue: 0, alpha: 1 },
textDocument,
range: {
start: { line: 0, character: 12 },
end: { line: 0, character: 39 },
},
})

expect(res).toEqual([])
})
})
57 changes: 50 additions & 7 deletions packages/tailwindcss-language-server/tests/hover/hover.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { test } from 'vitest'
import { withFixture } from '../common'

withFixture('basic', (c) => {
async function testHover(name, { text, lang, position, exact = false, 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', {
Expand Down Expand Up @@ -99,16 +102,56 @@ withFixture('basic', (c) => {
expected: {
contents: {
kind: 'markdown',
value: [
'```plaintext',
'1.25rem /* 20px */',
'```',
].join('\n'),
value: ['```plaintext', '1.25rem /* 20px */', '```'].join('\n'),
},
range: {
start: { line: 0, character: 24 },
end: { line: 0, character: 35 },
}
},
},
})

testHover('color equivalents supports in-gamut oklch/oklab', {
lang: 'html',
text: '<div class="text-[oklch(44.05%_0.16_303)]">',
position: { line: 0, character: 32 },

exact: true,
expected: {
contents: {
language: 'css',
value: [
'.text-\\[oklch\\(44\\.05\\%_0\\.16_303\\)\\] {',
' color: oklch(44.05% 0.16 303) /* #663399 */;',
'}',
].join('\n'),
},
range: {
start: { line: 0, character: 12 },
end: { line: 0, character: 41 },
},
},
})

testHover('color equivalents ignores wide-gamut oklch/oklab', {
lang: 'html',
text: '<div class="text-[oklch(60%_0.26_20)]">',
position: { line: 0, character: 32 },

exact: true,
expected: {
contents: {
language: 'css',
value: [
'.text-\\[oklch\\(60\\%_0\\.26_20\\)\\] {',
' color: oklch(60% 0.26 20);',
'}',
].join('\n'),
},
range: {
start: { line: 0, character: 12 },
end: { line: 0, character: 37 },
},
},
})
})
Expand Down
4 changes: 2 additions & 2 deletions packages/tailwindcss-language-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"@csstools/css-parser-algorithms": "2.1.1",
"@csstools/css-tokenizer": "2.1.1",
"@csstools/media-query-list-parser": "2.0.4",
"@types/culori": "^2.0.0",
"@types/culori": "^2.1.0",
"@types/moo": "0.5.3",
"@types/semver": "7.3.10",
"color-name": "1.1.4",
"css.escape": "1.5.1",
"culori": "0.20.1",
"culori": "^4.0.1",
"detect-indent": "6.0.0",
"dlv": "1.1.3",
"dset": "3.1.2",
Expand Down
8 changes: 4 additions & 4 deletions packages/tailwindcss-language-service/src/util/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function getKeywordColor(value: unknown): KeywordColor | null {

// https://github.com/khalilgharbaoui/coloregex
const colorRegex = new RegExp(
`(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgb|hsl)a?\\(\\s*(-?[\\d.]+%?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
`(?:^|\\s|\\(|,)(#(?:[0-9a-f]{2}){2,4}|(#[0-9a-f]{3})|(rgba?|hsla?|(?:ok)?(?:lab|lch))\\(\\s*(-?[\\d.]+%?(\\s*[,/]\\s*|\\s+)+){2,3}\\s*([\\d.]+%?|var\\([^)]+\\))?\\)|transparent|currentColor|${Object.keys(
namedColors,
).join('|')})(?:$|\\s|\\)|,)`,
'gi',
Expand All @@ -52,7 +52,7 @@ const colorRegex = new RegExp(
function replaceColorVarsWithTheirDefaults(str: string): string {
// rgb(var(--primary, 66 66 66))
// -> rgb(66 66 66)
return str.replace(/((?:rgb|hsl)a?\(\s*)var\([^,]+,\s*([^)]+)\)/gi, '$1$2')
return str.replace(/((?:rgba?|hsla?|(?:ok)?(?:lab|lch))\(\s*)var\([^,]+,\s*([^)]+)\)/gi, '$1$2')
}

function getColorsInString(str: string): (culori.Color | KeywordColor)[] {
Expand Down Expand Up @@ -205,7 +205,7 @@ export function getColorFromValue(value: unknown): culori.Color | KeywordColor |
return 'currentColor'
}
if (
!/^\s*(?:rgba?|hsla?)\s*\([^)]+\)\s*$/.test(trimmedValue) &&
!/^\s*(?:rgba?|hsla?|(?:ok)?(?:lab|lch))\s*\([^)]+\)\s*$/.test(trimmedValue) &&
!/^\s*#[0-9a-f]+\s*$/i.test(trimmedValue) &&
!Object.keys(namedColors).includes(trimmedValue)
) {
Expand All @@ -218,7 +218,7 @@ export function getColorFromValue(value: unknown): culori.Color | KeywordColor |
let toRgb = culori.converter('rgb')

export function culoriColorToVscodeColor(color: culori.Color): Color {
let rgb = toRgb(color)
let rgb = culori.clampRgb(toRgb(color))
return { red: rgb.r, green: rgb.g, blue: rgb.b, alpha: rgb.alpha ?? 1 }
}

Expand Down
18 changes: 10 additions & 8 deletions packages/tailwindcss-language-service/src/util/colorEquivalents.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { Plugin } from 'postcss'
import parseValue from 'postcss-value-parser'
import { inGamut } from 'culori'
import { formatColor, getColorFromValue } from './color'
import type { Comment } from './comments'

let allowedFunctions = ['rgb', 'rgba', 'hsl', 'hsla', 'lch', 'lab', 'oklch', 'oklab']

export function equivalentColorValues({ comments }: { comments: Comment[] }): Plugin {
return {
postcssPlugin: 'plugin',
Declaration(decl) {
if (!decl.value.includes('rgb') && !decl.value.includes('hsl')) {
if (!allowedFunctions.some((fn) => decl.value.includes(fn))) {
return
}

Expand All @@ -16,12 +19,7 @@ export function equivalentColorValues({ comments }: { comments: Comment[] }): Pl
return true
}

if (
node.value !== 'rgb' &&
node.value !== 'rgba' &&
node.value !== 'hsl' &&
node.value !== 'hsla'
) {
if (!allowedFunctions.includes(node.value)) {
return false
}

Expand All @@ -30,7 +28,11 @@ export function equivalentColorValues({ comments }: { comments: Comment[] }): Pl
return false
}

const color = getColorFromValue(`rgb(${values.join(', ')})`)
const color = getColorFromValue(`${node.value}(${values.join(' ')})`)
if (!inGamut('rgb')(color)) {
return false
}

if (!color || typeof color === 'string') {
return false
}
Expand Down
1 change: 1 addition & 0 deletions packages/vscode-tailwindcss/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Show pixel equivalents in completions and hovers of the theme() helper (#935)
- Handle `style` exports condition when processing `@import`s (#934)
- Highlight `@theme` contents as a rule list (#937)
- Show color decorators for `oklab` and `oklch` colors (#936)

## 0.10.5

Expand Down
Loading