diff --git a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs index 00095a8a4..73e4fb229 100644 --- a/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs +++ b/src/PowerShellEditorServices/Services/Workspace/Handlers/ConfigurationHandler.cs @@ -11,14 +11,13 @@ using Microsoft.PowerShell.EditorServices.Logging; using Microsoft.PowerShell.EditorServices.Services; using Microsoft.PowerShell.EditorServices.Services.Configuration; +using Microsoft.PowerShell.EditorServices.Services.Extension; +using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; using Newtonsoft.Json.Linq; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using OmniSharp.Extensions.LanguageServer.Protocol.Server; using OmniSharp.Extensions.LanguageServer.Protocol.Window; using OmniSharp.Extensions.LanguageServer.Protocol.Workspace; -using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host; -using Microsoft.PowerShell.EditorServices.Services.Extension; - namespace Microsoft.PowerShell.EditorServices.Handlers { @@ -31,7 +30,7 @@ internal class PsesConfigurationHandler : DidChangeConfigurationHandlerBase private readonly PsesInternalHost _psesHost; private readonly ILanguageServerFacade _languageServer; private bool _profilesLoaded; - private bool _extensionServiceInitialized; + private readonly bool _extensionServiceInitialized; private bool _cwdSet; public PsesConfigurationHandler( @@ -56,10 +55,10 @@ public PsesConfigurationHandler( public override async Task Handle(DidChangeConfigurationParams request, CancellationToken cancellationToken) { LanguageServerSettingsWrapper incomingSettings = request.Settings.ToObject(); - this._logger.LogTrace("Handling DidChangeConfiguration"); + _logger.LogTrace("Handling DidChangeConfiguration"); if (incomingSettings is null || incomingSettings.Powershell is null) { - this._logger.LogTrace("Incoming settings were null"); + _logger.LogTrace("Incoming settings were null"); return await Unit.Task.ConfigureAwait(false); } @@ -102,32 +101,30 @@ public override async Task Handle(DidChangeConfigurationParams request, Ca } // TODO: Load profiles when the host is already running - - if (!this._cwdSet) + if (!_cwdSet) { if (!string.IsNullOrEmpty(_configurationService.CurrentSettings.Cwd) && Directory.Exists(_configurationService.CurrentSettings.Cwd)) { - this._logger.LogTrace($"Setting CWD (from config) to {_configurationService.CurrentSettings.Cwd}"); + _logger.LogTrace($"Setting CWD (from config) to {_configurationService.CurrentSettings.Cwd}"); await _psesHost.SetInitialWorkingDirectoryAsync( _configurationService.CurrentSettings.Cwd, CancellationToken.None).ConfigureAwait(false); - } - else if (_workspaceService.WorkspacePath != null + else if (_workspaceService.WorkspacePath is not null && Directory.Exists(_workspaceService.WorkspacePath)) { - this._logger.LogTrace($"Setting CWD (from workspace) to {_workspaceService.WorkspacePath}"); + _logger.LogTrace($"Setting CWD (from workspace) to {_workspaceService.WorkspacePath}"); await _psesHost.SetInitialWorkingDirectoryAsync( _workspaceService.WorkspacePath, CancellationToken.None).ConfigureAwait(false); } else { - this._logger.LogTrace("Tried to set CWD but in bad state"); + _logger.LogTrace("Tried to set CWD but in bad state"); } - this._cwdSet = true; + _cwdSet = true; } if (!_extensionServiceInitialized) @@ -136,35 +133,46 @@ await _psesHost.SetInitialWorkingDirectoryAsync( } // Run any events subscribed to configuration updates - this._logger.LogTrace("Running configuration update event handlers"); + _logger.LogTrace("Running configuration update event handlers"); ConfigurationUpdated?.Invoke(this, _configurationService.CurrentSettings); // Convert the editor file glob patterns into an array for the Workspace // Both the files.exclude and search.exclude hash tables look like (glob-text, is-enabled): + // // "files.exclude" : { // "Makefile": true, // "*.html": true, + // "**/*.js": { "when": "$(basename).ts" }, // "build/*": true // } - var excludeFilePatterns = new List(); - if (incomingSettings.Files?.Exclude != null) + // + // TODO: We only support boolean values. The clause predicates are ignored, but perhaps + // they shouldn't be. At least it doesn't crash! + List excludeFilePatterns = new(); + if (incomingSettings.Files?.Exclude is not null) { - foreach(KeyValuePair patternEntry in incomingSettings.Files.Exclude) + foreach (KeyValuePair patternEntry in incomingSettings.Files.Exclude) { - if (patternEntry.Value) { excludeFilePatterns.Add(patternEntry.Key); } + if (patternEntry.Value is bool v && v) + { + excludeFilePatterns.Add(patternEntry.Key); + } } } - if (incomingSettings.Search?.Exclude != null) + if (incomingSettings.Search?.Exclude is not null) { - foreach(KeyValuePair patternEntry in incomingSettings.Search.Exclude) + foreach (KeyValuePair patternEntry in incomingSettings.Search.Exclude) { - if (patternEntry.Value && !excludeFilePatterns.Contains(patternEntry.Key)) { excludeFilePatterns.Add(patternEntry.Key); } + if (patternEntry.Value is bool v && v && !excludeFilePatterns.Contains(patternEntry.Key)) + { + excludeFilePatterns.Add(patternEntry.Key); + } } } _workspaceService.ExcludeFilesGlob = excludeFilePatterns; // Convert the editor file search options to Workspace properties - if (incomingSettings.Search?.FollowSymlinks != null) + if (incomingSettings.Search?.FollowSymlinks is not null) { _workspaceService.FollowSymlinks = incomingSettings.Search.FollowSymlinks; } @@ -176,11 +184,11 @@ private void SendFeatureChangesTelemetry(LanguageServerSettingsWrapper incomingS { if (incomingSettings is null) { - this._logger.LogTrace("Incoming settings were null"); + _logger.LogTrace("Incoming settings were null"); return; } - var configChanges = new Dictionary(); + Dictionary configChanges = new(); // Send telemetry if the user opted-out of ScriptAnalysis if (incomingSettings.Powershell.ScriptAnalysis.Enable == false && _configurationService.CurrentSettings.ScriptAnalysis.Enable != incomingSettings.Powershell.ScriptAnalysis.Enable) diff --git a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs index ed4768a2e..f538f7575 100644 --- a/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs +++ b/src/PowerShellEditorServices/Services/Workspace/LanguageServerSettings.cs @@ -15,28 +15,21 @@ namespace Microsoft.PowerShell.EditorServices.Services.Configuration { internal class LanguageServerSettings { - private readonly object updateLock = new object(); - - public bool EnableProfileLoading { get; set; } = false; - + private readonly object updateLock = new(); + public bool EnableProfileLoading { get; set; } public bool PromptToUpdatePackageManagement { get; set; } = true; - public ScriptAnalysisSettings ScriptAnalysis { get; set; } - public CodeFormattingSettings CodeFormatting { get; set; } - public CodeFoldingSettings CodeFolding { get; set; } - public PesterSettings Pester { get; set; } - public string Cwd { get; set; } public LanguageServerSettings() { - this.ScriptAnalysis = new ScriptAnalysisSettings(); - this.CodeFormatting = new CodeFormattingSettings(); - this.CodeFolding = new CodeFoldingSettings(); - this.Pester = new PesterSettings(); + ScriptAnalysis = new ScriptAnalysisSettings(); + CodeFormatting = new CodeFormattingSettings(); + CodeFolding = new CodeFoldingSettings(); + Pester = new PesterSettings(); } public void Update( @@ -44,20 +37,17 @@ public void Update( string workspaceRootPath, ILogger logger) { - if (settings != null) + if (settings is not null) { lock (updateLock) { - this.EnableProfileLoading = settings.EnableProfileLoading; - this.PromptToUpdatePackageManagement = settings.PromptToUpdatePackageManagement; - this.ScriptAnalysis.Update( - settings.ScriptAnalysis, - workspaceRootPath, - logger); - this.CodeFormatting = new CodeFormattingSettings(settings.CodeFormatting); - this.CodeFolding.Update(settings.CodeFolding, logger); - this.Pester.Update(settings.Pester, logger); - this.Cwd = settings.Cwd; + EnableProfileLoading = settings.EnableProfileLoading; + PromptToUpdatePackageManagement = settings.PromptToUpdatePackageManagement; + ScriptAnalysis.Update(settings.ScriptAnalysis, workspaceRootPath, logger); + CodeFormatting = new CodeFormattingSettings(settings.CodeFormatting); + CodeFolding.Update(settings.CodeFolding, logger); + Pester.Update(settings.Pester, logger); + Cwd = settings.Cwd; } } } @@ -65,28 +55,21 @@ public void Update( internal class ScriptAnalysisSettings { - private readonly object updateLock = new object(); - + private readonly object updateLock = new(); public bool? Enable { get; set; } - public string SettingsPath { get; set; } - - public ScriptAnalysisSettings() - { - this.Enable = true; - } + public ScriptAnalysisSettings() { Enable = true; } public void Update( ScriptAnalysisSettings settings, string workspaceRootPath, ILogger logger) { - if (settings != null) + if (settings is not null) { - lock(updateLock) + lock (updateLock) { - this.Enable = settings.Enable; - + Enable = settings.Enable; string settingsPath = settings.SettingsPath; try @@ -105,8 +88,7 @@ public void Update( // In this case we should just log an error and let // the specified settings path go through even though // it will fail to load. - logger.LogError( - "Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath."); + logger.LogError( "Could not resolve Script Analyzer settings path due to null or empty workspaceRootPath."); } else { @@ -114,20 +96,14 @@ public void Update( } } - this.SettingsPath = settingsPath; + SettingsPath = settingsPath; logger.LogTrace($"Using Script Analyzer settings path - '{settingsPath ?? ""}'."); } - catch (Exception ex) when ( - ex is NotSupportedException || - ex is PathTooLongException || - ex is SecurityException) + catch (Exception ex) when (ex is NotSupportedException or PathTooLongException or SecurityException) { // Invalid chars in path like ${env:HOME} can cause Path.GetFullPath() to throw, catch such errors here - logger.LogException( - $"Invalid Script Analyzer settings path - '{settingsPath}'.", - ex); - - this.SettingsPath = null; + logger.LogException($"Invalid Script Analyzer settings path - '{settingsPath}'.", ex); + SettingsPath = null; } } } @@ -192,9 +168,7 @@ internal class CodeFormattingSettings /// /// Default constructor. /// > - public CodeFormattingSettings() - { - } + public CodeFormattingSettings() { } /// /// Copy constructor. @@ -202,12 +176,12 @@ public CodeFormattingSettings() /// An instance of type CodeFormattingSettings. public CodeFormattingSettings(CodeFormattingSettings codeFormattingSettings) { - if (codeFormattingSettings == null) + if (codeFormattingSettings is null) { throw new ArgumentNullException(nameof(codeFormattingSettings)); } - foreach (var prop in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + foreach (PropertyInfo prop in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) { prop.SetValue(this, prop.GetValue(codeFormattingSettings)); } @@ -226,29 +200,28 @@ public CodeFormattingSettings(CodeFormattingSettings codeFormattingSettings) public bool WhitespaceBeforeOpenParen { get; set; } public bool WhitespaceAroundOperator { get; set; } public bool WhitespaceAfterSeparator { get; set; } - public bool WhitespaceBetweenParameters { get; set; } + public bool WhitespaceBetweenParameters { get; set; } public bool WhitespaceInsideBrace { get; set; } public bool IgnoreOneLineBlock { get; set; } public bool AlignPropertyValuePairs { get; set; } public bool UseCorrectCasing { get; set; } - /// /// Get the settings hashtable that will be consumed by PSScriptAnalyzer. /// /// The tab size in the number spaces. /// If true, insert spaces otherwise insert tabs for indentation. - /// public Hashtable GetPSSASettingsHashtable( int tabSize, bool insertSpaces, ILogger logger) { - var settings = GetCustomPSSASettingsHashtable(tabSize, insertSpaces); - var ruleSettings = (Hashtable)(settings["Rules"]); - var closeBraceSettings = (Hashtable)ruleSettings["PSPlaceCloseBrace"]; - var openBraceSettings = (Hashtable)ruleSettings["PSPlaceOpenBrace"]; - switch(Preset) + Hashtable settings = GetCustomPSSASettingsHashtable(tabSize, insertSpaces); + Hashtable ruleSettings = settings["Rules"] as Hashtable; + Hashtable closeBraceSettings = ruleSettings["PSPlaceCloseBrace"] as Hashtable; + Hashtable openBraceSettings = ruleSettings["PSPlaceOpenBrace"] as Hashtable; + + switch (Preset) { case CodeFormattingPreset.Allman: openBraceSettings["OnSameLine"] = false; @@ -268,6 +241,7 @@ public Hashtable GetPSSASettingsHashtable( closeBraceSettings["NewLineAfter"] = true; break; + case CodeFormattingPreset.Custom: default: break; } @@ -278,7 +252,7 @@ public Hashtable GetPSSASettingsHashtable( private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces) { - var ruleConfigurations = new Hashtable + Hashtable ruleConfigurations = new() { { "PSPlaceOpenBrace", new Hashtable { { "Enable", true }, @@ -337,8 +311,7 @@ private Hashtable GetCustomPSSASettingsHashtable(int tabSize, bool insertSpaces) "PSAlignAssignmentStatement", "PSAvoidUsingDoubleQuotesForConstantString", }}, - { - "Rules", ruleConfigurations + { "Rules", ruleConfigurations } }; } @@ -366,14 +339,17 @@ public void Update( CodeFoldingSettings settings, ILogger logger) { - if (settings != null) { - if (this.Enable != settings.Enable) { - this.Enable = settings.Enable; - logger.LogTrace(string.Format("Using Code Folding Enabled - {0}", this.Enable)); + if (settings is not null) + { + if (Enable != settings.Enable) + { + Enable = settings.Enable; + logger.LogTrace(string.Format("Using Code Folding Enabled - {0}", Enable)); } - if (this.ShowLastLine != settings.ShowLastLine) { - this.ShowLastLine = settings.ShowLastLine; - logger.LogTrace(string.Format("Using Code Folding ShowLastLine - {0}", this.ShowLastLine)); + if (ShowLastLine != settings.ShowLastLine) + { + ShowLastLine = settings.ShowLastLine; + logger.LogTrace(string.Format("Using Code Folding ShowLastLine - {0}", ShowLastLine)); } } } @@ -401,20 +377,21 @@ public void Update( PesterSettings settings, ILogger logger) { - if (settings is null) { + if (settings is null) + { return; } - if (this.CodeLens != settings.CodeLens) + if (CodeLens != settings.CodeLens) { - this.CodeLens = settings.CodeLens; - logger.LogTrace(string.Format("Using Pester Code Lens - {0}", this.CodeLens)); + CodeLens = settings.CodeLens; + logger.LogTrace(string.Format("Using Pester Code Lens - {0}", CodeLens)); } - if (this.UseLegacyCodeLens != settings.UseLegacyCodeLens) + if (UseLegacyCodeLens != settings.UseLegacyCodeLens) { - this.UseLegacyCodeLens = settings.UseLegacyCodeLens; - logger.LogTrace(string.Format("Using Pester Legacy Code Lens - {0}", this.UseLegacyCodeLens)); + UseLegacyCodeLens = settings.UseLegacyCodeLens; + logger.LogTrace(string.Format("Using Pester Legacy Code Lens - {0}", UseLegacyCodeLens)); } } } @@ -426,10 +403,10 @@ public void Update( internal class EditorFileSettings { /// - /// Exclude files globs consists of hashtable with the key as the glob and a boolean value to indicate if the - /// the glob is in effect. + /// Exclude files globs consists of hashtable with the key as the glob and a boolean value + /// OR object with a predicate clause to indicate if the glob is in effect. /// - public Dictionary Exclude { get; set; } + public Dictionary Exclude { get; set; } } /// @@ -439,10 +416,11 @@ internal class EditorFileSettings internal class EditorSearchSettings { /// - /// Exclude files globs consists of hashtable with the key as the glob and a boolean value to indicate if the - /// the glob is in effect. + /// Exclude files globs consists of hashtable with the key as the glob and a boolean value + /// OR object with a predicate clause to indicate if the glob is in effect. /// - public Dictionary Exclude { get; set; } + public Dictionary Exclude { get; set; } + /// /// Whether to follow symlinks when searching ///