Forge
csharp4405de34
1using System.Text;
2using CascadeIDE.Contracts;
3using CascadeIDE.Models.Shell;
4using CascadeIDE.Services;
5
6namespace CascadeIDE.Features.Shell.Application;
7
8/// <summary>Чистая логика текста и фильтрации CascadeChord (ADR 0060) без состояния VM.</summary>
9[PresentationProjection("presentation-cascade-chord")]
10public static class CascadeChordPresentationProjection
11{
12 public static string NormalizeMelodyInput(string? s)
13 {
14 if (string.IsNullOrEmpty(s))
15 return "";
16 var sb = new StringBuilder(s.Length);
17 foreach (var c in s.ToLowerInvariant())
18 {
19 if (c is >= 'a' and <= 'z' or >= '0' and <= '9')
20 sb.Append(c);
21 }
22 return sb.ToString();
23 }
24
25 public static string TruncateChordTitle(string s, int maxChars = 52)
26 {
27 if (string.IsNullOrEmpty(s))
28 return "";
29 s = s.Trim();
30 return s.Length <= maxChars ? s : s[..(maxChars - 1)] + "…";
31 }
32
33 public static IReadOnlyList<(string Alias, string CommandId)> FilterEligibleMatches(string tailNormalized) =>
34 IntentMelodyAliases.FilterByTailPrefix(tailNormalized)
35 .Where(m => ParametricIntentMelody.IsChordEligibleAlias(m.Alias))
36 .ToList();
37
38
39 public static IReadOnlyList<CascadeChordOverlaySuggestion> BuildSuggestionRows(
40 bool isAwaitMelodyTail,
41 string tailNormalized,
42 int maxItems)
43 {
44 if (!isAwaitMelodyTail)
45 return [];
46
47 var matches = FilterEligibleMatches(tailNormalized);
48 return matches
49 .Take(maxItems)
50 .Select(m => new CascadeChordOverlaySuggestion(
51 m.Alias,
52 TruncateChordTitle(IdeCommandDocDisplay.ShortTitleForCommandId(m.CommandId))))
53 .ToList();
54 }
55
56 public static string BuildOverlayHint(
57 bool isAwaitMelodyTail,
58 string melodyTail,
59 int timeoutSeconds,
60 IReadOnlyList<(string Alias, string CommandId)> eligibleMatches)
61 {
62 var buf = melodyTail;
63 var bufLine = string.IsNullOrEmpty(buf)
64 ? "Набрано: (пусто) — тот же хвост, что после c: в палитре (примеры alias — из каталога IntentMelody)."
65 : $"Набрано: «{buf}»";
66
67 if (!isAwaitMelodyTail)
68 {
69 return "CascadeChord\n" +
70 " Esc — отмена · таймаут " + timeoutSeconds + " с";
71 }
72
73 var matchLine = eligibleMatches.Count == 0
74 ? "Нет alias с таким префиксом."
75 : "Совпадения: " + string.Join(", ", eligibleMatches.Select(m => m.Alias));
76
77 return "CascadeChord · как c: после корня (латиница и цифры; в параметрике разделитель «;» без Shift или «:»)\n" +
78 bufLine + "\n" +
79 matchLine + "\n" +
80 " Без Enter: однозначный обычный alias. Параметрические корни каталога — по Enter.\n" +
81 " Backspace · Esc — отмена · таймаут " + timeoutSeconds + " с\n" +
82 "Палитра Ctrl+Q, c: — тот же каталог.";
83 }
84}
85
View only · write via MCP/CIDE