diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 31da667c..b9ba6e3f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -15,6 +15,10 @@ jobs: - uses: actions/checkout@v3 - name: setup-msbuild uses: microsoft/setup-msbuild@v1.1.3 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + global-json-file: global.json - name: Restore dependencies run: msbuild /t:restore New_Extensibility_Model/Samples/Samples.sln - name: Build diff --git a/New_Extensibility_Model/Samples/.editorconfig b/New_Extensibility_Model/Samples/.editorconfig index 52042730..7e9e5750 100644 --- a/New_Extensibility_Model/Samples/.editorconfig +++ b/New_Extensibility_Model/Samples/.editorconfig @@ -4,7 +4,7 @@ root = true [*] -indent_style = tab +indent_style = spaces # (Please don't specify an indent_size here; that has too many unintended consequences.) @@ -125,7 +125,7 @@ csharp_indent_labels = flush_left # Prefer "var" everywhere csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion -csharp_style_var_elsewhere = false:warning +csharp_style_var_elsewhere = false:suggestion # Prefer method-like constructs to have a block body csharp_style_expression_bodied_methods = false:none diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/CommandParentingSample/.vsextension/string-resources.json index 8b14f271..5e1a4891 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/CommandParentingSample/.vsextension/string-resources.json @@ -1,4 +1,4 @@ { - "CommandParentingSample.SampleCommand.DisplayName": "My Command", - "CommandParentingSample.ToolBar.DisplayName": "Command Parenting Sample Toolbar" + "CommandParentingSample.SampleCommand.DisplayName": "My Command", + "CommandParentingSample.ToolBar.DisplayName": "Command Parenting Sample Toolbar" } \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSample.csproj b/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSample.csproj index 96e09826..64f777d4 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSample.csproj +++ b/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSample.csproj @@ -1,26 +1,18 @@  - - net6.0 - enable - enable - 11 - true - SA1633;SA1600;CA1303;CA1016;CA1031;CA1812;$(NoWarn) + + net8.0-windows8.0 + enable + enable + 11 + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - - - - - - - - - - PreserveNewest - - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + + + + + diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSampleExtension.cs b/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSampleExtension.cs index fce70c1d..47b62c59 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSampleExtension.cs +++ b/New_Extensibility_Model/Samples/CommandParentingSample/CommandParentingSampleExtension.cs @@ -12,19 +12,20 @@ namespace CommandParentingSample; [VisualStudioContribution] public class CommandParentingSampleExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "CommandParentingSample.c30b7870-76bc-4ef6-93e8-15b9a54c9e2b", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Command Parenting Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "CommandParentingSample.c30b7870-76bc-4ef6-93e8-15b9a54c9e2b", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Command Parenting Sample Extension", + description: "Sample extension demonstrating command parenting"), + }; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } } diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/ExtensionCommandConfiguration.cs b/New_Extensibility_Model/Samples/CommandParentingSample/ExtensionCommandConfiguration.cs index 3209e00b..159ce7f4 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/ExtensionCommandConfiguration.cs +++ b/New_Extensibility_Model/Samples/CommandParentingSample/ExtensionCommandConfiguration.cs @@ -1,16 +1,19 @@ -namespace CommandParentingSample; +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace CommandParentingSample; using Microsoft.VisualStudio.Extensibility; using Microsoft.VisualStudio.Extensibility.Commands; internal static class ExtensionCommandConfiguration { - [VisualStudioContribution] - public static ToolbarConfiguration ToolBar => new("%CommandParentingSample.ToolBar.DisplayName%") - { - Children = new[] - { - ToolbarChild.Command(), - }, - }; + [VisualStudioContribution] + public static ToolbarConfiguration ToolBar => new("%CommandParentingSample.ToolBar.DisplayName%") + { + Children = new[] + { + ToolbarChild.Command(), + }, + }; } diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/README.md b/New_Extensibility_Model/Samples/CommandParentingSample/README.md index af41665d..4cb1f984 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/README.md +++ b/New_Extensibility_Model/Samples/CommandParentingSample/README.md @@ -1,10 +1,73 @@ --- title: Command Parenting Sample reference -date: 2022-10-14 +date: 2023-10-17 --- ## Command Parenting Sample -This extension is meant to act as a sample for how to author a command that can be parented to different aspects of the IDE. The command in this extension is parented to a new Toolbar created by the extension, the context menu when a project item is selected in the Solution Explorer, the context menu when a project is selected in the Solution Explorer, and the solution is selected in the Solution Explorer. +This extension is meant to act as a sample for how to author a command that can be parented to different aspects of the IDE. The command in this extension is parented to a new Toolbar created by the extension, the context menu when a project item is selected in the Solution Explorer, the context menu when a project is selected in the Solution Explorer, and the solution is selected in the Solution Explorer. -See also, [Add commands](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/command/command). +## Walkthrough: Command Parenting Sample + +This extension is meant to act as a sample for how to author a command that can be parented to different aspects of the IDE. The command in this extension is parented to a new Toolbar created by the extension, the context menu when a project item is selected in the `Solution Explorer`, the context menu when a project is selected in the `Solution Explorer`, and the context menu when a solution is selected in the `Solution Explorer`. + +### Command definition + +The extension contains a code file that defines a command and its properties starting with the `VisualStudioContribution` class attribute which makes the command available to Visual Studio: + +```csharp +[VisualStudioContribution] +internal class SampleCommand : Command +{ +``` + +The `VisualStudioContribution` attribute registers the command using the class full type name `CommandParentingSample.SampleCommand` as its unique identifier. + +The `CommandConfiguration` property defines information about the command that are available to Visual Studio even before the extension is loaded: + +```csharp + public override CommandConfiguration CommandConfiguration => new("%CommandParentingSample.SampleCommand.DisplayName%") + { + Placements = new[] + { + // File in project context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 521, priority: 0), + + // Project context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 518, priority: 0), + + // Solution context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 537, priority: 0), + }, + }; +``` + +The command is placed in the in 3 different places in the IDE using the `CommandPlacement.VsctParent` method. The command will always be enabled and visible. + +### Toolbar Definition + +Different from commands, toolbars are configured as static properties and the `VisualStudioContribution` attribute is placed on the property itself instead of the owning class. The attribute registers the toolbar using the owning class full type name plus the property name, `CommandParentingSample.ExtensionCommandConfiguration.ToolBar`, as its unique identifier. + +The `ToolbarConfiguration` property defines information about the toolbar that are available to Visual Studio even before the extension is loaded: + +```csharp + [VisualStudioContribution] + public static ToolbarConfiguration ToolBar => new("%CommandParentingSample.ToolBar.DisplayName%") + { + Children = new[] + { + ToolbarChild.Command(), + }, + }; +``` + +By default, all toolbars registered this way are parented to the `Standard Toolbar Tray` in Visual Studio. The toolbar can be made visible by clicking the command under `View` -> `Toolbars` -> `Command Parenting Sample Toolbar`. + +The `SampleCommand` from earlier is parented onto this toolbar using `ToolbarChild.Command()` in the toolbar's `Children`. + +### Additional Readings + +More information can be found at: + +- [Add Visual Studio commands](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/command/command) +- [Menus and Toolbars overview](https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/command/menus-and-toolbars?view=vs-2022) diff --git a/New_Extensibility_Model/Samples/CommandParentingSample/SampleCommand.cs b/New_Extensibility_Model/Samples/CommandParentingSample/SampleCommand.cs index 017d9b5f..e9c34692 100644 --- a/New_Extensibility_Model/Samples/CommandParentingSample/SampleCommand.cs +++ b/New_Extensibility_Model/Samples/CommandParentingSample/SampleCommand.cs @@ -11,30 +11,25 @@ namespace CommandParentingSample; [VisualStudioContribution] internal class SampleCommand : Command { - public SampleCommand(VisualStudioExtensibility extensibility) - : base(extensibility) - { - } + /// + public override CommandConfiguration CommandConfiguration => new("%CommandParentingSample.SampleCommand.DisplayName%") + { + Placements = new[] + { + // File in project context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 521, priority: 0), - /// - public override CommandConfiguration CommandConfiguration => new("%CommandParentingSample.SampleCommand.DisplayName%") - { - Placements = new[] - { - // File in project context menu - CommandPlacement.FromVsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), 521), + // Project context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 518, priority: 0), - // Project context menu - CommandPlacement.FromVsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), 518), + // Solution context menu + CommandPlacement.VsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), id: 537, priority: 0), + }, + }; - // Solution context menu - CommandPlacement.FromVsctParent(new Guid("{d309f791-903f-11d0-9efc-00a0c911004f}"), 537), - }, - }; - - /// - public override Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } + /// + public override Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } } diff --git a/New_Extensibility_Model/Samples/CommentRemover/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/CommentRemover/.vsextension/string-resources.json new file mode 100644 index 00000000..21a4a6a6 --- /dev/null +++ b/New_Extensibility_Model/Samples/CommentRemover/.vsextension/string-resources.json @@ -0,0 +1,9 @@ +{ + "CommentRemover.CommentRemoverMenu.DisplayName": "Comments", + "CommentRemover.RemoveAllComments.DisplayName": "Remove All", + "CommentRemover.RemoveAllExceptTaskComments.DisplayName": "Remove All Except Tasks", + "CommentRemover.RemoveAllExceptXmlDocComments.DisplayName": "Remove All Except Xml Docs", + "CommentRemover.RemoveRegions.DisplayName": "Remove Regions", + "CommentRemover.RemoveTasks.DisplayName": "Remove Tasks", + "CommentRemover.RemoveXmlDocComments.DisplayName": "Remove Xml Docs" +} \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/CommentRemover/BaseCommand.cs b/New_Extensibility_Model/Samples/CommentRemover/BaseCommand.cs new file mode 100644 index 00000000..221797de --- /dev/null +++ b/New_Extensibility_Model/Samples/CommentRemover/BaseCommand.cs @@ -0,0 +1,130 @@ +namespace CommentRemover; + +using EnvDTE; +using EnvDTE80; +using Microsoft; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Threading; +using System.Diagnostics; +using System.Text.RegularExpressions; + +internal abstract class CommentRemoverCommand : Microsoft.VisualStudio.Extensibility.Commands.Command +{ + protected static readonly ActivationConstraint CommandEnabledWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveSelectionFileName, @"\.(cs|vb|fs)$"); + + private static readonly string[] TaskCaptions = { "TODO", "HACK", "UNDONE", "UNRESOLVEDMERGECONFLICT" }; + + public CommentRemoverCommand( + TraceSource traceSource, + AsyncServiceProviderInjection dte, + MefInjection bufferTagAggregatorFactoryService, + MefInjection editorAdaptersFactoryService, + AsyncServiceProviderInjection textManager) + { + this.TraceSource = traceSource; + this.Dte = dte; + this.BufferTagAggregatorFactoryService = bufferTagAggregatorFactoryService; + this.EditorAdaptersFactoryService = editorAdaptersFactoryService; + this.TextManager = textManager; + } + + protected AsyncServiceProviderInjection Dte { get; private set; } + + protected MefInjection BufferTagAggregatorFactoryService { get; private set; } + + protected MefInjection EditorAdaptersFactoryService { get; private set; } + + protected AsyncServiceProviderInjection TextManager { get; private set; } + + protected TraceSource TraceSource { get; private set; } + + protected static bool IsLineEmpty(ITextSnapshotLine line) + { + var text = line.GetText().Trim(); + + return string.IsNullOrWhiteSpace(text) + || text == "" + || text == "<%%>" + || text == "<%" + || text == "%>" + || Regex.IsMatch(text, @""); + } + + protected static bool IsXmlDocComment(ITextSnapshotLine line) + { + var text = line.GetText().Trim(); + Microsoft.VisualStudio.Utilities.IContentType contentType = line.Snapshot.TextBuffer.ContentType; + + if (contentType.IsOfType("CSharp") && text.StartsWith("///", StringComparison.Ordinal)) + { + return true; + } + + if (contentType.IsOfType("FSharp") && text.StartsWith("///", StringComparison.Ordinal)) + { + return true; + } + + if (contentType.IsOfType("Basic") && text.StartsWith("'''", StringComparison.Ordinal)) + { + return true; + } + + return false; + } + + protected static bool ContainsTaskComment(ITextSnapshotLine line) + { + string text = line.GetText().ToUpperInvariant(); + + foreach (var task in TaskCaptions) + { + if (text.Contains(task + ":")) + { + return true; + } + } + + return false; + } + + protected async Task> GetClassificationSpansAsync(IWpfTextView view, string classificationName) + { + if (view is null) + { + return Enumerable.Empty(); + } + + IBufferTagAggregatorFactoryService bufferTagAggregatorFactoryService = await this.BufferTagAggregatorFactoryService.GetServiceAsync(); + ITagAggregator classifier = bufferTagAggregatorFactoryService.CreateTagAggregator(view.TextBuffer); + var snapshot = new SnapshotSpan(view.TextBuffer.CurrentSnapshot, 0, view.TextBuffer.CurrentSnapshot.Length); + + return from s in classifier.GetTags(snapshot).Reverse() + where s.Tag.ClassificationType.Classification.IndexOf(classificationName, StringComparison.OrdinalIgnoreCase) > -1 + select s.Span; + } + + protected async Task GetCurrentTextViewAsync() + { + IVsEditorAdaptersFactoryService editorAdapter = await this.EditorAdaptersFactoryService.GetServiceAsync(); + var view = editorAdapter.GetWpfTextView(await this.GetCurrentNativeTextViewAsync()); + Assumes.Present(view); + return view; + } + + protected async Task GetCurrentNativeTextViewAsync() + { + var textManager = await this.TextManager.GetServiceAsync(); + ErrorHandler.ThrowOnFailure(textManager.GetActiveView(1, null, out IVsTextView activeView)); + return activeView; + } +} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.csproj b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.csproj new file mode 100644 index 00000000..619f486d --- /dev/null +++ b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.csproj @@ -0,0 +1,28 @@ + + + + net472 + enable + enable + 11 + $(NoWarn);CS1591;CA1812;CA1303;SA1600 + $(NoWarn);SA1633 + + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + + true + + + + + + + + + + + + + + diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.sln b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.sln deleted file mode 100644 index 2bc7859f..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover.sln +++ /dev/null @@ -1,38 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32210.238 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommentRemoverContainer", "CommentRemoverContainer\CommentRemoverContainer.csproj", "{54B4B640-D71B-493D-80CF-DD37B17F1E75}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{21DFC4C7-A50F-4129-ABFD-841CC37A0848}" - ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml - CHANGELOG.md = CHANGELOG.md - README.md = README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommentRemover", "CommentRemover\CommentRemover.csproj", "{ECCF2CFA-84EC-458B-A226-9D2E363F1647}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {54B4B640-D71B-493D-80CF-DD37B17F1E75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54B4B640-D71B-493D-80CF-DD37B17F1E75}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54B4B640-D71B-493D-80CF-DD37B17F1E75}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54B4B640-D71B-493D-80CF-DD37B17F1E75}.Release|Any CPU.Build.0 = Release|Any CPU - {ECCF2CFA-84EC-458B-A226-9D2E363F1647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECCF2CFA-84EC-458B-A226-9D2E363F1647}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECCF2CFA-84EC-458B-A226-9D2E363F1647}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECCF2CFA-84EC-458B-A226-9D2E363F1647}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E59C05D7-6652-41A5-BB6A-A48420CA6C8E} - EndGlobalSection -EndGlobal diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/.vsextension/string-resources.json deleted file mode 100644 index 5cb1460a..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/.vsextension/string-resources.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "CommentRemover.CommentRemoverMenu.DisplayName": "Comments", - "CommentRemover.RemoveAllComments.DisplayName": "Remove All", - "CommentRemover.RemoveAllExceptTaskComments.DisplayName": "Remove All Except Tasks", - "CommentRemover.RemoveAllExceptXmlDocComments.DisplayName": "Remove All Except Xml Docs", - "CommentRemover.RemoveRegions.DisplayName": "Remove Regions", - "CommentRemover.RemoveTasks.DisplayName": "Remove Tasks", - "CommentRemover.RemoveXmlDocComments.DisplayName": "Remove Xml Docs" -} \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/BaseCommand.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/BaseCommand.cs deleted file mode 100644 index 435b113a..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/BaseCommand.cs +++ /dev/null @@ -1,132 +0,0 @@ -namespace CommentRemover; - -using EnvDTE; -using EnvDTE80; -using Microsoft; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Threading; -using System.Diagnostics; -using System.Text.RegularExpressions; - -internal abstract class CommentRemoverCommand : Microsoft.VisualStudio.Extensibility.Commands.Command -{ - protected static readonly ActivationConstraint CommandEnabledWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveSelectionFileName, @"\.(cs|vb|fs)$"); - - private static readonly string[] TaskCaptions = { "TODO", "HACK", "UNDONE", "UNRESOLVEDMERGECONFLICT" }; - - public CommentRemoverCommand( - VisualStudioExtensibility extensibility, - TraceSource traceSource, - AsyncServiceProviderInjection dte, - MefInjection bufferTagAggregatorFactoryService, - MefInjection editorAdaptersFactoryService, - AsyncServiceProviderInjection textManager) - : base(extensibility) - { - this.TraceSource = traceSource; - this.Dte = dte; - this.BufferTagAggregatorFactoryService = bufferTagAggregatorFactoryService; - this.EditorAdaptersFactoryService = editorAdaptersFactoryService; - this.TextManager = textManager; - } - - protected AsyncServiceProviderInjection Dte { get; private set; } - - protected MefInjection BufferTagAggregatorFactoryService { get; private set; } - - protected MefInjection EditorAdaptersFactoryService { get; private set; } - - protected AsyncServiceProviderInjection TextManager { get; private set; } - - protected TraceSource TraceSource { get; private set; } - - protected static bool IsLineEmpty(ITextSnapshotLine line) - { - var text = line.GetText().Trim(); - - return string.IsNullOrWhiteSpace(text) - || text == "" - || text == "<%%>" - || text == "<%" - || text == "%>" - || Regex.IsMatch(text, @""); - } - - protected static bool IsXmlDocComment(ITextSnapshotLine line) - { - var text = line.GetText().Trim(); - Microsoft.VisualStudio.Utilities.IContentType contentType = line.Snapshot.TextBuffer.ContentType; - - if (contentType.IsOfType("CSharp") && text.StartsWith("///", StringComparison.Ordinal)) - { - return true; - } - - if (contentType.IsOfType("FSharp") && text.StartsWith("///", StringComparison.Ordinal)) - { - return true; - } - - if (contentType.IsOfType("Basic") && text.StartsWith("'''", StringComparison.Ordinal)) - { - return true; - } - - return false; - } - - protected static bool ContainsTaskComment(ITextSnapshotLine line) - { - string text = line.GetText().ToUpperInvariant(); - - foreach (var task in TaskCaptions) - { - if (text.Contains(task + ":")) - { - return true; - } - } - - return false; - } - - protected async Task> GetClassificationSpansAsync(IWpfTextView view, string classificationName) - { - if (view is null) - { - return Enumerable.Empty(); - } - - IBufferTagAggregatorFactoryService bufferTagAggregatorFactoryService = await this.BufferTagAggregatorFactoryService.GetServiceAsync(); - ITagAggregator classifier = bufferTagAggregatorFactoryService.CreateTagAggregator(view.TextBuffer); - var snapshot = new SnapshotSpan(view.TextBuffer.CurrentSnapshot, 0, view.TextBuffer.CurrentSnapshot.Length); - - return from s in classifier.GetTags(snapshot).Reverse() - where s.Tag.ClassificationType.Classification.IndexOf(classificationName, StringComparison.OrdinalIgnoreCase) > -1 - select s.Span; - } - - protected async Task GetCurentTextViewAsync() - { - IVsEditorAdaptersFactoryService editorAdapter = await this.EditorAdaptersFactoryService.GetServiceAsync(); - var view = editorAdapter.GetWpfTextView(await this.GetCurrentNativeTextViewAsync()); - Assumes.Present(view); - return view; - } - - protected async Task GetCurrentNativeTextViewAsync() - { - var textManager = await this.TextManager.GetServiceAsync(); - ErrorHandler.ThrowOnFailure(textManager.GetActiveView(1, null, out IVsTextView activeView)); - return activeView; - } -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemover.csproj b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemover.csproj deleted file mode 100644 index 5a3d394d..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemover.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net472 - enable - enable - 11 - true - SA1633;SA1600;CA1303;CA1016;CA1031;CA1812;$(NoWarn>) - - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - - - - - - - - - - - PreserveNewest - - - - diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemoverExtension.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemoverExtension.cs deleted file mode 100644 index 71af4ff0..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/CommentRemoverExtension.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CommentRemover; - -using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.Extensibility; - -[VisualStudioContribution] -public class CommentRemoverExtension : Extension -{ - public override ExtensionConfiguration ExtensionConfiguration => new() { RequiresInProcessHosting = true }; - - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/ExtensionCommandConfiguration.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/ExtensionCommandConfiguration.cs deleted file mode 100644 index 3474b7ea..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/ExtensionCommandConfiguration.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace CommentRemover; - -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Commands; - -internal static class ExtensionCommandConfiguration -{ - [VisualStudioContribution] - public static MenuConfiguration CommentRemoverMenu => new("%CommentRemover.CommentRemoverMenu.DisplayName%") - { - Priority = 1, - Placements = new[] - { - CommandPlacement.KnownPlacements.ExtensionsMenu, - }, - Children = new[] - { - MenuChild.Command(), - MenuChild.Command(), - MenuChild.Command(), - MenuChild.Separator, - MenuChild.Command(), - MenuChild.Command(), - MenuChild.Separator, - MenuChild.Command(), - }, - }; -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/Images/DeleteRegions.xaml b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/Images/DeleteRegions.xaml deleted file mode 100644 index 5f000cf3..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/Images/DeleteRegions.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllComments.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllComments.cs deleted file mode 100644 index cdc56db6..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllComments.cs +++ /dev/null @@ -1,161 +0,0 @@ -namespace CommentRemover; - -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Commands; -using Microsoft.VisualStudio.Extensibility.Shell; -using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.TextManager.Interop; -using System.Diagnostics; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -[VisualStudioContribution] -internal class RemoveAllComments : CommentRemoverCommand -{ - private const string CommandDescription = "%CommentRemover.RemoveAllComments.DisplayName%"; - - public RemoveAllComments( - VisualStudioExtensibility extensibility, - TraceSource traceSource, - AsyncServiceProviderInjection dte, - MefInjection bufferTagAggregatorFactoryService, - MefInjection editorAdaptersFactoryService, - AsyncServiceProviderInjection textManager) - : base(extensibility, traceSource, dte, bufferTagAggregatorFactoryService, editorAdaptersFactoryService, textManager) - { - } - - /// - public override CommandConfiguration CommandConfiguration => new(CommandDescription) - { - Icon = new(ImageMoniker.KnownValues.Uncomment, IconSettings.IconAndText), - EnabledWhen = CommandEnabledWhen, - Shortcuts = new[] { new CommandShortcutConfiguration(ModifierKey.Control, Key.K, ModifierKey.Control, Key.Q) }, - }; - - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - if (!await this.Extensibility.Shell().ShowPromptAsync("All comments will be removed from the current document. Are you sure?", PromptOptions.OKCancel, cancellationToken)) - { - return; - } - - using var reporter = await this.Extensibility.Shell().StartProgressReportingAsync("Removing comments", options: new(isWorkCancellable: false), cancellationToken); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support classification yet. - var view = await this.GetCurentTextViewAsync(); - var mappingSpans = await this.GetClassificationSpansAsync(view, "comment"); - if (!mappingSpans.Any()) - { - return; - } - - var dte = await this.Dte.GetServiceAsync(); - try - { - dte.UndoContext.Open(CommandDescription); - - DeleteFromBuffer(view, mappingSpans); - } - catch (Exception ex) - { - this.TraceSource.TraceInformation(ex.ToString()); - } - finally - { - dte.UndoContext.Close(); - } - } - - private static void DeleteFromBuffer(IWpfTextView view, IEnumerable mappingSpans) - { - var affectedLines = new List(); - - RemoveCommentSpansFromBuffer(view, mappingSpans, affectedLines); - RemoveAffectedEmptyLines(view, affectedLines); - } - - private static void RemoveCommentSpansFromBuffer(IWpfTextView view, IEnumerable mappingSpans, IList affectedLines) - { - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var mappingSpan in mappingSpans) - { - var start = mappingSpan.Start.GetPoint(view.TextBuffer, PositionAffinity.Predecessor); - var end = mappingSpan.End.GetPoint(view.TextBuffer, PositionAffinity.Successor); - - if (!start.HasValue || !end.HasValue) - { - continue; - } - - var span = new Span(start.Value, end.Value - start.Value); - var lines = view.TextBuffer.CurrentSnapshot.Lines.Where(l => l.Extent.IntersectsWith(span)); - - foreach (var line in lines) - { - if (IsXmlDocComment(line)) - { - edit.Replace(line.Start, line.Length, string.Empty.PadLeft(line.Length)); - } - - if (!affectedLines.Contains(line.LineNumber)) - { - affectedLines.Add(line.LineNumber); - } - } - - var mappingText = view.TextBuffer.CurrentSnapshot.GetText(span.Start, span.Length); - string empty = Regex.Replace(mappingText, "([\\S]+)", string.Empty); - - edit.Replace(span.Start, span.Length, empty); - } - - edit.Apply(); - } - } - - private static void RemoveAffectedEmptyLines(IWpfTextView view, IList affectedLines) - { - if (!affectedLines.Any()) - { - return; - } - - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var lineNumber in affectedLines) - { - var line = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber); - - if (IsLineEmpty(line)) - { - // Strip next line if empty - if (view.TextBuffer.CurrentSnapshot.LineCount > line.LineNumber + 1) - { - var next = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber + 1); - - if (IsLineEmpty(next)) - { - edit.Delete(next.Start, next.LengthIncludingLineBreak); - } - } - - edit.Delete(line.Start, line.LengthIncludingLineBreak); - } - } - - edit.Apply(); - } - } -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptTaskComments.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptTaskComments.cs deleted file mode 100644 index cd9ce6d6..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptTaskComments.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace CommentRemover; - -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Commands; -using Microsoft.VisualStudio.Extensibility.Shell; -using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.TextManager.Interop; -using System.Diagnostics; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -[VisualStudioContribution] -internal class RemoveAllExceptTaskComments : CommentRemoverCommand -{ - private const string CommandDescription = "%CommentRemover.RemoveAllExceptTaskComments.DisplayName%"; - - public RemoveAllExceptTaskComments( - VisualStudioExtensibility extensibility, - TraceSource traceSource, - AsyncServiceProviderInjection dte, - MefInjection bufferTagAggregatorFactoryService, - MefInjection editorAdaptersFactoryService, - AsyncServiceProviderInjection textManager) - : base(extensibility, traceSource, dte, bufferTagAggregatorFactoryService, editorAdaptersFactoryService, textManager) - { - } - - /// - public override CommandConfiguration CommandConfiguration => new(CommandDescription) - { - Icon = new(ImageMoniker.KnownValues.TaskList, IconSettings.IconAndText), - EnabledWhen = CommandEnabledWhen, - }; - - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - if (!await this.Extensibility.Shell().ShowPromptAsync("All comments, except tasks, will be removed from the current document. Are you sure?", PromptOptions.OKCancel, cancellationToken)) - { - return; - } - - using var reporter = await this.Extensibility.Shell().StartProgressReportingAsync("Removing comments", options: new(isWorkCancellable: false), cancellationToken); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support classification yet. - var view = await this.GetCurentTextViewAsync(); - var mappingSpans = await this.GetClassificationSpansAsync(view, "comment"); - if (!mappingSpans.Any()) - { - return; - } - - var dte = await this.Dte.GetServiceAsync(); - try - { - dte.UndoContext.Open(CommandDescription); - - DeleteFromBuffer(view, mappingSpans); - } - catch (Exception ex) - { - this.TraceSource.TraceInformation(ex.ToString()); - } - finally - { - dte.UndoContext.Close(); - } - } - - private static void DeleteFromBuffer(IWpfTextView view, IEnumerable mappingSpans) - { - var affectedLines = new List(); - - RemoveCommentSpansFromBuffer(view, mappingSpans, affectedLines); - RemoveAffectedEmptyLines(view, affectedLines); - } - - private static void RemoveCommentSpansFromBuffer(IWpfTextView view, IEnumerable mappingSpans, IList affectedLines) - { - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var mappingSpan in mappingSpans) - { - var start = mappingSpan.Start.GetPoint(view.TextBuffer, PositionAffinity.Predecessor); - var end = mappingSpan.End.GetPoint(view.TextBuffer, PositionAffinity.Successor); - - if (!start.HasValue || !end.HasValue) - { - continue; - } - - var span = new Span(start.Value, end.Value - start.Value); - var lines = view.TextBuffer.CurrentSnapshot.Lines.Where(l => l.Extent.IntersectsWith(span)); - bool skip = false; - - foreach (var line in lines) - { - if (ContainsTaskComment(line)) - { - skip = true; - } - else - { - if (!affectedLines.Contains(line.LineNumber)) - { - affectedLines.Add(line.LineNumber); - } - } - } - - if (skip) - { - continue; - } - - var mappingText = view.TextBuffer.CurrentSnapshot.GetText(span.Start, span.Length); - string empty = Regex.Replace(mappingText, "([\\S]+)", string.Empty); - - edit.Replace(span.Start, span.Length, empty); - } - - edit.Apply(); - } - } - - private static void RemoveAffectedEmptyLines(IWpfTextView view, IList affectedLines) - { - if (!affectedLines.Any()) - { - return; - } - - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var lineNumber in affectedLines) - { - var line = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber); - - if (IsLineEmpty(line)) - { - // Strip next line if empty - if (view.TextBuffer.CurrentSnapshot.LineCount > line.LineNumber + 1) - { - var next = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber + 1); - - if (IsLineEmpty(next)) - { - edit.Delete(next.Start, next.LengthIncludingLineBreak); - } - } - - edit.Delete(line.Start, line.LengthIncludingLineBreak); - } - } - - edit.Apply(); - } - } -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptXmlDocComments.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptXmlDocComments.cs deleted file mode 100644 index de1ff6b2..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveAllExceptXmlDocComments.cs +++ /dev/null @@ -1,168 +0,0 @@ -namespace CommentRemover; - -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Commands; -using Microsoft.VisualStudio.Extensibility.Shell; -using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.TextManager.Interop; -using System.Diagnostics; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -[VisualStudioContribution] -internal class RemoveAllExceptXmlDocComments : CommentRemoverCommand -{ - private const string CommandDescription = "%CommentRemover.RemoveAllExceptXmlDocComments.DisplayName%"; - - public RemoveAllExceptXmlDocComments( - VisualStudioExtensibility extensibility, - TraceSource traceSource, - AsyncServiceProviderInjection dte, - MefInjection bufferTagAggregatorFactoryService, - MefInjection editorAdaptersFactoryService, - AsyncServiceProviderInjection textManager) - : base(extensibility, traceSource, dte, bufferTagAggregatorFactoryService, editorAdaptersFactoryService, textManager) - { - } - - /// - public override CommandConfiguration CommandConfiguration => new(CommandDescription) - { - Icon = new(ImageMoniker.KnownValues.Dictionary, IconSettings.IconAndText), - EnabledWhen = CommandEnabledWhen, - }; - - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - if (!await this.Extensibility.Shell().ShowPromptAsync("All task comments, except for Xml Docs, will be removed from the current document. Are you sure?", PromptOptions.OKCancel, cancellationToken)) - { - return; - } - - using var reporter = await this.Extensibility.Shell().StartProgressReportingAsync("Removing comments", options: new(isWorkCancellable: false), cancellationToken); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support classification yet. - var view = await this.GetCurentTextViewAsync(); - var mappingSpans = await this.GetClassificationSpansAsync(view, "comment"); - if (!mappingSpans.Any()) - { - return; - } - - var dte = await this.Dte.GetServiceAsync(); - try - { - dte.UndoContext.Open(CommandDescription); - - DeleteFromBuffer(view, mappingSpans); - } - catch (Exception ex) - { - this.TraceSource.TraceInformation(ex.ToString()); - } - finally - { - dte.UndoContext.Close(); - } - } - - private static void DeleteFromBuffer(IWpfTextView view, IEnumerable mappingSpans) - { - var affectedLines = new List(); - - RemoveCommentSpansFromBuffer(view, mappingSpans, affectedLines); - RemoveAffectedEmptyLines(view, affectedLines); - } - - private static void RemoveCommentSpansFromBuffer(IWpfTextView view, IEnumerable mappingSpans, IList affectedLines) - { - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var mappingSpan in mappingSpans) - { - var start = mappingSpan.Start.GetPoint(view.TextBuffer, PositionAffinity.Predecessor); - var end = mappingSpan.End.GetPoint(view.TextBuffer, PositionAffinity.Successor); - - if (!start.HasValue || !end.HasValue) - { - continue; - } - - var span = new Span(start.Value, end.Value - start.Value); - var lines = view.TextBuffer.CurrentSnapshot.Lines.Where(l => l.Extent.IntersectsWith(span)); - bool skip = false; - - foreach (var line in lines) - { - if (IsXmlDocComment(line)) - { - skip = true; - } - else - { - if (!affectedLines.Contains(line.LineNumber)) - { - affectedLines.Add(line.LineNumber); - } - } - } - - if (skip) - { - continue; - } - - var mappingText = view.TextBuffer.CurrentSnapshot.GetText(span.Start, span.Length); - string empty = Regex.Replace(mappingText, "([\\S]+)", string.Empty); - - edit.Replace(span.Start, span.Length, empty); - } - - edit.Apply(); - } - } - - private static void RemoveAffectedEmptyLines(IWpfTextView view, IList affectedLines) - { - if (!affectedLines.Any()) - { - return; - } - - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var lineNumber in affectedLines) - { - var line = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber); - - if (IsLineEmpty(line)) - { - // Strip next line if empty - if (view.TextBuffer.CurrentSnapshot.LineCount > line.LineNumber + 1) - { - var next = view.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber + 1); - - if (IsLineEmpty(next)) - { - edit.Delete(next.Start, next.LengthIncludingLineBreak); - } - } - - edit.Delete(line.Start, line.LengthIncludingLineBreak); - } - } - - edit.Apply(); - } - } -} diff --git a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveRegions.cs b/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveRegions.cs deleted file mode 100644 index 66a81737..00000000 --- a/New_Extensibility_Model/Samples/CommentRemover/CommentRemover/RemoveRegions.cs +++ /dev/null @@ -1,109 +0,0 @@ -namespace CommentRemover; - -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Commands; -using Microsoft.VisualStudio.Extensibility.Shell; -using Microsoft.VisualStudio.Extensibility.VSSdkCompatibility; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.TextManager.Interop; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -[VisualStudioContribution] -internal class RemoveRegions : CommentRemoverCommand -{ - private const string CommandDescription = "%CommentRemover.RemoveRegions.DisplayName%"; - - public RemoveRegions( - VisualStudioExtensibility extensibility, - TraceSource traceSource, - AsyncServiceProviderInjection dte, - MefInjection bufferTagAggregatorFactoryService, - MefInjection editorAdaptersFactoryService, - AsyncServiceProviderInjection textManager) - : base(extensibility, traceSource, dte, bufferTagAggregatorFactoryService, editorAdaptersFactoryService, textManager) - { - } - - /// - public override CommandConfiguration CommandConfiguration => new(CommandDescription) - { - Icon = new(ImageMoniker.Custom("DeleteRegions"), IconSettings.IconAndText), - EnabledWhen = CommandEnabledWhen, - }; - - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - if (!await this.Extensibility.Shell().ShowPromptAsync("All regions will be removed from the current document. Are you sure?", PromptOptions.OKCancel, cancellationToken)) - { - return; - } - - using var reporter = await this.Extensibility.Shell().StartProgressReportingAsync("Removing comments", options: new(isWorkCancellable: false), cancellationToken); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support classification yet. - var view = await this.GetCurentTextViewAsync(); - var dte = await this.Dte.GetServiceAsync(); - try - { - dte.UndoContext.Open(CommandDescription); - - this.RemoveRegionsFromBuffer(view); - } - catch (Exception ex) - { - this.TraceSource.TraceInformation(ex.ToString()); - } - finally - { - dte.UndoContext.Close(); - } - } - - private void RemoveRegionsFromBuffer(IWpfTextView view) - { - using (var edit = view.TextBuffer.CreateEdit()) - { - foreach (var line in view.TextBuffer.CurrentSnapshot.Lines.Reverse()) - { - if (line.Extent.IsEmpty) - { - continue; - } - - string text = line.GetText() - .TrimStart('/', '*') - .Replace(" - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - + + + + - - - - - - - - PreserveNewest - - + + + + diff --git a/New_Extensibility_Model/Samples/DialogSample/DialogSampleExtension.cs b/New_Extensibility_Model/Samples/DialogSample/DialogSampleExtension.cs index 22cd1de3..9a003053 100644 --- a/New_Extensibility_Model/Samples/DialogSample/DialogSampleExtension.cs +++ b/New_Extensibility_Model/Samples/DialogSample/DialogSampleExtension.cs @@ -12,19 +12,20 @@ namespace DialogSample; [VisualStudioContribution] public class DialogSampleExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "DialogSampleExtension.8709efb4-fe22-4848-8533-594f444861a1", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Dialog Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "DialogSampleExtension.8709efb4-fe22-4848-8533-594f444861a1", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Dialog Sample Extension", + description: "Sample extension demonstrating modal dialogs"), + }; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } } diff --git a/New_Extensibility_Model/Samples/DialogSample/MyDialogCommand.cs b/New_Extensibility_Model/Samples/DialogSample/MyDialogCommand.cs index 393ee3e9..ef224efc 100644 --- a/New_Extensibility_Model/Samples/DialogSample/MyDialogCommand.cs +++ b/New_Extensibility_Model/Samples/DialogSample/MyDialogCommand.cs @@ -14,35 +14,21 @@ namespace DialogSample; [VisualStudioContribution] public class MyDialogCommand : Command { - /// - /// Initializes a new instance of the class. - /// - /// - /// Extensibility object instance. - /// - /// - /// Command identifier. - /// - public MyDialogCommand(VisualStudioExtensibility extensibility) - : base(extensibility) - { - } + /// + public override CommandConfiguration CommandConfiguration => new("%DialogSample.MyDialogCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, + Icon = new(ImageMoniker.KnownValues.Dialog, IconSettings.IconAndText), + }; - /// - public override CommandConfiguration CommandConfiguration => new("%DialogSample.MyDialogCommand.DisplayName%") - { - Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, - Icon = new(ImageMoniker.KnownValues.Dialog, IconSettings.IconAndText), - }; + /// + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + // Ownership of the RemoteUserControl is transferred to VisualStudio, so it should not be disposed by the extension + #pragma warning disable CA2000 // Dispose objects before losing scope + var control = new MyDialogControl(null); + #pragma warning restore CA2000 // Dispose objects before losing scope - /// - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - // Ownership of the RemoteUserControl is transferred to VisualStudio, so it should not be disposed by the extension - #pragma warning disable CA2000 // Dispose objects before losing scope - var control = new MyDialogControl(null); - #pragma warning restore CA2000 // Dispose objects before losing scope - - await this.Extensibility.Shell().ShowDialogAsync(control, cancellationToken); - } + await this.Extensibility.Shell().ShowDialogAsync(control, cancellationToken); + } } diff --git a/New_Extensibility_Model/Samples/DialogSample/MyDialogControl.cs b/New_Extensibility_Model/Samples/DialogSample/MyDialogControl.cs index c7fe43cf..6c64c1ba 100644 --- a/New_Extensibility_Model/Samples/DialogSample/MyDialogControl.cs +++ b/New_Extensibility_Model/Samples/DialogSample/MyDialogControl.cs @@ -11,18 +11,18 @@ namespace DialogSample; /// internal class MyDialogControl : RemoteUserControl { - /// - /// Initializes a new instance of the class. - /// - /// - /// Data context of the remote control which can be referenced from xaml through data binding. - /// - /// - /// Optional synchronizationContext that the extender can provide to ensure that - /// are executed and properties are read and updated from the extension main thread. - /// - public MyDialogControl(object? dataContext, SynchronizationContext? synchronizationContext = null) - : base(dataContext, synchronizationContext) - { - } + /// + /// Initializes a new instance of the class. + /// + /// + /// Data context of the remote control which can be referenced from xaml through data binding. + /// + /// + /// Optional synchronizationContext that the extender can provide to ensure that + /// are executed and properties are read and updated from the extension main thread. + /// + public MyDialogControl(object? dataContext, SynchronizationContext? synchronizationContext = null) + : base(dataContext, synchronizationContext) + { + } } diff --git a/New_Extensibility_Model/Samples/DialogSample/README.md b/New_Extensibility_Model/Samples/DialogSample/README.md new file mode 100644 index 00000000..7fbaf264 --- /dev/null +++ b/New_Extensibility_Model/Samples/DialogSample/README.md @@ -0,0 +1,78 @@ +--- +title: Dialog Sample reference +description: A reference for Dialog sample +date: 2023-10-24 +--- + +# Walkthrough: Dialog Extension Sample + +This extension is a simple extension that shows how a command that shows a dialog can be quickly added to Visual Studio. + +## Command definition + +The extension contains a code file that defines a command and its properties starting with the `VisualStudioContribution` class attribute which makes the command available to Visual Studio: + +```csharp +[VisualStudioContribution] +public class MyDialogCommand : Command +{ +``` + +The `CommandConfiguration` property defines information about the command that are available to Visual Studio even before the extension is loaded: + +```csharp + public override CommandConfiguration CommandConfiguration => new("%DialogSample.MyDialogCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, + Icon = new(ImageMoniker.KnownValues.Dialog, IconSettings.IconAndText), + }; +``` + +The command is placed in the `Tools` top menu and uses the `Dialog` icon moniker. + +The dialog is shown when the command is executed using the Shell helpers: + +```csharp + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + // Ownership of the RemoteUserControl is transferred to VisualStudio, so it should not be disposed by the extension + #pragma warning disable CA2000 // Dispose objects before losing scope + var control = new MyDialogControl(null); + #pragma warning restore CA2000 // Dispose objects before losing scope + + await this.Extensibility.Shell().ShowDialogAsync(control, cancellationToken); + } +``` + +The dialog title and buttons can be customized as well, but are left as the default in the sample. You can refer to [Dialog](https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/dialog/dialog) for more information about setting up a dialog. + +## Dialog content creation + +The dialog content is created as a DataTemplate .xaml file and a separate control class .cs file: + +```xml + + ... + +``` + +```csharp +internal class MyDialogControl : RemoteUserControl +{ +``` + +The .cs and .xaml files should have the same name (MyDialogControl in this sample). Additionally the xaml file should be included as an EmbeddedResource instead of a Page, so editing the csproj file may be necessary in some cases. + +You can refer to [Add content to a tool window](https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/tool-window/tool-window#add-content-to-a-tool-window) to learn more about setting up the tool window content and remote UI. + +## Logging errors + +Each extension part including command sets is assigned a `TraceSource` instance that can be utilized to log diagnostic errors. Please see [Logging](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/inside-the-sdk/logging) section for more information. + +## Usage + +Once deployed, the Insert Guid command can be used when editing any code files. The command by default will be under Extensions menu. \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/Directory.Build.props b/New_Extensibility_Model/Samples/Directory.Build.props index dd36287e..207f082f 100644 --- a/New_Extensibility_Model/Samples/Directory.Build.props +++ b/New_Extensibility_Model/Samples/Directory.Build.props @@ -4,8 +4,6 @@ $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\')) $(RepoRootPath)obj\samples\$(MSBuildProjectName)\ $(RepoRootPath)bin\samples\$(MSBuildProjectName)\ - 8.0 - enable false false false diff --git a/New_Extensibility_Model/Samples/DocumentSelectorSample/DocumentSelectorSample.csproj b/New_Extensibility_Model/Samples/DocumentSelectorSample/DocumentSelectorSample.csproj index c318de5c..d7d0180a 100644 --- a/New_Extensibility_Model/Samples/DocumentSelectorSample/DocumentSelectorSample.csproj +++ b/New_Extensibility_Model/Samples/DocumentSelectorSample/DocumentSelectorSample.csproj @@ -1,39 +1,32 @@  - - net6.0-windows8.0 - enable - 11 - true - en-US - $(NoWarn);CS1591;IDE0008;CA1812;CA2007 + + net8.0-windows8.0 + enable + 11 + en-US + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - + + + + - - - True - True - Resources.resx - - + + + True + True + Resources.resx + + - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - PreserveNewest - - + + + ResXFileCodeGenerator + Resources.Designer.cs + + diff --git a/New_Extensibility_Model/Samples/DocumentSelectorSample/ExtensionEntrypoint.cs b/New_Extensibility_Model/Samples/DocumentSelectorSample/ExtensionEntrypoint.cs index d234561c..6a31ef27 100644 --- a/New_Extensibility_Model/Samples/DocumentSelectorSample/ExtensionEntrypoint.cs +++ b/New_Extensibility_Model/Samples/DocumentSelectorSample/ExtensionEntrypoint.cs @@ -12,19 +12,20 @@ namespace DocumentSelectorSample; [VisualStudioContribution] public class DocumentSelectorSampleExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "DocumentSelectorSampleExtension.541ed9b9-4dfa-4e0a-8184-65c09ea44343", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Document Selector Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "DocumentSelectorSampleExtension.541ed9b9-4dfa-4e0a-8184-65c09ea44343", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Document Selector Sample Extension", + description: "Sample extension demonstrating document selectors"), + }; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } } diff --git a/New_Extensibility_Model/Samples/DocumentSelectorSample/UnitTestRunner.cs b/New_Extensibility_Model/Samples/DocumentSelectorSample/UnitTestRunner.cs index 2ac59f8c..6bf47df7 100644 --- a/New_Extensibility_Model/Samples/DocumentSelectorSample/UnitTestRunner.cs +++ b/New_Extensibility_Model/Samples/DocumentSelectorSample/UnitTestRunner.cs @@ -18,75 +18,79 @@ namespace DocumentSelectorSample; [VisualStudioContribution] internal class UnitTestRunner : ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener { - private OutputWindow? outputWindow; + private OutputWindow? outputWindow; - /// - /// Initializes a new instance of the class. - /// - /// Extension instance. - /// Extensibility object. - public UnitTestRunner(Extension extension, VisualStudioExtensibility extensibility) - : base(extension, extensibility) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Extension instance. + /// Extensibility object. + public UnitTestRunner(Extension extension, VisualStudioExtensibility extensibility) + : base(extension, extensibility) + { + } - /// - public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() - { - AppliesTo = new[] - { - DocumentFilter.FromGlobPattern("**/tests/*.cs", relativePath: false), - }, - }; + /// + public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() + { + AppliesTo = new[] + { + DocumentFilter.FromGlobPattern("**/tests/*.cs", relativePath: false), + }, + }; - /// - public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken) - { - return this.RunUnitTestsAfterDelayAsync(args.AfterTextView, cancellationToken); - } + /// + public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken) + { + return this.RunUnitTestsAfterDelayAsync(args.AfterTextView, cancellationToken); + } - /// - public Task TextViewOpenedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - return this.RunUnitTestsAfterDelayAsync(textViewSnapshot, cancellationToken); - } + /// + public Task TextViewOpenedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + return this.RunUnitTestsAfterDelayAsync(textViewSnapshot, cancellationToken); + } - /// - public Task TextViewClosedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - return this.StopUnitTestsAsync(textViewSnapshot, cancellationToken); - } + /// + public Task TextViewClosedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + return this.StopUnitTestsAsync(textViewSnapshot, cancellationToken); + } - /// - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - this.outputWindow?.Dispose(); - } + /// + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + this.outputWindow?.Dispose(); + } - private async Task RunUnitTestsAfterDelayAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - await Task.Delay(500, cancellationToken); - await this.WriteToOutputWindowAsync($"Running unit tests in {textViewSnapshot.Document.Uri.LocalPath}", cancellationToken); - } + private async Task RunUnitTestsAfterDelayAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + await Task.Delay(500, cancellationToken); + await this.WriteToOutputWindowAsync($"Running unit tests in {textViewSnapshot.Document.Uri.LocalPath}", cancellationToken); + } - private async Task StopUnitTestsAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - await this.WriteToOutputWindowAsync($"Stop running unit tests in {textViewSnapshot.Document.Uri.LocalPath}", cancellationToken); - } + private async Task StopUnitTestsAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + await this.WriteToOutputWindowAsync($"Stop running unit tests in {textViewSnapshot.Document.Uri.LocalPath}", cancellationToken); + } - private async Task GetOutputWindowAsync(CancellationToken cancellationToken) - { - return this.outputWindow ??= await this.Extensibility.Views().Output.GetChannelAsync( - identifier: nameof(DocumentSelectorSample), - displayNameResourceId: nameof(Resources.OutputWindowPaneName), - cancellationToken); - } + private async Task GetOutputWindowAsync(CancellationToken cancellationToken) + { +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + return this.outputWindow ??= await this.Extensibility.Views().Output.GetChannelAsync( + identifier: nameof(DocumentSelectorSample), + displayNameResourceId: nameof(Resources.OutputWindowPaneName), + cancellationToken); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } - private async Task WriteToOutputWindowAsync(string message, CancellationToken cancellationToken) - { - var channel = await this.GetOutputWindowAsync(cancellationToken); - Assumes.NotNull(channel); - await channel.Writer.WriteLineAsync(message); - } + private async Task WriteToOutputWindowAsync(string message, CancellationToken cancellationToken) + { + var channel = await this.GetOutputWindowAsync(cancellationToken); + Assumes.NotNull(channel); +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + await channel.Writer.WriteLineAsync(message); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } } diff --git a/New_Extensibility_Model/Samples/InsertGuid/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/InsertGuid/.vsextension/string-resources.json index 8d0ea3ae..736374b0 100644 --- a/New_Extensibility_Model/Samples/InsertGuid/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/InsertGuid/.vsextension/string-resources.json @@ -1,3 +1,3 @@ { - "InsertGuid.InsertGuidCommand.DisplayName": "Insert new guid" + "InsertGuid.InsertGuidCommand.DisplayName": "Insert new guid" } \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/InsertGuid/InsertGuid.csproj b/New_Extensibility_Model/Samples/InsertGuid/InsertGuid.csproj index 1d601b1c..c3a77891 100644 --- a/New_Extensibility_Model/Samples/InsertGuid/InsertGuid.csproj +++ b/New_Extensibility_Model/Samples/InsertGuid/InsertGuid.csproj @@ -1,39 +1,32 @@  - - net6.0-windows8.0 - enable - 11 - true - en-US - $(NoWarn);CS1591;IDE0008;CA1812 + + net8.0-windows8.0 + enable + 11 + en-US + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - - - - - True - True - Resources.resx - - + + + + + + + + True + True + Resources.resx + + - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - - PreserveNewest - - + + + ResXFileCodeGenerator + Resources.Designer.cs + + diff --git a/New_Extensibility_Model/Samples/InsertGuid/InsertGuidCommand.cs b/New_Extensibility_Model/Samples/InsertGuid/InsertGuidCommand.cs index 6e92637e..751a313f 100644 --- a/New_Extensibility_Model/Samples/InsertGuid/InsertGuidCommand.cs +++ b/New_Extensibility_Model/Samples/InsertGuid/InsertGuidCommand.cs @@ -18,49 +18,47 @@ namespace InsertGuid; [VisualStudioContribution] internal class InsertGuidCommand : Command { - private readonly TraceSource logger; + private readonly TraceSource logger; - /// - /// Initializes a new instance of the class. - /// - /// Extensibility object. - /// Trace source instance to utilize. - public InsertGuidCommand(VisualStudioExtensibility extensibility, TraceSource traceSource) - : base(extensibility) - { - this.logger = Requires.NotNull(traceSource, nameof(traceSource)); - } + /// + /// Initializes a new instance of the class. + /// + /// Trace source instance to utilize. + public InsertGuidCommand(TraceSource traceSource) + { + this.logger = Requires.NotNull(traceSource, nameof(traceSource)); + } - /// - public override CommandConfiguration CommandConfiguration => new("%InsertGuid.InsertGuidCommand.DisplayName%") - { - Placements = new[] { CommandPlacement.KnownPlacements.ExtensionsMenu }, - Icon = new(ImageMoniker.KnownValues.OfficeWebExtension, IconSettings.IconAndText), - VisibleWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveEditorContentType, ".+"), - ClientContexts = new[] { "Editor", "Shell" }, - }; + /// + public override CommandConfiguration CommandConfiguration => new("%InsertGuid.InsertGuidCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ExtensionsMenu }, + Icon = new(ImageMoniker.KnownValues.OfficeWebExtension, IconSettings.IconAndText), + VisibleWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveEditorContentType, ".+"), + ClientContexts = new[] { "Editor", "Shell" }, + }; - /// - /// - /// Get the active text, if there is one replace the selection with a new guid string. - /// - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - Requires.NotNull(context, nameof(context)); - var newGuidString = Guid.NewGuid().ToString("N", CultureInfo.CurrentCulture); + /// + /// + /// Get the active text, if there is one replace the selection with a new guid string. + /// + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + Requires.NotNull(context, nameof(context)); + var newGuidString = Guid.NewGuid().ToString("N", CultureInfo.CurrentCulture); - using var textView = await context.GetActiveTextViewAsync(cancellationToken); - if (textView is null) - { - this.logger.TraceInformation("There was no active text view when command is executed."); - return; - } + using var textView = await context.GetActiveTextViewAsync(cancellationToken); + if (textView is null) + { + this.logger.TraceInformation("There was no active text view when command is executed."); + return; + } - await this.Extensibility.Editor().EditAsync( - batch => - { - textView.Document.AsEditable(batch).Replace(textView.Selection.Extent, newGuidString); - }, - cancellationToken); - } + await this.Extensibility.Editor().EditAsync( + batch => + { + textView.Document.AsEditable(batch).Replace(textView.Selection.Extent, newGuidString); + }, + cancellationToken); + } } diff --git a/New_Extensibility_Model/Samples/InsertGuid/InsertGuidExtension.cs b/New_Extensibility_Model/Samples/InsertGuid/InsertGuidExtension.cs index 3ec1e295..3fb1653d 100644 --- a/New_Extensibility_Model/Samples/InsertGuid/InsertGuidExtension.cs +++ b/New_Extensibility_Model/Samples/InsertGuid/InsertGuidExtension.cs @@ -12,19 +12,20 @@ namespace InsertGuid; [VisualStudioContribution] public class InsertGuidExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "InsertGuid.c5481000-68da-416d-b337-32122a638980", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Insert Guid Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "InsertGuid.c5481000-68da-416d-b337-32122a638980", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Insert Guid Sample Extension", + description: "Sample extension demonstrating inserting text in the current document"), + }; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } } diff --git a/New_Extensibility_Model/Samples/InsertGuid/README.md b/New_Extensibility_Model/Samples/InsertGuid/README.md index 1607770a..a3f99399 100644 --- a/New_Extensibility_Model/Samples/InsertGuid/README.md +++ b/New_Extensibility_Model/Samples/InsertGuid/README.md @@ -18,7 +18,7 @@ internal class InsertGuidCommand : Command { ``` -The `VisualStudioContribution` attribute registers the command using the class full type name `Microsoft.VisualStudio.Gladstone.InsertGuid.InsertGuidCommand` as its unique identifier. +The `VisualStudioContribution` attribute registers the command using the class full type name `InsertGuid.InsertGuidCommand` as its unique identifier. The `CommandConfiguration` property defines information about the command that are available to Visual Studio even before the extension is loaded: @@ -65,4 +65,4 @@ Each extension part including command sets is assigned a `TraceSource` instance ## Usage -Once deployed, the Insert Guid command can be used when editing any code files. The command by default will be under Extensions menu. \ No newline at end of file +Once deployed, the Insert Guid command can be used when editing any code files. The command by default will be under Extensions menu. diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json index 5713ce99..cb3d4d71 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json @@ -1,4 +1,4 @@ { - "MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName": "Run Linter on open file", - "MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution" + "MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName": "Run Linter on open file", + "MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution" } \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs b/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs index 152fa051..a70d8e39 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs @@ -6,6 +6,7 @@ namespace MarkdownLinter; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Configuration.Provider; using System.Diagnostics; using System.Globalization; using System.IO; @@ -14,6 +15,7 @@ namespace MarkdownLinter; using Microsoft; using Microsoft.VisualStudio.Extensibility.Editor; using Microsoft.VisualStudio.Extensibility.Languages; +using Microsoft.VisualStudio.RpcContracts; using Microsoft.VisualStudio.RpcContracts.DiagnosticManagement; using Microsoft.VisualStudio.Threading; @@ -22,204 +24,203 @@ namespace MarkdownLinter; /// internal static class LinterUtilities { - private static Regex linterOutputRegex = new Regex(@"(?[^:]+):(?\d*)(:(?\d*))? (?.*)/(?.*)", RegexOptions.Compiled); - - /// - /// Runs markdown linter on a file uri and returns diagnostic entries. - /// - /// File uri to run markdown linter on. - /// an enumeration of entries for warnings in the markdown file. - public static async Task> RunLinterOnFileAsync(Uri fileUri) - { - using var linter = new Process(); - var lineQueue = new AsyncQueue(); - - linter.StartInfo = new ProcessStartInfo() - { - FileName = "node.exe", - Arguments = $"\"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\node_modules\\markdownlint-cli\\markdownlint.js")}\" \"{fileUri.LocalPath}\"", - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - linter.EnableRaisingEvents = true; - linter.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => - { - if (e.Data is object) - { - lineQueue.Enqueue(e.Data); - } - else - { - lineQueue.Complete(); - } - }); - - try - { - linter.Start(); - linter.BeginErrorReadLine(); - } - catch (Win32Exception ex) - { - throw new InvalidOperationException(message: ex.Message, innerException: ex); - } - - var markdownDiagnostics = await ProcessLinterQueueAsync(lineQueue); - return CreateDocumentDiagnosticsForClosedDocument(fileUri, markdownDiagnostics); - } - - /// - /// Runs markdown linter on a given text document and returns diagnostic entries. - /// - /// Document to run markdown linter on. - /// an enumeration of entries for warnings in the markdown file. - public static async Task> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument) - { - using var linter = new Process(); - var lineQueue = new AsyncQueue(); - - var content = textDocument.Text.CopyToString(); - - linter.StartInfo = new ProcessStartInfo() - { - FileName = "cmd.exe", - Arguments = $"/k \"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\markdownlint.cmd")}\" -s", - RedirectStandardError = true, - RedirectStandardInput = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - linter.EnableRaisingEvents = true; - linter.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => - { - if (e.Data is object) - { - lineQueue.Enqueue(e.Data); - } - else - { - lineQueue.Complete(); - } - }); - - try - { - linter.Start(); - linter.BeginErrorReadLine(); - linter.StandardInput.AutoFlush = true; - await linter.StandardInput.WriteAsync(content); - - linter.StandardInput.Close(); - } - catch (Win32Exception ex) - { - throw new InvalidOperationException(message: ex.Message, innerException: ex); - } - - var markdownDiagnostics = await ProcessLinterQueueAsync(lineQueue); - return CreateDocumentDiagnosticsForOpenDocument(textDocument, markdownDiagnostics); - } - - /// - /// Checks if the given path is a valid markdown file. - /// - /// Local file path to verify. - /// true if file is a markdown file, false otherwise. - public static bool IsValidMarkdownFile(string localPath) - { - return localPath is object && Path.GetExtension(localPath).Equals(".md", StringComparison.OrdinalIgnoreCase); - } - - private static IEnumerable CreateDocumentDiagnosticsForOpenDocument(ITextDocumentSnapshot document, IEnumerable diagnostics) - { - foreach (var diagnostic in diagnostics) - { - var startindex = document.Lines[diagnostic.Range.StartLine].Text.Start.Offset; - if (diagnostic.Range.StartColumn >= 0) - { - startindex += diagnostic.Range.StartColumn; - } - - var endIndex = document.Lines[diagnostic.Range.EndLine].Text.Start.Offset; - if (diagnostic.Range.EndColumn >= 0) - { - endIndex += diagnostic.Range.EndColumn; - } - - yield return DocumentDiagnostic.CreateDocumentDiagnostic( - new TextRange(document, startindex, endIndex - startindex), - diagnostic.Message, - diagnostic.ErrorCode, - DiagnosticSeverity.Warning, - providerName: "Markdown Linter"); - } - } - - private static IEnumerable CreateDocumentDiagnosticsForClosedDocument(Uri fileUri, IEnumerable diagnostics) - { - foreach (var diagnostic in diagnostics) - { - yield return DocumentDiagnostic.CreateDocumentDiagnosticForClosedDocument( - uri: fileUri, - range: diagnostic.Range, - diagnostic.Message, - diagnostic.ErrorCode, - DiagnosticSeverity.Warning, - providerName: "Markdown Linter"); - } - } - - private static async Task> ProcessLinterQueueAsync(AsyncQueue lineQueue) - { - Requires.NotNull(lineQueue, nameof(lineQueue)); - - List diagnostics = new List(); - - while (!(lineQueue.IsCompleted && lineQueue.IsEmpty)) - { - string? line = null; - try - { - line = await lineQueue.DequeueAsync(); - } - catch (OperationCanceledException) - { - break; - } - - var diagnostic = line is object ? GetDiagnosticFromLinterOutput(line) : null; - if (diagnostic is object) - { - diagnostics.Add(diagnostic); - } - else - { - // Something went wrong so break and return the current set. - break; - } - } - - return diagnostics; - } - - private static MarkdownDiagnosticInfo? GetDiagnosticFromLinterOutput(string outputLine) - { - Requires.NotNull(outputLine, nameof(outputLine)); - var match = linterOutputRegex.Match(outputLine); - if (!match.Success) - { - return null; - } - - int line = int.Parse(match.Groups["Line"].Value, CultureInfo.InvariantCulture) - 1; - int column = match.Groups.ContainsKey("Column") && !string.IsNullOrEmpty(match.Groups["Column"].Value) ? (int.Parse(match.Groups["Column"].Value, CultureInfo.InvariantCulture) - 1) : -1; - - return new MarkdownDiagnosticInfo( - range: new Microsoft.VisualStudio.RpcContracts.Utilities.Range(startLine: line, startColumn: column), - message: match.Groups["Description"].Value, - errorCode: match.Groups["Error"].Value); - } + private static readonly Regex LinterOutputRegex = new(@"(?[^:]+):(?\d*)(:(?\d*))? (?.*)/(?.*)", RegexOptions.Compiled); + + /// + /// Runs markdown linter on a file uri and returns diagnostic entries. + /// + /// File uri to run markdown linter on. + /// an enumeration of entries for warnings in the markdown file. + public static async Task> RunLinterOnFileAsync(Uri fileUri) + { + using var linter = new Process(); + var lineQueue = new AsyncQueue(); + + linter.StartInfo = new ProcessStartInfo() + { + FileName = "node.exe", + Arguments = $"\"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\node_modules\\markdownlint-cli\\markdownlint.js")}\" \"{fileUri.LocalPath}\"", + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + linter.EnableRaisingEvents = true; + linter.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => + { + if (e.Data is not null) + { + lineQueue.Enqueue(e.Data); + } + else + { + lineQueue.Complete(); + } + }); + + try + { + linter.Start(); + linter.BeginErrorReadLine(); + } + catch (Win32Exception ex) + { + throw new InvalidOperationException(message: ex.Message, innerException: ex); + } + + var markdownDiagnostics = await ProcessLinterQueueAsync(lineQueue); + return CreateDocumentDiagnosticsForClosedDocument(fileUri, markdownDiagnostics); + } + + /// + /// Runs markdown linter on a given text document and returns diagnostic entries. + /// + /// Document to run markdown linter on. + /// an enumeration of entries for warnings in the markdown file. + public static async Task> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument) + { + using var linter = new Process(); + var lineQueue = new AsyncQueue(); + + var content = textDocument.Text.CopyToString(); + + linter.StartInfo = new ProcessStartInfo() + { + FileName = "cmd.exe", + Arguments = $"/k \"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\markdownlint.cmd")}\" -s", + RedirectStandardError = true, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + linter.EnableRaisingEvents = true; + linter.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => + { + if (e.Data is not null) + { + lineQueue.Enqueue(e.Data); + } + else + { + lineQueue.Complete(); + } + }); + + try + { + linter.Start(); + linter.BeginErrorReadLine(); + linter.StandardInput.AutoFlush = true; + await linter.StandardInput.WriteAsync(content); + + linter.StandardInput.Close(); + } + catch (Win32Exception ex) + { + throw new InvalidOperationException(message: ex.Message, innerException: ex); + } + + var markdownDiagnostics = await ProcessLinterQueueAsync(lineQueue); + return CreateDocumentDiagnosticsForOpenDocument(textDocument, markdownDiagnostics); + } + + /// + /// Checks if the given path is a valid markdown file. + /// + /// Local file path to verify. + /// true if file is a markdown file, false otherwise. + public static bool IsValidMarkdownFile(string localPath) + { + return localPath is not null && Path.GetExtension(localPath).Equals(".md", StringComparison.OrdinalIgnoreCase); + } + + private static IEnumerable CreateDocumentDiagnosticsForOpenDocument(ITextDocumentSnapshot document, IEnumerable diagnostics) + { + foreach (var diagnostic in diagnostics) + { + var startindex = document.Lines[diagnostic.Range.StartLine].Text.Start.Offset; + if (diagnostic.Range.StartColumn >= 0) + { + startindex += diagnostic.Range.StartColumn; + } + + var endIndex = document.Lines[diagnostic.Range.EndLine].Text.Start.Offset; + if (diagnostic.Range.EndColumn >= 0) + { + endIndex += diagnostic.Range.EndColumn; + } + + yield return new DocumentDiagnostic(new TextRange(document, startindex, endIndex - startindex), diagnostic.Message) + { + ErrorCode = diagnostic.ErrorCode, + Severity = DiagnosticSeverity.Warning, + ProviderName = "Markdown Linter", + }; + } + } + + private static IEnumerable CreateDocumentDiagnosticsForClosedDocument(Uri fileUri, IEnumerable diagnostics) + { + foreach (var diagnostic in diagnostics) + { + yield return new DocumentDiagnostic(fileUri, diagnostic.Range, diagnostic.Message) + { + ErrorCode = diagnostic.ErrorCode, + Severity = DiagnosticSeverity.Warning, + ProviderName = "Markdown Linter", + }; + } + } + + private static async Task> ProcessLinterQueueAsync(AsyncQueue lineQueue) + { + Requires.NotNull(lineQueue, nameof(lineQueue)); + + List diagnostics = new List(); + + while (!(lineQueue.IsCompleted && lineQueue.IsEmpty)) + { + string? line; + try + { + line = await lineQueue.DequeueAsync(); + } + catch (OperationCanceledException) + { + break; + } + + var diagnostic = line is not null ? GetDiagnosticFromLinterOutput(line) : null; + if (diagnostic is not null) + { + diagnostics.Add(diagnostic); + } + else + { + // Something went wrong so break and return the current set. + break; + } + } + + return diagnostics; + } + + private static MarkdownDiagnosticInfo? GetDiagnosticFromLinterOutput(string outputLine) + { + Requires.NotNull(outputLine, nameof(outputLine)); + var match = LinterOutputRegex.Match(outputLine); + if (!match.Success) + { + return null; + } + + int line = int.Parse(match.Groups["Line"].Value, CultureInfo.InvariantCulture) - 1; + int column = match.Groups.ContainsKey("Column") && !string.IsNullOrEmpty(match.Groups["Column"].Value) ? (int.Parse(match.Groups["Column"].Value, CultureInfo.InvariantCulture) - 1) : -1; + + return new MarkdownDiagnosticInfo( + range: new Microsoft.VisualStudio.RpcContracts.Utilities.Range(startLine: line, startColumn: column), + message: match.Groups["Description"].Value, + errorCode: match.Groups["Error"].Value); + } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticInfo.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticInfo.cs index 87a91ee8..c815d57d 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticInfo.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticInfo.cs @@ -13,31 +13,31 @@ namespace MarkdownLinter; /// public class MarkdownDiagnosticInfo { - /// - /// Initializes a new instance of the class. - /// - /// Range where the diagnostic exists. - /// Message to be presented with the diagnostic. - /// Unique error code of this type of diagnostic. - public MarkdownDiagnosticInfo(Range range, string message, string errorCode) - { - this.Range = range; - this.Message = message; - this.ErrorCode = errorCode; - } + /// + /// Initializes a new instance of the class. + /// + /// Range where the diagnostic exists. + /// Message to be presented with the diagnostic. + /// Unique error code of this type of diagnostic. + public MarkdownDiagnosticInfo(Range range, string message, string errorCode) + { + this.Range = range; + this.Message = message; + this.ErrorCode = errorCode; + } - /// - /// Gets the range of the diagnostic. - /// - public Range Range { get; } + /// + /// Gets the range of the diagnostic. + /// + public Range Range { get; } - /// - /// Gets the error message of the diagnostic. - /// - public string Message { get; } + /// + /// Gets the error message of the diagnostic. + /// + public string Message { get; } - /// - /// Gets the error code of the diagnostic. - /// - public string ErrorCode { get; } + /// + /// Gets the error code of the diagnostic. + /// + public string ErrorCode { get; } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs index c601f7b3..a18ff484 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs @@ -23,154 +23,159 @@ namespace MarkdownLinter; internal class MarkdownDiagnosticsService : DisposableObject { #pragma warning disable CA2213 // Disposable fields should be disposed, object now owned by this instance. - private readonly VisualStudioExtensibility extensibility; + private readonly VisualStudioExtensibility extensibility; #pragma warning restore CA2213 // Disposable fields should be disposed - - private OutputWindow? outputWindow; - private DiagnosticsReporter? diagnosticsReporter; - private Dictionary documentCancellationTokens; - private Task initializationTask; - - /// - /// Initializes a new instance of the class. - /// - /// Extensibility object. - public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility) - { - this.extensibility = Requires.NotNull(extensibility, nameof(extensibility)); - this.documentCancellationTokens = new Dictionary(); - this.initializationTask = Task.Run(this.InitializeAsync); - } - - /// - /// Processes the specified file for markdown errors and reports to the error list. - /// - /// Document uri to read the contents from. - /// Cancellation token to monitor. - /// Task indicating completion of reporting markdown errors to error list. - public async Task ProcessFileAsync(Uri documentUri, CancellationToken cancellationToken) - { - CancellationTokenSource newCts = new CancellationTokenSource(); - lock (this.documentCancellationTokens) - { - if (this.documentCancellationTokens.TryGetValue(documentUri, out var cts)) - { - cts.Cancel(); - } - - this.documentCancellationTokens[documentUri] = newCts; - } - - // Wait for 1 second to see if any other changes are being sent. - await Task.Delay(1000, cancellationToken); - - if (newCts.IsCancellationRequested) - { - return; - } - - try - { - var diagnostics = await LinterUtilities.RunLinterOnFileAsync(documentUri); - - await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken); - await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); - } - catch (InvalidOperationException) - { - if (this.outputWindow is object) - { - await this.outputWindow.Writer.WriteLineAsync(Strings.MissingLinterError); - } - } - } - - /// - /// Processes the current version instance for markdown errors and reports to the error list. - /// - /// Text View instance to read the contents from. - /// Cancellation token to monitor. - /// Task indicating completion of reporting markdown errors to error list. - public async Task ProcessTextViewAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - CancellationTokenSource newCts = new CancellationTokenSource(); - lock (this.documentCancellationTokens) - { - if (this.documentCancellationTokens.TryGetValue(textViewSnapshot.Document.Uri, out var cts)) - { - cts.Cancel(); - } - - this.documentCancellationTokens[textViewSnapshot.Document.Uri] = newCts; - } - - await this.ProcessDocumentAsync(textViewSnapshot.Document, cancellationToken.CombineWith(newCts.Token).Token); - } - - /// - /// Clears any of the existing entries for the specified document uri. - /// - /// Document uri to clear markdown error entries for. - /// Cancellation token to monitor. - /// Task indicating completion. - public async Task ClearEntriesForDocumentAsync(Uri documentUri, CancellationToken cancellationToken) - { - lock (this.documentCancellationTokens) - { - if (this.documentCancellationTokens.TryGetValue(documentUri, out var cts)) - { - cts.Cancel(); - this.documentCancellationTokens.Remove(documentUri); - } - } - - await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken); - } - - /// - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (isDisposing) - { - this.outputWindow?.Dispose(); - this.diagnosticsReporter?.Dispose(); - } - } - - private async Task ProcessDocumentAsync(ITextDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) - { - // Wait for 1 second to see if any other changes are being sent. - await Task.Delay(1000, cancellationToken); - - if (cancellationToken.IsCancellationRequested) - { - return; - } - - try - { - var diagnostics = await LinterUtilities.RunLinterOnDocumentAsync(documentSnapshot); - - await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentSnapshot, cancellationToken); - await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); - } - catch (InvalidOperationException) - { - if (this.outputWindow is object) - { - await this.outputWindow.Writer.WriteLineAsync(Strings.MissingLinterError); - } - } - } - - private async Task InitializeAsync() - { - this.outputWindow = await this.extensibility.Views().Output.GetChannelAsync(nameof(MarkdownLinterExtension) + Guid.NewGuid(), nameof(Strings.MarkdownLinterWindowName), default); - Assumes.NotNull(this.outputWindow); - - this.diagnosticsReporter = this.extensibility.Languages().GetDiagnosticsReporter(nameof(MarkdownLinterExtension)); - Assumes.NotNull(this.diagnosticsReporter); - } + private readonly Dictionary documentCancellationTokens; + private readonly Task initializationTask; + private OutputWindow? outputWindow; + private DiagnosticsReporter? diagnosticsReporter; + + /// + /// Initializes a new instance of the class. + /// + /// Extensibility object. + public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility) + { + this.extensibility = Requires.NotNull(extensibility, nameof(extensibility)); + this.documentCancellationTokens = new Dictionary(); + this.initializationTask = Task.Run(this.InitializeAsync); + } + + /// + /// Processes the specified file for markdown errors and reports to the error list. + /// + /// Document uri to read the contents from. + /// Cancellation token to monitor. + /// Task indicating completion of reporting markdown errors to error list. + public async Task ProcessFileAsync(Uri documentUri, CancellationToken cancellationToken) + { + CancellationTokenSource newCts = new CancellationTokenSource(); + lock (this.documentCancellationTokens) + { + if (this.documentCancellationTokens.TryGetValue(documentUri, out var cts)) + { + _ = cts.CancelAsync(); + } + + this.documentCancellationTokens[documentUri] = newCts; + } + + // Wait for 1 second to see if any other changes are being sent. + await Task.Delay(1000, cancellationToken); + + if (newCts.IsCancellationRequested) + { + return; + } + + try + { + var diagnostics = await LinterUtilities.RunLinterOnFileAsync(documentUri); + + await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken); + await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); + } + catch (InvalidOperationException) + { + if (this.outputWindow is not null) + { +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + await this.outputWindow.Writer.WriteLineAsync(Strings.MissingLinterError); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } + } + } + + /// + /// Processes the current version instance for markdown errors and reports to the error list. + /// + /// Text View instance to read the contents from. + /// Cancellation token to monitor. + /// Task indicating completion of reporting markdown errors to error list. + public async Task ProcessTextViewAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + CancellationTokenSource newCts = new CancellationTokenSource(); + lock (this.documentCancellationTokens) + { + if (this.documentCancellationTokens.TryGetValue(textViewSnapshot.Document.Uri, out var cts)) + { + _ = cts.CancelAsync(); + } + + this.documentCancellationTokens[textViewSnapshot.Document.Uri] = newCts; + } + + await this.ProcessDocumentAsync(textViewSnapshot.Document, cancellationToken.CombineWith(newCts.Token).Token); + } + + /// + /// Clears any of the existing entries for the specified document uri. + /// + /// Document uri to clear markdown error entries for. + /// Cancellation token to monitor. + /// Task indicating completion. + public async Task ClearEntriesForDocumentAsync(Uri documentUri, CancellationToken cancellationToken) + { + lock (this.documentCancellationTokens) + { + if (this.documentCancellationTokens.TryGetValue(documentUri, out var cts)) + { + _ = cts.CancelAsync(); + this.documentCancellationTokens.Remove(documentUri); + } + } + + await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken); + } + + /// + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (isDisposing) + { + this.outputWindow?.Dispose(); + this.diagnosticsReporter?.Dispose(); + } + } + + private async Task ProcessDocumentAsync(ITextDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) + { + // Wait for 1 second to see if any other changes are being sent. + await Task.Delay(1000, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + { + return; + } + + try + { + var diagnostics = await LinterUtilities.RunLinterOnDocumentAsync(documentSnapshot); + + await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentSnapshot, cancellationToken); + await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); + } + catch (InvalidOperationException) + { + if (this.outputWindow is not null) + { +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + await this.outputWindow.Writer.WriteLineAsync(Strings.MissingLinterError); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } + } + } + + private async Task InitializeAsync() + { +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + this.outputWindow = await this.extensibility.Views().Output.GetChannelAsync(nameof(MarkdownLinterExtension) + Guid.NewGuid(), nameof(Strings.MarkdownLinterWindowName), default); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + Assumes.NotNull(this.outputWindow); + + this.diagnosticsReporter = this.extensibility.Languages().GetDiagnosticsReporter(nameof(MarkdownLinterExtension)); + Assumes.NotNull(this.diagnosticsReporter); + } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinter.csproj b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinter.csproj index 446c3abb..73be59bd 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinter.csproj +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinter.csproj @@ -1,39 +1,32 @@  - - net6.0-windows8.0 - enable - 11 - true - en-US - $(NoWarn);CS1591;IDE0008;CA1812;CA2007 + + net8.0-windows8.0 + enable + 11 + en-US + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - - - - - True - True - Strings.resx - - + + + + + + + + True + True + Strings.resx + + - - - ResXFileCodeGenerator - Strings.Designer.cs - - - - - - PreserveNewest - - + + + ResXFileCodeGenerator + Strings.Designer.cs + + diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs index 2d09d8a9..2e741d32 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs @@ -15,30 +15,31 @@ namespace MarkdownLinter; [VisualStudioContribution] public class MarkdownLinterExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "MarkdownLinter.0cf26ba2-edd5-4419-8646-a55d0a83f7d8", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Markdown Linter Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "MarkdownLinter.0cf26ba2-edd5-4419-8646-a55d0a83f7d8", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Markdown Linter Sample Extension", + description: "Sample markdown linter extension"), + }; - /// - protected override ResourceManager? ResourceManager => Strings.ResourceManager; + /// + protected override ResourceManager? ResourceManager => Strings.ResourceManager; - /// - /// Initialize local services owned by the extension. These services can be shared - /// by various parts such as commands, editor listeners using dependency injection. - /// - /// Service collection to add services to. - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); + /// + /// Initialize local services owned by the extension. These services can be shared + /// by various parts such as commands, editor listeners using dependency injection. + /// + /// Service collection to add services to. + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); - // As of now, any instance that ingests VisualStudioExtensibility is required to be added as a scoped - // service. - serviceCollection.AddScoped(); - } + // As of now, any instance that ingests VisualStudioExtensibility is required to be added as a scoped + // service. + serviceCollection.AddScoped(); + } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtensionContributions.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtensionContributions.cs deleted file mode 100644 index 7173c88d..00000000 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtensionContributions.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace MarkdownLinter; - -using Microsoft.VisualStudio.Extensibility; -using Microsoft.VisualStudio.Extensibility.Editor; - -/// -/// Extension entry point for Markdown Linter sample extension showcasing new -/// out of proc Visual Studio Extensibilty APIs. -/// -internal static class MarkdownLinterExtensionContributions -{ - [VisualStudioContribution] - internal static DocumentTypeConfiguration MarkdownDocumentType => new("markdown") - { - FileExtensions = new[] { ".md", ".mdk", ".markdown" }, - BaseDocumentType = DocumentType.KnownValues.Text, - }; -} diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/README.md b/New_Extensibility_Model/Samples/MarkdownLinter/README.md index 3ad46e41..ccbe20e4 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/README.md +++ b/New_Extensibility_Model/Samples/MarkdownLinter/README.md @@ -62,16 +62,16 @@ foreach (var selectedItem in selectedItemPaths.Where(p => p.IsFile)) Another part of the extension is an editor component that listens for new editor view creation and changes to open views. This component monitors for events on `.md` files and routes the request to `MarkdownDiagnosticsService` as contents change. -The extension part also utilizes the `AppliesTo` configuration to indicate that it is interested in events from views with `markdown` content type. (The definition of `MarkdownLinterExtensionContributions.MarkdownDocumentType` as a custom content type is required as there is no `markdown` content type in Visual Studio) +The extension part also utilizes the `AppliesTo` configuration to indicate that it is interested in events from views related to files with `.md` extension. ```csharp public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() { AppliesTo = new[] { - DocumentFilter.FromDocumentType(MarkdownLinterExtensionContributions.MarkdownDocumentType), + DocumentFilter.FromGlobPattern("**/*.md", true), }, }; ``` -Even though this class implements 2 different contracts, a single instance of it will be created so that state can be shared between different editor components that interact together. \ No newline at end of file +Even though this class implements 2 different contracts, a single instance of it will be created so that state can be shared between different editor components that interact together. diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnCurrentFileCommand.cs b/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnCurrentFileCommand.cs index 1ffdfedc..d6026658 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnCurrentFileCommand.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnCurrentFileCommand.cs @@ -17,55 +17,56 @@ namespace MarkdownLinter; /// A command to execute linter on the current file selected in Solution Explorer. /// /// -/// This command utilizes to describe when commmand state is enabled. +/// This command utilizes to describe when command state is enabled. /// [VisualStudioContribution] internal class RunLinterOnCurrentFileCommand : Command { - private readonly TraceSource logger; - private MarkdownDiagnosticsService diagnosticsProvider; + private readonly TraceSource logger; - /// - /// Initializes a new instance of the class. - /// - /// Extensibility object. - /// Logger instance that can be used to log extension actions. - /// Local diagnostics provider service instance. - public RunLinterOnCurrentFileCommand(VisualStudioExtensibility extensibility, TraceSource traceSource, MarkdownDiagnosticsService diagnosticsProvider) - : base(extensibility) - { - this.logger = Requires.NotNull(traceSource, nameof(traceSource)); - this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); +#pragma warning disable CA2213 // This is an extension scoped service. + private readonly MarkdownDiagnosticsService diagnosticsProvider; +#pragma warning restore CA2213 - this.logger.TraceEvent(TraceEventType.Information, 0, $"Initializing {nameof(RunLinterOnCurrentFileCommand)} instance."); - } + /// + /// Initializes a new instance of the class. + /// + /// Logger instance that can be used to log extension actions. + /// Local diagnostics provider service instance. + public RunLinterOnCurrentFileCommand(TraceSource traceSource, MarkdownDiagnosticsService diagnosticsProvider) + { + this.logger = Requires.NotNull(traceSource, nameof(traceSource)); + this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); - /// - public override CommandConfiguration CommandConfiguration => new("%MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName%") - { - Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, - Icon = new(ImageMoniker.Custom("MarkdownIcon"), IconSettings.IconAndText), - EnabledWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveSelectionFileName, ".+"), - }; + this.logger.TraceEvent(TraceEventType.Information, 0, $"Initializing {nameof(RunLinterOnCurrentFileCommand)} instance."); + } - /// - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - try - { - // Get the selected item URIs from IDE context that reprents the state when command was executed. - var selectedItemPaths = new Uri[] { await context.GetSelectedPathAsync(cancellationToken) }; + /// + public override CommandConfiguration CommandConfiguration => new("%MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, + Icon = new(ImageMoniker.Custom("MarkdownIcon"), IconSettings.IconAndText), + EnabledWhen = ActivationConstraint.ClientContext(ClientContextKey.Shell.ActiveSelectionFileName, ".+"), + }; - // Enumerate through each selection and run linter on each selected item. - foreach (var selectedItem in selectedItemPaths.Where(p => p.IsFile)) - { - await this.diagnosticsProvider.ProcessFileAsync(selectedItem, cancellationToken); - } - } - catch (InvalidOperationException ex) - { - this.logger.TraceEvent(TraceEventType.Error, 0, ex.ToString()); - } - } + /// + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + // Get the selected item URIs from IDE context that represents the state when command was executed. + var selectedItemPaths = new Uri[] { await context.GetSelectedPathAsync(cancellationToken) }; + + // Enumerate through each selection and run linter on each selected item. + foreach (var selectedItem in selectedItemPaths.Where(p => p.IsFile)) + { + await this.diagnosticsProvider.ProcessFileAsync(selectedItem, cancellationToken); + } + } + catch (InvalidOperationException ex) + { + this.logger.TraceEvent(TraceEventType.Error, 0, ex.ToString()); + } + } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnSolutionCommand.cs b/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnSolutionCommand.cs index 10ceb42c..a3fdd210 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnSolutionCommand.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/RunLinterOnSolutionCommand.cs @@ -25,83 +25,84 @@ namespace MarkdownLinter; [VisualStudioContribution] internal class RunLinterOnSolutionCommand : Command { - private readonly TraceSource logger; - private MarkdownDiagnosticsService diagnosticsProvider; + private readonly TraceSource logger; - /// - /// Initializes a new instance of the class. - /// - /// Extensibility object. - /// Logger instance that can be used to log extension actions. - /// Local diagnostics provider service instance. - public RunLinterOnSolutionCommand(VisualStudioExtensibility extensibility, TraceSource traceSource, MarkdownDiagnosticsService diagnosticsProvider) - : base(extensibility) - { - this.logger = Requires.NotNull(traceSource, nameof(traceSource)); - this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); +#pragma warning disable CA2213 // This is an extension scoped service. + private readonly MarkdownDiagnosticsService diagnosticsProvider; +#pragma warning restore CA2213 - this.logger.TraceEvent(TraceEventType.Information, 0, $"Initializing {nameof(RunLinterOnSolutionCommand)} instance."); - } + /// + /// Initializes a new instance of the class. + /// + /// Logger instance that can be used to log extension actions. + /// Local diagnostics provider service instance. + public RunLinterOnSolutionCommand(TraceSource traceSource, MarkdownDiagnosticsService diagnosticsProvider) + { + this.logger = Requires.NotNull(traceSource, nameof(traceSource)); + this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); - /// - public override CommandConfiguration CommandConfiguration => new("%MarkdownLinter.RunLinterOnSolutionCommand.DisplayName%") - { - Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, - Icon = new(ImageMoniker.Custom("MarkdownIcon"), IconSettings.IconAndText), - EnabledWhen = ActivationConstraint.SolutionState(SolutionState.FullyLoaded), - }; + this.logger.TraceEvent(TraceEventType.Information, 0, $"Initializing {nameof(RunLinterOnSolutionCommand)} instance."); + } - /// - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - try - { - var markdownFiles = await this.Extensibility.Workspaces().QueryProjectsAsync( - query => query.Get(project => project.Files).With(file => file.Path).Where(file => file.Extension == ".md"), - cancellationToken); + /// + public override CommandConfiguration CommandConfiguration => new("%MarkdownLinter.RunLinterOnSolutionCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, + Icon = new(ImageMoniker.Custom("MarkdownIcon"), IconSettings.IconAndText), + EnabledWhen = ActivationConstraint.SolutionState(SolutionState.FullyLoaded), + }; - List filesToProcess = new List(markdownFiles.Select(f => new Uri(f.Path))); - if (filesToProcess.Count == 0) - { - return; - } + /// + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + try + { + var markdownFiles = await this.Extensibility.Workspaces().QueryProjectsAsync( + query => query.Get(project => project.Files).With(file => file.Path).Where(file => file.Extension == ".md"), + cancellationToken); - if (await this.Extensibility.Shell().ShowPromptAsync( - string.Format(Strings.Culture, Strings.MarkdownSolutionAnalysisPrompt, filesToProcess.Count), - PromptOptions.OKCancel, - cancellationToken)) - { - await this.ProcessFilesAsync(filesToProcess, cancellationToken); - } - } - catch (InvalidOperationException ex) - { - this.logger.TraceEvent(TraceEventType.Error, 0, ex.ToString()); - } - } + List filesToProcess = new List(markdownFiles.Select(f => new Uri(f.Path))); + if (filesToProcess.Count == 0) + { + return; + } - private static ProgressStatus CreateProgressStatus(int current, int max) - { - return new ProgressStatus((int)((current / (double)max) * 100)); - } + if (await this.Extensibility.Shell().ShowPromptAsync( + string.Format(Strings.Culture, Strings.MarkdownSolutionAnalysisPrompt, filesToProcess.Count), + PromptOptions.OKCancel, + cancellationToken)) + { + await this.ProcessFilesAsync(filesToProcess, cancellationToken); + } + } + catch (InvalidOperationException ex) + { + this.logger.TraceEvent(TraceEventType.Error, 0, ex.ToString()); + } + } - private async System.Threading.Tasks.Task ProcessFilesAsync(List filesToProcess, CancellationToken cancellationToken) - { - using ProgressReporter progress = await this.Extensibility.Shell().StartProgressReportingAsync( - Strings.MarkdownAnalysisMessage, - new ProgressReporterOptions(isWorkCancellable: true), - cancellationToken); + private static ProgressStatus CreateProgressStatus(int current, int max) + { + return new ProgressStatus((int)((current / (double)max) * 100)); + } - for (int i = 0; i < filesToProcess.Count; i++) - { - progress.CancellationToken.ThrowIfCancellationRequested(); - progress.Report(CreateProgressStatus(i, filesToProcess.Count)); + private async System.Threading.Tasks.Task ProcessFilesAsync(List filesToProcess, CancellationToken cancellationToken) + { + using ProgressReporter progress = await this.Extensibility.Shell().StartProgressReportingAsync( + Strings.MarkdownAnalysisMessage, + new ProgressReporterOptions(isWorkCancellable: true), + cancellationToken); - // Adding an artificial delay for demonstration purposes. - await Task.Delay(2000); - await this.diagnosticsProvider.ProcessFileAsync(filesToProcess[i], cancellationToken); - } + for (int i = 0; i < filesToProcess.Count; i++) + { + progress.CancellationToken.ThrowIfCancellationRequested(); + progress.Report(CreateProgressStatus(i, filesToProcess.Count)); - return progress; - } + // Adding an artificial delay for demonstration purposes. + await Task.Delay(2000); + await this.diagnosticsProvider.ProcessFileAsync(filesToProcess[i], cancellationToken); + } + + return progress; + } } diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/TextViewEventListener.cs b/New_Extensibility_Model/Samples/MarkdownLinter/TextViewEventListener.cs index a26a5c9b..e35adc2c 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/TextViewEventListener.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/TextViewEventListener.cs @@ -15,44 +15,46 @@ namespace MarkdownLinter; [VisualStudioContribution] internal class TextViewEventListener : ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener { - private readonly MarkdownDiagnosticsService diagnosticsProvider; - - /// - /// Initializes a new instance of the class. - /// - /// Extension instance. - /// Extensibility object. - /// Local diagnostics provider service instance. - public TextViewEventListener(MarkdownLinterExtension extension, VisualStudioExtensibility extensibility, MarkdownDiagnosticsService diagnosticsProvider) - : base(extension, extensibility) - { - this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); - } - - /// - public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() - { - AppliesTo = new[] - { - DocumentFilter.FromDocumentType(MarkdownLinterExtensionContributions.MarkdownDocumentType), - }, - }; - - /// - public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken) - { - return this.diagnosticsProvider.ProcessTextViewAsync(args.AfterTextView, cancellationToken); - } - - /// - public async Task TextViewClosedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - await this.diagnosticsProvider.ClearEntriesForDocumentAsync(textViewSnapshot.Document.Uri, cancellationToken); - } - - /// - public Task TextViewOpenedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) - { - return this.diagnosticsProvider.ProcessTextViewAsync(textViewSnapshot, cancellationToken); - } +#pragma warning disable CA2213 // This is an extension scoped service. + private readonly MarkdownDiagnosticsService diagnosticsProvider; +#pragma warning restore CA2213 + + /// + /// Initializes a new instance of the class. + /// + /// Extension instance. + /// Extensibility object. + /// Local diagnostics provider service instance. + public TextViewEventListener(MarkdownLinterExtension extension, VisualStudioExtensibility extensibility, MarkdownDiagnosticsService diagnosticsProvider) + : base(extension, extensibility) + { + this.diagnosticsProvider = Requires.NotNull(diagnosticsProvider, nameof(diagnosticsProvider)); + } + + /// + public TextViewExtensionConfiguration TextViewExtensionConfiguration => new() + { + AppliesTo = new[] + { + DocumentFilter.FromGlobPattern("**/*.md", true), + }, + }; + + /// + public Task TextViewChangedAsync(TextViewChangedArgs args, CancellationToken cancellationToken) + { + return this.diagnosticsProvider.ProcessTextViewAsync(args.AfterTextView, cancellationToken); + } + + /// + public async Task TextViewClosedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + await this.diagnosticsProvider.ClearEntriesForDocumentAsync(textViewSnapshot.Document.Uri, cancellationToken); + } + + /// + public Task TextViewOpenedAsync(ITextViewSnapshot textViewSnapshot, CancellationToken cancellationToken) + { + return this.diagnosticsProvider.ProcessTextViewAsync(textViewSnapshot, cancellationToken); + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj index fee2eed9..e2a183a4 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamObjectSource.csproj @@ -1,11 +1,11 @@  - - netstandard2.0 - enable - 11 - + + netstandard2.0 + enable + 11 + - - - + + + diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs index cde51f31..6f1167ff 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamObjectSource/MemoryStreamVisualizerObjectSource.cs @@ -11,49 +11,49 @@ namespace MemoryStreamObjectSource; /// public class MemoryStreamVisualizerObjectSource : VisualizerObjectSource { - /// - /// How many rows will be transfered, at most, responding to a single request. - /// - public const int RowsCountPerRequest = 1024; - - /// - /// How many bytes will be transfered for each row. - /// - public const int RowLength = 16; - - /// - public override void TransferData(object target, Stream incomingData, Stream outgoingData) - { - if (target is MemoryStream memoryStream) - { - using BinaryReader binaryReader = new(incomingData); - var index = binaryReader.ReadInt32(); // The extension will send the offset (Int32) to start reading from - - using BinaryWriter binaryWriter = new(outgoingData); - var backupPosition = memoryStream.Position; - - // Will reply with the current MemoryStream.Position (Int64), - // followed by MemoryStream.Length (Int64), - // followed by up to 16KB of data retrieved from the MemoryStream - binaryWriter.Write(backupPosition); - binaryWriter.Write(memoryStream.Length); - if (index < memoryStream.Length) - { - try - { - var data = new byte[RowsCountPerRequest * RowLength]; - memoryStream.Seek(index, SeekOrigin.Begin); - var count = memoryStream.Read(data, 0, data.Length); - binaryWriter.Write(data, 0, count); - } - finally - { - // Make sure to restore the MemoryStream to its original position - memoryStream.Seek(backupPosition, SeekOrigin.Begin); - } - } - - binaryWriter.Flush(); - } - } + /// + /// How many rows will be transfered, at most, responding to a single request. + /// + public const int RowsCountPerRequest = 1024; + + /// + /// How many bytes will be transfered for each row. + /// + public const int RowLength = 16; + + /// + public override void TransferData(object target, Stream incomingData, Stream outgoingData) + { + if (target is MemoryStream memoryStream) + { + using BinaryReader binaryReader = new(incomingData); + var index = binaryReader.ReadInt32(); // The extension will send the offset (Int32) to start reading from + + using BinaryWriter binaryWriter = new(outgoingData); + var backupPosition = memoryStream.Position; + + // Will reply with the current MemoryStream.Position (Int64), + // followed by MemoryStream.Length (Int64), + // followed by up to 16KB of data retrieved from the MemoryStream + binaryWriter.Write(backupPosition); + binaryWriter.Write(memoryStream.Length); + if (index < memoryStream.Length) + { + try + { + var data = new byte[RowsCountPerRequest * RowLength]; + memoryStream.Seek(index, SeekOrigin.Begin); + var count = memoryStream.Read(data, 0, data.Length); + binaryWriter.Write(data, 0, count); + } + finally + { + // Make sure to restore the MemoryStream to its original position + memoryStream.Seek(backupPosition, SeekOrigin.Begin); + } + } + + binaryWriter.Flush(); + } + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs index 1ba31f5a..7119fb19 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/ExtensionEntrypoint.cs @@ -12,21 +12,22 @@ namespace MemoryStreamVisualizer; [VisualStudioContribution] internal class ExtensionEntrypoint : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "MemoryStreamVisualizer.97a0a2fb-f163-4fa3-91f0-48a2d4ad9f57", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "MemoryStream Debugger Visualizer"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "MemoryStreamVisualizer.97a0a2fb-f163-4fa3-91f0-48a2d4ad9f57", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "MemoryStream Debugger Visualizer", + description: "A debugger visualizer for MemoryStream"), + }; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); - // You can configure dependency injection here by adding services to the serviceCollection. - } + // You can configure dependency injection here by adding services to the serviceCollection. + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs index 0aff97da..8d78106d 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/HexEditorRow.cs @@ -11,40 +11,40 @@ namespace MemoryStreamVisualizer; [DataContract] public class HexEditorRow { - /// - /// Initializes a new instance of the class. - /// - /// The index of this row. - /// The bytes making up this row of data, in their hex representation. - /// The bytes making up this row of data, in their Ascii representation. - public HexEditorRow(int index, string data, string ascii) - { - this.Index = index; - this.Data = data; - this.Ascii = ascii; - } + /// + /// Initializes a new instance of the class. + /// + /// The index of this row. + /// The bytes making up this row of data, in their hex representation. + /// The bytes making up this row of data, in their Ascii representation. + public HexEditorRow(int index, string data, string ascii) + { + this.Index = index; + this.Data = data; + this.Ascii = ascii; + } - /// - /// Gets the index of this row. - /// - [DataMember] - public int Index { get; } + /// + /// Gets the index of this row. + /// + [DataMember] + public int Index { get; } - /// - /// Gets the index of this row in hex format. - /// - [DataMember] - public string HexIndex => $"{this.Index:X8}h"; + /// + /// Gets the index of this row in hex format. + /// + [DataMember] + public string HexIndex => $"{this.Index:X8}h"; - /// - /// Gets the bytes making up this row of data, in their hex representation. - /// - [DataMember] - public string Data { get; } + /// + /// Gets the bytes making up this row of data, in their hex representation. + /// + [DataMember] + public string Data { get; } - /// - /// Gets the bytes making up this row of data, in their Ascii representation. - /// - [DataMember] - public string Ascii { get; } + /// + /// Gets the bytes making up this row of data, in their Ascii representation. + /// + [DataMember] + public string Ascii { get; } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs index a4c7e532..fa7d3962 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamData.cs @@ -14,67 +14,67 @@ namespace MemoryStreamVisualizer; [DataContract] public class MemoryStreamData : NotifyPropertyChangedObject { - private long length; - private long position; - private Visibility loadingVisibility = Visibility.Visible; + private long length; + private long position; + private Visibility loadingVisibility = Visibility.Visible; - /// - /// Gets or sets the length of the . - /// - [DataMember] - public long Length - { - get => this.length; - set - { - if (this.SetProperty(ref this.length, value)) - { - this.RaiseNotifyPropertyChangedEvent(nameof(this.HexLength)); - } - } - } + /// + /// Gets or sets the length of the . + /// + [DataMember] + public long Length + { + get => this.length; + set + { + if (this.SetProperty(ref this.length, value)) + { + this.RaiseNotifyPropertyChangedEvent(nameof(this.HexLength)); + } + } + } - /// - /// Gets the length of the in hex format. - /// - [DataMember] - public string HexLength => $"{this.length:X}h"; + /// + /// Gets the length of the in hex format. + /// + [DataMember] + public string HexLength => $"{this.length:X}h"; - /// - /// Gets or sets the current position of the . - /// - [DataMember] - public long Position - { - get => this.position; - set - { - if (this.SetProperty(ref this.position, value)) - { - this.RaiseNotifyPropertyChangedEvent(nameof(this.HexPosition)); - } - } - } + /// + /// Gets or sets the current position of the . + /// + [DataMember] + public long Position + { + get => this.position; + set + { + if (this.SetProperty(ref this.position, value)) + { + this.RaiseNotifyPropertyChangedEvent(nameof(this.HexPosition)); + } + } + } - /// - /// Gets the current position of the in hex format. - /// - [DataMember] - public string HexPosition => $"{this.position:X}h"; + /// + /// Gets the current position of the in hex format. + /// + [DataMember] + public string HexPosition => $"{this.position:X}h"; - /// - /// Gets the data currently contained in the . - /// - [DataMember] - public ObservableList Data { get; } = new(); + /// + /// Gets the data currently contained in the . + /// + [DataMember] + public ObservableList Data { get; } = new(); - /// - /// Gets or sets whether the loading bar should be visible. - /// - [DataMember] - public Visibility LoadingVisibility - { - get => this.loadingVisibility; - set => this.SetProperty(ref this.loadingVisibility, value); - } + /// + /// Gets or sets whether the loading bar should be visible. + /// + [DataMember] + public Visibility LoadingVisibility + { + get => this.loadingVisibility; + set => this.SetProperty(ref this.loadingVisibility, value); + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs index f186ee79..6a735b7a 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamDebuggerVisualizerProvider.cs @@ -17,18 +17,18 @@ namespace MemoryStreamVisualizer; [VisualStudioContribution] internal class MemoryStreamDebuggerVisualizerProvider : DebuggerVisualizerProvider { - /// - public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new( - visualizerDisplayName: "%MemoryStreamVisualizer.MemoryStreamDebuggerVisualizerProvider.Name%", - targetType: typeof(MemoryStream)) - { - VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(MemoryStreamVisualizerObjectSource)), - Style = VisualizerStyle.ToolWindow, - }; + /// + public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new( + visualizerDisplayName: "%MemoryStreamVisualizer.MemoryStreamDebuggerVisualizerProvider.Name%", + targetType: typeof(MemoryStream)) + { + VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(MemoryStreamVisualizerObjectSource)), + Style = VisualizerStyle.ToolWindow, + }; - /// - public override Task CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken) - { - return Task.FromResult(new MemoryStreamVisualizerUserControl(visualizerTarget)); - } + /// + public override Task CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken) + { + return Task.FromResult(new MemoryStreamVisualizerUserControl(visualizerTarget)); + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj index 1b1cf1cc..006342b0 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizer.csproj @@ -1,38 +1,38 @@  - - net6.0-windows8.0 - enable - 11 - true - en-US - true - $(NoWarn);CS1591;IDE0008;CA1812 + + net8.0-windows8.0 + enable + 11 + true + en-US + true + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - + + + + - - - PreserveNewest - - + + + PreserveNewest + + - - - - + + + + - - - + + + - - - + + + diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs index 3e727793..4004d046 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.cs @@ -18,131 +18,133 @@ namespace MemoryStreamVisualizer; using System.Windows; /// -/// Remote UI user control for the MemoryStreamVisualizer, +/// Remote UI user control for the MemoryStreamVisualizer. /// internal class MemoryStreamVisualizerUserControl : RemoteUserControl { - private readonly VisualizerTarget visualizerTarget; - private readonly MemoryStreamData dataContext; - - /// - /// Initializes a new instance of the class. - /// - /// The visualizer target to be used to retrieve data from. - public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget) - : base(new MemoryStreamData()) - { - visualizerTarget.StateChanged += this.VisualizerTargetStateChangedAsync; - - this.dataContext = (MemoryStreamData)this.DataContext!; - this.visualizerTarget = visualizerTarget; - } - - private Task VisualizerTargetStateChangedAsync(object? sender, VisualizerTargetStateNotification args) - { - if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated) - { - this.dataContext.Data.Clear(); - this.dataContext.Position = 0; - this.dataContext.Length = 0; - this.dataContext.LoadingVisibility = Visibility.Visible; - - return this.RetrieveDataAsync(); - } - - return Task.CompletedTask; - } - - private async Task RetrieveDataAsync() - { - ReadOnlySequence data; - do - { - using MemoryStream memoryStream = new(sizeof(int)); - using BinaryWriter binaryWriter = new(memoryStream); - int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength; - if (index >= 1024 * 1024) - { - break; // Let's not retrieve more than 1MB of data - } - - binaryWriter.Write(index); - binaryWriter.Flush(); - try - { - data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence(memoryStream.ToArray()), CancellationToken.None)).Value; - } - catch (Exception) - { - // I can get an exception if the debug session is unpaused, so I need to handle it gracefully - break; - } - } - while (data.Length > 0 && this.Read(data)); - - this.dataContext.LoadingVisibility = Visibility.Hidden; - } - - private bool Read(ReadOnlySequence data) - { - int byteInRowCount = 0; - StringBuilder binaryText = new(); - StringBuilder asciiText = new(); - - SequenceReader reader = new(data); - if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length)) - { - return false; - } - - this.dataContext.Position = position; - this.dataContext.Length = length; - - if (reader.UnreadSpan.Length == 0) - { - return false; // We always receive data unless we are at the end of the MemoryStream - } - - List rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest); - byte[] tmp = new byte[1]; - while (reader.TryRead(out byte b)) - { - byteInRowCount++; - if (byteInRowCount > 1) - { - binaryText.Append(' '); - } - - binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture)); - tmp[0] = b; - asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]); - - if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength) - { - CompleteRow(); - } - } - - if (byteInRowCount > 0) - { - CompleteRow(); - this.dataContext.Data.AddRange(rows); - return false; // We only receive partial rows at the end of the MemoryStream - } - - this.dataContext.Data.AddRange(rows); - return true; - - void CompleteRow() - { - rows.Add(new HexEditorRow( - index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength, - data: binaryText.ToString(), - ascii: asciiText.ToString())); - - byteInRowCount = 0; - binaryText.Clear(); - asciiText.Clear(); - } - } + private readonly VisualizerTarget visualizerTarget; + private readonly MemoryStreamData dataContext; + + /// + /// Initializes a new instance of the class. + /// + /// The visualizer target to be used to retrieve data from. + public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget) + : base(new MemoryStreamData()) + { + visualizerTarget.StateChanged += this.VisualizerTargetStateChangedAsync; + + this.dataContext = (MemoryStreamData)this.DataContext!; + this.visualizerTarget = visualizerTarget; + } + + private Task VisualizerTargetStateChangedAsync(object? sender, VisualizerTargetStateNotification args) + { + if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated) + { + this.dataContext.Data.Clear(); + this.dataContext.Position = 0; + this.dataContext.Length = 0; + this.dataContext.LoadingVisibility = Visibility.Visible; + + return this.RetrieveDataAsync(); + } + + return Task.CompletedTask; + } + + private async Task RetrieveDataAsync() + { + ReadOnlySequence data; + do + { + using MemoryStream memoryStream = new(sizeof(int)); + using BinaryWriter binaryWriter = new(memoryStream); + int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength; + if (index >= 1024 * 1024) + { + break; // Let's not retrieve more than 1MB of data + } + + binaryWriter.Write(index); + binaryWriter.Flush(); + try + { + data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence(memoryStream.ToArray()), CancellationToken.None)).Value; + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception) +#pragma warning restore CA1031 // Do not catch general exception types + { + // I can get an exception if the debug session is unpaused, so I need to handle it gracefully + break; + } + } + while (data.Length > 0 && this.Read(data)); + + this.dataContext.LoadingVisibility = Visibility.Hidden; + } + + private bool Read(ReadOnlySequence data) + { + int byteInRowCount = 0; + StringBuilder binaryText = new(); + StringBuilder asciiText = new(); + + SequenceReader reader = new(data); + if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length)) + { + return false; + } + + this.dataContext.Position = position; + this.dataContext.Length = length; + + if (reader.UnreadSpan.Length == 0) + { + return false; // We always receive data unless we are at the end of the MemoryStream + } + + List rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest); + byte[] tmp = new byte[1]; + while (reader.TryRead(out byte b)) + { + byteInRowCount++; + if (byteInRowCount > 1) + { + binaryText.Append(' '); + } + + binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture)); + tmp[0] = b; + asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]); + + if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength) + { + CompleteRow(); + } + } + + if (byteInRowCount > 0) + { + CompleteRow(); + this.dataContext.Data.AddRange(rows); + return false; // We only receive partial rows at the end of the MemoryStream + } + + this.dataContext.Data.AddRange(rows); + return true; + + void CompleteRow() + { + rows.Add(new HexEditorRow( + index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength, + data: binaryText.ToString(), + ascii: asciiText.ToString())); + + byteInRowCount = 0; + binaryText.Clear(); + asciiText.Clear(); + } + } } diff --git a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml index 03505773..2c6b164a 100644 --- a/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml +++ b/New_Extensibility_Model/Samples/MemoryStreamDebugVisualizer/MemoryStreamVisualizer/MemoryStreamVisualizerUserControl.xaml @@ -1,8 +1,8 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml" + xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" + xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"> diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/OutputWindowSample/.vsextension/string-resources.json index ac80323a..71ed907a 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/OutputWindowSample/.vsextension/string-resources.json @@ -1,3 +1,3 @@ { - "OutputWindowSample.TestOutputWindowCommand.DisplayName": "Test the Output Window" + "OutputWindowSample.TestOutputWindowCommand.DisplayName": "Test the Output Window" } \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSample.csproj b/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSample.csproj index 003e925b..2c114a12 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSample.csproj +++ b/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSample.csproj @@ -1,39 +1,32 @@  - - net6.0-windows8.0 - enable - 11 - true - en-US - $(NoWarn);CS1591;IDE0008;CA1812 + + net8.0-windows8.0 + enable + 11 + en-US + $(NoWarn);CS1591;CA1812;CA1303;SA1600 - - https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) - + + https://pkgs.dev.azure.com/azure-public/vside/_packaging/msft_consumption/nuget/v3/index.json;$(RestoreAdditionalProjectSources) + - - - - + + + + - - - True - True - Strings.resx - - + + + True + True + Strings.resx + + - - - ResXFileCodeGenerator - Strings.Designer.cs - - - - - - PreserveNewest - - + + + ResXFileCodeGenerator + Strings.Designer.cs + + diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSampleExtension.cs b/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSampleExtension.cs index 7d5320fb..e97c534e 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSampleExtension.cs +++ b/New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSampleExtension.cs @@ -13,22 +13,23 @@ namespace OutputWindowSample; [VisualStudioContribution] public class OutputWindowSampleExtension : Extension { - /// - public override ExtensionConfiguration ExtensionConfiguration => new() - { - Metadata = new( - id: "OutputWindowSample.f739efb2-fdb2-4ecf-b857-d8e11fd1e5d3", - version: this.ExtensionAssemblyVersion, - publisherName: "Microsoft", - displayName: "Output Window Sample Extension"), - }; + /// + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "OutputWindowSample.f739efb2-fdb2-4ecf-b857-d8e11fd1e5d3", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Output Window Sample Extension", + description: "Sample extension demonstrating writing to the output window"), + }; - /// - protected override ResourceManager? ResourceManager => Strings.ResourceManager; + /// + protected override ResourceManager? ResourceManager => Strings.ResourceManager; - /// - protected override void InitializeServices(IServiceCollection serviceCollection) - { - base.InitializeServices(serviceCollection); - } + /// + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } } diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/README.md b/New_Extensibility_Model/Samples/OutputWindowSample/README.md index 6ccd6053..04b316ce 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/README.md +++ b/New_Extensibility_Model/Samples/OutputWindowSample/README.md @@ -8,6 +8,9 @@ date: 2022-08-01 This extension demonstrates the most basic usage of the [Output Window API](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/output-window/output-window) +> [!IMPORTANT] +> The VisualStudio.Extensibility Output window APIs are currently in preview and are subject to change. Any extension that leverages these APIs may fail to work in future versions of Visual Studio and will need to be updated when a newer version of the APIs is released. + ## Summary This extension adds a [command](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/command/command) to the Tools menu called "Test the Output Window". When invoked, the command will print `"This is a test of the output window."` to the Output pane in a Channel called "MyOutputWindow" (look for the Channel name in the "Show output from:" dropdown in the Output pane). diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/Strings.Designer.cs b/New_Extensibility_Model/Samples/OutputWindowSample/Strings.Designer.cs index 75503d60..e8d8f7e7 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/Strings.Designer.cs +++ b/New_Extensibility_Model/Samples/OutputWindowSample/Strings.Designer.cs @@ -10,8 +10,8 @@ namespace OutputWindowSample { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace OutputWindowSample { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Strings() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal Strings() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,7 +59,7 @@ internal Strings() { resourceCulture = value; } } - + /// /// Looks up a localized string similar to My Output Window. /// diff --git a/New_Extensibility_Model/Samples/OutputWindowSample/TestOutputWindowCommand.cs b/New_Extensibility_Model/Samples/OutputWindowSample/TestOutputWindowCommand.cs index 8e5359de..6e851187 100644 --- a/New_Extensibility_Model/Samples/OutputWindowSample/TestOutputWindowCommand.cs +++ b/New_Extensibility_Model/Samples/OutputWindowSample/TestOutputWindowCommand.cs @@ -15,49 +15,39 @@ namespace OutputWindowSample; [VisualStudioContribution] public class TestOutputWindowCommand : Command { - private OutputWindow? outputWindow; - - /// - /// Initializes a new instance of the class. - /// - /// - /// Extensibility object instance. - /// - /// - /// Command identifier. - /// - public TestOutputWindowCommand(VisualStudioExtensibility extensibility) - : base(extensibility) - { - } - - /// - public override CommandConfiguration CommandConfiguration => new("%OutputWindowSample.TestOutputWindowCommand.DisplayName%") - { - Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, - Icon = new(ImageMoniker.KnownValues.ToolWindow, IconSettings.IconAndText), - }; - - /// - public override async Task InitializeAsync(CancellationToken cancellationToken) - { - this.outputWindow = await this.GetOutputWindowAsync(cancellationToken); - await base.InitializeAsync(cancellationToken); - } - - /// - public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) - { - if (this.outputWindow != null) - { - await this.outputWindow.Writer.WriteLineAsync("This is a test of the output window."); - } - } - - private async Task GetOutputWindowAsync(CancellationToken cancellationToken) - { - string id = "MyOutputWindow"; - string displayNameResourceId = nameof(Strings.OutputWindowDisplayName); - return await this.Extensibility.Views().Output.GetChannelAsync(id, displayNameResourceId, cancellationToken); - } + private OutputWindow? outputWindow; + + /// + public override CommandConfiguration CommandConfiguration => new("%OutputWindowSample.TestOutputWindowCommand.DisplayName%") + { + Placements = new[] { CommandPlacement.KnownPlacements.ToolsMenu }, + Icon = new(ImageMoniker.KnownValues.ToolWindow, IconSettings.IconAndText), + }; + + /// + public override async Task InitializeAsync(CancellationToken cancellationToken) + { + this.outputWindow = await this.GetOutputWindowAsync(cancellationToken); + await base.InitializeAsync(cancellationToken); + } + + /// + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + if (this.outputWindow != null) + { +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + await this.outputWindow.Writer.WriteLineAsync("This is a test of the output window."); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } + } + + private async Task GetOutputWindowAsync(CancellationToken cancellationToken) + { + string id = "MyOutputWindow"; + string displayNameResourceId = nameof(Strings.OutputWindowDisplayName); +#pragma warning disable VSEXTAPI0001 // This API is marked as Preview. + return await this.Extensibility.Views().Output.GetChannelAsync(id, displayNameResourceId, cancellationToken); +#pragma warning restore VSEXTAPI0001 // This API is marked as Preview. + } } diff --git a/New_Extensibility_Model/Samples/README.md b/New_Extensibility_Model/Samples/README.md index 90d2820c..34021f53 100644 --- a/New_Extensibility_Model/Samples/README.md +++ b/New_Extensibility_Model/Samples/README.md @@ -5,15 +5,22 @@ and call out interesting aspects to its sample. Samples include: -* SimpleRemoteCommandSample: A simple extension showing how to declare a single command. -* OutputWindowSample: An extension that uses a simple command to write a message to the output window. -* ToolWindowSample: An extension that creates a tool window and populates it with content. -* UserPromptSample: An extension that uses a simple command to display a prompt to the user. -* CommandRegistrationsSample: Showcases how to register commands in different ways and command placement, localization. -* InsertGuidExtension: A simple extension showing how to read/modify editor buffers. -* MarkdownLinter: An end-to-end example showing various features available in the extensibility SDK. -* DocumentSelectorSample: A sample demonstrating how to define an editor extension that is only applicable to files matching a file path pattern. -* CommentRemover: A conversion of Mads Kristensen's [Comment Remover](https://github.com/madskristensen/CommentRemover) extension to in-proc VisualStudio.Extensibility. This sample shows how to consume [Visual Studio SDK](https://www.nuget.org/packages/Microsoft.VisualStudio.SDK) services through .NET dependency injection and use VisualStudio.Extensibility APIs for commands, prompts and progress report. +| Sample | Description| +|-|-| +| [Simple command handler](./SimpleRemoteCommandSample) | Demonstrates the basics of working with commands. See also the [Create your first extension](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/get-started/create-your-first-extension) tutorial.| +| [Insert guid](./InsertGuid) | Shows how to insert text or code in the code editor, how to configure a command with a specific activation condition, and how to use a resource file for localization. See also the [tutorial](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/get-started/tutorial-create-simple-extension). | +| [Command parenting](./CommandParentingSample) | Shows how to author a command that can be parented to different aspects of the IDE. | +| [Document selector](./DocumentSelectorSample) | Shows how to create an editor extension that is only applicable to files matching a file path pattern. | +| [Output window](./OutputWindowSample) | Shows the most basic use of the [Output Window API](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/output-window/output-window)| +| [Tool window](./ToolWindowSample) | Shows how to create a tool window and populate it with content. | +| [User prompt](./UserPromptSample) | Shows how to display a prompt to the user. | +| [Dialog](./DialogSample) | Shows how to display a dialog with custom UI to the user. | +| [Word count margin](./WordCountMargin) | Shows how to create an editor margin extension that displays the word count in a document. | +| [Markdown linter](./MarkdownLinter) | A complete extension with many moving parts interacting to provide an enhanced experience in the editor for a certain file type. | +| [Project Query](./VSProjectQueryAPISample) | Shows several different kinds of project system queries you can make. | +| [Comment remover](./CommentRemover) | Shows how to consume [Visual Studio SDK](https://www.nuget.org/packages/Microsoft.VisualStudio.SDK) services through .NET dependency injection and use VisualStudio.Extensibility APIs for commands, prompts and progress report. | +| [RegexMatchDebugVisualizer](./RegexMatchDebugVisualizer) | Shows how to use [Remote UI](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/inside-the-sdk/remote-ui) to create a [Debugger Visualizer](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/debugger-visualizer/debugger-visualizers) to visualize regular expression matches that will launch in a modal dialog window. | +| [MemoryStreamDebugVisualizer](./MemoryStreamDebugVisualizer) | Shows how to create a [Debugger Visualizer](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/debugger-visualizer/debugger-visualizers) to visualize MemoryStream objects that launches in a non-modal tool window. | ## Contributing diff --git a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/.vsextension/string-resources.json index 7a73a41b..6232a048 100644 --- a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/.vsextension/string-resources.json @@ -1,2 +1,4 @@ { + "RegexMatchVisualizer.RegexMatchDebuggerVisualizerProvider.DisplayName": "Regex Match visualizer", + "RegexMatchVisualizer.RegexMatchCollectionDebuggerVisualizerProvider.DisplayName": "Regex Match visualizer" } \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchDebuggerVisualizerProvider.cs b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchDebuggerVisualizerProvider.cs index 83428b13..4f9c911f 100644 --- a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchDebuggerVisualizerProvider.cs +++ b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchDebuggerVisualizerProvider.cs @@ -17,27 +17,17 @@ namespace RegexMatchVisualizer; [VisualStudioContribution] internal class RegexMatchDebuggerVisualizerProvider : DebuggerVisualizerProvider { - /// - /// Initializes a new instance of the class. - /// - /// Extension instance. - /// Extensibility object. - public RegexMatchDebuggerVisualizerProvider(RegexMatchVisualizerExtension extension, VisualStudioExtensibility extensibility) - : base(extension, extensibility) - { - } + /// + public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("%RegexMatchVisualizer.RegexMatchDebuggerVisualizerProvider.DisplayName%", typeof(Match)) + { + VisualizerObjectSourceType = new("RegexMatchVisualizer.ObjectSource.RegexMatchObjectSource, RegexMatchObjectSource"), + }; - /// - public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("Regex Match visualizer", typeof(Match)) - { - VisualizerObjectSourceType = new("RegexMatchVisualizer.ObjectSource.RegexMatchObjectSource, RegexMatchObjectSource"), - }; + /// + public override async Task CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken) + { + var regexMatch = await visualizerTarget.ObjectSource.RequestDataAsync(jsonSerializer: null, cancellationToken); - /// - public override async Task CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken) - { - var regexMatch = await visualizerTarget.ObjectSource.RequestDataAsync(jsonSerializer: null, cancellationToken); - - return new RegexMatchVisualizerUserControl(regexMatch); - } + return new RegexMatchVisualizerUserControl(regexMatch); + } } diff --git a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.cs b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.cs index 5a048fec..f2cd28da 100644 --- a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.cs +++ b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.cs @@ -14,12 +14,12 @@ namespace RegexMatchVisualizer; /// internal class RegexMatchVisualizerUserControl : RemoteUserControl { - /// - /// Initializes a new instance of the class. - /// - /// Data context of the remote control. - public RegexMatchVisualizerUserControl(RegexMatch dataContext) - : base(dataContext) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Data context of the remote control. + public RegexMatchVisualizerUserControl(RegexMatch dataContext) + : base(dataContext) + { + } } diff --git a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.xaml b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.xaml index 662c7769..91032e3b 100644 --- a/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.xaml +++ b/New_Extensibility_Model/Samples/RegexMatchDebugVisualizer/RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.xaml @@ -1,86 +1,86 @@  - - - + - +