Skip to content

Send sendKeyPress event across DAP for temporary integrated consoles #1791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ public PsesDebugServer CreateDebugServerForTempSession(
_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,30 @@ 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