Forge
csharpdeeb25a2
1#nullable enable
2
3using System.Text.Json;
4using CascadeIDE.Models.Editor;
5using CascadeIDE.Models.Intercom;
6using CascadeIDE.Services.Intercom;
7
8namespace CascadeIDE.Services;
9
10/// <summary>План reveal по <see cref="AttachmentAnchor"/> у получателя (ADR 0128 §89.1).</summary>
11public sealed class IntercomAttachmentRevealPlan
12{
13 public const string OutcomeResolved = "resolved";
14 public const string OutcomeFileMissing = "file_missing";
15 public const string OutcomeMemberNotFound = "member_not_found";
16 public const string OutcomeLinesDrift = "lines_drift";
17 public const string OutcomeExcerptOnly = "excerpt_only";
18
19 public string ResolveOutcome { get; init; } = OutcomeExcerptOnly;
20
21 public string? AbsoluteFilePath { get; init; }
22
23 public LineRange? Lines { get; init; }
24
25 public bool OpenFileOnly { get; init; }
26
27 public string Message { get; init; } = "";
28
29 public static IntercomAttachmentRevealPlan Create(
30 AttachmentAnchor anchor,
31 string? workspaceRoot,
32 string? solutionPath = null)
33 {
34 if (string.IsNullOrWhiteSpace(anchor.File))
35 {
36 return new IntercomAttachmentRevealPlan
37 {
38 ResolveOutcome = OutcomeExcerptOnly,
39 Message = "excerpt_only: нет file в anchor.",
40 };
41 }
42
43 if (!AttachmentAnchorPaths.TryResolveAbsolute(anchor.File, workspaceRoot, out var absolute, out var pathErr))
44 {
45 return new IntercomAttachmentRevealPlan
46 {
47 ResolveOutcome = OutcomeFileMissing,
48 Message = $"file_missing: {pathErr}",
49 };
50 }
51
52 if (!File.Exists(absolute))
53 {
54 var hint = string.IsNullOrWhiteSpace(anchor.Excerpt) ? "" : " excerpt доступен в anchor.";
55 return new IntercomAttachmentRevealPlan
56 {
57 ResolveOutcome = OutcomeFileMissing,
58 AbsoluteFilePath = absolute,
59 Message = $"file_missing: «{anchor.File}» нет в текущем workspace.{hint}",
60 };
61 }
62
63 var hasMember = !string.IsNullOrWhiteSpace(anchor.MemberKey);
64 AttachmentSyntaxScope.TryParse(anchor.SyntaxScope, out var syntaxScope);
65 var hasScope = syntaxScope is not null;
66 var hasLines = tryBuildLineRange(anchor.LineStart, anchor.LineEnd, out var lines);
67
68 if (hasMember || hasScope)
69 {
70 var cacheContext = IntercomAttachResolveCacheContext.From(
71 workspaceRoot,
72 solutionPath,
73 anchor.File);
74 if (AttachmentAnchorRoslynResolver.TryResolveLineRange(
75 null,
76 absolute,
77 anchor.MemberKey,
78 syntaxScope,
79 cacheContext,
80 out var resolved,
81 out var resolveDetail))
82 {
83 return new IntercomAttachmentRevealPlan
84 {
85 ResolveOutcome = OutcomeResolved,
86 AbsoluteFilePath = absolute,
87 Lines = resolved,
88 Message = $"OK resolveOutcome={OutcomeResolved} lines={resolved.Start.Value}-{resolved.End.Value} ({resolveDetail})",
89 };
90 }
91
92 if (hasLines)
93 {
94 return new IntercomAttachmentRevealPlan
95 {
96 ResolveOutcome = OutcomeMemberNotFound,
97 AbsoluteFilePath = absolute,
98 Lines = lines,
99 Message = $"member_not_found: {resolveDetail}; fallback lines {lines!.Value.Start.Value}–{lines.Value.End.Value} @ send.",
100 };
101 }
102
103 return new IntercomAttachmentRevealPlan
104 {
105 ResolveOutcome = OutcomeMemberNotFound,
106 AbsoluteFilePath = absolute,
107 OpenFileOnly = true,
108 Message = $"member_not_found: {resolveDetail}; диапазон строк не задан.",
109 };
110 }
111
112 if (!hasLines)
113 {
114 return new IntercomAttachmentRevealPlan
115 {
116 ResolveOutcome = OutcomeExcerptOnly,
117 AbsoluteFilePath = absolute,
118 OpenFileOnly = true,
119 Message = "excerpt_only: открыт файл без подсветки диапазона.",
120 };
121 }
122
123 return new IntercomAttachmentRevealPlan
124 {
125 ResolveOutcome = OutcomeResolved,
126 AbsoluteFilePath = absolute,
127 Lines = lines,
128 Message = $"OK resolveOutcome={OutcomeResolved} lines={lines!.Value.Start.Value}-{lines.Value.End.Value}",
129 };
130 }
131
132 private static bool tryBuildLineRange(int? start, int? end, out LineRange? lines)
133 {
134 lines = null;
135 if (!start.HasValue || !end.HasValue)
136 return false;
137
138 if (!LineNumber.TryCreate(start.Value, out var lnStart))
139 return false;
140 if (!LineNumber.TryCreate(end.Value, out var lnEnd))
141 return false;
142 if (!LineRange.TryCreate(lnStart, lnEnd, out var range))
143 return false;
144
145 lines = range;
146 return true;
147 }
148}
149
View only · write via MCP/CIDE