Skip to content

Commit

Permalink
feat(vscode): introduce strictAnnotationMatch and turn on by default,
Browse files Browse the repository at this point in the history
close #3278
  • Loading branch information
antfu committed Dec 28, 2023
1 parent 532cc8a commit 8cbe6af
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 35 deletions.
65 changes: 58 additions & 7 deletions packages/shared-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,48 @@ export function getPlainClassMatchedPositionsForPug(codeSplit: string, matchedPl
return result
}

export interface GetMatchedPositionsOptions {
isPug?: boolean
/**
* Regex to only limit the matched positions for certain code
*/
includeRegex?: RegExp[]
/**
* Regex to exclude the matched positions for certain code, excludeRegex has higher priority than includeRegex
*/
excludeRegex?: RegExp[]
}

export function getMatchedPositions(
code: string,
matched: string[],
extraAnnotations: HighlightAnnotation[] = [],
isPug = false,
options: GetMatchedPositionsOptions = {},
) {
const result: (readonly [start: number, end: number, text: string])[] = []
const attributify: RegExpMatchArray[] = []
const plain = new Set<string>()

const includeRanges: [number, number][] = []
const excludeRanges: [number, number][] = []

if (options.includeRegex) {
for (const regex of options.includeRegex) {
for (const match of code.matchAll(regex))
includeRanges.push([match.index!, match.index! + match[0].length])
}
}
else {
includeRanges.push([0, code.length])
}

if (options.excludeRegex) {
for (const regex of options.excludeRegex) {
for (const match of code.matchAll(regex))
excludeRanges.push([match.index!, match.index! + match[0].length])
}
}

Array.from(matched)
.forEach((v) => {
const match = isAttributifySelector(v)
Expand All @@ -106,7 +138,9 @@ export function getMatchedPositions(
highlightLessGreaterThanSign(match[1])
plain.add(match[1])
}
else { attributify.push(match) }
else {
attributify.push(match)
}
})

// highlight classes that includes `><`
Expand All @@ -124,7 +158,7 @@ export function getMatchedPositions(
let start = 0
code.split(splitWithVariantGroupRE).forEach((i) => {
const end = start + i.length
if (isPug) {
if (options.isPug) {
result.push(...getPlainClassMatchedPositionsForPug(i, plain, start))
}
else {
Expand Down Expand Up @@ -172,9 +206,18 @@ export function getMatchedPositions(
})
})

result.push(...extraAnnotations.map(i => [i.offset, i.offset + i.length, i.className] as const))
result
.push(...extraAnnotations.map(i => [i.offset, i.offset + i.length, i.className] as const))

return result.sort((a, b) => a[0] - b[0])
return result
.filter(([start, end]) => {
if (excludeRanges.some(([s, e]) => start >= s && end <= e))
return false
if (includeRanges.some(([s, e]) => start >= s && end <= e))
return true
return false
})
.sort((a, b) => a[0] - b[0])
}

// remove @unocss/transformer-directives transformer to get matched result from source code
Expand All @@ -183,7 +226,12 @@ const ignoreTransformers = [
'@unocss/transformer-compile-class',
]

export async function getMatchedPositionsFromCode(uno: UnoGenerator, code: string, id = '') {
export async function getMatchedPositionsFromCode(
uno: UnoGenerator,
code: string,
id = '',
options: GetMatchedPositionsOptions = {},
) {
const s = new MagicString(code)
const tokens = new Set()
const ctx = { uno, tokens } as any
Expand All @@ -201,5 +249,8 @@ export async function getMatchedPositionsFromCode(uno: UnoGenerator, code: strin

const { pug, code: pugCode } = await isPug(uno, s.toString(), id)
const result = await uno.generate(pug ? pugCode : s.toString(), { preflights: false })
return getMatchedPositions(code, [...result.matched], annotations, pug)
return getMatchedPositions(code, [...result.matched], annotations, {
isPug: pug,
...options,
})
}
18 changes: 18 additions & 0 deletions packages/shared-integration/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,21 @@ export const defaultPipelineInclude = [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|
export const defaultFilesystemGlobs = [
'**/*.{html,js,ts,jsx,tsx,vue,svelte,astro,elm,php,phtml,mdx,md}',
]

/**
* Default match includes in getMatchedPositions for IDE
*/
export const defaultIdeMatchInclude: RegExp[] = [
// String literals
/(['"`])[^\1]*?\1/g,
// HTML tags
/<[^>]+?>/g,
// CSS directives
/(@apply|--uno|--at-apply)[^;]*?;/g,
]

/**
* Default match includes in getMatchedPositions for IDE
*/
export const defaultIdeMatchExclude: RegExp[] = [
]
64 changes: 36 additions & 28 deletions packages/vscode/src/annotation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path'
import type { DecorationOptions, Disposable, ExtensionContext, StatusBarItem, TextEditor } from 'vscode'
import { DecorationRangeBehavior, MarkdownString, Range, window, workspace } from 'vscode'
import { INCLUDE_COMMENT_IDE, getMatchedPositionsFromCode, isCssId } from './integration'
import { INCLUDE_COMMENT_IDE, defaultIdeMatchExclude, defaultIdeMatchInclude, getMatchedPositionsFromCode, isCssId } from './integration'
import { log } from './log'
import { getColorString, getPrettiedMarkdown, throttle } from './utils'
import type { ContextLoader } from './contextLoader'
Expand All @@ -14,7 +14,8 @@ export async function registerAnnotations(
) {
const { configuration, watchChanged, disposable } = useConfigurations(ext)
const disposals: Disposable[] = []
watchChanged(['underline', 'colorPreview', 'remToPxPreview', 'remToPxRatio'], () => {

watchChanged(['underline', 'colorPreview', 'remToPxPreview', 'remToPxRatio', 'strictAnnotationMatch'], () => {
updateAnnotation()
})

Expand Down Expand Up @@ -103,35 +104,42 @@ export async function registerAnnotations(
? configuration.remToPxRatio
: -1

const options = configuration.strictAnnotationMatch
? {
includeRegex: defaultIdeMatchInclude,
excludeRegex: defaultIdeMatchExclude,
}
: undefined

const positions = await getMatchedPositionsFromCode(ctx.uno, code, id, options)

const ranges: DecorationOptions[] = (
await Promise.all(
(await getMatchedPositionsFromCode(ctx.uno, code))
.map(async (i): Promise<DecorationOptions> => {
try {
const md = await getPrettiedMarkdown(ctx!.uno, i[2], remToPxRatio)

if (configuration.colorPreview) {
const color = getColorString(md)
if (color && !colorRanges.find(r => r.range.start.isEqual(doc.positionAt(i[0])))) {
colorRanges.push({
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
renderOptions: { before: { backgroundColor: color } },
})
}
}
return {
await Promise.all(positions.map(async (i): Promise<DecorationOptions> => {
try {
const md = await getPrettiedMarkdown(ctx!.uno, i[2], remToPxRatio)

if (configuration.colorPreview) {
const color = getColorString(md)
if (color && !colorRanges.find(r => r.range.start.isEqual(doc.positionAt(i[0])))) {
colorRanges.push({
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
get hoverMessage() {
return new MarkdownString(md)
},
}
}
catch (e: any) {
log.appendLine(`⚠️ Failed to parse ${i[2]}`)
log.appendLine(String(e.stack ?? e))
return undefined!
renderOptions: { before: { backgroundColor: color } },
})
}
}),
}
return {
range: new Range(doc.positionAt(i[0]), doc.positionAt(i[1])),
get hoverMessage() {
return new MarkdownString(md)
},
}
}
catch (e: any) {
log.appendLine(`⚠️ Failed to parse ${i[2]}`)
log.appendLine(String(e.stack ?? e))
return undefined!
}
}),
)
).filter(Boolean)

Expand Down
1 change: 1 addition & 0 deletions packages/vscode/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export function useConfigurations(ext: ExtensionContext) {
remToPxRatio: 16,
underline: true,
selectionStyle: true,
strictAnnotationMatch: true,
},
alias: {
matchType: 'autocomplete.matchType',
Expand Down
110 changes: 110 additions & 0 deletions test/pos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getMatchedPositionsFromCode as match } from '@unocss/shared-common'
import transformerVariantGroup from '@unocss/transformer-variant-group'
import cssDirectives from '@unocss/transformer-directives'
import extractorPug from '@unocss/extractor-pug'
import { defaultIdeMatchExclude, defaultIdeMatchInclude } from '@unocss/shared-integration'

describe('matched-positions', async () => {
it('attributify', async () => {
Expand Down Expand Up @@ -248,6 +249,115 @@ describe('matched-positions', async () => {
]
`)
})

it('with include and exclude', async () => {
const uno = createGenerator({
presets: [
presetUno(),
],
})

const code = `
<script setup>
let transition = 'ease-in-out duration-300'
</script>
<template>
<div class="h-1 text-red" />
</template>
<style>
.css {
transform: translateX(0);
@apply: text-blue;
--uno:
text-purple;
}
</style>
`

expect(await match(uno, code))
.toMatchInlineSnapshot(`
[
[
20,
30,
"transition",
],
[
34,
45,
"ease-in-out",
],
[
46,
58,
"duration-300",
],
[
96,
99,
"h-1",
],
[
100,
108,
"text-red",
],
[
144,
153,
"transform",
],
[
180,
189,
"text-blue",
],
[
204,
215,
"text-purple",
],
]
`)

expect(await match(uno, code, undefined, { includeRegex: defaultIdeMatchInclude, excludeRegex: defaultIdeMatchExclude }))
.toMatchInlineSnapshot(`
[
[
34,
45,
"ease-in-out",
],
[
46,
58,
"duration-300",
],
[
96,
99,
"h-1",
],
[
100,
108,
"text-red",
],
[
180,
189,
"text-blue",
],
[
204,
215,
"text-purple",
],
]
`)
})
})

describe('matched-positions-pug', async () => {
Expand Down

0 comments on commit 8cbe6af

Please sign in to comment.