ADR 0112: Режимы строки палитры (f: / t: / m: / x: / c:) — модель режимов, стратегии и бэкенды workspace-поиска¶
Статус: Accepted · Implemented
Дата: 2026-05-11 · обновлено 2026-05-12
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0013 | палитра, keyboard-first |
| 0030 | IdeCommands, каталог палитры |
| 0060 | Command Melody c: и CascadeChord — ортогонально префиксам в строке палитры |
| 0070 | палитра как overlay |
| 0079 | IDS, подсказки оверлея |
| 0081 | параметрический хвост после alias в c: |
| 0109 | каталог melody |
| 0105 | Hybrid Codebase Index (ядро + MCP) for C# stacks with Roslyn Truth |
| 0106 | Hybrid Codebase Index — HCI |
Вне ADR¶
| Документ | Роль |
|---|---|
| intent-melody-language-v1.md | intent melody language v1 |
Точки кода (фактический baseline): IdeCommandPaletteFilterOrchestrator, IntentMelodyAliases.TryGetTail, GoToAllQueryParser, CommandPaletteChromeProjection, IdeCommandPaletteExecutionOrchestrator. |
Снимок реализации¶
| Элемент | Значение |
|---|---|
| — | этапы 1–4: режимная модель разбора, chrome, rg/hci/auto бэкенды, TOML |
| — | стратегии по режиму — см. код CommandPaletteParsedQueryParser и ветви оркестратора |
Резюме¶
- Палитра (Ctrl+Q): режимы строки запроса (
t:/m:/x:), стратегии бэкенда. - Контракт workspace-поиска и переключение в
settings.toml. - Direct overlay surface — 0070.
Контекст¶
Хоткей палитры (по умолчанию Ctrl+Q) открывает одну строку запроса и общий список результатов, но семантика ввода не однородна:
| Префикс | Имя (рабочее) | Смысл запроса | Где задаётся сейчас |
|---|---|---|---|
| (нет) | каталог команд | fuzzy по названию/command_id |
дефолтная ветка оркестратора |
f: |
go-to файл | фильтрация файлов решения | GoToAllQueryParser + RefreshGoToPaletteFilter |
t: |
go-to тип | поиск типов по .cs |
то же |
m: |
go-to член | эвристика по членам .cs |
то же |
x: |
текст | ripgrep по workspace | то же |
c: |
Command Melody | alias → command_id, параметрика :start:end |
IntentMelodyAliases.TryGetTail + RefreshMelodyPaletteFilter |
Подсказки в футере/плейсхолдере дублируют этот набор текстом (CommandPaletteChromeProjection), а разветвление по режимам сосредоточено в цепочке if в начале RefreshCommandPaletteFilter: сначала c:, потом f|t|m|x, иначе каталог.
Проблема: режим — неименованная концепция в коде; порядок проверок, отмена async go-to (goToHandle.Cancel()), типы результатов строк и особенности UI (IdeCommandPaletteRowViewModel: melody vs каталог vs go-to) размазаны по методам без единого контрактa «режим палитры».
Риски при росте: новый префикс или изменение приоритета (например, конфликт с будущим w:) — правки в нескольких местах; тестируемость режимов не структурирована.
Решение¶
Ввести два согласованных слоя:
- Режим строки (что ввёл пользователь) —
CommandPaletteQueryMode/CommandPaletteParsedQuery(§1–2). - Бэкенд workspace-поиска для подрежимов
t:/m:/x:— отдельный контракт с подключаемыми реализациями и настройкой переключения (§7). Это не режим UI: один и тот же префикс может обслуживаться разными движками без смены синтаксиса палитры.
Обработку режимов оформить как стратегию на режим (паттерн Strategy / небольшой реестр — детали реализации не фиксируем жёстко). Стратегия go-to для t:/m:/x: делегирует поиск выбранному бэкенду (или цепочке fallback), а не вызывает rg напрямую из оркестратора.
1. Модель¶
CommandPaletteParsedQuery(или аналог): дискриминированное объединение:- Melody — нормализованный хвост после
c:(как сейчасTryGetTail); - GoTo —
GoToAllQuery(префиксf|t|m|x+ терм); - Catalog — строка без зарезервированного режимного префикса (трим + fuzzy по каталогу).
Явно задокументировать приоритет разбора (совместимость с текущим поведением):
c:— первым (чтобыc:не пересекался с go-to и каталогом);- затем
f:/t:/m:/x:; - иначе каталог.
2. Стратегия на режим¶
Для каждого режима — один вход: контекст фильтрации (решение, roots, путь файла, текст редактора, горячие клавиши, семья UI mode и т.д.) + действие:
- очистить/заполнить
filteredEntries; - выставить выбранный индекс;
- вызвать
refreshCommandPaletteSurfaceSnapshot; - для go-to — управление отменой фонового поиска (
CommandPaletteGoToAsyncHandle), как сейчас.
Цель рефакторинга: RefreshCommandPaletteFilter становится диспетчером: parse → lookup strategy → execute, без дублирования правил «кто отменяет go-to».
3. Связь с исполнением (Enter)¶
Выполнение выбора уже частично унифицировано через IdeCommandPaletteRowViewModel.RowKind и IdeCommandPaletteExecutionOrchestrator (команда MCP vs go-to navigation). Режим фильтрации и RowKind должны оставаться согласованными; при внедрении стратегий не менять пользовательский контракт строк без отдельного ADR.
4. Chrome и обнаруживаемость¶
Тексты CommandPaletteChromeProjection (footer / placeholder) должны строиться из канонического списка режимов (имя префикса + короткая подпись), чтобы новый режим добавлялся в одном месте данных, а не копированием строки "f: файл · …".
5. Тестируемость¶
- Юнит-тесты на разбор сырой строки → режим + полезная нагрузка (граничные случаи:
c:без хвоста,x:только пробелы, конфликт не ожидается в v1). - По возможности — тесты на порядок стратегий (регресс:
c:somethingне уходит в каталог как plain text без отдельного решения продукта).
6. Бэкенд go-to: ripgrep vs HCI (Hybrid Codebase Index)¶
Идея использовать SearchHybridAsync / индекс вместо RipgrepWorkspaceSearchService для t: / m: / x: не автоматически «быстрее и эффективнее» для всех режимов — зависит от цели режима и свежести индекса.
| Префикс | Сейчас (baseline) | HCI как кандидат | Нюансы |
|---|---|---|---|
f: |
дерево решения, фильтрация путей в памяти | обычно не нужен | Узкое место — не поиск по диску; индекс почти не даёт выигрыша. |
x: |
литерал через ripgrep по workspace | часто уместен: FTS по проиндексированным чанкам | Плюс: нет процесса rg, предсказуемая задержка при прогретом индексе. Минус: до reindex — рассинхрон с диском; семантика FTS (токены, AND) ≠ «как rg»; для дословного «как текстовый редактор» палитре может понадаться fallback на rg. |
t: / m: |
regex по .cs (GoToPaletteRipgrepPatternBuilder: объявление типа / идентификатор() |
спорно без доработки модели попаданий | HCI индексирует текстовые фрагменты, не обязательно с тем же качеством, что объявление Roslyn/VS «Go to Type/Member». Возможны лишние или пропущенные строки по сравнению с текущей эвристикой regex. Целевая точность для «тип/член» ближе к Roslyn (отдельный трек), чем к «просто FTS». |
| (опционально) | — | semantic в HCI |
Полезно для похожего по смыслу, а не для контрактов «строго по имени символа» — не подменять t:/m: без явного режима продукта. |
Детали контракта и переключения — в §7.
7. Первоклассный бэкенд workspace-поиска (t: / m: / x:)¶
Граница: понятие «бэкенд» относится к асинхронному поиску по содержимому файлов в корне workspace для префиксов t:, m:, x:. Режим f: (фильтрация путей по дереву решения) в этот контракт не входит — отдельная синхронная стратегия без смены движка «как у rg».
Контракт (working name): абстракция уровня ICommandPaletteGoToSearchBackend (или эквивалент) с единым входом и выходом:
- Вход: нормализованный
GoToAllQuery, корень workspace, лимиты (MaxRipgrepMatches/ аналог), scope индекса (согласованный с HCI scope),CancellationToken. - Выход: упорядоченный список канонических попаданий для строк палитры (путь, строка, колонка, короткая подпись/категория — совместимо с проекцией в
IdeCommandPaletteRowViewModel/ существующимиCommandPaletteGoTo*NavRowsProjection).
Реализации (минимальный набор):
| Реализация | Роль |
|---|---|
| Ripgrep | Поведение как сейчас: GoToPaletteRipgrepPatternBuilder + RipgrepWorkspaceSearchService. |
| HCI-FTS | Запрос к Hybrid Index (без обязательного semantic), маппинг hit → тот же DTO попадания. |
| Composite / Auto | Цепочка: например HCI → при пустом результате, ошибке или «индекс не готов» — fallback на Ripgrep. Политика задаётся явно, а не неявно в коде оркестратора. |
Конфигурация переключения — только в пользовательском settings.toml (%LocalAppData%\CascadeIDE\, см. 0028): то же дерево, что CascadeIdeSettings / SettingsService.Load, не репозиторный .cascade/workspace.toml (UiWorkspaceToml — другая модель: маршрутизация зон, пресеты навигации и т.д.). Выбор «как искать из палитры» — предпочтение рабочего места, а не артефакта конкретного репозитория (командную политику «только rg» при необходимости можно добавить позже отдельным механизмом overlay).
Куда положить в схеме (согласовано с имеющимися ключами):
| Вариант | Вердикт |
|---|---|
[hybrid_index] |
Не использовать под backend: при значении rg настройка не про индекс; смешивает включение/параметры HCI и независимый выбор движка палитры. Связь auto ↔ HCI — только в коде фасада. |
[workspace] |
Не использовать: в коде это WorkspaceSettings — панели, Flight, splitters (docs/samples/settings.toml); к поисковому бэкенду не относится. |
[command_palette…] |
Да: новое верхнеуровневое свойство на CascadeIdeSettings (рядом с HybridIndex, Workspace, …), вложенная таблица в TOML — как у [display.screens.grammar] / markdown: snake_case ключи (CascadeIdeSettingsTomlDeserializeTests). |
Маппинг при реализации (ориентир): CascadeIdeSettings.CommandPalette.GoToSearch.Backend → таблица [command_palette.go_to_search], поле backend.
Канонические значения бэкенда (короткие строковые литералы для TOML):
| Значение | Смысл |
|---|---|
rg |
Только ripgrep (текущее baseline-поведение; рекомендуемый дефолт до стабилизации HCI-пути). |
hci |
Только Hybrid Codebase Index (FTS-хит → попадания палитры). |
auto |
Сначала HCI; при неготовом индексе, ошибке или пустом ответе уместного рода — fallback на rg. Политика fallback — в коде фасада Composite, не размазана по UI. |
Пример (в том же пользовательском файле, что и [hybrid_index]):
[command_palette.go_to_search]
backend = "rg" # "rg" | "hci" | "auto"
- Пер-префиксные оверрайды (
x:≠t:) — опционально вторая итерация (напр. отдельный ключ или подтаблица), если без них проще выпустить v1.
Поддержка и тестирование:
- Новый бэкенд добавляется одной реализацией интерфейса + регистрация в DI / фабрике выбора; оркестратор не ветвится по
if (useHci). - Юнит-тесты оркестратора на фейковом бэкенде; отдельные тесты на каждую реализацию (маппинг hit ↔ строка палитры).
UX (опционально): короткая метка источника в subtitle («rg» / «index») или только в диагностике — решение продукта; по умолчанию пользователю не обязательно видеть имя бэкенда.
Отклонённые / отложенные альтернативы¶
- Плагинируемые префиксы из TOML в v1 — дороже протокола и валидации; оставить на будущее, когда появится второй источник префиксов кроме кода.
- Объединить
c:и go-to в одну грамматику — ломает ментальную модель IML и VS-style go-to; не делаем. - Единый regex-парсер всей строки — возможно как имплементация-деталь; архитектурно важен не синтаксис, а тип режима и стратегия.
Последствия¶
- Положительные: один явный вход для документации режимов; проще добавлять префикс или менять побочные эффекты (отмена go‑to); меньше расхождения между подсказками chrome и кодом; смена движка поиска (
rg↔ HCI ↔ цепочка) без правок разветвления вIdeCommandPaletteFilterOrchestrator. - Отрицательные: объём рефакторинга — orchestrator, DI, настройки, тесты; поведение для пользователя в v1 после выноса интерфейса должно оставаться паритетным с Ripgrep-backend по умолчанию (смена только через настройку — осознанно).
Статус реализации¶
Реализовано в приложении и тестах: CommandPaletteParsedQuery / CommandPaletteParsedQueryParser, ICommandPaletteGoToSearchBackend (+ rg / HCI / auto), [command_palette.go_to_search] в settings, канон подсказок CommandPaletteChromeModeHints.
Дальнейшие улучшения (например плагинируемые префиксы или расширение семантики x:) — по этому ADR как открытым направлениям, не блокеры текущего объёма.