Skip to content

Commit 3cba964

Browse files
committed
add RunTSGenerator
1 parent 811d9a6 commit 3cba964

6 files changed

Lines changed: 269 additions & 6 deletions

File tree

507 Bytes
Loading

Signum.TSCBuild/RunTSGenerator.cs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
using Microsoft.VisualStudio;
2+
using Microsoft.VisualStudio.Shell;
3+
using Microsoft.VisualStudio.Shell.Interop;
4+
using System;
5+
using System.ComponentModel.Design;
6+
using System.Globalization;
7+
using System.Threading.Tasks;
8+
using System.Collections.Generic;
9+
using Task = System.Threading.Tasks.Task;
10+
using System.IO;
11+
using System.Text;
12+
using Microsoft.VisualStudio.PlatformUI;
13+
using Microsoft.VisualStudio.Threading;
14+
using System.Linq;
15+
using Microsoft.Build.Execution;
16+
using Microsoft.Build.Framework;
17+
18+
namespace Signum.TSCBuild
19+
{
20+
internal sealed class RunTSGenerator
21+
{
22+
public const int RunTSGeneratorCommandId = 0x0112;
23+
public static readonly Guid CommandSet = new Guid("57018ec6-5e1b-4ac7-8226-30120d45e7c0");
24+
private readonly AsyncPackage package;
25+
26+
private RunTSGenerator(AsyncPackage package, OleMenuCommandService commandService)
27+
{
28+
this.package = package ?? throw new ArgumentNullException(nameof(package));
29+
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
30+
31+
var buildTSGenCommandID = new CommandID(CommandSet, RunTSGeneratorCommandId);
32+
var buildTSGenMenuItem = new OleMenuCommand(this.ExecuteRunTSGenerator, buildTSGenCommandID);
33+
buildTSGenMenuItem.BeforeQueryStatus += BuildTSGenMenuItem_BeforeQueryStatus;
34+
commandService.AddCommand(buildTSGenMenuItem);
35+
}
36+
37+
private void BuildTSGenMenuItem_BeforeQueryStatus(object sender, EventArgs e)
38+
{
39+
ThreadHelper.ThrowIfNotOnUIThread();
40+
var menuCommand = sender as OleMenuCommand;
41+
menuCommand.Visible = menuCommand.Enabled = IsSingleCsprojSelected();
42+
}
43+
44+
private bool IsSingleCsprojSelected()
45+
{
46+
ThreadHelper.ThrowIfNotOnUIThread();
47+
var monitorSelection = ServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).GetAwaiter().GetResult() as IVsMonitorSelection;
48+
if (monitorSelection == null)
49+
return false;
50+
51+
monitorSelection.GetCurrentSelection(out var hierarchyPtr, out var itemid, out _, out _);
52+
if (hierarchyPtr == IntPtr.Zero || itemid == (uint)VSConstants.VSITEMID.Nil)
53+
return false;
54+
55+
var hierarchy = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
56+
if (hierarchy == null)
57+
return false;
58+
59+
hierarchy.GetCanonicalName(itemid, out var itemFullPath);
60+
if (itemFullPath == null || !itemFullPath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
61+
return false;
62+
63+
return true;
64+
}
65+
66+
private async void ExecuteRunTSGenerator(object sender, EventArgs e)
67+
{
68+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
69+
var projectFile = GetSelectedProjectFile();
70+
if (projectFile == null)
71+
{
72+
VsShellUtilities.ShowMessageBox(
73+
this.package,
74+
"No .csproj project selected.",
75+
"Run TSGenerator",
76+
OLEMSGICON.OLEMSGICON_WARNING,
77+
OLEMSGBUTTON.OLEMSGBUTTON_OK,
78+
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
79+
return;
80+
}
81+
82+
await BuildProjectWithTSGeneratorEnabledAsync(projectFile);
83+
}
84+
85+
private string GetSelectedProjectFile()
86+
{
87+
ThreadHelper.ThrowIfNotOnUIThread();
88+
var monitorSelection = ServiceProvider.GetServiceAsync(typeof(SVsShellMonitorSelection)).GetAwaiter().GetResult() as IVsMonitorSelection;
89+
if (monitorSelection == null)
90+
return null;
91+
92+
monitorSelection.GetCurrentSelection(out var hierarchyPtr, out var itemid, out _, out _);
93+
if (hierarchyPtr == IntPtr.Zero || itemid == (uint)VSConstants.VSITEMID.Nil)
94+
return null;
95+
96+
var hierarchy = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
97+
if (hierarchy == null)
98+
return null;
99+
100+
hierarchy.GetCanonicalName(itemid, out var itemFullPath);
101+
102+
return itemFullPath;
103+
}
104+
105+
private async Task BuildProjectWithTSGeneratorEnabledAsync(string projectFile)
106+
{
107+
try
108+
{
109+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
110+
111+
var pane = await GetOutputPaneAsync();
112+
pane.Activate();
113+
pane.OutputStringThreadSafe("--- Run TSGenerator started ---\n");
114+
115+
string projectDir = Path.GetDirectoryName(projectFile);
116+
117+
// Invoke dotnet build with the TSGeneratorDisabled=false property
118+
var process = new System.Diagnostics.Process
119+
{
120+
StartInfo = new System.Diagnostics.ProcessStartInfo
121+
{
122+
FileName = "dotnet",
123+
//Arguments = $"build \"{projectFile}\" -p:TSGeneratorDisabled=false",
124+
Arguments = $"build -p:TSGeneratorDisabled=false",
125+
WorkingDirectory = projectDir,
126+
RedirectStandardOutput = true,
127+
RedirectStandardError = true,
128+
UseShellExecute = false,
129+
CreateNoWindow = true
130+
},
131+
EnableRaisingEvents = true
132+
};
133+
134+
StringBuilder sb = new StringBuilder();
135+
var tcs = new TaskCompletionSource<bool>();
136+
137+
process.OutputDataReceived += (s, e) =>
138+
{
139+
try
140+
{
141+
if (e.Data != null)
142+
{
143+
ThreadHelper.JoinableTaskFactory.Run(async () =>
144+
{
145+
pane.OutputStringThreadSafe(e.Data + "\n");
146+
sb.AppendLine(e.Data);
147+
await Task.CompletedTask;
148+
});
149+
}
150+
}
151+
catch (Exception ex)
152+
{
153+
ShowError(ex);
154+
}
155+
};
156+
157+
process.ErrorDataReceived += (s, e) =>
158+
{
159+
if (e.Data != null)
160+
ThreadHelper.JoinableTaskFactory.Run(() => { pane.OutputStringThreadSafe(e.Data + "\n"); return Task.CompletedTask; });
161+
};
162+
163+
process.Exited += (s, e) =>
164+
{
165+
tcs.TrySetResult(true);
166+
};
167+
168+
try
169+
{
170+
process.Start();
171+
}
172+
catch (Exception ex)
173+
{
174+
pane.OutputStringThreadSafe($"Failed to start 'dotnet': {ex.Message}\n");
175+
VsShellUtilities.ShowMessageBox(
176+
this.package,
177+
"Could not start 'dotnet'. Ensure the .NET SDK/CLI is installed and available on PATH.",
178+
"Run TSGenerator",
179+
OLEMSGICON.OLEMSGICON_CRITICAL,
180+
OLEMSGBUTTON.OLEMSGBUTTON_OK,
181+
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
182+
return;
183+
}
184+
185+
process.BeginOutputReadLine();
186+
process.BeginErrorReadLine();
187+
188+
await tcs.Task; // Wait for process to exit
189+
190+
pane.OutputStringThreadSafe("--- Run TSGenerator finished ---\n");
191+
192+
if (process.ExitCode != 0)
193+
{
194+
VsShellUtilities.ShowMessageBox(
195+
this.package,
196+
"TSGenerator run failed. See Output window for details.",
197+
"Run TSGenerator Error",
198+
OLEMSGICON.OLEMSGICON_CRITICAL,
199+
OLEMSGBUTTON.OLEMSGBUTTON_OK,
200+
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
201+
}
202+
}
203+
catch (Exception ex)
204+
{
205+
ShowError(ex);
206+
}
207+
}
208+
209+
private async Task<IVsOutputWindowPane> GetOutputPaneAsync()
210+
{
211+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
212+
var outputWindow = await ServiceProvider.GetServiceAsync(typeof(SVsOutputWindow)) as IVsOutputWindow;
213+
Guid paneGuid = new Guid("c7c7c7c7-c7c7-c7c7-c7c7-c7c7c7c7c7c7"); // Unique GUID for TSGenerator
214+
string paneTitle = "Run TSGenerator";
215+
outputWindow.CreatePane(ref paneGuid, paneTitle, 1, 1);
216+
outputWindow.GetPane(ref paneGuid, out IVsOutputWindowPane pane);
217+
return pane;
218+
}
219+
220+
private void ShowError(Exception ex)
221+
{
222+
VsShellUtilities.ShowMessageBox(
223+
this.package,
224+
$"An error occurred during TSGenerator run: {ex.Message}\nStackTrace:\n{ex.StackTrace}",
225+
"Run TSGenerator Error",
226+
OLEMSGICON.OLEMSGICON_CRITICAL,
227+
OLEMSGBUTTON.OLEMSGBUTTON_OK,
228+
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
229+
}
230+
231+
public static RunTSGenerator Instance { get; private set; }
232+
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => this.package;
233+
234+
public static async Task InitializeAsync(AsyncPackage package)
235+
{
236+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
237+
OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
238+
Instance = new RunTSGenerator(package, commandService);
239+
}
240+
241+
242+
}
243+
}

Signum.TSCBuild/Signum.TSCBuild.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
<ItemGroup>
4848
<Compile Include="CompileTypeScript.cs" />
4949
<Compile Include="Properties\AssemblyInfo.cs" />
50+
<Compile Include="RunTSGenerator.cs" />
5051
<Compile Include="Signum.TSCBuildPackage.cs" />
5152
</ItemGroup>
5253
<ItemGroup>

Signum.TSCBuild/Signum.TSCBuildPackage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
4848
// Do any initialization that requires the UI thread after switching to the UI thread.
4949
await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
5050
await Signum.TSCBuild.CompileTypeScript.InitializeAsync(this);
51+
await Signum.TSCBuild.RunTSGenerator.InitializeAsync(this);
5152
}
5253

5354
#endregion

Signum.TSCBuild/SignumTSCBuildPackage.vsct

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
<Group guid="guidSignumTSCBuildPackageCmdSet" id="ProjectContextMenuGroup" priority="0x0000">
99
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/>
1010
</Group>
11+
12+
<!-- Separate group so Run TSGenerator can be positioned below general Build -->
13+
<Group guid="guidSignumTSCBuildPackageBottomGroup" id="RunTSGeneratorGroup" priority="0x0200">
14+
<Parent guid="guidSHLMainMenu" id="IDM_VS_CTXT_PROJNODE"/>
15+
</Group>
1116
</Groups>
1217
<Buttons>
1318
<!-- Build TypeScript button in project context menu -->
@@ -18,19 +23,36 @@
1823
<ButtonText>Build TypeScript</ButtonText>
1924
</Strings>
2025
</Button>
26+
27+
<!-- Run TSGenerator button in its own group so it can appear below general Build -->
28+
<Button guid="guidSignumTSCBuildPackageCmdSet" id="RunTSGeneratorId" priority="0x0101" type="Button">
29+
<Parent guid="guidSignumTSCBuildPackageBottomGroup" id="RunTSGeneratorGroup" />
30+
<Icon guid="guidImages" id="bmpPic2" />
31+
<Strings>
32+
<ButtonText>Run TSGenerator (C# -> TS)</ButtonText>
33+
</Strings>
34+
</Button>
2135
</Buttons>
2236
<Bitmaps>
23-
<Bitmap guid="guidImages" href="Resources\CompileTypeScript.png" usedList="bmpPic1"/>
37+
<Bitmap guid="guidImages" href="Resources\\CompileTypeScript.png" usedList="bmpPic1,bmpPic2"/>
2438
</Bitmaps>
2539
</Commands>
2640
<Symbols>
2741
<GuidSymbol name="guidSignumTSCBuildPackage" value="{58ecfa23-b4d1-4b4c-bb23-b8ad57291e70}" />
2842
<GuidSymbol name="guidSignumTSCBuildPackageCmdSet" value="{57018ec6-5e1b-4ac7-8226-30120d45e7c0}">
2943
<IDSymbol name="ProjectContextMenuGroup" value="0x0110" />
3044
<IDSymbol name="BuildTypeScriptId" value="0x0111" />
45+
<IDSymbol name="RunTSGeneratorId" value="0x0112" />
46+
</GuidSymbol>
47+
48+
<!-- New GUID/group for placing Run TSGenerator below Build -->
49+
<GuidSymbol name="guidSignumTSCBuildPackageBottomGroup" value="{a2d5f6e1-3c2b-4a9f-8d4c-1b2e3f4a5b6c}">
50+
<IDSymbol name="RunTSGeneratorGroup" value="0x0120" />
3151
</GuidSymbol>
52+
3253
<GuidSymbol name="guidImages" value="{23cba6d2-98f6-4688-ac94-56b64f2a71d8}">
3354
<IDSymbol name="bmpPic1" value="1" />
55+
<IDSymbol name="bmpPic2" value="2" />
3456
</GuidSymbol>
3557
</Symbols>
3658
</CommandTable>

Signum/Signum.csproj

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,12 @@
77
<OutputType>Library</OutputType>
88
<NoWarn>8618</NoWarn>
99
<UserSecretsId>4236b9c6-a674-4f4f-8ce6-6a591967257e</UserSecretsId>
10+
<TSGeneratorDisabled>false</TSGeneratorDisabled>
1011
</PropertyGroup>
1112
<ItemGroup>
1213
<FrameworkReference Include="Microsoft.AspNetCore.App" />
1314
</ItemGroup>
1415

15-
<Target Name="PrintStartupDir" BeforeTargets="Build">
16-
<Message Text="MSBuildStartupDirectory = $(MSBuildStartupDirectory)" Importance="High" />
17-
</Target>
18-
19-
2016
<ItemGroup>
2117
<TypeScriptCompile Remove="node_modules\**" />
2218
</ItemGroup>

0 commit comments

Comments
 (0)