Skip to content

Commit

Permalink
Send sendKeyPress event across DAP for temporary integrated consoles (
Browse files Browse the repository at this point in the history
#1791)

Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
  • Loading branch information
andyleejordan and SeeminglyScience committed May 5, 2022
1 parent f29b4e4 commit dd870ec
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ public static EditorServicesServerFactory Create(string logPath, int minimumLogL
_loggerFactory,
inputStream,
outputStream,
serviceProvider);
serviceProvider,
isTemp: true);
}

/// <summary>
Expand Down
13 changes: 12 additions & 1 deletion src/PowerShellEditorServices/Server/PsesDebugServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>();
}

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -41,6 +45,14 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns

private readonly ILanguageServerFacade _languageServer;

/// <summary>
/// TODO: Improve this coupling. It's assigned by <see cref="PsesDebugServer.StartAsync()" />
/// so that the PowerShell process started when <see cref="PsesLaunchRequestArguments.CreateTemporaryIntegratedConsole" />
/// is true can also receive the required 'sendKeyPress' notification to return from a
/// canceled <see cref="System.Console.ReadKey()" />.
/// </summary>
internal IDebugAdapterServerFacade DebugServer;

private readonly HostStartupInfo _hostInfo;

private readonly BlockingConcurrentDeque<ISynchronousTask> _taskQueue;
Expand Down Expand Up @@ -1053,20 +1065,31 @@ 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
// this call, but the .NET API ReadKey is not cancellable, and is stuck until we send
// 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;
Expand Down

0 comments on commit dd870ec

Please sign in to comment.