Forge
csharpdeeb25a2
1using CascadeIDE.Cockpit.DataBus;
2using CascadeIDE.Features.Launch.Application;
3using CommunityToolkit.Mvvm.ComponentModel;
4using CommunityToolkit.Mvvm.Input;
5
6namespace CascadeIDE.ViewModels;
7
8/// <summary>Стартовый проект.</summary>
9public partial class MainWindowViewModel
10{
11 [ObservableProperty]
12 private string? _startupProjectCsprojFullPath;
13
14 [ObservableProperty]
15 private string _startupProjectShortLabel = "";
16
17 /// <summary>Краткая подпись для панели дерева: имя стартового проекта для F5.</summary>
18 public bool HasStartupProject => !string.IsNullOrEmpty(StartupProjectCsprojFullPath);
19
20 public string StartupProjectBanner =>
21 StartupProjectBannerProjection.Format(
22 HasStartupProject,
23 ShowLaunchProfilePicker,
24 SelectedLaunchProfileId,
25 StartupProjectShortLabel);
26
27 partial void OnStartupProjectCsprojFullPathChanged(string? value)
28 {
29 PublishToIdeDataBusAndRebuild(new StartupProjectPathChanged(value));
30 OnPropertyChanged(nameof(HasStartupProject));
31 OnPropertyChanged(nameof(StartupProjectBanner));
32 SetStartupProjectFromSelectionCommand.NotifyCanExecuteChanged();
33 ClearStartupProjectCommand.NotifyCanExecuteChanged();
34 }
35
36 partial void OnStartupProjectShortLabelChanged(string value) =>
37 OnPropertyChanged(nameof(StartupProjectBanner));
38
39 /// <summary>После загрузки дерева: прочитать <c>launch-profiles.toml</c> (миграция с <c>startup-project.json</c>) и проверить, что путь есть в решении.</summary>
40 public void RefreshStartupProjectAfterSolutionLoad()
41 {
42 StartupProjectCsprojFullPath = null;
43 StartupProjectShortLabel = "";
44
45 var sln = Workspace.SolutionPath;
46 var solutionDir = string.IsNullOrEmpty(sln)
47 ? null
48 : Services.BreakpointsFileService.GetWorkspaceRoot(sln);
49 if (string.IsNullOrEmpty(solutionDir))
50 {
51 RefreshLaunchProfilePickerFromStore();
52 return;
53 }
54
55 var resolved = StartupProjectRefreshProjection.ResolveAfterSolutionLoad(
56 sln,
57 Workspace.SolutionRoots,
58 solutionDir);
59 if (!string.IsNullOrEmpty(resolved.CsprojFullPath))
60 ApplyStartupProject(resolved.CsprojFullPath);
61
62 RefreshLaunchProfilePickerFromStore();
63 }
64
65 private void ApplyStartupProject(string csprojFullPath)
66 {
67 StartupProjectCsprojFullPath = csprojFullPath;
68 StartupProjectShortLabel = Path.GetFileNameWithoutExtension(csprojFullPath);
69 }
70
71 private void ClearStartupProjectInMemoryOnly()
72 {
73 StartupProjectCsprojFullPath = null;
74 StartupProjectShortLabel = "";
75 }
76
77 [RelayCommand(CanExecute = nameof(CanSetStartupProjectFromSelection))]
78 private async Task SetStartupProjectFromSelectionAsync()
79 {
80 var item = Workspace.SelectedSolutionItem;
81 var path = item?.FullPath;
82 if (string.IsNullOrEmpty(path) || !path.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
83 return;
84
85 var sln = Workspace.SolutionPath;
86 if (string.IsNullOrEmpty(sln))
87 return;
88
89 var solutionDir = Services.BreakpointsFileService.GetWorkspaceRoot(sln);
90 if (string.IsNullOrEmpty(solutionDir))
91 return;
92
93 var full = CanonicalFilePath.Normalize(path);
94 try
95 {
96 if (!LaunchProjectRelativePath.TryGetRelativeToSolutionDirectory(solutionDir, full, out var rel, out var relErr))
97 {
98 await ShowDebugInfoAsync("Стартовый проект",
99 string.IsNullOrEmpty(relErr) ? "Не удалось вычислить относительный путь к проекту." : relErr)
100 .ConfigureAwait(false);
101 return;
102 }
103
104 StartupProjectStore.Save(sln, rel);
105 ApplyStartupProject(full);
106 RefreshLaunchProfilePickerFromStore();
107 }
108 catch (Exception ex)
109 {
110 await ShowDebugInfoAsync("Стартовый проект", ex.Message).ConfigureAwait(false);
111 }
112 }
113
114 private bool CanSetStartupProjectFromSelection()
115 {
116 var p = Workspace.SelectedSolutionItem?.FullPath;
117 return !string.IsNullOrEmpty(p) && p.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase);
118 }
119
120 [RelayCommand(CanExecute = nameof(CanClearStartupProject))]
121 private void ClearStartupProject()
122 {
123 var sln = Workspace.SolutionPath;
124 if (!string.IsNullOrEmpty(sln))
125 StartupProjectStore.Clear(sln);
126 ClearStartupProjectInMemoryOnly();
127 }
128
129 private bool CanClearStartupProject() => HasStartupProject;
130
131 /// <summary>MSBuild + launch profile (ADR 0090) или унаследованный стартовый <c>.csproj</c>.</summary>
132 internal Task<DebugLaunchResolution?> TryResolveDebugLaunchForF5Async() =>
133 DebugLaunchForF5Orchestrator.TryResolveAsync(
134 Workspace.SolutionRoots,
135 CurrentFilePath,
136 Workspace.SelectedSolutionItem?.FullPath,
137 () => StartupProjectCsprojFullPath,
138 ApplyStartupProject,
139 Workspace.SolutionPath,
140 Services.BreakpointsFileService.GetWorkspaceRoot(Workspace.SolutionPath),
141 _dotnetRunner,
142 ShowDebugInfoAsync);
143
144 /// <summary>
145 /// Режим B <c>debug_launch</c> (MCP): <paramref name="profileName"/> или активный профиль; при явном <paramref name="mcpProgramArgs"/> — вместо аргументов из профиля.
146 /// </summary>
147 internal Task<string> DebugLaunchByProfileOrResolvedTargetAsync(
148 string workspacePath,
149 string? targetPath,
150 string? profileName,
151 string? netcoredbgPath,
152 IReadOnlyList<string>? mcpProgramArgs,
153 CancellationToken cancellationToken = default) =>
154 DebugLaunchByProfileMcpOrchestrator.RunAsync(
155 workspacePath,
156 targetPath,
157 profileName,
158 netcoredbgPath,
159 mcpProgramArgs,
160 _dotnetRunner,
161 DapDebug,
162 cancellationToken);
163
164}
165
View only · write via MCP/CIDE