| 1 | using CascadeIDE.Features.Shell.Application; |
| 2 | using CascadeIDE.Features.UiChrome; |
| 3 | |
| 4 | namespace CascadeIDE.ViewModels; |
| 5 | |
| 6 | public enum IdeCommandPaletteRowKind |
| 7 | { |
| 8 | Command, |
| 9 | GoTo, |
| 10 | Hint, |
| 11 | } |
| 12 | |
| 13 | /// <summary>Строка списка палитры команд (команда, go-to, подсказка).</summary> |
| 14 | public sealed class IdeCommandPaletteRowViewModel : ViewModelBase |
| 15 | { |
| 16 | public IdeCommandPaletteRowViewModel( |
| 17 | IdeCommandPaletteCatalog.Entry entry, |
| 18 | string? hotkeyHint, |
| 19 | UiModeFamily currentFamily, |
| 20 | string? melodyAliasTail = null, |
| 21 | string? argsJsonOverride = null) |
| 22 | { |
| 23 | RowKind = IdeCommandPaletteRowKind.Command; |
| 24 | PaletteId = entry.PaletteId; |
| 25 | CommandId = entry.CommandId; |
| 26 | Category = entry.Category; |
| 27 | if (!string.IsNullOrEmpty(melodyAliasTail)) |
| 28 | { |
| 29 | // Режим c: — акцент на мелодии (первая строка), не на command_id. |
| 30 | Title = $"c:{melodyAliasTail}"; |
| 31 | Subtitle = CommandPaletteSubtitleProjection.MelodyPaletteSecondaryLine(entry.Title, entry.CommandId, entry.Category); |
| 32 | } |
| 33 | else |
| 34 | { |
| 35 | Title = entry.Title; |
| 36 | Subtitle = CommandPaletteSubtitleProjection.CommandPaletteSubtitle(entry.CommandId, entry.Category); |
| 37 | } |
| 38 | IsMelodyAccentRow = !string.IsNullOrEmpty(melodyAliasTail); |
| 39 | ArgsJson = argsJsonOverride ?? entry.ArgsJson; |
| 40 | HotkeyHint = hotkeyHint; |
| 41 | IsAvailable = IdeCommandPaletteMatch.IsEntryAvailable(entry, currentFamily); |
| 42 | UnavailableHint = IdeCommandPaletteMatch.UnavailableHint(entry, currentFamily); |
| 43 | } |
| 44 | |
| 45 | /// <summary>Строка навигации (файл / совпадение rg).</summary> |
| 46 | public IdeCommandPaletteRowViewModel( |
| 47 | string title, |
| 48 | string category, |
| 49 | string fullPath, |
| 50 | int line, |
| 51 | int column, |
| 52 | string prefixHint) |
| 53 | { |
| 54 | RowKind = IdeCommandPaletteRowKind.GoTo; |
| 55 | PaletteId = "__goto__"; |
| 56 | CommandId = "__goto__"; |
| 57 | Title = title; |
| 58 | Category = category; |
| 59 | Subtitle = category; |
| 60 | ArgsJson = null; |
| 61 | HotkeyHint = prefixHint; |
| 62 | NavigateFilePath = fullPath; |
| 63 | NavigateLine = line; |
| 64 | NavigateColumn = column; |
| 65 | IsAvailable = true; |
| 66 | UnavailableHint = null; |
| 67 | IsMelodyAccentRow = false; |
| 68 | } |
| 69 | |
| 70 | public IdeCommandPaletteRowViewModel(string title, string category) |
| 71 | { |
| 72 | RowKind = IdeCommandPaletteRowKind.Hint; |
| 73 | PaletteId = "__hint__"; |
| 74 | CommandId = "__hint__"; |
| 75 | Title = title; |
| 76 | Category = category; |
| 77 | Subtitle = category; |
| 78 | ArgsJson = null; |
| 79 | HotkeyHint = null; |
| 80 | IsAvailable = false; |
| 81 | UnavailableHint = null; |
| 82 | IsMelodyAccentRow = false; |
| 83 | } |
| 84 | |
| 85 | /// <summary> |
| 86 | /// Melody: команда исполняется через MCP, но нет строки в <see cref="IdeCommandPaletteCatalog"/> — заголовок из дока протокола. |
| 87 | /// </summary> |
| 88 | public IdeCommandPaletteRowViewModel( |
| 89 | string commandId, |
| 90 | string melodyAliasTail, |
| 91 | string titleFromDoc, |
| 92 | string? hotkeyHint, |
| 93 | string? argsJson = null) |
| 94 | { |
| 95 | RowKind = IdeCommandPaletteRowKind.Command; |
| 96 | PaletteId = commandId; |
| 97 | CommandId = commandId; |
| 98 | Title = $"c:{melodyAliasTail}"; |
| 99 | Category = "ide_execute_command"; |
| 100 | Subtitle = $"{titleFromDoc} · {commandId} · ide_execute_command"; |
| 101 | ArgsJson = argsJson; |
| 102 | HotkeyHint = hotkeyHint; |
| 103 | IsAvailable = true; |
| 104 | UnavailableHint = null; |
| 105 | IsMelodyAccentRow = true; |
| 106 | } |
| 107 | |
| 108 | public IdeCommandPaletteRowKind RowKind { get; } |
| 109 | |
| 110 | public bool ShowUnavailableHint => RowKind == IdeCommandPaletteRowKind.Command && !IsAvailable && !string.IsNullOrEmpty(UnavailableHint); |
| 111 | |
| 112 | /// <summary>Строка из режима <c>c:</c> — увеличенный шрифт заголовка в палитре.</summary> |
| 113 | public bool IsMelodyAccentRow { get; } |
| 114 | |
| 115 | /// <summary>Размер шрифта первой строки: чуть крупнее для мелодии <c>c:</c>.</summary> |
| 116 | public double PaletteTitleFontSize => IsMelodyAccentRow ? 14.0 : 12.0; |
| 117 | |
| 118 | public string PaletteId { get; } |
| 119 | public string CommandId { get; } |
| 120 | public string Title { get; } |
| 121 | /// <summary>Сырой тег группы из каталога (раздел палитры); для подписи в UI см. <see cref="Subtitle"/>.</summary> |
| 122 | public string Category { get; } |
| 123 | /// <summary>Вторая строка палитры: в обычном режиме <c>command_id · раздел</c>; в <c>c:</c> — подпись команды и id под мелодией.</summary> |
| 124 | public string Subtitle { get; } |
| 125 | public string? ArgsJson { get; } |
| 126 | public string? HotkeyHint { get; } |
| 127 | public bool IsAvailable { get; } |
| 128 | public string? UnavailableHint { get; } |
| 129 | public double RowOpacity => IsAvailable ? 1.0 : 0.45; |
| 130 | |
| 131 | public string? NavigateFilePath { get; } |
| 132 | public int NavigateLine { get; } |
| 133 | public int NavigateColumn { get; } = 1; |
| 134 | } |
| 135 | |