diff --git a/packages/autocomplete/src/create.ts b/packages/autocomplete/src/create.ts index e826f8213b..26d9388cdb 100644 --- a/packages/autocomplete/src/create.ts +++ b/packages/autocomplete/src/create.ts @@ -7,7 +7,7 @@ import { searchUsageBoundary } from './utils' export function createAutocomplete(uno: UnoGenerator) { const templateCache = new Map() - const cache = new LRU({ max: 1000 }) + const cache = new LRU({ max: 5000 }) let staticUtils: string[] = [] const templates: (AutoCompleteTemplate | AutoCompleteFunction)[] = [] @@ -64,11 +64,11 @@ export function createAutocomplete(uno: UnoGenerator) { // match and ignore existing variants const [, processed, , variants] = uno.matchVariants(input) - const idx = processed ? input.search(escapeRegExp(processed)) : input.length + let idx = processed ? input.search(escapeRegExp(processed)) : input.length // This input contains variants that modifies the processed part, // autocomplete will need to reverse it which is not possible if (idx === -1) - return [] + idx = 0 const variantPrefix = input.slice(0, idx) const variantSuffix = input.slice(idx + input.length) @@ -76,6 +76,7 @@ export function createAutocomplete(uno: UnoGenerator) { await Promise.all([ suggestSelf(processed), suggestStatic(processed), + suggestUnoCache(processed), ...suggestFromPreset(processed), ...suggestVariant(processed, variants), ]), @@ -132,6 +133,12 @@ export function createAutocomplete(uno: UnoGenerator) { return staticUtils.filter(i => i.startsWith(input)) } + async function suggestUnoCache(input: string) { + // @ts-expect-error private + const keys = Array.from(uno._cache.entries()) + return keys.filter(i => i[1] && i[0].startsWith(input)).map(i => i[0]) + } + function suggestFromPreset(input: string) { return templates.map(fn => typeof fn === 'function' @@ -154,7 +161,10 @@ export function createAutocomplete(uno: UnoGenerator) { function reset() { templateCache.clear() cache.clear() - staticUtils = Object.keys(uno.config.rulesStaticMap) + staticUtils = [ + ...Object.keys(uno.config.rulesStaticMap), + ...uno.config.shortcuts.filter(i => typeof i[0] === 'string').map(i => i[0] as string), + ] templates.length = 0 templates.push( ...uno.config.autocomplete.templates || [], diff --git a/test/__snapshots__/autocomplete.test.ts.snap b/test/__snapshots__/autocomplete.test.ts.snap new file mode 100644 index 0000000000..0783eab3d2 --- /dev/null +++ b/test/__snapshots__/autocomplete.test.ts.snap @@ -0,0 +1,84 @@ +// Vitest Snapshot v1 + +exports[`autocomplete > should accept variants 1`] = ` +[ + "dark:md:m-0", + "dark:md:m-1", + "dark:md:m-2", + "dark:md:m-3", + "dark:md:m-4", + "dark:md:m-5", + "dark:md:m-6", + "dark:md:m-8", + "dark:md:m-10", + "dark:md:m-12", + "dark:md:m-24", + "dark:md:m-36", + "dark:md:m-xy", +] +`; + +exports[`autocomplete > should provide autocomplete 1`] = ` +{ + "align-": "align-base align-baseline align-bottom align-btm align-mid align-middle align-sub align-super align-text-bottom align-text-top", + "bg-": "bg-amber bg-auto bg-black bg-blend-color bg-blend-color-burn bg-blend-color-dodge bg-blend-darken bg-blend-difference bg-blend-exclusion bg-blend-hard-light", + "bg-gradient-": "bg-gradient-conic bg-gradient-from bg-gradient-linear bg-gradient-radial bg-gradient-repeating bg-gradient-shape bg-gradient-to bg-gradient-via", + "bg-r": "bg-red bg-repeat bg-repeat-round bg-repeat-space bg-repeat-x bg-repeat-y bg-rose", + "border": "border border-collapse border-separate border-spacing border-style", + "border-": "border-0 border-1 border-2 border-3 border-4 border-5 border-6 border-8 border-10 border-12", + "border-r": "border-r border-r-style border-rd border-red border-revert border-ridge border-rose border-rounded", + "border-spacing-": "border-spacing-2xl border-spacing-3xl border-spacing-4xl border-spacing-5xl border-spacing-6xl border-spacing-7xl border-spacing-8xl border-spacing-9xl border-spacing-lg border-spacing-none", + "columns-": "columns-0 columns-1 columns-2 columns-3 columns-4 columns-5 columns-6 columns-8 columns-10 columns-12", + "divide-": "divide-amber divide-black divide-block divide-block-reverse divide-blue divide-bluegray divide-blueGray divide-coolgray divide-coolGray divide-current", + "fill-": "fill-amber fill-black fill-blue fill-bluegray fill-blueGray fill-coolgray fill-coolGray fill-current fill-cyan fill-dark", + "filter-": "filter-drop filter-drop-shadow filter-drop-shadow-color filter-grayscale filter-invert filter-none filter-saturate filter-sepia", + "fle": "flex flex-1 flex-auto flex-col flex-col-reverse flex-initial flex-inline flex-none flex-nowrap flex-row", + "font-": "font-100 font-200 font-300 font-400 font-500 font-600 font-700 font-800 font-900 font-black", + "keyframes-": "keyframes-back-in-down keyframes-back-in-left keyframes-back-in-right keyframes-back-in-up keyframes-back-out-down keyframes-back-out-left keyframes-back-out-right keyframes-back-out-up keyframes-bounce keyframes-bounce-alt", + "leading-": "leading-loose leading-none leading-normal leading-relaxed leading-snug leading-tight", + "line-clamp-": "line-clamp-0 line-clamp-1 line-clamp-2 line-clamp-3 line-clamp-4 line-clamp-5 line-clamp-6 line-clamp-8 line-clamp-10 line-clamp-12", + "m-": "m-0 m-1 m-2 m-3 m-4 m-5 m-6 m-8 m-10 m-12", + "max-w-": "max-w-2xl max-w-3xl max-w-4xl max-w-5xl max-w-6xl max-w-7xl max-w-auto max-w-lg max-w-md max-w-none", + "mx-": "mx-0 mx-1 mx-2 mx-3 mx-4 mx-5 mx-6 mx-8 mx-10 mx-12", + "object-": "object-b object-bc object-bl object-bottom object-bottom-center object-bottom-left object-bottom-right object-br object-c object-cb", + "origin-": "origin-b origin-bc origin-bl origin-bottom origin-bottom-center origin-bottom-left origin-bottom-right origin-br origin-c origin-cb", + "outline-": "outline-amber outline-auto outline-black outline-blue outline-bluegray outline-blueGray outline-coolgray outline-coolGray outline-current outline-cyan", + "outline-offset-": "outline-offset-0 outline-offset-1 outline-offset-2 outline-offset-3 outline-offset-4 outline-offset-5 outline-offset-6 outline-offset-8 outline-offset-10 outline-offset-12", + "placeholder-": "placeholder-.dark: placeholder-.light: placeholder-@dark: placeholder-@light: placeholder-active: placeholder-after: placeholder-animate-delay placeholder-animate-direction placeholder-animate-duration placeholder-animate-none", + "scroll-": "scroll-auto scroll-block scroll-inline scroll-m scroll-ma scroll-p scroll-pa scroll-smooth", + "scroll-m-": "scroll-m-2xl scroll-m-3xl scroll-m-4xl scroll-m-5xl scroll-m-6xl scroll-m-7xl scroll-m-8xl scroll-m-9xl scroll-m-b scroll-m-be", + "shadow-": "shadow-2xl shadow-amber shadow-black shadow-blue shadow-bluegray shadow-blueGray shadow-coolgray shadow-coolGray shadow-current shadow-cyan", + "space-": "space-block space-block-reverse space-inline space-inline-reverse space-x space-x-reverse space-y space-y-reverse", + "text-r": "text-red text-revert text-right text-rose", + "text-red-": "text-red-1 text-red-2 text-red-3 text-red-4 text-red-5 text-red-6 text-red-7 text-red-8 text-red-9 text-red-50", + "touch-": "touch-auto touch-manipulation touch-none touch-pan touch-pinch touch-pinch-zoom", + "transition-": "transition-all transition-colors transition-none transition-opacity transition-shadow transition-transform", + "v-": "v-base v-baseline v-bottom v-btm v-mid v-middle v-sub v-super v-text-bottom v-text-top", + "w-": "w-2xl w-3xl w-4xl w-5xl w-6xl w-7xl w-auto w-lg w-md w-none", + "z-": "z-0 z-1 z-2 z-3 z-4 z-5 z-6 z-8 z-10 z-12", +} +`; + +exports[`autocomplete > should provide skip DEFAULT 1`] = ` +[ + "text-red-1", + "text-red-2", + "text-red-3", + "text-red-4", + "text-red-5", + "text-red-6", + "text-red-7", + "text-red-8", + "text-red-9", + "text-red-50", + "text-red-100", + "text-red-200", + "text-red-300", + "text-red-400", + "text-red-500", + "text-red-600", + "text-red-700", + "text-red-800", + "text-red-900", +] +`; diff --git a/test/autocomplete.test.ts b/test/autocomplete.test.ts index 9059f74650..f58e6ba4fd 100644 --- a/test/autocomplete.test.ts +++ b/test/autocomplete.test.ts @@ -4,29 +4,35 @@ import { describe, expect, it } from 'vitest' import { createAutocomplete, parseAutocomplete } from '@unocss/autocomplete' import presetAttributify from '@unocss/preset-attributify' -const uno = createGenerator({ - presets: [ - presetAttributify(), - presetUno(), - ], -}) +describe('autocomplete', () => { + const uno = createGenerator({ + presets: [ + presetAttributify(), + presetUno(), + ], + shortcuts: [ + { + 'foo': 'text-red', + 'foo-bar': 'text-red', + }, + ], + }) -const ac = createAutocomplete(uno) + const ac = createAutocomplete(uno) -async function enumerateSuggestions(inputs: string[]) { - return Object.fromEntries(await Promise.all(inputs.map(async input => [ - input, - (await ac.suggest(input)).slice(0, 10).join(' '), - ]))) -} + async function enumerateSuggestions(inputs: string[]) { + return Object.fromEntries(await Promise.all(inputs.map(async input => [ + input, + (await ac.suggest(input)).slice(0, 10).join(' '), + ]))) + } -const fixture = ` + const fixture = `
{ expect((await ac.suggest('text-red-'))) - .toMatchInlineSnapshot(` - [ - "text-red-1", - "text-red-2", - "text-red-3", - "text-red-4", - "text-red-5", - "text-red-6", - "text-red-7", - "text-red-8", - "text-red-9", - "text-red-50", - "text-red-100", - "text-red-200", - "text-red-300", - "text-red-400", - "text-red-500", - "text-red-600", - "text-red-700", - "text-red-800", - "text-red-900", - ] - `) + .toMatchSnapshot() }) it('should provide variants', async () => { @@ -170,23 +115,7 @@ describe('autocomplete', () => { it('should accept variants', async () => { expect(await ac.suggest('dark:md:m-')) - .toMatchInlineSnapshot(` - [ - "dark:md:m-0", - "dark:md:m-1", - "dark:md:m-2", - "dark:md:m-3", - "dark:md:m-4", - "dark:md:m-5", - "dark:md:m-6", - "dark:md:m-8", - "dark:md:m-10", - "dark:md:m-12", - "dark:md:m-24", - "dark:md:m-36", - "dark:md:m-xy", - ] - `) + .toMatchSnapshot() }) it('should skip single-pass variants', async () => { @@ -223,4 +152,54 @@ describe('autocomplete', () => { expect((await ac.suggest('m-['))[0]) .toMatchInlineSnapshot('undefined') }) + + it('should suggest static shortcuts', async () => { + expect(await ac.suggest('foo')) + .toMatchInlineSnapshot(` + [ + "foo", + "foo-bar", + ] + `) + }) +}) + +describe('use uno cache', () => { + const uno = createGenerator({ + presets: [ + presetUno(), + ], + shortcuts: [ + { + 'foo': 'text-red', + 'foo-bar': 'text-red', + }, + [/^btn-(red|green)$/, m => `text-${m[1]}`], + ], + }) + + const ac = createAutocomplete(uno) + + it('use cache', async () => { + expect(await ac.suggest('btn')) + .toMatchInlineSnapshot('[]') + + await uno.generate('btn-red btn-green m-100') + ac.reset() + + expect(await ac.suggest('btn')) + .toMatchInlineSnapshot(` + [ + "btn-green", + "btn-red", + ] + `) + expect(await ac.suggest('m-1')) + .toMatchInlineSnapshot(` + [ + "m-1", + "m-100", + ] + `) + }) })