Skip to content

PSReadLine integration (WIP) #671

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

Closed
Closed
Show file tree
Hide file tree
Changes from 4 commits
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
8 changes: 8 additions & 0 deletions PowerShellEditorServices.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,18 @@ task LayoutModule -After Build {
New-Item -Force $PSScriptRoot\module\PowerShellEditorServices\bin\Core -Type Directory | Out-Null

Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
if ($Configuration -eq 'Debug') {
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\* -Filter Microsoft.PowerShell.EditorServices*.pdb -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
}

Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\netstandard1.6\libdisablekeyecho.* -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Core\
if (!$script:IsUnix) {
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\* -Filter Microsoft.PowerShell.EditorServices*.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
if ($Configuration -eq 'Debug') {
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\* -Filter Microsoft.PowerShell.EditorServices*.pdb -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
}

Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\Newtonsoft.Json.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
Copy-Item -Force -Path $PSScriptRoot\src\PowerShellEditorServices.Host\bin\$Configuration\net451\UnixConsoleEcho.dll -Destination $PSScriptRoot\module\PowerShellEditorServices\bin\Desktop\
}
Expand Down
6 changes: 5 additions & 1 deletion module/Start-EditorServices.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ param(
[ValidateSet("Normal", "Verbose", "Error","Diagnostic")]
$LogLevel,

[string[]]
$FeatureFlags = @(),

[switch]
$WaitForDebugger,

Expand Down Expand Up @@ -163,7 +166,8 @@ $editorServicesHost =
-LanguageServicePort $languageServicePort `
-DebugServicePort $debugServicePort `
-BundledModulesPath $BundledModulesPath `
-WaitForDebugger:$WaitForDebugger.IsPresent
-WaitForDebugger:$WaitForDebugger.IsPresent `
-FeatureFlags $FeatureFlags

# TODO: Verify that the service is started

Expand Down
8 changes: 5 additions & 3 deletions src/PowerShellEditorServices.Host/EditorServicesHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ private EditorSession CreateSession(
bool enableConsoleRepl)
{
EditorSession editorSession = new EditorSession(this.logger);
PowerShellContext powerShellContext = new PowerShellContext(this.logger);
PowerShellContext powerShellContext = new PowerShellContext(this.logger, this.featureFlags.Contains("PSReadLine"));

EditorServicesPSHostUserInterface hostUserInterface =
enableConsoleRepl
Expand Down Expand Up @@ -394,7 +394,9 @@ private EditorSession CreateDebugSession(
bool enableConsoleRepl)
{
EditorSession editorSession = new EditorSession(this.logger);
PowerShellContext powerShellContext = new PowerShellContext(this.logger);
PowerShellContext powerShellContext = new PowerShellContext(
this.logger,
this.featureFlags.Contains("PSReadLine"));

EditorServicesPSHostUserInterface hostUserInterface =
enableConsoleRepl
Expand Down Expand Up @@ -444,4 +446,4 @@ private void CurrentDomain_UnhandledException(

#endregion
}
}
}
36 changes: 30 additions & 6 deletions src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public DebugAdapter(
}

/// <summary>
/// Gets a boolean that indicates whether the current debug adapter is
/// Gets a boolean that indicates whether the current debug adapter is
/// using a temporary integrated console.
/// </summary>
public bool IsUsingTempIntegratedConsole { get; private set; }
Expand Down Expand Up @@ -118,6 +118,17 @@ protected Task LaunchScript(RequestContext<object> requestContext)

private async Task OnExecutionCompleted(Task executeTask)
{
try
{
await executeTask;
}
catch (Exception e)
{
Logger.Write(
LogLevel.Error,
"Exception occurred while awaiting debug launch task.\n\n" + e.ToString());
}

Logger.Write(LogLevel.Verbose, "Execution completed, terminating...");

this.executionCompleted = true;
Expand Down Expand Up @@ -471,7 +482,7 @@ protected async Task HandleDisconnectRequest(
if (this.executionCompleted == false)
{
this.disconnectRequestContext = requestContext;
this.editorSession.PowerShellContext.AbortExecution();
this.editorSession.PowerShellContext.AbortExecution(shouldAbortDebugSession: true);

if (this.isInteractiveDebugSession)
{
Expand Down Expand Up @@ -506,7 +517,7 @@ protected async Task HandleSetBreakpointsRequest(
}
}
catch (Exception e) when (
e is FileNotFoundException ||
e is FileNotFoundException ||
e is DirectoryNotFoundException ||
e is IOException ||
e is NotSupportedException ||
Expand Down Expand Up @@ -653,7 +664,7 @@ protected async Task HandleSetExceptionBreakpointsRequest(
RequestContext<object> requestContext)
{
// TODO: When support for exception breakpoints (unhandled and/or first chance)
// are added to the PowerShell engine, wire up the VSCode exception
// are added to the PowerShell engine, wire up the VSCode exception
// breakpoints here using the pattern below to prevent bug regressions.
//if (!this.noDebug)
//{
Expand Down Expand Up @@ -756,6 +767,20 @@ protected async Task HandleStackTraceRequest(
StackFrameDetails[] stackFrames =
editorSession.DebugService.GetStackFrames();

// Handle a rare race condition where the adapter requests stack frames before they've
// begun building.
if (stackFrames == null)
{
await requestContext.SendResult(
new StackTraceResponseBody
{
StackFrames = new StackFrame[0],
TotalFrames = 0
});

return;
}

List<StackFrame> newStackFrames = new List<StackFrame>();

int startFrameIndex = stackTraceParams.StartFrame ?? 0;
Expand All @@ -779,8 +804,7 @@ protected async Task HandleStackTraceRequest(
i));
}

await requestContext.SendResult(
new StackTraceResponseBody
await requestContext.SendResult( new StackTraceResponseBody
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mistake, must have done a vim line join and didn't notice.

{
StackFrames = newStackFrames.ToArray(),
TotalFrames = newStackFrames.Count
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,15 @@ private static async Task DelayThenInvokeDiagnostics(
catch (TaskCanceledException)
{
// If the task is cancelled, exit directly
foreach (var script in filesToAnalyze)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment needs to be updated.

{
await PublishScriptDiagnostics(
script,
script.SyntaxMarkers,
correctionIndex,
eventSender);
}

return;
}

Expand Down
83 changes: 83 additions & 0 deletions src/PowerShellEditorServices/Console/ConsoleProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.PowerShell.EditorServices.Console
{
internal static class ConsoleProxy
{
private static IConsoleOperations s_consoleProxy;

static ConsoleProxy()
{
// Maybe we should just include the RuntimeInformation package for FullCLR?
#if CoreCLR
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
s_consoleProxy = new WindowsConsoleOperations();
return;
}

s_consoleProxy = new UnixConsoleOperations();
#else
s_consoleProxy = new WindowsConsoleOperations();
#endif
}

public static Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) =>
s_consoleProxy.ReadKeyAsync(cancellationToken);

public static int GetCursorLeft() =>
s_consoleProxy.GetCursorLeft();

public static int GetCursorLeft(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorLeft(cancellationToken);

public static Task<int> GetCursorLeftAsync() =>
s_consoleProxy.GetCursorLeftAsync();

public static Task<int> GetCursorLeftAsync(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorLeftAsync(cancellationToken);

public static int GetCursorTop() =>
s_consoleProxy.GetCursorTop();

public static int GetCursorTop(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorTop(cancellationToken);

public static Task<int> GetCursorTopAsync() =>
s_consoleProxy.GetCursorTopAsync();

public static Task<int> GetCursorTopAsync(CancellationToken cancellationToken) =>
s_consoleProxy.GetCursorTopAsync(cancellationToken);

/// <summary>
/// On Unix platforms this method is sent to PSReadLine as a work around for issues
/// with the System.Console implementation for that platform. Functionally it is the
/// same as System.Console.ReadKey, with the exception that it will not lock the
/// standard input stream.
/// </summary>
/// <param name="intercept">
/// Determines whether to display the pressed key in the console window.
/// true to not display the pressed key; otherwise, false.
/// </param>
/// <returns>
/// 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.
/// </returns>
internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken)
{
try
{
return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken);
}
catch (OperationCanceledException)
{
return default(ConsoleKeyInfo);
}
}
}
}
52 changes: 29 additions & 23 deletions src/PowerShellEditorServices/Console/ConsoleReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -20,27 +19,13 @@ namespace Microsoft.PowerShell.EditorServices.Console
internal class ConsoleReadLine
{
#region Private Field
private static IConsoleOperations s_consoleProxy;

private PowerShellContext powerShellContext;

#endregion

#region Constructors
static ConsoleReadLine()
{
// Maybe we should just include the RuntimeInformation package for FullCLR?
#if CoreCLR
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
s_consoleProxy = new WindowsConsoleOperations();
return;
}

s_consoleProxy = new UnixConsoleOperations();
#else
s_consoleProxy = new WindowsConsoleOperations();
#endif
}

public ConsoleReadLine(PowerShellContext powerShellContext)
Expand All @@ -66,8 +51,8 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
{
SecureString secureString = new SecureString();

int initialPromptRow = Console.CursorTop;
int initialPromptCol = Console.CursorLeft;
int initialPromptRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
int initialPromptCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
int previousInputLength = 0;

Console.TreatControlCAsInput = true;
Expand Down Expand Up @@ -114,7 +99,8 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok
}
else if (previousInputLength > 0 && currentInputLength < previousInputLength)
{
int row = Console.CursorTop, col = Console.CursorLeft;
int row = await ConsoleProxy.GetCursorTopAsync(cancellationToken);
int col = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);

// Back up the cursor before clearing the character
col--;
Expand Down Expand Up @@ -146,10 +132,30 @@ public async Task<SecureString> ReadSecureLine(CancellationToken cancellationTok

private static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
{
return await s_consoleProxy.ReadKeyAsync(cancellationToken);
return await ConsoleProxy.ReadKeyAsync(cancellationToken);
}

private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancellationToken)
{
return await this.powerShellContext.InvokeReadLine(isCommandLine, cancellationToken);
}

/// <summary>
/// Invokes a custom ReadLine method that is similar to but more basic than PSReadLine.
/// This method should be used when PSReadLine is disabled, either by user settings or
/// unsupported PowerShell versions.
/// </summary>
/// <param name="isCommandLine">
/// Indicates whether ReadLine should act like a command line.
/// </param>
/// <param name="cancellationToken">
/// The cancellation token that will be checked prior to completing the returned task.
/// </param>
/// <returns>
/// A task object representing the asynchronus operation. The Result property on
/// the task object returns the user input string.
/// </returns>
internal async Task<string> InvokeLegacyReadLine(bool isCommandLine, CancellationToken cancellationToken)
{
string inputBeforeCompletion = null;
string inputAfterCompletion = null;
Expand All @@ -160,8 +166,8 @@ private async Task<string> ReadLine(bool isCommandLine, CancellationToken cancel

StringBuilder inputLine = new StringBuilder();

int initialCursorCol = Console.CursorLeft;
int initialCursorRow = Console.CursorTop;
int initialCursorCol = await ConsoleProxy.GetCursorLeftAsync(cancellationToken);
int initialCursorRow = await ConsoleProxy.GetCursorTopAsync(cancellationToken);

int initialWindowLeft = Console.WindowLeft;
int initialWindowTop = Console.WindowTop;
Expand Down Expand Up @@ -492,8 +498,8 @@ private int CalculateIndexFromCursor(
int consoleWidth)
{
return
((Console.CursorTop - promptStartRow) * consoleWidth) +
Console.CursorLeft - promptStartCol;
((ConsoleProxy.GetCursorTop() - promptStartRow) * consoleWidth) +
ConsoleProxy.GetCursorLeft() - promptStartCol;
}

private void CalculateCursorFromIndex(
Expand Down
Loading