diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json index cb3d4d71..11ca1a99 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json +++ b/New_Extensibility_Model/Samples/MarkdownLinter/.vsextension/string-resources.json @@ -1,4 +1,8 @@ { "MarkdownLinter.RunLinterOnCurrentFileCommand.DisplayName": "Run Linter on open file", - "MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution" + "MarkdownLinter.RunLinterOnSolutionCommand.DisplayName": "Run Linter on solution", + "MarkdownLinter.Settings.Category.DisplayName": "Markdown Linter", + "MarkdownLinter.Settings.Category.Description": "Markdown Linter Settings", + "MarkdownLinter.Settings.DisabledRules.DisplayName": "Disabled rules", + "MarkdownLinter.Settings.DisabledRules.Description": "List of disabled rules to pass to markdown-cli" } \ 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 a70d8e39..4ca1c3fd 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/LinterUtilities.cs @@ -11,6 +11,7 @@ namespace MarkdownLinter; using System.Globalization; using System.IO; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft; using Microsoft.VisualStudio.Extensibility.Editor; @@ -19,27 +20,44 @@ namespace MarkdownLinter; using Microsoft.VisualStudio.RpcContracts.DiagnosticManagement; using Microsoft.VisualStudio.Threading; +#pragma warning disable VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + /// /// Helper class for running linter on a string or file. /// -internal static class LinterUtilities +internal class LinterUtilities { private static readonly Regex LinterOutputRegex = new(@"(?[^:]+):(?\d*)(:(?\d*))? (?.*)/(?.*)", RegexOptions.Compiled); + private readonly Settings.MarkdownLinterCategoryObserver settingsObserver; + + public LinterUtilities(Settings.MarkdownLinterCategoryObserver settingsObserver) + { + this.settingsObserver = settingsObserver; + } + /// /// Runs markdown linter on a file uri and returns diagnostic entries. /// /// File uri to run markdown linter on. + /// Cancellation token to monitor. /// an enumeration of entries for warnings in the markdown file. - public static async Task> RunLinterOnFileAsync(Uri fileUri) + public async Task> RunLinterOnFileAsync(Uri fileUri, CancellationToken cancellationToken) { using var linter = new Process(); var lineQueue = new AsyncQueue(); + var snapshot = await this.settingsObserver.GetSnapshotAsync(cancellationToken); + string disabledRules = snapshot.DisabledRules.ValueOrDefault(string.Empty); + + string args = "/c \"npx markdownlint-cli" + + (disabledRules.Length > 0 ? $" --disable {disabledRules} --" : string.Empty) + + $" \"{fileUri.LocalPath}\"\""; + linter.StartInfo = new ProcessStartInfo() { - FileName = "node.exe", - Arguments = $"\"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\node_modules\\markdownlint-cli\\markdownlint.js")}\" \"{fileUri.LocalPath}\"", + FileName = "cmd.exe", + Arguments = args, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, @@ -76,18 +94,25 @@ public static async Task> RunLinterOnFileAsync(U /// Runs markdown linter on a given text document and returns diagnostic entries. /// /// Document to run markdown linter on. + /// Cancellation token to monitor. /// an enumeration of entries for warnings in the markdown file. - public static async Task> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument) + public async Task> RunLinterOnDocumentAsync(ITextDocumentSnapshot textDocument, CancellationToken cancellationToken) { using var linter = new Process(); var lineQueue = new AsyncQueue(); var content = textDocument.Text.CopyToString(); + var snapshot = await this.settingsObserver.GetSnapshotAsync(cancellationToken); + string disabledRules = snapshot.DisabledRules.ValueOrDefault(string.Empty); + + string args = "/k \"npx markdownlint-cli --stdin" + + (disabledRules.Length > 0 ? $" --disable {disabledRules}" : string.Empty) + "\""; + linter.StartInfo = new ProcessStartInfo() { FileName = "cmd.exe", - Arguments = $"/k \"{Environment.ExpandEnvironmentVariables("%APPDATA%\\npm\\markdownlint.cmd")}\" -s", + Arguments = args, RedirectStandardError = true, RedirectStandardInput = true, UseShellExecute = false, @@ -125,16 +150,6 @@ public static async Task> RunLinterOnDocumentAsy 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) diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs index 7070eedf..288c89fc 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownDiagnosticsService.cs @@ -7,6 +7,7 @@ namespace MarkdownLinter; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Windows.Media.Animation; using Microsoft; using Microsoft.VisualStudio.Extensibility; using Microsoft.VisualStudio.Extensibility.Documents; @@ -28,6 +29,7 @@ internal class MarkdownDiagnosticsService : DisposableObject #pragma warning restore CA2213 // Disposable fields should be disposed private readonly Dictionary documentCancellationTokens; private readonly Task initializationTask; + private readonly LinterUtilities linterUtilities; private OutputChannel? outputChannel; private DiagnosticsReporter? diagnosticsReporter; @@ -35,10 +37,12 @@ internal class MarkdownDiagnosticsService : DisposableObject /// Initializes a new instance of the class. /// /// Extensibility object. - public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility) + /// Service for running the linter on markdown files. + public MarkdownDiagnosticsService(VisualStudioExtensibility extensibility, LinterUtilities linterUtilities) { this.extensibility = Requires.NotNull(extensibility, nameof(extensibility)); this.documentCancellationTokens = new Dictionary(); + this.linterUtilities = Requires.NotNull(linterUtilities, nameof(linterUtilities)); this.initializationTask = Task.Run(this.InitializeAsync); } @@ -71,7 +75,7 @@ public async Task ProcessFileAsync(Uri documentUri, CancellationToken cancellati try { - var diagnostics = await LinterUtilities.RunLinterOnFileAsync(documentUri); + var diagnostics = await this.linterUtilities.RunLinterOnFileAsync(documentUri, cancellationToken); await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentUri, cancellationToken); await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); @@ -151,7 +155,7 @@ private async Task ProcessDocumentAsync(ITextDocumentSnapshot documentSnapshot, try { - var diagnostics = await LinterUtilities.RunLinterOnDocumentAsync(documentSnapshot); + var diagnostics = await this.linterUtilities.RunLinterOnDocumentAsync(documentSnapshot, cancellationToken); await this.diagnosticsReporter!.ClearDiagnosticsAsync(documentSnapshot, cancellationToken); await this.diagnosticsReporter!.ReportDiagnosticsAsync(diagnostics, cancellationToken); diff --git a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs index 2e741d32..aa15f70c 100644 --- a/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterExtension.cs @@ -38,6 +38,12 @@ protected override void InitializeServices(IServiceCollection serviceCollection) { base.InitializeServices(serviceCollection); + // Add the settings observer created by MarkdownLinterSettingDefinitions to use in LinterUtilies. + serviceCollection.AddSettingsObservers(); + + // Add linter utilities as a singleton, it depends on settings observer. + serviceCollection.AddSingleton(); + // 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/MarkdownLinterSettingDefinitions.cs b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterSettingDefinitions.cs new file mode 100644 index 00000000..3ceeb782 --- /dev/null +++ b/New_Extensibility_Model/Samples/MarkdownLinter/MarkdownLinterSettingDefinitions.cs @@ -0,0 +1,25 @@ +// 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.Settings; + +#pragma warning disable VSEXTPREVIEW_SETTINGS // The settings API is currently in preview and marked as experimental + +internal static class MarkdownLinterSettingDefinitions +{ + [VisualStudioContribution] + internal static SettingCategory MarkdownLinterCategory { get; } = new("markdownLinter", "%MarkdownLinter.Settings.Category.DisplayName%") + { + Description = "%MarkdownLinter.Settings.Category.Description%", + GenerateObserverClass = true, + }; + + [VisualStudioContribution] + internal static Setting.String DisabledRules { get; } = new("disabledRules", "%MarkdownLinter.Settings.DisabledRules.DisplayName%", MarkdownLinterCategory, defaultValue: string.Empty) + { + Description = "%MarkdownLinter.Settings.DisabledRules.Description%", + }; +}