Skip to content

Commit

Permalink
feat(vscode): add color preview (#1191)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
zam157 and antfu committed Jun 30, 2022
1 parent d241fec commit bc3f293
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 3 deletions.
7 changes: 6 additions & 1 deletion packages/vscode/package.json
@@ -1,6 +1,6 @@
{
"publisher": "antfu",
"name": "@unocss/vscode",
"name": "@vscode/unocss",
"displayName": "UnoCSS",
"version": "0.41.2",
"private": true,
Expand Down Expand Up @@ -47,6 +47,11 @@
"type": "boolean",
"default": true,
"description": "Enable/disable underline decoration for class names"
},
"unocss.colorPreview": {
"type": "boolean",
"default": true,
"description": "Enable/disable color preview decorations"
}
}
}
Expand Down
44 changes: 43 additions & 1 deletion packages/vscode/src/annotation.ts
Expand Up @@ -3,7 +3,7 @@ import type { DecorationOptions, ExtensionContext, StatusBarItem } from 'vscode'
import { DecorationRangeBehavior, MarkdownString, Range, window, workspace } from 'vscode'
import { INCLUDE_COMMENT_IDE, getMatchedPositions } from './integration'
import { log } from './log'
import { getPrettiedMarkdown, isCssId, throttle } from './utils'
import { getColorsMap, getPrettiedMarkdown, isCssId, throttle } from './utils'
import type { ContextLoader } from './contextLoader'

export async function registerAnnotations(
Expand All @@ -13,11 +13,16 @@ export async function registerAnnotations(
ext: ExtensionContext,
) {
let underline: boolean = workspace.getConfiguration().get('unocss.underline') ?? true
let colorPreview: boolean = workspace.getConfiguration().get('unocss.colorPreview') ?? true
ext.subscriptions.push(workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration('unocss.underline')) {
underline = workspace.getConfiguration().get('unocss.underline') ?? true
updateAnnotation()
}
if (event.affectsConfiguration('unocss.colorPreview')) {
colorPreview = workspace.getConfiguration().get('unocss.colorPreview') ?? true
updateAnnotation()
}
}))

workspace.onDidSaveTextDocument(async (doc) => {
Expand Down Expand Up @@ -47,6 +52,26 @@ export async function registerAnnotations(
rangeBehavior: DecorationRangeBehavior.ClosedClosed,
})

const colorDecoration = window.createTextEditorDecorationType({
before: {
width: '0.9em',
height: '0.9em',
contentText: ' ',
border: '1px solid',
margin: 'auto 0.2em auto 0;vertical-align: middle;border-radius:50%;',
},
dark: {
before: {
borderColor: '#eeeeee50',
},
},
light: {
before: {
borderColor: '#00000050',
},
},
})

async function updateAnnotation(editor = window.activeTextEditor) {
try {
const doc = editor?.document
Expand All @@ -67,10 +92,23 @@ export async function registerAnnotations(

const result = await ctx.uno.generate(code, { id, preflights: false, minify: true })

const colorsMap = getColorsMap(ctx.uno, result)
const colorRanges: DecorationOptions[] = []
const _colorPositionsCache = new Map<string, string>() // cache for avoid duplicated color ranges

const ranges: DecorationOptions[] = (
await Promise.all(
getMatchedPositions(code, Array.from(result.matched))
.map(async (i): Promise<DecorationOptions> => {
// side-effect: update colorRanges
if (colorPreview && colorsMap.has(i[2]) && !_colorPositionsCache.has(`${i[0]}:${i[1]}`)) {
_colorPositionsCache.set(`${i[0]}:${i[1]}`, i[2])
colorRanges.push({
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
renderOptions: { before: { backgroundColor: colorsMap.get(i[2]) } },
})
}

try {
const md = await getPrettiedMarkdown(ctx!.uno, i[2])
return {
Expand All @@ -89,6 +127,9 @@ export async function registerAnnotations(
)
).filter(Boolean)

_colorPositionsCache.clear()
editor.setDecorations(colorDecoration, colorRanges)

if (underline) {
editor.setDecorations(NoneDecoration, [])
editor.setDecorations(UnderlineDecoration, ranges)
Expand All @@ -105,6 +146,7 @@ export async function registerAnnotations(
function reset() {
editor?.setDecorations(UnderlineDecoration, [])
editor?.setDecorations(NoneDecoration, [])
editor?.setDecorations(colorDecoration, [])
status.hide()
}
}
Expand Down
44 changes: 43 additions & 1 deletion packages/vscode/src/utils.ts
@@ -1,8 +1,11 @@
import path from 'path'
import type { UnoGenerator } from '@unocss/core'
import type { GenerateResult, UnoGenerator } from '@unocss/core'
import { cssIdRE } from '@unocss/core'
import prettier from 'prettier/standalone'
import parserCSS from 'prettier/parser-postcss'
import type { Theme } from '@unocss/preset-mini'
import { parseColor } from '@unocss/preset-mini'
import { colorToString } from '@unocss/preset-mini/utils'

export function throttle<T extends ((...args: any) => any)>(func: T, timeFrame: number): T {
let lastTime = 0
Expand Down Expand Up @@ -37,6 +40,45 @@ export async function getPrettiedMarkdown(uno: UnoGenerator, util: string) {
return `\`\`\`css\n${(await getPrettiedCSS(uno, util)).prettified}\n\`\`\``
}

const matchedAttributifyRE = /(?<=^\[.+~?=").*(?="\]$)/
const _colorsMapCache = new Map<string, string>()
export function getColorsMap(uno: UnoGenerator, result: GenerateResult) {
const theme = uno.config.theme as Theme
const themeColorNames = Object.keys(theme.colors ?? {})
const colorNames = themeColorNames.concat(themeColorNames.map(colorName => colorName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()))
const colorsMap = new Map<string, string>()

for (const i of result.matched) {
const _i = i.replace('~="', '="')
if (_colorsMapCache.get(_i)) {
colorsMap.set(_i, _colorsMapCache.get(_i)!)
continue
}

const matchedAttr = i.match(matchedAttributifyRE)
const body = matchedAttr ? matchedAttr[0].split(':').at(-1) ?? '' : i // remove prefix e.g. `dark:` `hover:`

for (const colorName of colorNames) {
const nameIndex = body.indexOf(colorName)
if (nameIndex > -1) {
const parsedResult = parseColor(body.substring(nameIndex), theme)
if (parsedResult?.cssColor) {
const color = colorToString(parsedResult.cssColor, parsedResult.alpha)
colorsMap.set(_i, color)
_colorsMapCache.set(_i, color)
}

break
}
}
}

if (_colorsMapCache.size > 5000)
_colorsMapCache.clear()

return colorsMap
}

export function isCssId(id: string) {
return cssIdRE.test(id)
}
Expand Down

0 comments on commit bc3f293

Please sign in to comment.