Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Models.Intercom;
4
5namespace CascadeIDE.Features.Chat;
6
7public sealed class MessageAnchorSlashCompletionProvider : IMessageAnchorSlashCompletionProvider
8{
9 public const int DefaultLimit = 20;
10
11 private readonly Func<IReadOnlyList<AttachmentAnchor>> _getSelectedMessageAnchors;
12
13 public MessageAnchorSlashCompletionProvider(Func<IReadOnlyList<AttachmentAnchor>> getSelectedMessageAnchors) =>
14 _getSelectedMessageAnchors = getSelectedMessageAnchors;
15
16 public IReadOnlyList<MessageAnchorSlashMatch> GetMatches(string ordinalOrIdPrefix, int limit)
17 {
18 if (limit <= 0)
19 return [];
20
21 var prefix = (ordinalOrIdPrefix ?? "").Trim();
22 var anchors = _getSelectedMessageAnchors();
23 if (anchors.Count == 0)
24 return [];
25
26 var matches = new List<MessageAnchorSlashMatch>(Math.Min(anchors.Count, limit));
27 for (var i = 0; i < anchors.Count && matches.Count < limit; i++)
28 {
29 var ordinal = (i + 1).ToString();
30 var anchor = anchors[i];
31 if (!matchesPrefix(prefix, ordinal, anchor))
32 continue;
33
34 var label = anchor.DisplayLabel ?? anchor.MemberKey ?? anchor.File ?? "—";
35 var status = IntercomAnchorSlash.FormatOutcomeShort(anchor.ResolveOutcome);
36 var id = anchor.Id ?? "";
37 var help = string.IsNullOrWhiteSpace(id) ? status : $"a:{id} · {status}";
38 matches.Add(new MessageAnchorSlashMatch(ordinal, $"{ordinal} · {label}", help));
39 }
40
41 return matches;
42 }
43
44 private static bool matchesPrefix(string prefix, string ordinal, AttachmentAnchor anchor)
45 {
46 if (prefix.Length == 0)
47 return true;
48
49 if (ordinal.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
50 return true;
51
52 var id = anchor.Id ?? "";
53 if (id.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
54 return true;
55
56 return $"a:{id}".StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
57 }
58}
59
View only · write via MCP/CIDE