Forge
csharpdeeb25a2
1#nullable enable
2
3using Avalonia.Threading;
4using CascadeIDE.Cockpit.Composition.HostSurface;
5using CascadeIDE.Cockpit.DataBus;
6using CascadeIDE.Features.Agent.Environment;
7using CascadeIDE.Features.SolutionWarmup.Application;
8using CascadeIDE.Features.Workspace;
9using CascadeIDE.Models;
10using CommunityToolkit.Mvvm.ComponentModel;
11using CommunityToolkit.Mvvm.Input;
12
13namespace CascadeIDE.ViewModels;
14
15/// <summary>
16/// Компактная полоса статуса над PFD/Forward: Verify Epoch (AEE W3) и solution warm-up (ADR 0141).
17/// </summary>
18public partial class MainWindowViewModel
19{
20 private const int PfdStatusMinVisibleMs = 400;
21
22 private IDisposable? _pfdBackgroundStatusWarmupSubscription;
23 private IDisposable? _pfdBackgroundStatusHciSubscription;
24 private bool _hciReindexPending;
25 private DateTimeOffset _pfdStatusVisibleSinceUtc;
26 private IDisposable? _pfdStatusHideTimer;
27 private IDisposable? _pfdAgentEnvironmentTaskSubscription;
28 private DispatcherTimer? _verifyEpochActiveTicker;
29
30 [ObservableProperty]
31 [NotifyPropertyChangedFor(nameof(ShowPfdBackgroundStatusBar))]
32 [NotifyPropertyChangedFor(nameof(ShowWorkspaceBackgroundStatusOnPfd))]
33 [NotifyPropertyChangedFor(nameof(ShowWorkspaceBackgroundStatusOnForward))]
34 private string? _pfdBackgroundStatusText;
35
36 [ObservableProperty]
37 [NotifyPropertyChangedFor(nameof(ShowPfdAgentEnvironmentCancel))]
38 [NotifyPropertyChangedFor(nameof(ShowPfdVerifyEpochRetry))]
39 private bool _isPfdBackgroundStatusCaution;
40
41 [ObservableProperty]
42 [NotifyPropertyChangedFor(nameof(ShowPfdAgentEnvironmentCancel))]
43 private bool _pfdAgentEnvironmentCancelVisible;
44
45 public bool ShowPfdAgentEnvironmentCancel =>
46 ShowPfdBackgroundStatusBar && PfdAgentEnvironmentCancelVisible;
47
48 public bool ShowPfdVerifyEpochRetry =>
49 ShowPfdBackgroundStatusBar && _verifyEpochInstrument.ShowRetry;
50
51 public bool ShowPfdVerifyEpochExpandToggle => _verifyEpochInstrument.IsVisible;
52
53 public bool ShowPfdBackgroundStatusBar =>
54 _settings.SolutionWarmup.ShowBackgroundStatusOnPfd
55 && (!string.IsNullOrWhiteSpace(PfdBackgroundStatusText)
56 || ShowPfdVerifyEpochExpandedPanel);
57
58 /// <summary>Полоса на PFD: master + <c>pfd_status_strip</c> в <c>[display.instruments]</c>.</summary>
59 public bool ShowWorkspaceBackgroundStatusOnPfd =>
60 ShowPfdBackgroundStatusBar
61 && InstrumentStatusStripPlacement.IsVisibleOnPfd(_settings.Display, masterEnabled: true);
62
63 /// <summary>Полоса на Forward: master + <c>forward_status_strip</c>.</summary>
64 public bool ShowWorkspaceBackgroundStatusOnForward =>
65 ShowPfdBackgroundStatusBar
66 && InstrumentStatusStripPlacement.IsVisibleOnForward(_settings.Display, masterEnabled: true);
67
68 internal void NotifyWorkspaceBackgroundStatusStripPlacement()
69 {
70 OnPropertyChanged(nameof(ShowWorkspaceBackgroundStatusOnPfd));
71 OnPropertyChanged(nameof(ShowWorkspaceBackgroundStatusOnForward));
72 OnPropertyChanged(nameof(ShowPfdBackgroundStatusBar));
73 }
74
75 private void EnsurePfdBackgroundStatusSubscription()
76 {
77 if (_pfdBackgroundStatusWarmupSubscription is not null)
78 return;
79
80 _pfdBackgroundStatusWarmupSubscription = _ideDataBus.Subscribe<SolutionWarmupStateChanged>(_ =>
81 UiScheduler.Default.Post(RefreshPfdBackgroundStatusBar, DispatcherPriority.Background));
82
83 _pfdBackgroundStatusHciSubscription = _ideDataBus.Subscribe<HybridIndexStateChanged>(_ =>
84 UiScheduler.Default.Post(RefreshPfdBackgroundStatusBar, DispatcherPriority.Background));
85 }
86
87 internal void MarkHciReindexPendingForPfdStatus()
88 {
89 if (!_settings.HybridIndex.Enabled || !_settings.HybridIndex.AutoReindexOnSolutionOpen)
90 {
91 _hciReindexPending = false;
92 return;
93 }
94
95 _hciReindexPending = true;
96 RefreshPfdBackgroundStatusBar();
97 }
98
99 internal void RefreshPfdBackgroundStatusBar()
100 {
101 if (!_settings.SolutionWarmup.ShowBackgroundStatusOnPfd)
102 {
103 applyPfdStatusHidden(immediate: true);
104 return;
105 }
106
107 if (_settings.Agent.Environment.TimeAccounting.PfdInstrumentEnabled
108 && TryApplyVerifyEpochPfdStatus())
109 {
110 NotifyWorkspaceBackgroundStatusStripPlacement();
111 return;
112 }
113
114 var workspaceRoot = WorkspaceDirectoryFromSolutionPath.Resolve(Workspace.SolutionPath ?? "");
115 var solutionPath = Workspace.SolutionPath;
116
117 var snap = PfdBackgroundStatusPresentation.Compute(
118 workspaceRoot,
119 solutionPath,
120 SolutionWarmupLast,
121 HybridIndexLast,
122 _hciReindexPending,
123 _settings.HybridIndex);
124
125 if (PfdBackgroundStatusPresentation.MatchesScope(
126 HybridIndexLast?.WorkspaceRoot,
127 HybridIndexLast?.SolutionPath,
128 workspaceRoot,
129 solutionPath)
130 && string.IsNullOrWhiteSpace(HybridIndexLast?.LastError))
131 _hciReindexPending = false;
132
133 if (snap.Show)
134 {
135 StopPfdStatusHideTimer();
136 _pfdStatusVisibleSinceUtc = DateTimeOffset.UtcNow;
137 PfdBackgroundStatusText = snap.Text;
138 IsPfdBackgroundStatusCaution = snap.IsCaution;
139 PfdAgentEnvironmentCancelVisible = false;
140 NotifyWorkspaceBackgroundStatusStripPlacement();
141 return;
142 }
143
144 var elapsed = DateTimeOffset.UtcNow - _pfdStatusVisibleSinceUtc;
145 if (elapsed.TotalMilliseconds < PfdStatusMinVisibleMs && !string.IsNullOrWhiteSpace(PfdBackgroundStatusText))
146 {
147 schedulePfdStatusHide(TimeSpan.FromMilliseconds(PfdStatusMinVisibleMs) - elapsed);
148 return;
149 }
150
151 applyPfdStatusHidden(immediate: true);
152 }
153
154 private bool TryApplyVerifyEpochPfdStatus()
155 {
156 var snap = _verifyEpochInstrument.Snapshot();
157 if (!snap.IsVisible && string.IsNullOrWhiteSpace(snap.CompactLine))
158 return false;
159
160 StopPfdStatusHideTimer();
161 _pfdStatusVisibleSinceUtc = DateTimeOffset.UtcNow;
162 PfdBackgroundStatusText = snap.CompactLine;
163 IsPfdBackgroundStatusCaution = snap.IsCaution;
164 PfdAgentEnvironmentCancelVisible = snap.ShowCancel;
165 EnsureVerifyEpochActiveTicker();
166
167 if (!snap.IsVisible && !snap.IsCaution)
168 {
169 schedulePfdStatusHide(TimeSpan.FromMilliseconds(PfdStatusMinVisibleMs));
170 return true;
171 }
172
173 return true;
174 }
175
176 internal void EnsurePfdAgentEnvironmentTaskSubscription()
177 {
178 if (_pfdAgentEnvironmentTaskSubscription is not null)
179 return;
180
181 _pfdAgentEnvironmentTaskSubscription = _ideDataBus.Subscribe<AgentEnvironmentTaskChanged>(_ =>
182 {
183 UiScheduler.Default.Post(RefreshPfdBackgroundStatusBar, DispatcherPriority.Background);
184 });
185 }
186
187 [RelayCommand]
188 private void CancelPfdAgentEnvironmentVerify()
189 {
190 if (_agentEnvironment.CancelActive())
191 RefreshPfdBackgroundStatusBar();
192 }
193
194 [RelayCommand]
195 private void RetryPfdAgentEnvironmentVerify()
196 {
197 var solutionPath = Workspace.SolutionPath;
198 if (string.IsNullOrWhiteSpace(solutionPath))
199 return;
200
201 _agentEnvironment.StartVerify(solutionPath, AgentVerifyPolicy.Standard);
202 RefreshPfdBackgroundStatusBar();
203 }
204
205 private void EnsureVerifyEpochActiveTicker()
206 {
207 if (!_verifyEpochInstrument.IsActive)
208 {
209 StopVerifyEpochActiveTicker();
210 return;
211 }
212
213 if (_verifyEpochActiveTicker is not null)
214 return;
215
216 _verifyEpochActiveTicker = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
217 _verifyEpochActiveTicker.Tick += (_, _) =>
218 UiScheduler.Default.Post(RefreshPfdBackgroundStatusBar, DispatcherPriority.Background);
219 _verifyEpochActiveTicker.Start();
220 }
221
222 private void StopVerifyEpochActiveTicker()
223 {
224 if (_verifyEpochActiveTicker is null)
225 return;
226
227 _verifyEpochActiveTicker.Stop();
228 _verifyEpochActiveTicker = null;
229 }
230
231 [RelayCommand]
232 private void OpenPfdBackgroundStatusDetails()
233 {
234 if (_settings.Agent.Environment.TimeAccounting.PfdInstrumentEnabled
235 && _verifyEpochInstrument.IsVisible)
236 {
237 TogglePfdVerifyEpochExpandedCommand.Execute(null);
238 return;
239 }
240
241 if (!string.IsNullOrWhiteSpace(HybridIndexLast?.LastError)
242 || _hciReindexPending
243 || HybridIndexLast is null)
244 {
245 TryNavigateToMfdShellPage(MfdShellPage.HybridIndex);
246 return;
247 }
248
249 if (SolutionWarmupLast?.Lifecycle == SolutionWarmupLifecycle.Partial)
250 TryNavigateToMfdShellPage(MfdShellPage.HybridIndex);
251 }
252
253 private void schedulePfdStatusHide(TimeSpan delay)
254 {
255 StopPfdStatusHideTimer();
256 _pfdStatusHideTimer = DispatcherTimer.RunOnce(() =>
257 {
258 _pfdStatusHideTimer = null;
259 _verifyEpochInstrument.HideAfterIdle();
260 applyPfdStatusHidden(immediate: true);
261 }, delay);
262 }
263
264 private void applyPfdStatusHidden(bool immediate)
265 {
266 if (!immediate && IsPfdBackgroundStatusCaution)
267 return;
268
269 StopPfdStatusHideTimer();
270 StopVerifyEpochActiveTicker();
271 PfdBackgroundStatusText = null;
272 IsPfdBackgroundStatusCaution = false;
273 PfdAgentEnvironmentCancelVisible = false;
274 IsPfdVerifyEpochExpanded = false;
275 NotifyWorkspaceBackgroundStatusStripPlacement();
276 }
277
278 private void StopPfdStatusHideTimer()
279 {
280 _pfdStatusHideTimer?.Dispose();
281 _pfdStatusHideTimer = null;
282 }
283}
284
View only · write via MCP/CIDE