Поиск
Нечёткий поиск
Встроенная функция fuzzySearch экспортируется для автономного использования:
import { fuzzySearch, highlightMatches } from '@macrulez/vue-command-palette'
const results = fuzzySearch('git cm', commands)
// отсортировано по счёту: точное → префикс → подстрока → нечёткое
// Рендер подсвеченного label в кастомном слоте
const vnode = highlightMatches(command.label, result.matches)
// возвращает VNode: <span>git <mark class="vcp-match">c</mark>o<mark class="vcp-match">m</mark>mit</span>
// Диапазоны подсветки для результатов из внешнего источника
// (асинхронные группы, страницы, режимы — они уже подсвечены внутренне):
import { getMatchRanges } from '@macrulez/vue-command-palette'
const ranges = getMatchRanges('al', 'Alan Turing') // → [[0, 1]]Таблица подсчёта очков
| Тип совпадения | Счёт |
|---|---|
| Точное совпадение | 100 |
| Совпадение по префиксу | 80 |
| Подстрока (содержит) | 60 |
| Нечёткое (все символы по порядку) | 1–40 (штраф за пропуски символов) |
| Нет совпадения | −1 (исключается из результатов) |
Движок проверяет label, все keywords[] и все aliases[]. Побеждает наивысший счёт среди всех полей. Команды, у которых disabled: true или enabled() возвращает false, исключаются до подсчёта очков.
Нормализация диакритики
Строки нормализуются через Unicode-разложение NFD перед сравнением, поэтому диакритические знаки игнорируются:
fuzzySearch('cafe', [{ id: '1', label: 'Café', perform: () => {} }]) // → совпадение
fuzzySearch('muller', [{ id: '2', label: 'Müller', perform: () => {} }]) // → совпадениеУбираются только диакритические знаки (
é→e,ü→u). Буквы без канонического разложения — напримерß— остаются как есть, поэтомуßне совпадает сss.
Режимы / области
Определяйте активируемые префиксом области (как команды > или символы @ в VS Code). Когда запрос начинается с prefix режима, префикс отбрасывается, плейсхолдер меняется, появляется чип, а результаты приходят из onSearch режима (либо, если он не задан, из обычного нечёткого поиска по оставшемуся запросу).
<CommandPalette
:modes="[
{ prefix: '>', label: 'Run', placeholder: 'Run a command…', onSearch: searchCommands },
{ prefix: '@', label: 'People', placeholder: 'Find a person…', onSearch: searchPeople },
]"
/>type PaletteMode = {
prefix: string
placeholder?: string
label?: string
onSearch?: (query: string) => Command[] | Promise<Command[]>
}Backspace над префиксом выходит из режима. Результаты приходят с debounce 200 мс.
Асинхронный поиск
Каждая группа может предоставить колбэк onSearch, возвращающий динамические команды для заданного запроса. Полезно для поиска по внешним API, базам данных или документации.
useRegisterGroup({
id: 'docs-search',
label: 'Documentation',
commands: [], // статичные команды (может быть пустым для групп только с поиском)
onSearch: async (query: string) => {
const results = await searchDocs(query)
return results.slice(0, 5).map((doc) => ({
id: `doc-${doc.slug}`,
label: doc.title,
description: doc.excerpt,
icon: '📄',
perform: () => window.open(doc.url, '_blank'),
}))
},
})- Debounce 200 мс, чтобы избежать избыточных запросов
- Ненавязчивый спиннер появляется в углу поля ввода, пока запрос выполняется — уже показанные результаты остаются видимыми (без затемнения). Центрированный текст загрузки появляется, только если показывать ещё нечего.
- Асинхронные результаты объединяются с синхронными и пересортировываются по счёту
- Пустой запрос немедленно очищает асинхронные результаты (без debounce)
- Опция уровня плагина
onSearchпредоставляет один глобальный асинхронный источник (не привязанный к группе), объединяется тем же образом:tsapp.use(VCommandPalettePlugin, { onSearch: (q) => api.search(q) })
Кастомная стратегия поиска
Замените встроенный нечёткий движок любым скорером — например, Fuse.js. Функция получает запрос и все доступные команды и возвращает ранжированный SearchResult[] (наивысший счёт первым). Хранилище всё равно проставляет groupId каждому результату после этого.
import Fuse from 'fuse.js'
import type { SearchFn } from '@macrulez/vue-command-palette'
const fuseSearch: SearchFn = (query, commands) => {
const fuse = new Fuse(commands, {
keys: ['label', 'description', 'keywords'],
includeScore: true,
})
return fuse.search(query).map((r) => ({
command: r.item,
score: 1 - (r.score ?? 0),
matches: [],
}))
}
app.use(VCommandPalettePlugin, { search: fuseSearch })