Skip to content
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

fix(language-server): support escape hatched colors #73

Merged
merged 1 commit into from
Feb 24, 2025
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
5 changes: 5 additions & 0 deletions .changeset/modern-clocks-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@pandacss/language-server': minor
---

handle escape hatched colors
59 changes: 54 additions & 5 deletions packages/language-server/__tests__/get-token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,42 @@ test('CSS var', () => {
test('token reference with curly braces', () => {
const ctx = createContext()
expect(getTokenFromPropValue(ctx, 'border', '{colors.gray.300}')).toMatchInlineSnapshot(`
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@astahmer this test was broken due to the backing regex maintaining state across invocation due to the g flag being used on the expression. Removed the /g flags from the expression.

see: https://stackoverflow.com/a/1520853/684869

{
_Token {
"description": undefined,
"extensions": {
"kind": "invalid-token-path",
"category": "colors",
"colorPalette": "gray",
"colorPaletteRoots": [
[
"gray",
],
],
"colorPaletteTokenKeys": [
[
"300",
],
],
"condition": "base",
"kind": "semantic-color",
"prop": "gray.300",
"var": "--colors-gray-300",
"varRef": "var(--colors-gray-300)",
"vscodeColor": {
"alpha": 1,
"blue": 0.8588235294117647,
"green": 0.8352941176470589,
"red": 0.8196078431372549,
},
},
"name": "{colors.gray.300}",
"path": "borders.{colors.gray.300}",
"name": "colors.gray.300",
"originalValue": "#d1d5db",
"path": [
"colors",
"gray",
"300",
],
"type": "color",
"value": "{colors.gray.300}",
"value": "#d1d5db",
}
`)
})
Expand Down Expand Up @@ -189,6 +217,27 @@ test('color: #fff', () => {
`)
})

test('color: [#fff]', () => {
const ctx = createContext()
expect(getTokenFromPropValue(ctx, 'color', '[#fff]')).toMatchInlineSnapshot(`
{
"extensions": {
"kind": "native-color",
"vscodeColor": {
"alpha": 1,
"blue": 1,
"green": 1,
"red": 1,
},
},
"name": "#fff",
"path": "colors.#fff",
"type": "color",
"value": "#fff",
}
`)
})

test('width: xs', () => {
const ctx = createContext()
expect(getTokenFromPropValue(ctx, 'width', 'xs')).toMatchInlineSnapshot(`
Expand Down
4 changes: 2 additions & 2 deletions packages/language-server/src/tokens/expand-token-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import type { Token } from '@pandacss/token-dictionary'
/**
* Regex for matching a tokenized reference.
*/
const REFERENCE_REGEX = /({([^}]*)})/g
const curlyBracketRegex = /[{}]/g
const REFERENCE_REGEX = /({([^}]*)})/
const curlyBracketRegex = /[{}]/

/**
* Returns all references in a string
Expand Down
18 changes: 15 additions & 3 deletions packages/language-server/src/tokens/get-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import { color2kToVsCodeColor } from './color2k-to-vscode-color'
import { isColor } from './is-color'
import { getTokensInString, hasTokenRef } from './expand-token-fn'

const ESCAPE_HATCHED = /^(\[)(.*)(\])$/

const removeEscapeHatch = (value: string) => {
if (ESCAPE_HATCHED.test(value)) {
return value.match(ESCAPE_HATCHED)?.[2] as string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it matches, the second group should always be available

[#fff]
0: [#fff]
1: [
2: #fff
3: ]

}
return value
}

const getColorExtensions = (value: string, kind: string) => {
const vscodeColor = color2kToVsCodeColor(value)
if (!vscodeColor) return
Expand Down Expand Up @@ -43,12 +52,15 @@ export const getTokenFromPropValue = (ctx: PandaContext, prop: string, value: st
.find((t) => t.token !== undefined)

const token = matchedToken?.token
// If token was located use the first category and value
const tokenPath = matchedToken?.tokenPath ?? [potentialCategories[0], value].join('.')

// arbitrary value like
// display: "block", zIndex: 1, ...
if (!token) {
// remove escape hatch if found
value = removeEscapeHatch(value)
// Use the first category for the token path
const tokenPath = [potentialCategories[0], value].join('.')

// any color
// color: "blue", color: "#000", color: "rgb(0, 0, 0)", ...
if (isColor(value)) {
Expand Down Expand Up @@ -96,7 +108,7 @@ export const getTokenFromPropValue = (ctx: PandaContext, prop: string, value: st

let color = token.value
// could be a semantic token, so the token.value wouldn't be a color directly, it's actually a CSS variable
if (!isColor(color) && typeof token.value === "string" && token.value.startsWith('var(--')) {
if (!isColor(color) && typeof token.value === 'string' && token.value.startsWith('var(--')) {
const [tokenRef] = ctx.tokens.getReferences(token.originalValue)
if (tokenRef?.value) {
color = tokenRef.value
Expand Down