@pandacss/language-server 0.0.0-dev-20230607205019

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,601 @@
1
+ import { CompletionItem, CompletionItemKind, Position, Range } from 'vscode-languageserver'
2
+ import { TextDocument } from 'vscode-languageserver-textdocument'
3
+ import { type PandaExtensionSetup } from '../setup-builder'
4
+
5
+ import {
6
+ type Dict,
7
+ type ParserResultType,
8
+ type RawCondition,
9
+ type ResultItem,
10
+ type SystemStyleObject,
11
+ } from '@pandacss/types'
12
+ import { CallExpression, Identifier, JsxOpeningElement, JsxSelfClosingElement, Node, ts } from 'ts-morph'
13
+
14
+ import {
15
+ BoxNodeArray,
16
+ BoxNodeLiteral,
17
+ BoxNodeMap,
18
+ BoxNodeObject,
19
+ box,
20
+ extractCallExpressionArguments,
21
+ extractJsxAttribute,
22
+ extractJsxElementProps,
23
+ findIdentifierValueDeclaration,
24
+ maybeBoxNode,
25
+ unbox,
26
+ type BoxContext,
27
+ type PrimitiveType,
28
+ type Unboxed,
29
+ } from '@pandacss/extractor'
30
+ import { type PandaContext } from '@pandacss/node'
31
+ import { walkObject } from '@pandacss/shared'
32
+ import { type Token } from '@pandacss/token-dictionary'
33
+ import { Bool } from 'lil-fp'
34
+ import { type PandaVSCodeSettings } from '@pandacss/extension-shared'
35
+ import { match } from 'ts-pattern'
36
+ import { color2kToVsCodeColor } from './color2k-to-vscode-color'
37
+ import { expandTokenFn, extractTokenPaths } from './expand-token-fn'
38
+ import { isColor } from './is-color'
39
+ import { getMarkdownCss, isObjectLike, nodeRangeToVsCodeRange, printTokenValue } from './utils'
40
+
41
+ type ClosestMatch = {
42
+ range: Range
43
+ propName: string
44
+ propNode: BoxNodeWithValue
45
+ }
46
+ type ClosestTokenMatch = ClosestMatch & {
47
+ kind: 'token'
48
+ token: Token
49
+ propValue: PrimitiveType
50
+ }
51
+
52
+ type ClosestConditionMatch = ClosestMatch & {
53
+ kind: 'condition'
54
+ condition: RawCondition
55
+ propValue: Unboxed['raw']
56
+ }
57
+ type ClosestToken = ClosestTokenMatch | ClosestConditionMatch
58
+
59
+ type ClosestInstanceMatch = { name: string }
60
+ type ClosestStylesInstance = { kind: 'styles'; props: BoxNodeMap | BoxNodeObject }
61
+ type ClosestInstance = ClosestInstanceMatch & ClosestStylesInstance
62
+
63
+ type OnTokenCallback = (args: ClosestToken) => void
64
+ type BoxNodeWithValue = BoxNodeObject | BoxNodeLiteral | BoxNodeMap | BoxNodeArray
65
+
66
+ const extractableFns = ['css', 'cx'] as const
67
+ type ExtractableFnName = (typeof extractableFns)[number]
68
+ const canEvalFn = (name: string): name is ExtractableFnName => extractableFns.includes(name as any)
69
+
70
+ const mergeCx = (...args: any[]) =>
71
+ args.filter(Boolean).reduce((acc, curr) => {
72
+ if (typeof curr === 'object') return Object.assign(acc, curr)
73
+
74
+ return acc
75
+ }, {})
76
+
77
+ const isFunctionMadeFromDefineParts = (expr: Identifier) => {
78
+ const declaration = findIdentifierValueDeclaration(expr, [], boxCtx)
79
+ if (!Node.isVariableDeclaration(declaration)) return
80
+
81
+ const initializer = declaration.getInitializer()
82
+ if (!Node.isCallExpression(initializer)) return
83
+
84
+ const fromFunctionName = initializer.getExpression().getText()
85
+ return fromFunctionName === 'defineParts'
86
+ }
87
+
88
+ const boxCtx: BoxContext = {
89
+ flags: { skipTraverseFiles: true },
90
+ getEvaluateOptions: (node) => {
91
+ if (!Node.isCallExpression(node)) return
92
+ const expr = node.getExpression()
93
+
94
+ if (!Node.isIdentifier(expr)) return
95
+ const name = expr.getText()
96
+
97
+ // TODO - check for import alias ? kinda overkill for now
98
+ if (!canEvalFn(name as string) && !isFunctionMadeFromDefineParts(expr)) {
99
+ return
100
+ }
101
+
102
+ return {
103
+ environment: {
104
+ extra: {
105
+ cx: mergeCx,
106
+ css: (styles: SystemStyleObject) => styles,
107
+ },
108
+ },
109
+ } as any
110
+ },
111
+ }
112
+
113
+ const isNodeJsx = Bool.or(Node.isJsxSelfClosingElement, Node.isJsxOpeningElement)
114
+ const getNestedBoxProp = (map: BoxNodeMap, path: string[]) => {
115
+ return path.reduce((acc, curr) => {
116
+ if (box.isMap(acc)) return acc.value.get(curr)
117
+ if (box.isObject(acc)) return acc.value[curr]
118
+
119
+ return acc
120
+ }, map as any)
121
+ }
122
+
123
+ export function setupTokensHelpers(setup: PandaExtensionSetup): any {
124
+ function getSourceFile(doc: TextDocument) {
125
+ const ctx = setup.getContext()
126
+ if (!ctx) return
127
+
128
+ return ctx.project.getSourceFile(doc.uri)
129
+ }
130
+
131
+ /**
132
+ * Get the local component list of local tokens.
133
+ */
134
+ function parseSourceFile(doc: TextDocument) {
135
+ const ctx = setup.getContext()
136
+ if (!ctx) return
137
+
138
+ const project = ctx.project
139
+
140
+ project.addSourceFile(doc.uri, doc.getText())
141
+ return project.parseSourceFile(doc.uri)
142
+ }
143
+
144
+ const createResultTokensGetter = (onToken: OnTokenCallback) => {
145
+ const ctx = setup.getContext()
146
+ if (!ctx)
147
+ return () => {
148
+ //
149
+ }
150
+
151
+ return (result: ResultItem) => {
152
+ const boxNode = result.box
153
+
154
+ result.data.forEach((styles) => {
155
+ const keys = Object.keys(styles)
156
+ if (!keys.length) return
157
+
158
+ walkObject(styles, (value, paths) => {
159
+ // if value doesn't exist
160
+ if (value == null) return
161
+
162
+ const [prop, ..._allConditions] = ctx.conditions.shift(paths)
163
+ const propNode = getNestedBoxProp(boxNode, paths)
164
+ if (!box.isLiteral(propNode)) return
165
+
166
+ const propName = ctx.utility.resolveShorthand(prop)
167
+ const token = getTokenFromPropValue(ctx, propName, value)
168
+ if (!token) return
169
+
170
+ const range = nodeRangeToVsCodeRange(propNode.getRange())
171
+ onToken?.({ kind: 'token', token, range, propName, propValue: value, propNode })
172
+ })
173
+ })
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Get all the tokens from the document and call a callback on it.
179
+ */
180
+ function getFileTokens(_doc: TextDocument, parserResult: ParserResultType, onToken: OnTokenCallback) {
181
+ const ctx = setup.getContext()
182
+ if (!ctx) return
183
+
184
+ const onResult = createResultTokensGetter(onToken)
185
+
186
+ parserResult.css.forEach(onResult)
187
+ parserResult.jsx.forEach(onResult)
188
+ parserResult.cva.forEach((item) =>
189
+ item.data.forEach(({ base }) =>
190
+ onResult(Object.assign({}, item, { box: item.box.value.get('base'), data: [base] })),
191
+ ),
192
+ )
193
+ }
194
+
195
+ const getNodeAtPosition = (doc: TextDocument, position: Position) => {
196
+ const ctx = setup.getContext()
197
+ if (!ctx) return
198
+
199
+ const sourceFile = getSourceFile(doc)
200
+ if (!sourceFile) return
201
+
202
+ const charIndex = ts.getPositionOfLineAndCharacter(sourceFile.compilerNode, position.line, position.character)
203
+ return getDescendantAtPos(sourceFile, charIndex)
204
+ }
205
+
206
+ const findClosestInstance = <Return>(
207
+ node: Node,
208
+ stack: Node[],
209
+ onFoundInstance: (args: ClosestInstance) => Return,
210
+ ) => {
211
+ const ctx = setup.getContext()
212
+ if (!ctx) return
213
+
214
+ let current: Node | undefined = node
215
+
216
+ return match(node)
217
+ .when(
218
+ () => {
219
+ const callExpression = getFirstAncestorMatching(stack, (node): node is CallExpression => {
220
+ if (!Node.isCallExpression(node)) return false
221
+ const expr = node.getExpression()
222
+
223
+ // TODO - check for import alias ? kinda overkill for now
224
+ if (Node.isIdentifier(expr) && !canEvalFn(expr.getText())) return false
225
+
226
+ return true
227
+ })
228
+ if (!callExpression) return
229
+
230
+ current = callExpression
231
+ return current
232
+ },
233
+ () => {
234
+ const callExpression = current as CallExpression
235
+ const name = callExpression.getExpression().getText() as ExtractableFnName
236
+
237
+ if (name === 'cx') {
238
+ const list = extractCallExpressionArguments(
239
+ callExpression,
240
+ boxCtx,
241
+ () => true,
242
+ () => true,
243
+ )
244
+ const styles = mergeCx(
245
+ ...list.value
246
+ .filter((node) => isObjectLike(node))
247
+ .map((item) => (item as BoxNodeMap | BoxNodeObject).value),
248
+ )
249
+
250
+ return onFoundInstance({
251
+ kind: 'styles',
252
+ name,
253
+ props: box.object(styles, callExpression, stack),
254
+ })
255
+ }
256
+
257
+ const list = extractCallExpressionArguments(
258
+ callExpression,
259
+ boxCtx,
260
+ () => true,
261
+ (args) => args.index === 0,
262
+ )
263
+ const config = list.value[0]
264
+ if (!isObjectLike(config)) return
265
+
266
+ if (name === 'css' || name === 'defineStyles') {
267
+ return onFoundInstance({ kind: 'styles', name, props: config })
268
+ }
269
+ },
270
+ )
271
+ .when(
272
+ () => {
273
+ current = getFirstAncestorMatching(stack, isNodeJsx)
274
+ return current
275
+ },
276
+ () => {
277
+ const componentNode = current as JsxSelfClosingElement | JsxOpeningElement
278
+ const { name, props } = extractJsxElementProps(componentNode, boxCtx)
279
+ if (!props.size) return
280
+
281
+ return onFoundInstance({ kind: 'styles', name, props: box.map(props, componentNode, stack) })
282
+ },
283
+ )
284
+ .otherwise(() => {
285
+ //
286
+ return undefined
287
+ })
288
+ }
289
+
290
+ const findClosestToken = <Return>(
291
+ node: Node,
292
+ stack: Node[],
293
+ onFoundToken: (args: Pick<ClosestToken, 'propName' | 'propNode'>) => Return,
294
+ ) => {
295
+ const ctx = setup.getContext()
296
+ if (!ctx) return
297
+
298
+ return match(node)
299
+ .when(
300
+ () => getFirstAncestorMatching(stack, Node.isPropertyAssignment),
301
+ () => {
302
+ const propAssignment = getFirstAncestorMatching(stack, Node.isPropertyAssignment)!
303
+ const name = propAssignment.getName()
304
+
305
+ const objectLiteral = getFirstAncestorMatching(stack, Node.isObjectLiteralExpression)!
306
+ const maybeBox = maybeBoxNode(objectLiteral, [], boxCtx, (args) => args.propName === name)
307
+ if (!box.isMap(maybeBox)) return
308
+
309
+ const propNode = maybeBox.value.get(name)
310
+ if (!box.hasValue(propNode)) return
311
+
312
+ const propName = ctx.utility.resolveShorthand(name)
313
+
314
+ return onFoundToken({ propName, propNode })
315
+ },
316
+ )
317
+ .when(
318
+ () => getFirstAncestorMatching(stack, Node.isJsxAttribute),
319
+ () => {
320
+ const attrNode = getFirstAncestorMatching(stack, Node.isJsxAttribute)!
321
+
322
+ const nameNode = attrNode.getNameNode()
323
+ const name = nameNode.getText()
324
+
325
+ const attrBox = extractJsxAttribute(attrNode, boxCtx)
326
+ if (!box.hasValue(attrBox)) return
327
+
328
+ const propName = ctx.utility.resolveShorthand(name)
329
+
330
+ return onFoundToken({ propName, propNode: attrBox })
331
+ },
332
+ )
333
+ .otherwise(() => {
334
+ //
335
+ return undefined
336
+ })
337
+ }
338
+
339
+ const getClosestToken = (doc: TextDocument, position: Position): ClosestToken | undefined => {
340
+ const ctx = setup.getContext()
341
+ if (!ctx) return
342
+
343
+ const match = getNodeAtPosition(doc, position)
344
+ if (!match) return
345
+
346
+ const { node, stack } = match
347
+
348
+ return findClosestToken(node, stack, ({ propName, propNode }) => {
349
+ if (box.isLiteral(propNode)) {
350
+ const propValue = propNode.value
351
+ const maybeToken = getTokenFromPropValue(ctx, propName, String(propValue))
352
+ if (!maybeToken) return
353
+
354
+ const range = nodeRangeToVsCodeRange(propNode.getRange())
355
+ return { kind: 'token', token: maybeToken, range, propName, propValue, propNode } as ClosestTokenMatch
356
+ }
357
+
358
+ if (box.isMap(propNode) && ctx.conditions.isCondition(propName)) {
359
+ const objectBox = maybeBoxNode(propNode.getNode(), [], boxCtx, () => true)
360
+ if (!objectBox) return
361
+
362
+ if (box.isMap(objectBox)) {
363
+ const propValue = unbox(propNode).raw
364
+ const condition = ctx.conditions.getRaw(propName)
365
+
366
+ const range = nodeRangeToVsCodeRange(propNode.getRange())
367
+ return { kind: 'condition', condition, range, propName, propValue, propNode } as ClosestConditionMatch
368
+ }
369
+ }
370
+ })
371
+ }
372
+
373
+ const getClosestInstance = (doc: TextDocument, position: Position) => {
374
+ const ctx = setup.getContext()
375
+ if (!ctx) return
376
+
377
+ const match = getNodeAtPosition(doc, position)
378
+ if (!match) return
379
+
380
+ const { node, stack } = match
381
+
382
+ return findClosestInstance(node, stack, (instance) => {
383
+ if (instance.kind === 'styles') {
384
+ const { name, props } = instance
385
+
386
+ const unboxed = unbox(props)
387
+ const { className, css, ...rest } = unboxed.raw
388
+ return {
389
+ kind: 'styles',
390
+ name,
391
+ props,
392
+ styles: Object.assign({}, className, css, rest),
393
+ } as ClosestStylesInstance & { styles: Dict }
394
+ }
395
+ })
396
+ }
397
+
398
+ const getClosestCompletionList = async (doc: TextDocument, position: Position) => {
399
+ const ctx = setup.getContext()
400
+ if (!ctx) return
401
+
402
+ const match = getNodeAtPosition(doc, position)
403
+ if (!match) return
404
+
405
+ const settings = await setup.getPandaSettings()
406
+ const { node, stack } = match
407
+
408
+ return findClosestToken(node, stack, ({ propName, propNode }) => {
409
+ if (!box.isLiteral(propNode)) return undefined
410
+ return getCompletionFor(ctx, propName, propNode, settings)
411
+ })
412
+ }
413
+
414
+ return {
415
+ getSourceFile,
416
+ parseSourceFile,
417
+ getFileTokens,
418
+ findClosestToken,
419
+ getClosestToken,
420
+ getClosestInstance,
421
+ getClosestCompletionList,
422
+ }
423
+ }
424
+
425
+ const getDescendantAtPos = (from: Node, pos: number) => {
426
+ let node: Node | undefined = from
427
+ const stack: Node[] = [from]
428
+
429
+ // eslint-disable-next-line no-constant-condition
430
+ while (true) {
431
+ const nextNode: Node | undefined = node.getChildAtPos(pos)
432
+ if (nextNode == null) return { node, stack }
433
+ else {
434
+ node = nextNode
435
+ stack.push(node)
436
+ }
437
+ }
438
+ }
439
+
440
+ const getColorExtensions = (value: string, kind: string) => {
441
+ const vscodeColor = color2kToVsCodeColor(value)
442
+ if (!vscodeColor) return
443
+
444
+ return { vscodeColor, kind }
445
+ }
446
+
447
+ const getTokenFromPropValue = (ctx: PandaContext, prop: string, value: string): Token | undefined => {
448
+ const utility = ctx.config.utilities?.[prop]
449
+
450
+ const category = typeof utility?.values === 'string' && utility?.values
451
+ if (!category) return
452
+
453
+ const tokenPath = [category, value].join('.')
454
+ const token = ctx.tokens.getByName(tokenPath)
455
+
456
+ // arbitrary value like
457
+ // display: "block", zIndex: 1, ...
458
+ if (!token) {
459
+ // any color
460
+ // color: "blue", color: "#000", color: "rgb(0, 0, 0)", ...
461
+ if (isColor(value)) {
462
+ const extensions = getColorExtensions(value, 'native-color')
463
+ if (!extensions) return
464
+
465
+ return { value, name: value, path: tokenPath, type: 'color', extensions } as unknown as Token
466
+ }
467
+
468
+ // border="1px solid token(colors.gray.300)"
469
+ if (typeof value === 'string' && value.includes('token(')) {
470
+ const matches = expandTokenFn(value, ctx.tokens.getByName)
471
+
472
+ // wrong token path
473
+ if (!matches.length) {
474
+ return {
475
+ value,
476
+ name: value,
477
+ path: tokenPath,
478
+ type: 'color',
479
+ extensions: { kind: 'invalid-token-path' },
480
+ } as unknown as Token
481
+ }
482
+
483
+ // TODO: handle multiple tokens like : "token(colors.gray.300), token(colors.gray.500)"
484
+ const first = matches?.[0]?.token
485
+ if (!first) return
486
+
487
+ if (isColor(first.value)) {
488
+ const extensions = getColorExtensions(first.value, 'semantic-color')
489
+ if (!extensions) return
490
+
491
+ return first.setExtensions(extensions)
492
+ }
493
+
494
+ return first
495
+ }
496
+
497
+ return
498
+ }
499
+
500
+ // known theme token
501
+ // px: "2", fontSize: "xl", ...
502
+ // color: "blue.300"
503
+ if (isColor(token.value)) {
504
+ const extensions = getColorExtensions(token.value, 'color')
505
+ if (!extensions) return
506
+
507
+ return token.setExtensions(extensions)
508
+ }
509
+
510
+ return token
511
+ }
512
+
513
+ const completionCache = new Map<string, CompletionItem[]>()
514
+ const itemCache = new Map<string, CompletionItem>()
515
+
516
+ const getCompletionFor = (
517
+ ctx: PandaContext,
518
+ propName: string,
519
+ propNode: BoxNodeLiteral,
520
+ settings: PandaVSCodeSettings,
521
+ ) => {
522
+ const propValue = propNode.value
523
+
524
+ let str = String(propValue)
525
+
526
+ // also provide completion in string such as: token('colors.blue.300')
527
+ if (settings['completions.token-fn.enabled'] && str.includes('token(')) {
528
+ const matches = extractTokenPaths(str)
529
+ const tokenPath = matches[0] ?? ''
530
+ const split = tokenPath.split('.')
531
+
532
+ // provide completion for token category when token() is empty or partial
533
+ if (split.length <= 1) {
534
+ return Array.from(ctx.tokens.categoryMap.keys()).map((category) => {
535
+ return {
536
+ label: category,
537
+ kind: CompletionItemKind.EnumMember,
538
+ sortText: '-' + category,
539
+ preselect: true,
540
+ } as CompletionItem
541
+ })
542
+ }
543
+
544
+ str = tokenPath.split('.').slice(1).join('.')
545
+ }
546
+
547
+ const cachePath = propName + '.' + str
548
+ const cachedList = completionCache.get(cachePath)
549
+ if (cachedList) return cachedList
550
+
551
+ const utility = ctx.config.utilities?.[propName]
552
+ const category = typeof utility?.values === 'string' && utility?.values
553
+ if (!category) return []
554
+
555
+ const values = ctx.tokens.categoryMap.get(category)
556
+ if (!values) return []
557
+
558
+ const items = [] as CompletionItem[]
559
+ values.forEach((token, name) => {
560
+ if (!name.includes(str)) return
561
+
562
+ const tokenPath = token.name
563
+ const cachedItem = itemCache.get(tokenPath)
564
+ if (cachedItem) {
565
+ items.push(cachedItem)
566
+ return
567
+ }
568
+
569
+ const isColor = token.extensions.category === 'colors'
570
+ const completionItem = {
571
+ label: name,
572
+ kind: isColor ? CompletionItemKind.Color : CompletionItemKind.EnumMember,
573
+ documentation: { kind: 'markdown', value: getMarkdownCss(ctx, { [propName]: token.value }, settings).withCss },
574
+ labelDetails: { description: printTokenValue(token, settings), detail: ` ${token.extensions.varRef}` },
575
+ sortText: '-' + name,
576
+ preselect: true,
577
+ } as CompletionItem
578
+
579
+ if (isColor) {
580
+ completionItem.detail = token.value
581
+ // TODO rgb conversion ?
582
+ }
583
+
584
+ items.push(completionItem)
585
+ itemCache.set(tokenPath, completionItem)
586
+ })
587
+
588
+ completionCache.set(cachePath, items)
589
+ return items
590
+ }
591
+
592
+ // quick index based loop
593
+ const getFirstAncestorMatching = <Ancestor extends Node>(
594
+ stack: Node[],
595
+ callback: (parent: Node, index: number) => parent is Ancestor,
596
+ ) => {
597
+ for (let i = stack.length - 1; i >= 0; i--) {
598
+ const parent = stack[i]
599
+ if (callback(parent, i)) return parent
600
+ }
601
+ }
@@ -0,0 +1,82 @@
1
+ import { box, type NodeRange } from '@pandacss/extractor'
2
+ import { Bool } from 'lil-fp'
3
+ import { Range } from 'vscode-languageserver'
4
+
5
+ import { type SystemStyleObject } from '@pandacss/types'
6
+
7
+ import { AtomicRule, optimizeCss } from '@pandacss/core'
8
+ import { type PandaContext } from '@pandacss/node'
9
+ import { mapObject, toPx } from '@pandacss/shared'
10
+ import * as base64 from 'base-64'
11
+
12
+ import postcss from 'postcss'
13
+ import parserCSS from 'prettier/parser-postcss'
14
+ import prettier from 'prettier/standalone'
15
+ import * as utf8 from 'utf8'
16
+ import { match } from 'ts-pattern'
17
+
18
+ import { type PandaVSCodeSettings } from '@pandacss/extension-shared'
19
+ import { type Token } from '@pandacss/token-dictionary'
20
+
21
+ export const isObjectLike = Bool.or(box.isObject, box.isMap)
22
+
23
+ export const nodeRangeToVsCodeRange = (range: NodeRange) =>
24
+ Range.create(
25
+ { line: range.startLineNumber - 1, character: range.startColumn - 1 },
26
+ { line: range.endLineNumber - 1, character: range.endColumn - 1 },
27
+ )
28
+
29
+ const helpers = { map: mapObject }
30
+ export const makeSheetCtx = (ctx: PandaContext) => ({
31
+ root: postcss.root(),
32
+ conditions: ctx.conditions,
33
+ utility: ctx.utility,
34
+ hash: ctx.config.hash,
35
+ helpers,
36
+ })
37
+
38
+ function getPrettiedCSS(css: string) {
39
+ return prettier.format(css, {
40
+ parser: 'css',
41
+ plugins: [parserCSS],
42
+ })
43
+ }
44
+
45
+ export type DisplayOptions = {
46
+ mode?: PandaVSCodeSettings['hovers.display.mode']
47
+ forceHash?: PandaVSCodeSettings['hovers.display.force-hash']
48
+ }
49
+
50
+ export const getMarkdownCss = (ctx: PandaContext, styles: SystemStyleObject, settings: PandaVSCodeSettings) => {
51
+ const mode = settings['hovers.display.mode']
52
+ const forceHash = settings['hovers.display.force-hash']
53
+
54
+ const hash = ctx.config.hash
55
+ if (forceHash) {
56
+ ctx.config.hash = true
57
+ }
58
+
59
+ const rule = new AtomicRule(makeSheetCtx(ctx))
60
+ rule.process({ styles })
61
+
62
+ const css = match(mode ?? 'optimized')
63
+ .with('nested', () => rule.toCss())
64
+ .with('optimized', () => optimizeCss(rule.toCss()))
65
+ .with('minified', () => optimizeCss(rule.toCss(), { minify: true }))
66
+ .run()
67
+ const raw = getPrettiedCSS(css)
68
+ const withCss = '```css' + '\n' + raw + '\n' + '```'
69
+
70
+ // restore hash
71
+ ctx.config.hash = hash
72
+
73
+ return { raw, withCss }
74
+ }
75
+
76
+ export const printTokenValue = (token: Token, settings: PandaVSCodeSettings) =>
77
+ `${token.value}${settings['rem-to-px.enabled'] && token.value.endsWith('rem') ? ` (${toPx(token.value)})` : ''}`
78
+
79
+ export const svgToMarkdownLink = (svg: string) => {
80
+ const dataUri = 'data:image/svg+xml;charset=UTF-8;base64,' + base64.encode(utf8.encode(svg))
81
+ return `![](${dataUri})`
82
+ }