| 1 | using CascadeIDE.Models; |
| 2 | using CascadeIDE.Services; |
| 3 | using CascadeIDE.ViewModels; |
| 4 | using CommunityToolkit.Mvvm.Input; |
| 5 | |
| 6 | namespace CascadeIDE.Features.Debug; |
| 7 | |
| 8 | /// <summary>Relay: DAP отладка (композитор — <see cref="MainWindowViewModel"/>).</summary> |
| 9 | public sealed partial class MainWindowDebugSessionViewModel : ViewModelBase |
| 10 | { |
| 11 | private static readonly string[] DebugRelayUiPresentationNames = |
| 12 | [ |
| 13 | nameof(MainWindowViewModel.HasDebugSession), |
| 14 | nameof(MainWindowViewModel.IsDebugExecutionPaused), |
| 15 | nameof(MainWindowViewModel.IsDebugExecutionRunning), |
| 16 | nameof(MainWindowViewModel.IdeHealthDebugText), |
| 17 | nameof(MainWindowViewModel.IdeHealthDebugCockpitShort), |
| 18 | nameof(MainWindowViewModel.IdeHealthMountPayload), |
| 19 | nameof(MainWindowViewModel.PfdIdeHealthMountContext), |
| 20 | nameof(MainWindowViewModel.MfdIdeHealthMountContext), |
| 21 | ]; |
| 22 | |
| 23 | private readonly MainWindowViewModel _host; |
| 24 | |
| 25 | public MainWindowDebugSessionViewModel(MainWindowViewModel host) => _host = host; |
| 26 | |
| 27 | internal void NotifyRelayCommandsChanged() |
| 28 | { |
| 29 | DebugStartOrContinueCommand.NotifyCanExecuteChanged(); |
| 30 | DebugAttachCommand.NotifyCanExecuteChanged(); |
| 31 | DebugStopCommand.NotifyCanExecuteChanged(); |
| 32 | DebugStepOverCommand.NotifyCanExecuteChanged(); |
| 33 | DebugStepIntoCommand.NotifyCanExecuteChanged(); |
| 34 | DebugStepOutCommand.NotifyCanExecuteChanged(); |
| 35 | foreach (var name in DebugRelayUiPresentationNames) |
| 36 | _host.McpNotifyPropertyChanged(name); |
| 37 | } |
| 38 | |
| 39 | /// <summary><c>debug_launch</c> без JSON: тот же поток, что F5.</summary> |
| 40 | internal async Task<string> DebugLaunchInteractiveAsync() |
| 41 | { |
| 42 | await DebugStartOrContinueAsync().ConfigureAwait(true); |
| 43 | return "OK"; |
| 44 | } |
| 45 | |
| 46 | [RelayCommand(CanExecute = nameof(CanDebugStartOrContinue))] |
| 47 | private async Task DebugStartOrContinueAsync() |
| 48 | { |
| 49 | var dap = _host.DapDebug; |
| 50 | if (dap.HasActiveSession && dap.IsExecutionStopped) |
| 51 | { |
| 52 | try { await dap.ContinueAsync().ConfigureAwait(false); } |
| 53 | catch (Exception ex) { await ShowDebugInfoAsync("Отладка", ex.Message).ConfigureAwait(false); } |
| 54 | return; |
| 55 | } |
| 56 | |
| 57 | if (dap.HasActiveSession && !dap.IsExecutionStopped) |
| 58 | { |
| 59 | await ShowDebugInfoAsync("Отладка", "Выполнение не остановлено. Дождись брейкпоинта или останови отладку.").ConfigureAwait(false); |
| 60 | return; |
| 61 | } |
| 62 | |
| 63 | var ws = _host.GetWorkspacePath(); |
| 64 | if (string.IsNullOrEmpty(ws)) |
| 65 | { |
| 66 | await ShowDebugInfoAsync("Отладка", "Сначала открой решение — нужен каталог workspace для брейкпоинтов.").ConfigureAwait(false); |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | var res = await _host.TryResolveDebugLaunchForF5Async().ConfigureAwait(false); |
| 71 | if (res is not { } r) |
| 72 | { |
| 73 | var target = _host.RequestPickDebugTarget != null ? await _host.RequestPickDebugTarget().ConfigureAwait(false) : null; |
| 74 | if (string.IsNullOrEmpty(target)) |
| 75 | return; |
| 76 | r = new DebugLaunchResolution(target, null, null, null, OpenLaunchBrowser: false, LaunchUrl: null); |
| 77 | } |
| 78 | |
| 79 | try |
| 80 | { |
| 81 | _host.IsInstrumentationDockVisible = true; |
| 82 | _host.CurrentMfdShellPage = MfdShellPage.DebugStack; |
| 83 | _ = await dap.LaunchAsync( |
| 84 | ws, |
| 85 | r.TargetDllPath, |
| 86 | netcoredbgPath: null, |
| 87 | r.ProgramArgs, |
| 88 | r.Environment, |
| 89 | r.WorkingDirectoryRelativeToSolution).ConfigureAwait(false); |
| 90 | if (r.OpenLaunchBrowser) |
| 91 | KestrelLaunchBrowser.TryOpenAfterLaunch(r.Environment, r.LaunchUrl); |
| 92 | } |
| 93 | catch (Exception ex) |
| 94 | { |
| 95 | await ShowDebugInfoAsync("Ошибка запуска отладки", ex.Message).ConfigureAwait(false); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | private bool CanDebugStartOrContinue() |
| 100 | { |
| 101 | if (_host.DapDebug.HasActiveSession) |
| 102 | return _host.DapDebug.IsExecutionStopped; |
| 103 | return true; |
| 104 | } |
| 105 | |
| 106 | [RelayCommand(CanExecute = nameof(CanDebugAttach))] |
| 107 | private async Task DebugAttachAsync() |
| 108 | { |
| 109 | var ws = _host.GetWorkspacePath(); |
| 110 | if (string.IsNullOrEmpty(ws)) |
| 111 | { |
| 112 | await ShowDebugInfoAsync("Отладка", "Сначала открой решение.").ConfigureAwait(false); |
| 113 | return; |
| 114 | } |
| 115 | |
| 116 | var pid = _host.RequestAttachProcessId != null ? await _host.RequestAttachProcessId().ConfigureAwait(false) : null; |
| 117 | if (pid is null or <= 0) |
| 118 | return; |
| 119 | |
| 120 | try |
| 121 | { |
| 122 | _host.IsInstrumentationDockVisible = true; |
| 123 | _host.CurrentMfdShellPage = MfdShellPage.DebugStack; |
| 124 | _ = await _host.DapDebug.AttachAsync(ws, pid.Value, targetPath: null, netcoredbgPath: null).ConfigureAwait(false); |
| 125 | } |
| 126 | catch (Exception ex) |
| 127 | { |
| 128 | await ShowDebugInfoAsync("Ошибка присоединения", ex.Message).ConfigureAwait(false); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | private bool CanDebugAttach() => !_host.DapDebug.HasActiveSession; |
| 133 | |
| 134 | [RelayCommand(CanExecute = nameof(CanDebugStop))] |
| 135 | private async Task DebugStopAsync() |
| 136 | { |
| 137 | try { await _host.DapDebug.StopAsync().ConfigureAwait(false); } |
| 138 | catch (Exception ex) { await ShowDebugInfoAsync("Отладка", ex.Message).ConfigureAwait(false); } |
| 139 | } |
| 140 | |
| 141 | private bool CanDebugStop() => _host.DapDebug.HasActiveSession; |
| 142 | |
| 143 | [RelayCommand(CanExecute = nameof(CanDebugStep))] |
| 144 | private async Task DebugStepOverAsync() |
| 145 | { |
| 146 | try { _ = await _host.DapDebug.StepOverAsync().ConfigureAwait(false); } |
| 147 | catch (Exception ex) { await ShowDebugInfoAsync("Отладка", ex.Message).ConfigureAwait(false); } |
| 148 | } |
| 149 | |
| 150 | [RelayCommand(CanExecute = nameof(CanDebugStep))] |
| 151 | private async Task DebugStepIntoAsync() |
| 152 | { |
| 153 | try { _ = await _host.DapDebug.StepIntoAsync().ConfigureAwait(false); } |
| 154 | catch (Exception ex) { await ShowDebugInfoAsync("Отладка", ex.Message).ConfigureAwait(false); } |
| 155 | } |
| 156 | |
| 157 | [RelayCommand(CanExecute = nameof(CanDebugStep))] |
| 158 | private async Task DebugStepOutAsync() |
| 159 | { |
| 160 | try { _ = await _host.DapDebug.StepOutAsync().ConfigureAwait(false); } |
| 161 | catch (Exception ex) { await ShowDebugInfoAsync("Отладка", ex.Message).ConfigureAwait(false); } |
| 162 | } |
| 163 | |
| 164 | private bool CanDebugStep() => _host.DapDebug.HasActiveSession && _host.DapDebug.IsExecutionStopped; |
| 165 | |
| 166 | private Task ShowDebugInfoAsync(string title, string message) => |
| 167 | _host.ShowDebugInfoAsync(title, message); |
| 168 | } |
| 169 | |