From c3f0769bd3d5f63fb4d40922dad81767e616c119 Mon Sep 17 00:00:00 2001 From: Reza Date: Mon, 19 Jan 2026 16:48:14 +0100 Subject: [PATCH] Add async single file generator VS extension Introduce a complete Visual Studio extension sample for asynchronous single file generation and minification of .js, .css, and .html files. Includes generator logic, async base class, context menu command, package registration, VSIX manifest, icons, and documentation. Adds supporting resources and MIT license. --- .../AsyncSingleFileGeneratorSample.sln | 30 +++ AsyncSingleFileGenerator/LICENSE | 21 ++ AsyncSingleFileGenerator/README.md | 153 +++++++++++++ AsyncSingleFileGenerator/art/code-behind.png | Bin 0 -> 1038 bytes AsyncSingleFileGenerator/art/context-menu.png | Bin 0 -> 5247 bytes .../art/property-grid.png | Bin 0 -> 3798 bytes .../src/Commands/ApplyCustomTool.cs | 44 ++++ .../BaseCodeGeneratorWithSiteAsync.cs | 209 ++++++++++++++++++ .../src/Generators/MinifyGenerator.cs | 70 ++++++ .../src/Properties/AssemblyInfo.cs | 16 ++ .../src/Resources/ApplyCustomToolPackage.ico | Bin 0 -> 428446 bytes .../src/Resources/Icon128x128.png | Bin 0 -> 1508 bytes .../src/SingleFileGeneratorSample.csproj | 128 +++++++++++ .../src/VSCommandTable.vsct | 34 +++ AsyncSingleFileGenerator/src/VSPackage.cs | 27 +++ AsyncSingleFileGenerator/src/VSPackage.resx | 140 ++++++++++++ .../src/source.extension.vsixmanifest | 23 ++ 17 files changed, 895 insertions(+) create mode 100644 AsyncSingleFileGenerator/AsyncSingleFileGeneratorSample.sln create mode 100644 AsyncSingleFileGenerator/LICENSE create mode 100644 AsyncSingleFileGenerator/README.md create mode 100644 AsyncSingleFileGenerator/art/code-behind.png create mode 100644 AsyncSingleFileGenerator/art/context-menu.png create mode 100644 AsyncSingleFileGenerator/art/property-grid.png create mode 100644 AsyncSingleFileGenerator/src/Commands/ApplyCustomTool.cs create mode 100644 AsyncSingleFileGenerator/src/Generators/BaseCodeGeneratorWithSiteAsync.cs create mode 100644 AsyncSingleFileGenerator/src/Generators/MinifyGenerator.cs create mode 100644 AsyncSingleFileGenerator/src/Properties/AssemblyInfo.cs create mode 100644 AsyncSingleFileGenerator/src/Resources/ApplyCustomToolPackage.ico create mode 100644 AsyncSingleFileGenerator/src/Resources/Icon128x128.png create mode 100644 AsyncSingleFileGenerator/src/SingleFileGeneratorSample.csproj create mode 100644 AsyncSingleFileGenerator/src/VSCommandTable.vsct create mode 100644 AsyncSingleFileGenerator/src/VSPackage.cs create mode 100644 AsyncSingleFileGenerator/src/VSPackage.resx create mode 100644 AsyncSingleFileGenerator/src/source.extension.vsixmanifest diff --git a/AsyncSingleFileGenerator/AsyncSingleFileGeneratorSample.sln b/AsyncSingleFileGenerator/AsyncSingleFileGeneratorSample.sln new file mode 100644 index 00000000..492d80bf --- /dev/null +++ b/AsyncSingleFileGenerator/AsyncSingleFileGeneratorSample.sln @@ -0,0 +1,30 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27620.3002 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SingleFileGeneratorSample", "src\SingleFileGeneratorSample.csproj", "{154F8BFA-6D1D-4FEF-95AC-95D4222B34DA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D8854F3A-BEAA-44E0-A140-413225798789}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {154F8BFA-6D1D-4FEF-95AC-95D4222B34DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154F8BFA-6D1D-4FEF-95AC-95D4222B34DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154F8BFA-6D1D-4FEF-95AC-95D4222B34DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154F8BFA-6D1D-4FEF-95AC-95D4222B34DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C1C1BBB7-1562-4820-8E03-795009DFE966} + EndGlobalSection +EndGlobal diff --git a/AsyncSingleFileGenerator/LICENSE b/AsyncSingleFileGenerator/LICENSE new file mode 100644 index 00000000..49d21669 --- /dev/null +++ b/AsyncSingleFileGenerator/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/AsyncSingleFileGenerator/README.md b/AsyncSingleFileGenerator/README.md new file mode 100644 index 00000000..36ddd3b2 --- /dev/null +++ b/AsyncSingleFileGenerator/README.md @@ -0,0 +1,153 @@ +# Single File Generator sample + +**Applies to Visual Studio 2017 and newer** + +This example shows how to write an Asynchronous Single File Generator in a Visual Studio extension that will output a nested file when the parent file is modified. + +> [!NOTE] +> Please note that there are two flavours of Single File Generators (aka Custom Tools) in Visual Studio: +> * Single File Generators that run synchronously on the UI thread +> * Asynchronous Single File Generators that run on a background thread (VS 2017 and newer) + +This sample demonstrates how to create an **Asynchronous Single File Generator** that is compatible with synchrouns one and tries to use async APIs where possible otherwise will fallback to sync APIs. + +To test the asynchronous behavior you will need to apply the code generator to a CPS-based project like a .NET Core project, or any SDK-style project. + +Clone the repo to test out the sample in Visual Studio 2017 yourself. + +## What is a Single File Generator +A Single File Generator is a mechanism that will auto-create and nest an output file when the source file changes. In this sample, the generator is applied to a **.js** file that will then output a **.min.js** file like so: + +![Nested file](art/code-behind.png) + +It is also known as a Custom Tool which can be manually set in the properties of supported files. + +![Property Grid](art/property-grid.png) + +The most well-known examples of existing generators are the ones creating a strongly typed C#/VB nested file for .resx files. + +Every time the file with the Custom Tool property is modified, the Single File Generator will execute to update the nested file. + +The nested file can be of any type - code, image, etc. - the sky is the limit. + +## Let's get started +Before we begin, make sure you have created a VSIX project in Visual Studio. See how to [create a VSIX project](https://docs.microsoft.com/en-us/visualstudio/extensibility/extensibility-hello-world) if you don't already have one ready. + +### Install NuGet package +The base classes for the Single File Generator are located in the [Microsoft.VisualStudio.TextTemplating.VSHost.15.0](https://www.nuget.org/packages/Microsoft.VisualStudio.TextTemplating.VSHost.15.0/) NuGet package, so go ahead and install that into your VSIX project. + +We also need the [Nuglify](https://www.nuget.org/packages/NUglify/) NuGet package that can minify JavaScript. + +### The generator +The generator is a simple class that inherits from the *BaseCodeGeneratorWithSite* and has 2 methods for us to implement. + +```c# +using Microsoft.VisualStudio.TextTemplating.VSHost; + +[Guid("82ca81c8-b507-4ba1-a33d-ff6cdad20e36")] // change this GUID +public sealed class MinifyCodeGenerator : BaseCodeGeneratorWithSite +{ + public override string GetDefaultExtension() + { + return ".min.js"; + } + + protected override byte[] GenerateCode(string inputFileName, string inputFileContent) + { + UglifyResult minified = Uglify.Js(inputFileContent); + return Encoding.UTF8.GetBytes(minified.Code); + } +} +``` + +[See full generator class in the source](src/Generators/MinifyGenerator.cs). + +That's it, you now have a Single File Generator that writes a .min.js file with the minified content of the source .js file. Now we must register the generator to make it work. + +### Registering the generator +Decorate your *Package* class with the `ProvideCodeGenerator` attribute. + +```c# +[ProvideCodeGenerator(typeof(MinifyCodeGenerator), nameof(MinifyCodeGenerator), "Minifies JavaScript", true)] +public class VSPackage : AsyncPackage +{ + ... +} +``` + +[See full Package class in the source](src/VSPackage.cs). + +> Note: if you don't have a *Package* class, add one to your project using the Add New Item dialog. The template is called *Visual Studio AsyncPackage* in VS 2017.7 + +Now the generator is registered, and you can now manually give the Custom Tool property on .js files the *MinifyCodeGenerator* value. + +That's it. We've now implemented a Single File Generator that minifies JavaScript files. + +However, it would be much easier if we give our users a command in the context-menu of files in Solution Explorer to add the value for them so they don't have to type *MinifyCodeGenerator* in the Property Grid manually. + +### Add the command button +In the .VSCT file you must specify a new button. It could look like this: + +```c# + +``` + +[See full .vsct file in the source](src/VSCommandTable.vsct). + +That will place the button in the context-menu in Solution Explorer. + +![Context Menu](art/context-menu.png) + +Then we need to add the command handler C# file. It will look similar to this: + +```c# +internal sealed class ApplyCustomTool +{ + private const int _commandId = 0x0100; + private static readonly Guid _commandSet = new Guid("4aaf93c0-70ae-4a4b-9fb6-1ad3997a9adf"); + private static DTE _dte; + + public static async Task InitializeAsync(AsyncPackage package) + { + _dte = await package.GetServiceAsync(typeof(DTE)) as DTE; + + var commandService = await package.GetServiceAsync((typeof(IMenuCommandService))) as IMenuCommandService; + var cmdId = new CommandID(_commandSet, _commandId); + var cmd = new MenuCommand(OnExecute, cmdId) + commandService.AddCommand(cmd); + } + + private static void OnExecute(object sender, EventArgs e) + { + ProjectItem item = _dte.SelectedItems.Item(1).ProjectItem; + item.Properties.Item("CustomTool").Value = nameof(MinifyCodeGenerator); + } +} +``` + +[See full command handler in the source](src/Commands/ApplyCustomTool.cs). + +And then finally initialize the command handler from the *Package* initialization method. + +```c# +protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) +{ + await ApplyCustomTool.InitializeAsync(this); +} +``` + +[See full Package class in the source](src/VSPackage.cs). + +### Single File Generators in the wild +Here are more samples of open source extensions implementing Single File Generators. + +* [VSIX Synchronizer](https://github.com/madskristensen/VsixSynchronizer) +* [Extensibility Tools](https://github.com/madskristensen/extensibilitytools) + +## License +[Apache 2.0](LICENSE) \ No newline at end of file diff --git a/AsyncSingleFileGenerator/art/code-behind.png b/AsyncSingleFileGenerator/art/code-behind.png new file mode 100644 index 0000000000000000000000000000000000000000..2e52dd0b4ae3b925ee7522d3d3589b1078c88bb1 GIT binary patch literal 1038 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 + /// Get a wrapper on the containing project system's Service provider + /// + /// + /// This is a limited service provider that can only reliably provide VxDTE::SID_SVSProjectItem + /// SID_SVSWebReferenceDynamicProperties IID_IVsHierarchy SID_SVsApplicationSettings + /// To get the global provider, call GetSite on IVSHierarchy or use the GlobalServiceProvider + /// property + /// + protected ServiceProvider SiteServiceProvider + { + get + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (serviceProvider == null) + { + var sp = site as Microsoft.VisualStudio.OLE.Interop.IServiceProvider; + serviceProvider = new ServiceProvider(sp); + } + + return serviceProvider; + } + } + + /// + /// Provides a wrapper on the global service provider for Visual Studio + /// + protected ServiceProvider GlobalServiceProvider + { + get + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (globalProvider == null) + { + var siteServiceProvider = SiteServiceProvider; + if (siteServiceProvider != null && siteServiceProvider.GetService(typeof(IVsHierarchy)) is IVsHierarchy vsHierarchy) + { + ErrorHandler.ThrowOnFailure(vsHierarchy.GetSite(out var ppSP)); + if (ppSP != null) + { + globalProvider = new ServiceProvider(ppSP); + } + } + } + + return globalProvider; + } + } + + /// + /// SetSite method of IOleObjectWithSite + /// + /// site for this object to use + public virtual void SetSite(object pUnkSite) + { + site = pUnkSite; + serviceProvider = null; + } + + /// + /// GetSite method of IOleObjectWithSite + /// + /// interface to get + /// array in which to stuff return value + public virtual void GetSite(ref Guid riid, out IntPtr ppvSite) + { + if (site == null) + { + Marshal.ThrowExceptionForHR(-2147467259); + } + + IntPtr iUnknownForObject = Marshal.GetIUnknownForObject(site); + Marshal.QueryInterface(iUnknownForObject, ref riid, out var ppv); + if (ppv == IntPtr.Zero) + { + Marshal.ThrowExceptionForHR(-2147467262); + } + + ppvSite = ppv; + } + + protected object GetService(Guid service) => SiteServiceProvider?.GetService(service); + + protected object GetService(Type service) => SiteServiceProvider?.GetService(service); + + int IVsSingleFileGenerator.DefaultExtension(out string pbstrDefaultExtension) + { + pbstrDefaultExtension = GetDefaultExtension(); + return VSConstants.S_OK; + } + + int IVsSingleFileGenerator.Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] rgbOutputFileContents, out uint pcbOutput, IVsGeneratorProgress pGenerateProgress) + { + try + { + IntPtr outputFileContents = IntPtr.Zero; + GenerateInternal(wszInputFilePath, bstrInputFileContents, wszDefaultNamespace, out outputFileContents, out var output, pGenerateProgress); + rgbOutputFileContents[0] = outputFileContents; + pcbOutput = (uint)output; + } + catch + { + pcbOutput = 0u; + rgbOutputFileContents[0] = IntPtr.Zero; + return VSConstants.E_FAIL; + } + + return VSConstants.S_OK; + } + + protected abstract string GetDefaultExtension(); + + private void GenerateInternal( + string inputFilePath, string inputFileContents, string defaultNamespace, + out IntPtr outputFileContents, out int output, IVsGeneratorProgress pGenerateProgress) + { + if (inputFileContents == null) + { + throw new ArgumentNullException(nameof(inputFileContents)); + } + + var array = ThreadHelper.JoinableTaskFactory.Run( + async () => await GenerateCodeAsync(inputFilePath, inputFileContents, defaultNamespace, pGenerateProgress)); + if (array == null) + { + outputFileContents = IntPtr.Zero; + output = 0; + } + else + { + output = array.Length; + outputFileContents = Marshal.AllocCoTaskMem(output); + Marshal.Copy(array, 0, outputFileContents, output); + } + } + + async Task IVsSingleFileGeneratorAsync.GetDefaultExtensionAsync(CancellationToken cancellationToken) + => GetDefaultExtension(); + + async Task IVsSingleFileGeneratorAsync.GenerateAsync(string inputFilePath, string inputFileContents, string defaultNamespace, Stream outputStream, IVsGeneratorProgress generatorProgress, CancellationToken cancellationToken) + { + var resultBytes = await GenerateCodeAsync(inputFilePath, inputFileContents, defaultNamespace, generatorProgress, cancellationToken); + if (resultBytes != null) + { + await outputStream.WriteAsync(resultBytes, 0, resultBytes.Length); + } + + return GeneratorResult.Success; + } + + protected abstract Task GenerateCodeAsync( + string inputFilePath, string inputFileContents, string defaultNamespace, + IVsGeneratorProgress generateProgress, CancellationToken cancellationToken = default); + + ~BaseCodeGeneratorWithSiteAsync() => Dispose(disposing: false); + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// In derived classes, override Dispose(bool disposing) and make sure you call this method + /// even in case of exceptions (in finally). + /// + /// True to release managed resources; false from finalizer. + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + try + { + if (disposing) + { + serviceProvider?.Dispose(); + serviceProvider = null; + + globalProvider?.Dispose(); + globalProvider = null; + } + } + finally + { + _disposed = true; + } + } +} diff --git a/AsyncSingleFileGenerator/src/Generators/MinifyGenerator.cs b/AsyncSingleFileGenerator/src/Generators/MinifyGenerator.cs new file mode 100644 index 00000000..bb6b2b89 --- /dev/null +++ b/AsyncSingleFileGenerator/src/Generators/MinifyGenerator.cs @@ -0,0 +1,70 @@ +namespace AsyncSingleFileGeneratorSample; + +using EnvDTE; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextTemplating.VSHost; +using NUglify; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +/// +/// Most of the magic is done in the BaseCodeGeneratorWithSiteAsync class, don't forget to check it out! +/// +[Guid("AE47AF68-696D-491E-B536-32F859E0B988")] +public sealed class MinifyCodeGenerator : BaseCodeGeneratorWithSiteAsync +{ + public const string Name = nameof(MinifyCodeGenerator); + public const string Description = "Generates a minified version of JavaScript, CSS and HTML files files."; + + protected override string GetDefaultExtension() + { + var item = GetService(typeof(ProjectItem)) as ProjectItem; + return ".min" + Path.GetExtension(item?.FileNames[1]); + } + + protected override async Task GenerateCodeAsync( + string inputFileName, string inputFileContent, string defaultNamespace, + IVsGeneratorProgress generateProgress, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(inputFileContent)) { return null; } + + var result = await MinifyAsync(inputFileName, inputFileContent); + + if (result.HasErrors) + { + return Encoding.UTF8.GetBytes("// Source file contains errors"); + } + else + { + return Encoding.UTF8.GetBytes(result.Code); + } + } + + private static Task MinifyAsync(string inputFileName, string inputFileContent) + { + // Note! Uglify does not have async methods, so we run it in a Task to avoid blocking. + + return Task.Run(() => + { + string ext = Path.GetExtension(inputFileName).ToLowerInvariant(); + + switch (ext) + { + case ".js": + return Uglify.Js(inputFileContent); + case ".css": + return Uglify.Css(inputFileContent); + case ".htm": + case ".html": + return Uglify.Html(inputFileContent); + } + + return new UglifyResult(inputFileContent, new List()); + }); + } +} \ No newline at end of file diff --git a/AsyncSingleFileGenerator/src/Properties/AssemblyInfo.cs b/AsyncSingleFileGenerator/src/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ddaa6ae2 --- /dev/null +++ b/AsyncSingleFileGenerator/src/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Async Single File Generator Sample")] +[assembly: AssemblyDescription("Shows how to implement an async single file generator for Visual Studio")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Reza Niroomand")] +[assembly: AssemblyProduct("Async Single File Generator Sample")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AsyncSingleFileGenerator/src/Resources/ApplyCustomToolPackage.ico b/AsyncSingleFileGenerator/src/Resources/ApplyCustomToolPackage.ico new file mode 100644 index 0000000000000000000000000000000000000000..d323b07fb8d8b417bfc12e7c46dea88bee17760c GIT binary patch literal 428446 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

N3mPu2mM3; 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 + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + true + 10.0 + + + + false + + + + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {154F8BFA-6D1D-4FEF-95AC-95D4222B34DA} + Library + Properties + AsyncSingleFileGeneratorSample + AsyncSingleFileGeneratorSample + v4.8 + true + true + true + true + true + false + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + Menus.ctmenu + + + + + true + VSPackage + + + + + + true + Always + + + + + 17.14.40265 + + + 17.14.2120 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 13.0.4 + + + 1.21.17 + + + + + + \ No newline at end of file diff --git a/AsyncSingleFileGenerator/src/VSCommandTable.vsct b/AsyncSingleFileGenerator/src/VSCommandTable.vsct new file mode 100644 index 00000000..b7494fec --- /dev/null +++ b/AsyncSingleFileGenerator/src/VSCommandTable.vsct @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AsyncSingleFileGenerator/src/VSPackage.cs b/AsyncSingleFileGenerator/src/VSPackage.cs new file mode 100644 index 00000000..6aac96e3 --- /dev/null +++ b/AsyncSingleFileGenerator/src/VSPackage.cs @@ -0,0 +1,27 @@ +namespace AsyncSingleFileGeneratorSample; + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TextTemplating.VSHost; +using Task = System.Threading.Tasks.Task; + +[Guid("2e927fa3-8684-47fc-9674-0046499860d3")] // Must match the GUID in the .vsct file +[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] +[InstalledProductRegistration("Single File Generator Sample", "", "1.0")] +[ProvideMenuResource("Menus.ctmenu", 1)] +[ProvideCodeGenerator(typeof(MinifyCodeGenerator), MinifyCodeGenerator.Name, MinifyCodeGenerator.Description, true)] +[ProvideUIContextRule("69760bd3-80f0-4901-818d-c4656aaa08e9", // Must match the GUID in the .vsct file + name: "UI Context", + expression: "js | css | html", // This will make the button only show on .js, .css and .htm(l) files + termNames: new[] { "js", "css", "html" }, + termValues: new[] { "HierSingleSelectionName:.js$", "HierSingleSelectionName:.css$", "HierSingleSelectionName:.html?$" })] +public sealed class VSPackage : AsyncPackage +{ + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await ApplyCustomTool.InitializeAsync(this); + } +} diff --git a/AsyncSingleFileGenerator/src/VSPackage.resx b/AsyncSingleFileGenerator/src/VSPackage.resx new file mode 100644 index 00000000..8d8bd213 --- /dev/null +++ b/AsyncSingleFileGenerator/src/VSPackage.resx @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ApplyCustomTool Extension + + + ApplyCustomTool Visual Studio Extension Detailed Info + + + Resources\ApplyCustomToolPackage.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/AsyncSingleFileGenerator/src/source.extension.vsixmanifest b/AsyncSingleFileGenerator/src/source.extension.vsixmanifest new file mode 100644 index 00000000..d790c55a --- /dev/null +++ b/AsyncSingleFileGenerator/src/source.extension.vsixmanifest @@ -0,0 +1,23 @@ + + + + + Single File Generator Sample + Shows how to implement a single file generator for Visual Studio + Resources\Icon128x128.png + Resources\Icon128x128.png + + + + + + + + + + + + + + +