From 1e221435b1b35b52e50dff717d3a095f1c057518 Mon Sep 17 00:00:00 2001 From: Andrew Schwartzmeyer Date: Thu, 5 May 2022 10:57:24 -0700 Subject: [PATCH 1/2] Send `sendKeyPress` event across DAP for temporary integrated consoles --- .../Hosting/EditorServicesServerFactory.cs | 3 +- .../Server/PsesDebugServer.cs | 13 ++++++- .../PowerShell/Host/PsesInternalHost.cs | 36 +++++++++++++++---- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs index 361d292ea..e9704e8cd 100644 --- a/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs +++ b/src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs @@ -169,7 +169,8 @@ public PsesDebugServer CreateDebugServerForTempSession( _loggerFactory, inputStream, outputStream, - serviceProvider); + serviceProvider, + isTemp: true); } /// diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index 789c727b4..c9f263216 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -30,18 +30,22 @@ internal class PsesDebugServer : IDisposable private bool _startedPses; + private readonly bool _isTemp; + protected readonly ILoggerFactory _loggerFactory; public PsesDebugServer( ILoggerFactory factory, Stream inputStream, Stream outputStream, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + bool isTemp = false) { _loggerFactory = factory; _inputStream = inputStream; _outputStream = outputStream; ServiceProvider = serviceProvider; + _isTemp = isTemp; _serverStopped = new TaskCompletionSource(); } @@ -92,6 +96,13 @@ public async Task StartAsync() // We need to make sure the host has been started _startedPses = !await _psesHost.TryStartAsync(new HostStartOptions(), CancellationToken.None).ConfigureAwait(false); + // We need to give the host a handle to the DAP so it can register + // notifications (specifically for sendKeyPress). + if (_isTemp) + { + _psesHost._debugServer = server; + } + // Ensure the debugger mode is set correctly - this is required for remote debugging to work _psesHost.DebugContext.EnableDebugMode(); diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 1d9813a48..1a2117b17 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -24,6 +24,10 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host { using System.Management.Automation; using System.Management.Automation.Runspaces; + // NOTE: These last three are for a workaround for temporary integrated consoles. + using Microsoft.PowerShell.EditorServices.Handlers; + using Microsoft.PowerShell.EditorServices.Server; + using OmniSharp.Extensions.DebugAdapter.Protocol.Server; internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRunspaceContext, IInternalPowerShellExecutionService { @@ -41,6 +45,14 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns private readonly ILanguageServerFacade _languageServer; + /// + /// TODO: Improve this coupling. It's assigned by + /// so that the PowerShell process started when + /// is true can also receive the required 'sendKeyPress' notification to return from a + /// canceled . + /// + internal IDebugAdapterServerFacade _debugServer; + private readonly HostStartupInfo _hostInfo; private readonly BlockingConcurrentDeque _taskQueue; @@ -1053,11 +1065,6 @@ private void OnCancelKeyPress(object sender, ConsoleCancelEventArgs args) private ConsoleKeyInfo ReadKey(bool intercept) { - // PSRL 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 - // NOTE: This requests that the client (the Code extension) send a non-printing key back // to the terminal on stdin, emulating a user pressing a button. This allows // PSReadLine's thread waiting on Console.ReadKey to return. Normally we'd just cancel @@ -1065,8 +1072,23 @@ private ConsoleKeyInfo ReadKey(bool intercept) // input. This leads to a myriad of problems, but we circumvent them by pretending to // press a key, thus allowing ReadKey to return, and us to ignore it. using CancellationTokenRegistration registration = _readKeyCancellationToken.Register( - () => _languageServer?.SendNotification("powerShell/sendKeyPress")); - + () => + { + // For the regular integrated console, we have an associated language server on + // which we can send a notification, and have the client subscribe an action to + // send a key press. + _languageServer?.SendNotification("powerShell/sendKeyPress"); + // When temporary integrated consoles are spawned, there will be no associated + // language server, but instead a debug adaptor server. In this case, the + // notification sent here will come across as a DebugSessionCustomEvent to which + // 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); return _lastKey.Value; From 8fb208b1beec38422457975679a739fcdce2ed91 Mon Sep 17 00:00:00 2001 From: Andy Schwartzmeyer Date: Thu, 5 May 2022 12:19:59 -0700 Subject: [PATCH 2/2] Apply suggestions from code review Co-authored-by: Patrick Meinecke --- src/PowerShellEditorServices/Server/PsesDebugServer.cs | 2 +- .../Services/PowerShell/Host/PsesInternalHost.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PowerShellEditorServices/Server/PsesDebugServer.cs b/src/PowerShellEditorServices/Server/PsesDebugServer.cs index c9f263216..b84c2cbf6 100644 --- a/src/PowerShellEditorServices/Server/PsesDebugServer.cs +++ b/src/PowerShellEditorServices/Server/PsesDebugServer.cs @@ -100,7 +100,7 @@ public async Task StartAsync() // notifications (specifically for sendKeyPress). if (_isTemp) { - _psesHost._debugServer = server; + _psesHost.DebugServer = server; } // Ensure the debugger mode is set correctly - this is required for remote debugging to work diff --git a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs index 1a2117b17..64abf455d 100644 --- a/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs +++ b/src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs @@ -51,7 +51,7 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns /// is true can also receive the required 'sendKeyPress' notification to return from a /// canceled . /// - internal IDebugAdapterServerFacade _debugServer; + internal IDebugAdapterServerFacade DebugServer; private readonly HostStartupInfo _hostInfo; @@ -1078,11 +1078,12 @@ private ConsoleKeyInfo ReadKey(bool intercept) // which we can send a notification, and have the client subscribe an action to // send a key press. _languageServer?.SendNotification("powerShell/sendKeyPress"); + // When temporary integrated consoles are spawned, there will be no associated // language server, but instead a debug adaptor server. In this case, the // notification sent here will come across as a DebugSessionCustomEvent to which // we can subscribe in the same way. - _debugServer?.SendNotification("powerShell/sendKeyPress"); + DebugServer?.SendNotification("powerShell/sendKeyPress"); }); // PSReadLine doesn't tell us when CtrlC was sent. So instead we keep track of the last