| 1 | # ADR 0112: Режимы строки палитры (`f:` / `t:` / `m:` / `x:` / `c:`) — модель режимов, стратегии и **бэкенды** workspace-поиска |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-05-11 · обновлено 2026-05-12 |
| 5 | ## Связанные ADR |
| 6 | |
| 7 | | ADR | Роль | |
| 8 | |-----|------| |
| 9 | | [0013](0013-command-surface-and-discoverability.md) | палитра, keyboard-first | |
| 10 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | `IdeCommands`, каталог палитры | |
| 11 | | [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md) | Command Melody `c:` и CascadeChord — ортогонально префиксам **в строке** палитры | |
| 12 | | [0070](0070-command-palette-direct-overlay-surface.md) | палитра как overlay | |
| 13 | | [0079](0079-ide-display-system-ids-overlay-pipeline.md) | IDS, подсказки оверлея | |
| 14 | | [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | параметрический хвост после alias в `c:` | |
| 15 | | [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | каталог melody | |
| 16 | | [0105](0105-hybrid-codebase-index-for-csharp-web.md) | Hybrid Codebase Index (ядро + MCP) for C# stacks with Roslyn Truth | |
| 17 | | [0106](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md) | Hybrid Codebase Index — HCI | |
| 18 | |
| 19 | ### Вне ADR |
| 20 | |
| 21 | | Документ | Роль | |
| 22 | |----------|------| |
| 23 | | [intent-melody-language-v1.md](../intent-melody-language-v1.md) | intent melody language v1 | |
| 24 | **Точки кода (фактический baseline):** `IdeCommandPaletteFilterOrchestrator`, `IntentMelodyAliases.TryGetTail`, `GoToAllQueryParser`, `CommandPaletteChromeProjection`, `IdeCommandPaletteExecutionOrchestrator`. |
| 25 | |
| 26 | ### Снимок реализации |
| 27 | |
| 28 | | Элемент | Значение | |
| 29 | |---------|----------| |
| 30 | | — | этапы 1–4: режимная модель разбора, chrome, rg/hci/auto бэкенды, TOML | |
| 31 | | — | стратегии по режиму — см. код `CommandPaletteParsedQueryParser` и ветви оркестратора | |
| 32 | |
| 33 | ## Резюме |
| 34 | |
| 35 | - Палитра (**Ctrl+Q**): режимы строки запроса (`t:`/`m:`/`x:`), стратегии бэкенда. |
| 36 | - Контракт workspace-поиска и переключение в `settings.toml`. |
| 37 | - Direct overlay surface — [0070](0070-command-palette-direct-overlay-surface.md). |
| 38 | |
| 39 | --- |
| 40 | ## Контекст |
| 41 | |
| 42 | Хоткей палитры (по умолчанию **Ctrl+Q**) открывает **одну строку запроса** и **общий список результатов**, но семантика ввода **не однородна**: |
| 43 | |
| 44 | | Префикс | Имя (рабочее) | Смысл запроса | Где задаётся сейчас | |
| 45 | |---------|---------------|---------------|---------------------| |
| 46 | | *(нет)* | каталог команд | fuzzy по названию/`command_id` | дефолтная ветка оркестратора | |
| 47 | | `f:` | go-to файл | фильтрация файлов решения | `GoToAllQueryParser` + `RefreshGoToPaletteFilter` | |
| 48 | | `t:` | go-to тип | поиск типов по `.cs` | то же | |
| 49 | | `m:` | go-to член | эвристика по членам `.cs` | то же | |
| 50 | | `x:` | текст | ripgrep по workspace | то же | |
| 51 | | `c:` | Command Melody | alias → `command_id`, параметрика `:start:end` | `IntentMelodyAliases.TryGetTail` + `RefreshMelodyPaletteFilter` | |
| 52 | |
| 53 | Подсказки в футере/плейсхолдере дублируют этот набор текстом (**`CommandPaletteChromeProjection`**), а **разветвление по режимам** сосредоточено в **цепочке `if` в начале `RefreshCommandPaletteFilter`**: сначала `c:`, потом `f|t|m|x`, иначе каталог. |
| 54 | |
| 55 | **Проблема:** режим — **неименованная концепция в коде**; порядок проверок, отмена async go-to (`goToHandle.Cancel()`), типы результатов строк и особенности UI (`IdeCommandPaletteRowViewModel`: melody vs каталог vs go-to) размазаны по методам без единого контрактa «режим палитры». |
| 56 | |
| 57 | Риски при росте: новый префикс или изменение приоритета (например, конфликт с будущим `w:`) — правки в нескольких местах; тестируемость режимов не структурирована. |
| 58 | |
| 59 | --- |
| 60 | |
| 61 | ## Решение |
| 62 | |
| 63 | Ввести два согласованных слоя: |
| 64 | |
| 65 | 1. **Режим строки** (что ввёл пользователь) — **`CommandPaletteQueryMode`** / **`CommandPaletteParsedQuery`** (§1–2). |
| 66 | 2. **Бэкенд workspace-поиска** для подрежимов `t:` / `m:` / `x:` — **отдельный контракт** с подключаемыми реализациями и настройкой переключения (§7). Это **не** режим UI: один и тот же префикс может обслуживаться разными движками без смены синтаксиса палитры. |
| 67 | |
| 68 | Обработку режимов оформить как **стратегию на режим** (паттерн Strategy / небольшой реестр — детали реализации не фиксируем жёстко). Стратегия go-to для `t:`/`m:`/`x:` **делегирует** поиск выбранному бэкенду (или цепочке fallback), а не вызывает `rg` напрямую из оркестратора. |
| 69 | |
| 70 | <a id="adr0112-p1"></a> |
| 71 | |
| 72 | ### 1. Модель |
| 73 | |
| 74 | - **`CommandPaletteParsedQuery`** (или аналог): дискриминированное объединение: |
| 75 | - **Melody** — нормализованный хвост после `c:` (как сейчас `TryGetTail`); |
| 76 | - **GoTo** — `GoToAllQuery` (префикс `f` | `t` | `m` | `x` + терм); |
| 77 | - **Catalog** — строка без зарезервированного режимного префикса (трим + fuzzy по каталогу). |
| 78 | |
| 79 | Явно задокументировать **приоритет разбора** (совместимость с текущим поведением): |
| 80 | |
| 81 | 1. `c:` — первым (чтобы `c:` не пересекался с go-to и каталогом); |
| 82 | 2. затем `f:` / `t:` / `m:` / `x:`; |
| 83 | 3. иначе каталог. |
| 84 | |
| 85 | <a id="adr0112-p2"></a> |
| 86 | |
| 87 | ### 2. Стратегия на режим |
| 88 | |
| 89 | Для каждого режима — **один вход**: контекст фильтрации (решение, roots, путь файла, текст редактора, горячие клавиши, семья UI mode и т.д.) + **действие**: |
| 90 | |
| 91 | - очистить/заполнить `filteredEntries`; |
| 92 | - выставить выбранный индекс; |
| 93 | - вызвать `refreshCommandPaletteSurfaceSnapshot`; |
| 94 | - для go-to — управление **отменой** фонового поиска (`CommandPaletteGoToAsyncHandle`), как сейчас. |
| 95 | |
| 96 | **Цель рефакторинга:** `RefreshCommandPaletteFilter` становится **диспетчером**: `parse → lookup strategy → execute`, без дублирования правил «кто отменяет go-to». |
| 97 | |
| 98 | <a id="adr0112-p3"></a> |
| 99 | |
| 100 | ### 3. Связь с исполнением (Enter) |
| 101 | |
| 102 | **Выполнение** выбора уже частично унифицировано через `IdeCommandPaletteRowViewModel.RowKind` и `IdeCommandPaletteExecutionOrchestrator` (команда MCP vs go-to navigation). Режим фильтрации и RowKind должны оставаться **согласованными**; при внедрении стратегий не менять пользовательский контракт строк без отдельного ADR. |
| 103 | |
| 104 | <a id="adr0112-p4"></a> |
| 105 | |
| 106 | ### 4. Chrome и обнаруживаемость |
| 107 | |
| 108 | Тексты **`CommandPaletteChromeProjection`** (footer / placeholder) должны **строиться из канонического списка режимов** (имя префикса + короткая подпись), чтобы новый режим добавлялся **в одном месте** данных, а не копированием строки `"f: файл · …"`. |
| 109 | |
| 110 | <a id="adr0112-p5"></a> |
| 111 | |
| 112 | ### 5. Тестируемость |
| 113 | |
| 114 | - Юнит-тесты на **разбор** сырой строки → режим + полезная нагрузка (граничные случаи: `c:` без хвоста, `x:` только пробелы, конфликт не ожидается в v1). |
| 115 | - По возможности — тесты на **порядок** стратегий (регресс: `c:something` не уходит в каталог как plain text без отдельного решения продукта). |
| 116 | |
| 117 | <a id="adr0112-p6"></a> |
| 118 | |
| 119 | ### 6. Бэкенд go-to: ripgrep vs **HCI** (Hybrid Codebase Index) |
| 120 | |
| 121 | Идея использовать **`SearchHybridAsync`** / индекс вместо **`RipgrepWorkspaceSearchService`** для `t:` / `m:` / `x:` **не автоматически** «быстрее и эффективнее» для всех режимов — зависит от **цели режима** и **свежести индекса**. |
| 122 | |
| 123 | | Префикс | Сейчас (baseline) | HCI как кандидат | Нюансы | |
| 124 | |---------|-------------------|------------------|--------| |
| 125 | | `f:` | дерево решения, фильтрация путей в памяти | обычно **не нужен** | Узкое место — не поиск по диску; индекс почти не даёт выигрыша. | |
| 126 | | `x:` | литерал через ripgrep по workspace | **часто уместен**: FTS по проиндексированным чанкам | Плюс: нет процесса `rg`, предсказуемая задержка при **прогретом** индексе. Минус: до reindex — **рассинхрон** с диском; семантика FTS (токены, AND) ≠ «как rg»; для дословного «как текстовый редактор» палитре может понадаться **fallback на rg**. | |
| 127 | | `t:` / `m:` | **regex по `.cs`** (`GoToPaletteRipgrepPatternBuilder`: объявление типа / `идентификатор(`) | **спорно без доработки модели попаданий** | HCI индексирует **текстовые фрагменты**, не обязательно с тем же качеством, что объявление Roslyn/VS «Go to Type/Member». Возможны лишние или пропущенные строки по сравнению с текущей эвристикой regex. Целевая точность для «тип/член» ближе к **Roslyn** (отдельный трек), чем к «просто FTS». | |
| 128 | | (опционально) | — | **`semantic`** в HCI | Полезно для **похожего по смыслу**, а не для контрактов «строго по имени символа» — не подменять `t:`/`m:` без явного режима продукта. | |
| 129 | |
| 130 | Детали контракта и переключения — в §7. |
| 131 | |
| 132 | <a id="adr0112-p7"></a> |
| 133 | |
| 134 | ### 7. Первоклассный **бэкенд** workspace-поиска (`t:` / `m:` / `x:`) |
| 135 | |
| 136 | **Граница:** понятие «бэкенд» относится к **асинхронному поиску по содержимому файлов в корне workspace** для префиксов **`t:`**, **`m:`**, **`x:`**. Режим **`f:`** (фильтрация путей по дереву решения) **в этот контракт не входит** — отдельная синхронная стратегия без смены движка «как у rg». |
| 137 | |
| 138 | **Контракт (working name):** абстракция уровня **`ICommandPaletteGoToSearchBackend`** (или эквивалент) с единым входом и выходом: |
| 139 | |
| 140 | - **Вход:** нормализованный `GoToAllQuery`, корень workspace, лимиты (`MaxRipgrepMatches` / аналог), scope индекса (согласованный с [HCI scope](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md)), `CancellationToken`. |
| 141 | - **Выход:** упорядоченный список **канонических попаданий** для строк палитры (путь, строка, колонка, короткая подпись/категория — совместимо с проекцией в `IdeCommandPaletteRowViewModel` / существующими `CommandPaletteGoTo*NavRowsProjection`). |
| 142 | |
| 143 | **Реализации (минимальный набор):** |
| 144 | |
| 145 | | Реализация | Роль | |
| 146 | |------------|------| |
| 147 | | **Ripgrep** | Поведение как сейчас: `GoToPaletteRipgrepPatternBuilder` + `RipgrepWorkspaceSearchService`. | |
| 148 | | **HCI-FTS** | Запрос к Hybrid Index (без обязательного semantic), маппинг hit → тот же DTO попадания. | |
| 149 | | **Composite / Auto** | Цепочка: например HCI → при пустом результате, ошибке или «индекс не готов» — **fallback** на Ripgrep. Политика задаётся явно, а не неявно в коде оркестратора. | |
| 150 | |
| 151 | **Конфигурация переключения** — только в **пользовательском** **`settings.toml`** (`%LocalAppData%\CascadeIDE\`, см. [0028](0028-user-settings-toml-localappdata-and-secrets.md)): то же дерево, что `CascadeIdeSettings` / `SettingsService.Load`, **не** репозиторный `.cascade/workspace.toml` (`UiWorkspaceToml` — другая модель: маршрутизация зон, пресеты навигации и т.д.). Выбор «как искать из палитры» — предпочтение **рабочего места**, а не артефакта конкретного репозитория (командную политику «только rg» при необходимости можно добавить позже отдельным механизмом overlay). |
| 152 | |
| 153 | **Куда положить в схеме (согласовано с имеющимися ключами):** |
| 154 | |
| 155 | | Вариант | Вердикт | |
| 156 | |---------|---------| |
| 157 | | **`[hybrid_index]`** | **Не использовать** под `backend`: при значении **`rg`** настройка не про индекс; смешивает включение/параметры HCI и независимый выбор движка палитры. Связь `auto` ↔ HCI — только в **коде** фасада. | |
| 158 | | **`[workspace]`** | **Не использовать**: в коде это `WorkspaceSettings` — панели, `Flight`, splitters (`docs/samples/settings.toml`); к поисковому бэкенду не относится. | |
| 159 | | **`[command_palette…]`** | **Да**: новое верхнеуровневое свойство на `CascadeIdeSettings` (рядом с `HybridIndex`, `Workspace`, …), вложенная таблица в TOML — как у `[display.screens.grammar]` / markdown: snake_case ключи (`CascadeIdeSettingsTomlDeserializeTests`). | |
| 160 | |
| 161 | Маппинг при реализации (ориентир): `CascadeIdeSettings.CommandPalette.GoToSearch.Backend` → таблица **`[command_palette.go_to_search]`**, поле **`backend`**. |
| 162 | |
| 163 | Канонические значения бэкенда (короткие строковые литералы для TOML): |
| 164 | |
| 165 | | Значение | Смысл | |
| 166 | |----------|--------| |
| 167 | | **`rg`** | Только **ripgrep** (текущее baseline-поведение; рекомендуемый **дефолт** до стабилизации HCI-пути). | |
| 168 | | **`hci`** | Только **Hybrid Codebase Index** (FTS-хит → попадания палитры). | |
| 169 | | **`auto`** | Сначала HCI; при неготовом индексе, ошибке или пустом ответе уместного рода — **fallback** на `rg`. Политика fallback — в коде фасада Composite, не размазана по UI. | |
| 170 | |
| 171 | Пример (в том же пользовательском файле, что и `[hybrid_index]`): |
| 172 | |
| 173 | ```toml |
| 174 | [command_palette.go_to_search] |
| 175 | backend = "rg" # "rg" | "hci" | "auto" |
| 176 | ``` |
| 177 | |
| 178 | - **Пер-префиксные оверрайды** (`x:` ≠ `t:`) — опционально **вторая итерация** (напр. отдельный ключ или подтаблица), если без них проще выпустить v1. |
| 179 | |
| 180 | **Поддержка и тестирование:** |
| 181 | |
| 182 | - Новый бэкенд добавляется **одной реализацией интерфейса** + регистрация в DI / фабрике выбора; оркестратор не ветвится по `if (useHci)`. |
| 183 | - Юнит-тесты оркестратора на **фейковом бэкенде**; отдельные тесты на каждую реализацию (маппинг hit ↔ строка палитры). |
| 184 | |
| 185 | **UX (опционально):** короткая метка источника в subtitle («rg» / «index») или только в диагностике — решение продукта; по умолчанию пользователю не обязательно видеть имя бэкенда. |
| 186 | |
| 187 | --- |
| 188 | |
| 189 | ## Отклонённые / отложенные альтернативы |
| 190 | |
| 191 | - **Плагинируемые префиксы из TOML в v1** — дороже протокола и валидации; оставить на будущее, когда появится второй источник префиксов кроме кода. |
| 192 | - **Объединить `c:` и go-to в одну грамматику** — ломает ментальную модель IML и VS-style go-to; не делаем. |
| 193 | - **Единый regex-парсер всей строки** — возможно как имплементация-деталь; архитектурно важен не синтаксис, а **тип режима и стратегия**. |
| 194 | |
| 195 | --- |
| 196 | |
| 197 | ## Последствия |
| 198 | |
| 199 | - Положительные: один явный вход для документации режимов; проще добавлять префикс или менять побочные эффекты (отмена go‑to); меньше расхождения между подсказками chrome и кодом; **смена движка поиска** (`rg` ↔ HCI ↔ цепочка) без правок разветвления в `IdeCommandPaletteFilterOrchestrator`. |
| 200 | - Отрицательные: объём рефакторинга — orchestrator, DI, настройки, тесты; поведение для пользователя **в v1 после выноса интерфейса** должно оставаться **паритетным** с Ripgrep-backend по умолчанию (смена только через настройку — осознанно). |
| 201 | |
| 202 | --- |
| 203 | |
| 204 | ## Статус реализации |
| 205 | |
| 206 | Реализовано в приложении и тестах: `CommandPaletteParsedQuery` / `CommandPaletteParsedQueryParser`, `ICommandPaletteGoToSearchBackend` (+ `rg` / HCI / `auto`), `[command_palette.go_to_search]` в settings, канон подсказок `CommandPaletteChromeModeHints`. |
| 207 | |
| 208 | Дальнейшие улучшения (например плагинируемые префиксы или расширение семантики `x:`) — по этому ADR как открытым направлениям, не блокеры текущего объёма. |
| 209 | |