Skip to content

PSReadLine integration #672

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 23 commits into from
Aug 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5a6eba6
Add infrastructure for managing context
SeeminglyScience Jun 2, 2018
1930afe
Console related classes changes
SeeminglyScience Jun 2, 2018
7e26e4e
Rewrite command invocation operations for PSRL
SeeminglyScience Jun 2, 2018
ac44055
Rewrite direct SessionStateProxy calls
SeeminglyScience Jun 2, 2018
d2e1ceb
Pass feature flags to Start-EditorServicesHost
SeeminglyScience Jun 3, 2018
a507705
Address feedback and fix travis build error
SeeminglyScience Jun 3, 2018
a870ee2
Fix all tests except ServiceLoadsProfileOnDemand
SeeminglyScience Jun 3, 2018
190cc0c
Fix extra new lines outputted after each command
SeeminglyScience Jun 5, 2018
49db2ba
Remove unused field from InvocationEventQueue
SeeminglyScience Jun 5, 2018
379eee4
Remove copying of PDB's in build script
SeeminglyScience Jun 5, 2018
e16c823
Add AppVeyor tracking to branch 2.0.0
SeeminglyScience Jun 5, 2018
cc62dab
Fix ambiguous method crash on CoreCLR
SeeminglyScience Jun 5, 2018
7f2b5b8
first round of feedback changes
SeeminglyScience Jun 9, 2018
e19afe6
Some more feedback changes
SeeminglyScience Jun 9, 2018
afdfb43
add a bunch of copyright headers I missed
SeeminglyScience Jun 9, 2018
3575c79
remove KeyAvailable query
TylerLeonhardt Jul 22, 2018
6a3f7c9
Get the latest PSReadLine module installed
rjmholt Aug 1, 2018
cc10b91
Add PSReadLine installation to build script
rjmholt Aug 1, 2018
86ab115
the file should be downloaded as a .zip
TylerLeonhardt Aug 2, 2018
b51cc75
Address remaining feedback
SeeminglyScience Aug 19, 2018
1682410
Attempt to fix issue with native apps and input
SeeminglyScience Aug 19, 2018
d68fb70
Revert "Attempt to fix issue with native apps and input"
SeeminglyScience Aug 20, 2018
2968d1f
Fix build failure
SeeminglyScience Aug 20, 2018
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ registered_data.ini
.dotnet/
module/Plaster
module/PSScriptAnalyzer
module/PSReadLine
docs/_site/
docs/_repo/
docs/metadata/
Expand Down
19 changes: 19 additions & 0 deletions PowerShellEditorServices.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,25 @@ task RestorePsesModules -After Build {

Save-Module @splatParameters
}

# TODO: Replace this with adding a new module to Save when a new PSReadLine release comes out to the Gallery
if (-not (Test-Path $PSScriptRoot/module/PSReadLine))
{
Write-Host "`tInstalling module: PSReadLine"

# Download AppVeyor zip
$jobId = (Invoke-RestMethod https://ci.appveyor.com/api/projects/lzybkr/PSReadLine).build.jobs[0].jobId
Invoke-RestMethod https://ci.appveyor.com/api/buildjobs/$jobId/artifacts/bin%2FRelease%2FPSReadLine.zip -OutFile $PSScriptRoot/module/PSRL.zip

# Position PSReadLine
Expand-Archive $PSScriptRoot/module/PSRL.zip $PSScriptRoot/module/PSRL
Move-Item $PSScriptRoot/module/PSRL/PSReadLine $PSScriptRoot/module

# Clean up
Remove-Item -Force -Recurse $PSScriptRoot/module/PSRL.zip
Remove-Item -Force -Recurse $PSScriptRoot/module/PSRL
}

Write-Host "`n"
}

Expand Down
1 change: 1 addition & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ skip_tags: true
branches:
only:
- master
- 2.0.0

environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Don't download unneeded packages
Expand Down
3 changes: 2 additions & 1 deletion module/PowerShellEditorServices/Start-EditorServices.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,8 @@ try {
-BundledModulesPath $BundledModulesPath `
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
-WaitForDebugger:$WaitForDebugger.IsPresent
-WaitForDebugger:$WaitForDebugger.IsPresent `
-FeatureFlags $FeatureFlags

# TODO: Verify that the service is started
Log "Start-EditorServicesHost returned $editorServicesHost"
Expand Down
6 changes: 4 additions & 2 deletions src/PowerShellEditorServices.Host/EditorServicesHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,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 @@ -405,7 +405,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
27 changes: 26 additions & 1 deletion src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs
Original file line number Diff line number Diff line change
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());
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest using string interpolation and use platform independent newlines:
$"Exception occurred while awaiting debug launch task.{Environment.Newline}{Environment.Newline}{e.ToString()}"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For consistency I'd like to keep this the same for now. Ultimately we shouldn't be making our own strings while logging at all, that would better be handled by Serilog.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's fine, I just wanted to bring it up as some people are not aware of some of the new C# features

Copy link
Contributor

Choose a reason for hiding this comment

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

You should be able to use Logger.WriteException here and it will handle the newlines/indentation.

}

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

this.executionCompleted = true;
Expand Down Expand Up @@ -470,7 +481,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 @@ -755,6 +766,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 Down
Original file line number Diff line number Diff line change
Expand Up @@ -1502,6 +1502,15 @@ private static async Task DelayThenInvokeDiagnostics(
catch (TaskCanceledException)
{
// If the task is cancelled, exit directly
foreach (var script in filesToAnalyze)
{
await PublishScriptDiagnostics(
script,
script.SyntaxMarkers,
correctionIndex,
eventSender);
}

return;
}

Expand Down
95 changes: 95 additions & 0 deletions src/PowerShellEditorServices/Console/ConsoleProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.PowerShell.EditorServices.Console
{
/// <summary>
/// Provides asynchronous implementations of the <see cref="Console" /> API's as well as
/// synchronous implementations that work around platform specific issues.
/// </summary>
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>
/// <param name="cancellationToken">
/// The <see cref="CancellationToken" /> that can be used to cancel the request.
/// </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.
Copy link
Member

Choose a reason for hiding this comment

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

nit: keys were pressed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can change it, but it's from the docs for Console.ReadKey.

/// </returns>
internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is really nice!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks! Admitably most of that comes from System.Console.ReadKey, so I can't take much credit there 😉

{
try
{
return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken);
}
catch (OperationCanceledException)
{
return default(ConsoleKeyInfo);
}
}
}
}
58 changes: 32 additions & 26 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 @@ -54,20 +39,20 @@ public ConsoleReadLine(PowerShellContext powerShellContext)

public Task<string> ReadCommandLine(CancellationToken cancellationToken)
{
return this.ReadLine(true, cancellationToken);
return this.ReadLineAsync(true, cancellationToken);
}

public Task<string> ReadSimpleLine(CancellationToken cancellationToken)
{
return this.ReadLine(false, cancellationToken);
return this.ReadLineAsync(false, cancellationToken);
}

public async Task<SecureString> ReadSecureLine(CancellationToken cancellationToken)
{
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> ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken)
{
return await this.powerShellContext.InvokeReadLineAsync(isCommandLine, cancellationToken);
}

private async Task<string> ReadLine(bool isCommandLine, CancellationToken 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> InvokeLegacyReadLineAsync(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