diff --git a/module/PowerShellEditorServices/Start-EditorServices.ps1 b/module/PowerShellEditorServices/Start-EditorServices.ps1 index 47ba523c2..9721cf536 100644 --- a/module/PowerShellEditorServices/Start-EditorServices.ps1 +++ b/module/PowerShellEditorServices/Start-EditorServices.ps1 @@ -52,10 +52,10 @@ param( [ValidateSet("Diagnostic", "Verbose", "Normal", "Warning", "Error")] $LogLevel, - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $SessionDetailsPath, + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [string] + $SessionDetailsPath, [switch] $EnableConsoleRepl, @@ -63,6 +63,9 @@ param( [switch] $UseLegacyReadLine, + [switch] + $UseHostReadKey, + [switch] $DebugServiceOnly, diff --git a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs index 3d31ede06..599b14662 100644 --- a/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs +++ b/src/PowerShellEditorServices.Hosting/Commands/StartEditorServicesCommand.cs @@ -161,6 +161,12 @@ public StartEditorServicesCommand() [Parameter] public SwitchParameter UseLegacyReadLine { get; set; } + /// + /// When set and the console is enabled and legacy readline + /// is enabled, console operations will use PSHostreadline implementation will be used instead of PSReadLine. + /// + [Parameter] + public SwitchParameter UseHostReadKey { get; set; } /// /// When set, do not enable LSP service, only the debug adapter. /// @@ -345,8 +351,9 @@ private EditorServicesConfig CreateConfigObject() hostInfo, Host, SessionDetailsPath, - bundledModulesPath, - LogPath) + bundledModulesPath, + LogPath, + UseHostReadKey) { FeatureFlags = FeatureFlags, LogLevel = LogLevel, diff --git a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs index f8b6ef30f..81c70dd3e 100644 --- a/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs +++ b/src/PowerShellEditorServices.Hosting/Configuration/EditorServicesConfig.cs @@ -17,7 +17,7 @@ public enum ConsoleReplKind /// Use a REPL with the legacy readline implementation. This is generally used when PSReadLine is unavailable. LegacyReadLine = 1, /// Use a REPL with the PSReadLine module for console interaction. - PSReadLine = 2, + PSReadLine = 2 } /// @@ -39,13 +39,15 @@ public EditorServicesConfig( PSHost psHost, string sessionDetailsPath, string bundledModulePath, - string logPath) + string logPath, + bool useHostReadKey) { HostInfo = hostInfo; PSHost = psHost; SessionDetailsPath = sessionDetailsPath; BundledModulePath = bundledModulePath; LogPath = logPath; + UseHostReadKey = useHostReadKey; } /// @@ -72,6 +74,7 @@ public EditorServicesConfig( /// The path to use for logging for Editor Services. /// public string LogPath { get; } + public bool UseHostReadKey { get; } /// /// Names of or paths to any additional modules to load on startup. @@ -88,7 +91,7 @@ public EditorServicesConfig( /// (including none to disable the integrated console). /// public ConsoleReplKind ConsoleRepl { get; set; } = ConsoleReplKind.None; - + /// /// The minimum log level to log events with. /// diff --git a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs index 924619283..e1bc6f24f 100644 --- a/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs +++ b/src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs @@ -112,7 +112,7 @@ public static EditorServicesLoader Create( string asmPath = Path.Combine(s_psesDependencyDirPath, $"{asmName.Name}.dll"); - logger.Log(PsesLogLevel.Verbose, "Loading PSES DLL using new assembly load context"); + logger.Log(PsesLogLevel.Verbose, $"Loading {asmName.Name}.dll using new assembly load context"); return psesLoadContext.LoadFromAssemblyPath(asmPath); }; diff --git a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs index 3e6626f20..d7743b96a 100644 --- a/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs +++ b/src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs @@ -290,6 +290,7 @@ private HostStartupInfo CreateHostStartupInfo() (int)_config.LogLevel, consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None, usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine, + useHostReadKey: _config.UseHostReadKey, bundledModulePath: _config.BundledModulePath); } diff --git a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs index e34df5912..d35a87b91 100644 --- a/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs +++ b/src/PowerShellEditorServices/Hosting/HostStartupInfo.cs @@ -80,7 +80,11 @@ public sealed class HostStartupInfo /// If the console REPL is not enabled, this setting will be ignored. /// public bool UsesLegacyReadLine { get; } - + /// + /// If true, the legacy PSES readline implementation but calls ReadKey in the provided host + /// If the console REPL is not enabled, this setting will be ignored. + /// + public bool UseHostReadKey { get; } /// /// The PowerShell host to use with Editor Services. /// @@ -153,6 +157,7 @@ public HostStartupInfo( int logLevel, bool consoleReplEnabled, bool usesLegacyReadLine, + bool useHostReadKey, string bundledModulePath) { Name = name ?? DefaultHostName; @@ -167,6 +172,7 @@ public HostStartupInfo( LogLevel = logLevel; ConsoleReplEnabled = consoleReplEnabled; UsesLegacyReadLine = usesLegacyReadLine; + UseHostReadKey = useHostReadKey; BundledModulePath = bundledModulePath; } diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs index 39e12974b..4b78931cb 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/LegacyReadLine.cs @@ -63,6 +63,7 @@ public override string ReadLine(CancellationToken cancellationToken) // because the window could have been resized before then int promptStartCol = initialCursorCol; int promptStartRow = initialCursorRow; + int consoleWidth = Console.WindowWidth; switch (keyInfo.Key) diff --git a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs index cda3af925..c3513a5fd 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Console/PsrlReadLine.cs @@ -17,6 +17,7 @@ internal class PsrlReadLine : TerminalReadLine private readonly PsesInternalHost _psesHost; private readonly EngineIntrinsics _engineIntrinsics; + public PsrlReadLine( PSReadLineProxy psrlProxy, @@ -29,7 +30,7 @@ public PsrlReadLine( _psesHost = psesHost; _engineIntrinsics = engineIntrinsics; _psrlProxy.OverrideReadKey(readKeyFunc); - _psrlProxy.OverrideIdleHandler(onIdleAction); + _psrlProxy.OverrideIdleHandler(onIdleAction); } public override string ReadLine(CancellationToken cancellationToken) => _psesHost.InvokeDelegate( @@ -39,7 +40,7 @@ public override string ReadLine(CancellationToken cancellationToken) => _psesHos cancellationToken); protected override ConsoleKeyInfo ReadKey(CancellationToken cancellationToken) => _psesHost.ReadKey(intercept: true, cancellationToken); - + private string InvokePSReadLine(CancellationToken cancellationToken) { EngineIntrinsics engineIntrinsics = _psesHost.IsRunspacePushed ? null : _engineIntrinsics; diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 271518add..0f267cb5b 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -98,6 +98,7 @@ public PsesInternalHost( _logger = loggerFactory.CreateLogger(); _languageServer = languageServer; _hostInfo = hostInfo; + // Respect a user provided bundled module path. if (Directory.Exists(hostInfo.BundledModulePath)) @@ -136,6 +137,7 @@ public PsesInternalHost( Version = hostInfo.Version; DebugContext = new PowerShellDebugContext(loggerFactory, this); + UI = hostInfo.ConsoleReplEnabled ? new EditorServicesConsolePSHostUserInterface(loggerFactory, hostInfo.PSHost.UI) : new NullPSHostUI(); @@ -1071,6 +1073,24 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) StopDebugContext(); } } + /// + /// This method is sent to PSReadLine as a workaround for issues with the System.Console + /// implementation. Functionally it is the same as System.Console.ReadKey, + /// with the exception that it will not lock the standard input stream. + /// + /// + /// Determines whether to display the pressed key in the console window. + /// true to not display the pressed key; otherwise, false. + /// + /// + /// The that can be used to cancel the request. + /// + /// + /// An object that describes the ConsoleKey constant and Unicode character, if any, + /// that correspond to the pressed console key. The ConsoleKeyInfo object also describes, + /// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt, + /// or Ctrl modifier keys was pressed simultaneously with the console key. + /// private ConsoleKeyInfo ReadKey(bool intercept) { @@ -1094,13 +1114,24 @@ private ConsoleKeyInfo ReadKey(bool intercept) // we can subscribe in the same way. DebugServer?.SendNotification("powerShell/sendKeyPress"); }); - + // PSReadLine doesn't tell us when CtrlC was sent. So instead we keep track of the last // key here. This isn't functionally required, but helps us determine when the prompt // needs a newline added // // TODO: We may want to allow users of PSES to override this method call. - _lastKey = System.Console.ReadKey(intercept); + if (!_hostInfo.UseHostReadKey) + { + _lastKey = System.Console.ReadKey(intercept); + } + else + { + KeyInfo keyInfo = _hostInfo.PSHost.UI.RawUI.ReadKey(); + _lastKey = new(keyInfo.Character, (ConsoleKey)keyInfo.Character, (keyInfo.ControlKeyState & ControlKeyStates.ShiftPressed) > 0, + (keyInfo.ControlKeyState & (ControlKeyStates.RightAltPressed | ControlKeyStates.LeftAltPressed)) > 0, + (keyInfo.ControlKeyState & (ControlKeyStates.RightCtrlPressed | ControlKeyStates.LeftCtrlPressed)) > 0); + } + return _lastKey.Value; } diff --git a/test/PowerShellEditorServices.Test/PsesHostFactory.cs b/test/PowerShellEditorServices.Test/PsesHostFactory.cs index 3f7ab95b9..81852d163 100644 --- a/test/PowerShellEditorServices.Test/PsesHostFactory.cs +++ b/test/PowerShellEditorServices.Test/PsesHostFactory.cs @@ -57,6 +57,7 @@ public static PsesInternalHost Create(ILoggerFactory loggerFactory) logLevel: (int)LogLevel.None, consoleReplEnabled: false, usesLegacyReadLine: false, + useHostReadKey: false, bundledModulePath: BundledModulePath); PsesInternalHost psesHost = new(loggerFactory, null, testHostDetails);