Skip to content

Add color format options (rgb, hex) #831

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
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { test } from 'vitest'
import { withFixture } from '../common'

withFixture('basic', (c) => {
async function completion({
function buildCompletion(c) {
return async function completion({
lang,
text,
position,
Expand All @@ -19,6 +19,10 @@ withFixture('basic', (c) => {
context,
})
}
}

withFixture('basic', (c) => {
let completion = buildCompletion(c)

async function expectCompletions({ expect, lang, text, position, settings }) {
let result = await completion({ lang, text, position, settings })
Expand Down Expand Up @@ -148,7 +152,7 @@ withFixture('basic', (c) => {
expect(result).toBe(null)
})

test('classRegex matching empty string', async ({ expect }) => {
test.concurrent('classRegex matching empty string', async ({ expect }) => {
try {
let result = await completion({
text: "let _ = ''",
Expand Down Expand Up @@ -203,24 +207,87 @@ withFixture('basic', (c) => {
})
})

withFixture('overrides-variants', (c) => {
async function completion({
lang,
text,
position,
context = {
triggerKind: 1,
},
settings,
}) {
let textDocument = await c.openDocument({ text, lang, settings })
withFixture('basic', (c) => {
let completion = buildCompletion(c)

return c.sendRequest('textDocument/completion', {
textDocument,
position,
context,
test('Completions have default pixel equivalents (1rem == 16px)', async ({ expect }) => {
let result = await completion({
lang: 'html',
text: '<div class=""></div>',
position: { line: 0, character: 12 },
})
}

let item = result.items.find((item) => item.label === 'text-sm')
let resolved = await c.sendRequest('completionItem/resolve', item)

expect(resolved).toEqual({
...item,
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```',
},
})
})
})

withFixture('basic', (c) => {
let completion = buildCompletion(c)

test('Completions have customizable pixel equivalents (1rem == 10px)', async ({ expect }) => {
await c.updateSettings({
tailwindCSS: {
rootFontSize: 10,
},
})

let result = await completion({
lang: 'html',
text: '<div class=""></div>',
position: { line: 0, character: 12 },
})

let item = result.items.find((item) => item.label === 'text-sm')

let resolved = await c.sendRequest('completionItem/resolve', item)

expect(resolved).toEqual({
...item,
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```',
},
})
})
})

withFixture('basic', (c) => {
let completion = buildCompletion(c)

test('Completions have color equivalents presented as hex', async ({ expect }) => {
let result = await completion({
lang: 'html',
text: '<div class=""></div>',
position: { line: 0, character: 12 },
})

let item = result.items.find((item) => item.label === 'bg-red-500')

let resolved = await c.sendRequest('completionItem/resolve', item)

expect(resolved).toEqual({
...item,
detail: '--tw-bg-opacity: 1; background-color: rgb(239 68 68 / var(--tw-bg-opacity));',
documentation: '#ef4444',
})
})
})

withFixture('overrides-variants', (c) => {
let completion = buildCompletion(c)

test.concurrent(
'duplicate variant + value pairs do not produce multiple completions',
Expand All @@ -236,23 +303,7 @@ withFixture('overrides-variants', (c) => {
})

withFixture('v4/basic', (c) => {
async function completion({
lang,
text,
position,
context = {
triggerKind: 1,
},
settings,
}) {
let textDocument = await c.openDocument({ text, lang, settings })

return c.sendRequest('textDocument/completion', {
textDocument,
position,
context,
})
}
let completion = buildCompletion(c)

async function expectCompletions({ expect, lang, text, position, settings }) {
let result = await completion({ lang, text, position, settings })
Expand Down Expand Up @@ -439,11 +490,65 @@ withFixture('v4/basic', (c) => {

expect(resolved).toEqual({
...item,
detail: 'text-transform: uppercase',
detail: 'text-transform: uppercase;',
documentation: {
kind: 'markdown',
value: '```css\n.uppercase {\n text-transform: uppercase;\n}\n```',
},
})
})
})

withFixture('v4/basic', (c) => {
let completion = buildCompletion(c)

test('Completions have customizable pixel equivalents (1rem == 10px)', async ({ expect }) => {
await c.updateSettings({
tailwindCSS: {
rootFontSize: 10,
},
})

let result = await completion({
lang: 'html',
text: '<div class=""></div>',
position: { line: 0, character: 12 },
})

let item = result.items.find((item) => item.label === 'text-sm')

let resolved = await c.sendRequest('completionItem/resolve', item)

expect(resolved).toEqual({
...item,
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```',
},
})
})
})

withFixture('v4/basic', (c) => {
let completion = buildCompletion(c)

test('Completions have color equivalents presented as hex', async ({ expect }) => {
let result = await completion({
lang: 'html',
text: '<div class=""></div>',
position: { line: 0, character: 12 },
})

let item = result.items.find((item) => item.label === 'bg-red-500')

let resolved = await c.sendRequest('completionItem/resolve', item)

expect(resolved).toEqual({
...item,
detail: 'background-color: #ef4444;',
documentation: '#ef4444',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -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));\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 } },
})
Expand All @@ -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));\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 } },
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));\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 } },
})
Expand All @@ -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));\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 } },
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));\n' +
' background-color: rgb(239 68 68 / var(--tw-bg-opacity))/* #ef4444 */;\n' +
'}',
expectedRange: {
start: { line: 0, character: 12 },
Expand Down
24 changes: 13 additions & 11 deletions packages/tailwindcss-language-service/src/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import type { TextDocument } from 'vscode-languageserver-textdocument'
import dlv from 'dlv'
import removeMeta from './util/removeMeta'
import { getColor, getColorFromValue } from './util/color'
import { formatColor, getColor, getColorFromValue } from './util/color'
import { isHtmlContext } from './util/html'
import { isCssContext } from './util/css'
import { findLast, matchClassAttributes } from './util/find'
Expand All @@ -33,7 +33,6 @@ import { validateApply } from './util/validateApply'
import { flagEnabled } from './util/flagEnabled'
import * as jit from './util/jit'
import { getVariantsFromClassName } from './util/getVariantsFromClassName'
import * as culori from 'culori'
import {
addPixelEquivalentsToMediaQuery,
addPixelEquivalentsToValue,
Expand Down Expand Up @@ -102,9 +101,10 @@ export function completionsFromClassList(
if (color !== null) {
kind = CompletionItemKind.Color
if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}
}

return {
label: className,
...(documentation ? { documentation } : {}),
Expand Down Expand Up @@ -298,7 +298,7 @@ export function completionsFromClassList(
let documentation: string | undefined

if (color && typeof color !== 'string') {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}

items.push({
Expand Down Expand Up @@ -367,7 +367,7 @@ export function completionsFromClassList(
if (color !== null) {
kind = CompletionItemKind.Color
if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}
}

Expand Down Expand Up @@ -528,7 +528,7 @@ export function completionsFromClassList(
let documentation: string | undefined

if (color && typeof color !== 'string') {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}

items.push({
Expand Down Expand Up @@ -575,7 +575,7 @@ export function completionsFromClassList(
if (color !== null) {
kind = CompletionItemKind.Color
if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}
}

Expand Down Expand Up @@ -661,7 +661,7 @@ export function completionsFromClassList(
if (color !== null) {
kind = CompletionItemKind.Color
if (typeof color !== 'string' && (color.alpha ?? 1) !== 0) {
documentation = culori.formatRgb(color)
documentation = formatColor(color)
}
}

Expand Down Expand Up @@ -1050,7 +1050,7 @@ function provideCssHelperCompletions(
// VS Code bug causes some values to not display in some cases
detail: detail === '0' || detail === 'transparent' ? `${detail} ` : detail,
...(color && typeof color !== 'string' && (color.alpha ?? 1) !== 0
? { documentation: culori.formatRgb(color) }
? { documentation: formatColor(color) }
: {}),
...(insertClosingBrace ? { textEditText: `${item}]` } : {}),
additionalTextEdits: replaceDot
Expand Down Expand Up @@ -1727,7 +1727,9 @@ export async function resolveCompletionItem(
decls.push(node)
})

item.detail = state.designSystem.toCss(decls)
item.detail = await jit.stringifyDecls(state, postcss.rule({
nodes: decls,
}))
} else {
item.detail = `${rules.length} rules`
}
Expand All @@ -1736,7 +1738,7 @@ export async function resolveCompletionItem(
if (!item.documentation) {
item.documentation = {
kind: 'markdown' as typeof MarkupKind.Markdown,
value: ['```css', state.designSystem.toCss(rules), '```'].join('\n'),
value: ['```css', await jit.stringifyRoot(state, postcss.root({ nodes: rules })), '```'].join('\n'),
}
}

Expand Down
10 changes: 9 additions & 1 deletion packages/tailwindcss-language-service/src/util/color.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dlv from 'dlv'
import { State } from './state'
import removeMeta from './removeMeta'
import { ensureArray, dedupe, flatten } from './array'
import { ensureArray, dedupe } from './array'
import type { Color } from 'vscode-languageserver'
import { getClassNameParts } from './getClassNameAtPosition'
import * as jit from './jit'
Expand Down Expand Up @@ -229,3 +229,11 @@ export function culoriColorToVscodeColor(color: culori.Color): Color {
let rgb = toRgb(color)
return { red: rgb.r, green: rgb.g, blue: rgb.b, alpha: rgb.alpha ?? 1 }
}

export function formatColor(color: culori.Color): string {
if (color.alpha === undefined || color.alpha === 1) {
return culori.formatHex(color)
}

return culori.formatHex8(color)
}
Loading