Forge
csharp4405de34
1using CascadeIDE.Features.UiChrome;
2
3namespace CascadeIDE.Services;
4
5/// <summary>Поиск и ранжирование записей палитры команд (подстрока + простой fuzzy по подпоследовательности).</summary>
6public static class IdeCommandPaletteMatch
7{
8 /// <summary>Возвращает упорядоченный список записей для отображения.</summary>
9 public static IReadOnlyList<IdeCommandPaletteCatalog.Entry> FilterAndRank(
10 IReadOnlyList<IdeCommandPaletteCatalog.Entry> all,
11 string query)
12 {
13 var q = query.Trim();
14 if (string.IsNullOrEmpty(q))
15 {
16 return all.OrderBy(x => x.Category, StringComparer.OrdinalIgnoreCase)
17 .ThenBy(x => x.Title, StringComparer.OrdinalIgnoreCase)
18 .ToList();
19 }
20
21 var scored = new List<(IdeCommandPaletteCatalog.Entry Entry, int Score)>();
22 foreach (var e in all)
23 {
24 var hay = BuildHaystack(e);
25 var score = MatchScore(hay, q, e.Title);
26 if (score >= 0)
27 scored.Add((e, score));
28 }
29
30 return scored
31 .OrderByDescending(x => x.Score)
32 .ThenBy(x => x.Entry.Category, StringComparer.OrdinalIgnoreCase)
33 .ThenBy(x => x.Entry.Title, StringComparer.OrdinalIgnoreCase)
34 .Select(x => x.Entry)
35 .ToList();
36 }
37
38 public static bool IsEntryAvailable(IdeCommandPaletteCatalog.Entry e, UiModeFamily family)
39 {
40 if (e.AllowedFamilies is not { } allowed || allowed.IsDefaultOrEmpty)
41 return true;
42 foreach (var f in allowed)
43 {
44 if (f == family)
45 return true;
46 }
47
48 return false;
49 }
50
51 /// <summary>Подсказка для недоступной строки (UX: «Недоступно в режиме …»).</summary>
52 public static string? UnavailableHint(IdeCommandPaletteCatalog.Entry e, UiModeFamily current)
53 {
54 if (IsEntryAvailable(e, current))
55 return null;
56 if (e.AllowedFamilies is { } fam && !fam.IsDefaultOrEmpty && fam.Length == 1)
57 return $"Только в режиме {FormatFamily(fam[0])}";
58 return "Недоступно в текущем UI-режиме";
59 }
60
61 private static string FormatFamily(UiModeFamily f) =>
62 f switch
63 {
64 UiModeFamily.Focus => "Focus",
65 UiModeFamily.Editor => "Editor",
66 UiModeFamily.Balanced => "Balanced",
67 UiModeFamily.Power => "Power",
68 UiModeFamily.AgentChat => "Agent Chat",
69 UiModeFamily.Debug => "Debug",
70 UiModeFamily.Flight => "Flight",
71 _ => f.ToString(),
72 };
73
74 private static string BuildHaystack(IdeCommandPaletteCatalog.Entry e) =>
75 $"{e.Title} {e.Category} {e.CommandId} {e.PaletteId}";
76
77 /// <summary>Чем выше, тем лучше совпадение; -1 — не подходит.</summary>
78 private static int MatchScore(string haystack, string query, string titleForWordBonus)
79 {
80 if (query.Length == 0)
81 return 0;
82
83 var idx = haystack.IndexOf(query, StringComparison.OrdinalIgnoreCase);
84 if (idx >= 0)
85 {
86 var baseScore = 10_000_000 - idx * 100;
87 if (TitleHasWordStartingWith(titleForWordBonus, query))
88 baseScore += 50_000;
89 return baseScore;
90 }
91
92 if (!IsSubsequenceIgnoreCase(haystack, query))
93 return -1;
94
95 var spread = SubsequenceSpread(haystack, query);
96 var fuzzy = 5_000_000 - spread * 1000 - Math.Min(haystack.Length, 500);
97 if (TitleHasWordStartingWith(titleForWordBonus, query))
98 fuzzy += 25_000;
99 return fuzzy;
100 }
101
102 private static bool TitleHasWordStartingWith(string title, string q)
103 {
104 foreach (var word in title.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries))
105 {
106 if (word.StartsWith(q, StringComparison.OrdinalIgnoreCase))
107 return true;
108 }
109
110 return false;
111 }
112
113 private static bool IsSubsequenceIgnoreCase(string hay, string q)
114 {
115 var hi = 0;
116 for (var qi = 0; qi < q.Length; qi++)
117 {
118 var qc = char.ToLowerInvariant(q[qi]);
119 while (hi < hay.Length && char.ToLowerInvariant(hay[hi]) != qc)
120 hi++;
121 if (hi >= hay.Length)
122 return false;
123 hi++;
124 }
125
126 return true;
127 }
128
129 private static int SubsequenceSpread(string hay, string q)
130 {
131 var hi = 0;
132 var spread = 0;
133 var last = -1;
134 for (var qi = 0; qi < q.Length; qi++)
135 {
136 var qc = char.ToLowerInvariant(q[qi]);
137 while (hi < hay.Length && char.ToLowerInvariant(hay[hi]) != qc)
138 hi++;
139 if (hi >= hay.Length)
140 return int.MaxValue;
141 if (last >= 0)
142 spread += hi - last;
143 last = hi;
144 hi++;
145 }
146
147 return spread;
148 }
149}
150
View only · write via MCP/CIDE