Forge
csharpdeeb25a2
1#nullable enable
2
3namespace CascadeIDE.Features.Chat;
4
5public partial class ChatPanelViewModel
6{
7 /// <summary>Выбор сообщения по номеру gutter (1-based) в активной ветке detail-ленты (ADR 0136).</summary>
8 public string SelectMessageByOrdinalInDetailLane(int ordinal) =>
9 SelectMessageByOrdinalRangeInDetailLane(ordinal, ordinal);
10
11 /// <summary>
12 /// Диапазон gutter-номеров (1-based), синтаксис как <c>/editor line select</c>: одно число, «L R» или «L:R».
13 /// Активным становится сообщение с номером <paramref name="endOrdinal"/> (конец диапазона).
14 /// </summary>
15 public string SelectMessageByOrdinalRangeInDetailLane(int startOrdinal, int endOrdinal)
16 {
17 if (IsChatOverviewMode)
18 return "Открой тему (detail): /intercom topic open или клик по карточке.";
19
20 if (startOrdinal < 1 || endOrdinal < 1)
21 return "Номера сообщений должны быть ≥ 1.";
22
23 if (endOrdinal < startOrdinal)
24 return "Конец диапазона не может быть меньше начала.";
25
26 if (!TryGetActiveDetailLaneMessageIndices(out var indices))
27 return "В активной ветке нет сообщений.";
28
29 if (startOrdinal > indices.Count)
30 return $"Нет сообщения #{startOrdinal} (в ветке {indices.Count}).";
31
32 if (endOrdinal > indices.Count)
33 return $"Нет сообщения #{endOrdinal} (в ветке {indices.Count}).";
34
35 ApplyMessageOrdinalSelection(indices, startOrdinal, endOrdinal);
36 return "OK";
37 }
38
39 /// <summary>Disjoint multi-range по gutter (ADR 0138). Активным — конец последнего сегмента.</summary>
40 public string SelectMessagesByOrdinalRangesInDetailLane(IReadOnlyList<ParametricIntRange> segments)
41 {
42 if (IsChatOverviewMode)
43 return "Открой тему (detail): /intercom topic open или клик по карточке.";
44
45 if (segments.Count == 0)
46 return "Укажи хотя бы один сегмент [n] или [a;b].";
47
48 if (!TryGetActiveDetailLaneMessageIndices(out var indices))
49 return "В активной ветке нет сообщений.";
50
51 var lastSegment = segments[^1];
52 if (lastSegment.End > indices.Count)
53 return $"Нет сообщения #{lastSegment.End} (в ветке {indices.Count}).";
54
55 foreach (var segment in segments)
56 {
57 if (segment.Start < 1 || segment.End < 1)
58 return "Номера сообщений должны быть ≥ 1.";
59 if (segment.End < segment.Start)
60 return "Конец диапазона не может быть меньше начала.";
61 if (segment.Start > indices.Count)
62 return $"Нет сообщения #{segment.Start} (в ветке {indices.Count}).";
63 if (segment.End > indices.Count)
64 return $"Нет сообщения #{segment.End} (в ветке {indices.Count}).";
65 }
66
67 ApplyMessageOrdinalSelection(indices, segments[0].Start, lastSegment.End, segments);
68 return "OK";
69 }
70
71 /// <summary>Сбросить multi-highlight и активное сообщение в detail-ленте (slash <c>/intercom message select clear</c>).</summary>
72 public string ClearMessageSelectionInDetailLane()
73 {
74 if (IsChatOverviewMode)
75 return "Открой тему (detail): /intercom topic open или клик по карточке.";
76
77 HighlightedMessageIndices = new HashSet<int>();
78 SelectedMessageIndex = -1;
79 RefreshChatSurfaceSnapshot();
80 return "OK";
81 }
82
83 private void ApplyMessageOrdinalSelection(
84 IReadOnlyList<int> laneIndices,
85 int startOrdinal,
86 int endOrdinal,
87 IReadOnlyList<ParametricIntRange>? highlightSegments = null)
88 {
89 var highlighted = new HashSet<int>();
90 if (highlightSegments is { Count: > 1 })
91 {
92 foreach (var segment in highlightSegments)
93 {
94 for (var ord = segment.Start; ord <= segment.End; ord++)
95 highlighted.Add(laneIndices[ord - 1]);
96 }
97 }
98 else
99 {
100 for (var ord = startOrdinal; ord <= endOrdinal; ord++)
101 highlighted.Add(laneIndices[ord - 1]);
102 }
103
104 HighlightedMessageIndices = highlighted;
105 SelectedMessageIndex = laneIndices[endOrdinal - 1];
106 RefreshChatSurfaceSnapshot();
107 }
108
109 public bool TryGetFeedOrdinalForMessageIndex(int messageIndex, out int ordinal)
110 {
111 ordinal = 0;
112 if (!TryGetActiveDetailLaneMessageIndices(out var indices))
113 return false;
114
115 for (var i = 0; i < indices.Count; i++)
116 {
117 if (indices[i] != messageIndex)
118 continue;
119 ordinal = i + 1;
120 return true;
121 }
122
123 return false;
124 }
125
126 private bool TryGetActiveDetailLaneMessageIndices(out IReadOnlyList<int> messageIndices)
127 {
128 messageIndices = [];
129 var threadId = _activeThreadId;
130 if (threadId == Guid.Empty)
131 return false;
132
133 foreach (var lane in ChatSurfaceSnapshot.Layout.Lanes)
134 {
135 if (lane.Thread.ThreadId != threadId)
136 continue;
137
138 var list = new List<int>();
139 foreach (var entry in lane.Entries)
140 {
141 if (entry.Kind != ChatSurfaceEntryKind.Message)
142 continue;
143 if (entry.MessageIndex is { } messageIndex)
144 list.Add(messageIndex);
145 }
146
147 messageIndices = list;
148 return list.Count > 0;
149 }
150
151 return false;
152 }
153}
154
View only · write via MCP/CIDE