Перейти к содержанию

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:) — правки в нескольких местах; тестируемость режимов не структурирована.


Решение

Ввести два согласованных слоя:

  1. Режим строки (что ввёл пользователь) — CommandPaletteQueryMode / CommandPaletteParsedQuery (§1–2).
  2. Бэкенд workspace-поиска для подрежимов t: / m: / x:отдельный контракт с подключаемыми реализациями и настройкой переключения (§7). Это не режим UI: один и тот же префикс может обслуживаться разными движками без смены синтаксиса палитры.

Обработку режимов оформить как стратегию на режим (паттерн Strategy / небольшой реестр — детали реализации не фиксируем жёстко). Стратегия go-to для t:/m:/x: делегирует поиск выбранному бэкенду (или цепочке fallback), а не вызывает rg напрямую из оркестратора.

1. Модель

  • CommandPaletteParsedQuery (или аналог): дискриминированное объединение:
  • Melody — нормализованный хвост после c: (как сейчас TryGetTail);
  • GoToGoToAllQuery (префикс f | t | m | x + терм);
  • Catalog — строка без зарезервированного режимного префикса (трим + fuzzy по каталогу).

Явно задокументировать приоритет разбора (совместимость с текущим поведением):

  1. c: — первым (чтобы c: не пересекался с go-to и каталогом);
  2. затем f: / t: / m: / x:;
  3. иначе каталог.

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 как открытым направлениям, не блокеры текущего объёма.