Skip to content

Commit ca987b5

Browse files
SeeminglySciencerjmholt
authored andcommitted
PSReadLine integration (#672)
* Add infrastructure for managing context Adds classes that manage the state of the prompt, nested contexts, and multiple ReadLine implementations of varying complexity. (cherry picked from commit 7ca8b9b) * Console related classes changes Change ReadLine method to call out to PowerShellContext. This lets the PowerShellContext determine which ReadLine implementation to use based on available modules. Also includes some changes to the System.Console proxy classes to account for PSReadLine. (cherry picked from commit 59bfa3b) * Rewrite command invocation operations for PSRL Refactor PowerShellContext to have a more robust system for tracking the context in which commands are invoked. This is a significant change in that all interactions with the runspace must be done through methods in PowerShellContext. These changes also greatly increase stability. (cherry picked from commit 21e6b5f) * Rewrite direct SessionStateProxy calls All interactions with the runspace must be done through PowerShellContext now that nested PowerShell instances are encountered frequently. Also fix a bunch of race conditions that were made more obvious with the changes. (cherry picked from commit fa2faba) * Pass feature flags to Start-EditorServicesHost * Address feedback and fix travis build error - Address feedback from @bergmeister - Fix a few other similar mistakes I found - Fix travis build failing due to missing documentation comment tag * Fix all tests except ServiceLoadsProfileOnDemand - Fix an issue where intellisense wouldn't finish if PSReadLine was not running - Fix a crash that would occur if the PSHost was not set up for input like the one used in our tests - Fix a compile error when building against PSv3/4 - Fix a hang that occurred when the PromptNest was disposed during a debug session - Fix some XML documentation comment syntax errors * Fix extra new lines outputted after each command Removed a call to WriteOutput where it wasn't required. This was creating extra new lines which failed tests (and obviously didn't look right). * Remove unused field from InvocationEventQueue And also fix spacing between the other fields. * Remove copying of PDB's in build script @rjmholt did a better job of this in a different PR that we can merge into 2.0.0 later. It also doesn't make sense in this PR. * Add AppVeyor tracking to branch 2.0.0 * Fix ambiguous method crash on CoreCLR Simplify delegate creation in PSReadLineProxy and fix the immediate ambiguous method crash the complicated code caused on CoreCLR. * first round of feedback changes * Some more feedback changes * add a bunch of copyright headers I missed * remove KeyAvailable query * Get the latest PSReadLine module installed * Add PSReadLine installation to build script * the file should be downloaded as a .zip * Address remaining feedback * Attempt to fix issue with native apps and input On Unix like platforms some native applications do not work properly if our event subscriber is active. I suspect this is due to PSReadLine querying cursor position prior to checking for events. I believe the cursor position response emitted is being read as input. I've attempted to fix this by hooking into PSHost.NotifyBeginApplication to temporarly remove the event subscriber, and PSHost.NotifyEndApplication to recreate it afterwards. * Revert "Attempt to fix issue with native apps and input" This reverts commit 1682410. * Fix build failure Use latest PSReadLine (#745) * Use PSRL release and filter out previous betas * Use fully qualified type name for broad PowerShell support * Fix CI module installation
1 parent 161a3ed commit ca987b5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3416
-473
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ registered_data.ini
2929
.dotnet/
3030
module/Plaster
3131
module/PSScriptAnalyzer
32+
module/PSReadLine
3233
docs/_site/
3334
docs/_repo/
3435
docs/metadata/

PowerShellEditorServices.build.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ task RestorePsesModules -After Build {
368368

369369
Save-Module @splatParameters
370370
}
371+
371372
Write-Host "`n"
372373
}
373374

appveyor.yml

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ skip_tags: true
66
branches:
77
only:
88
- master
9+
- 2.0.0
910

1011
environment:
1112
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Don't download unneeded packages
1213
DOTNET_CLI_TELEMETRY_OPTOUT: true # Don't send telemetry
1314

1415
install:
1516
- ps: |
16-
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
17+
Get-Module PowerShellGet,PackageManagement | Remove-Module -Force -Verbose
18+
powershell -Command { Install-Module -Name PowershellGet -MinimumVersion 1.6 -force -confirm:$false -verbose }
19+
powershell -Command { Install-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Force -Confirm:$false -Verbose }
20+
Import-Module -Name PowerShellGet -MinimumVersion 1.6 -Force
21+
Import-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Force
22+
Install-PackageProvider -Name NuGet -Force | Out-Null
1723
Import-PackageProvider NuGet -Force | Out-Null
1824
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | Out-Null
1925
Install-Module InvokeBuild -MaximumVersion 5.1.0 -Scope CurrentUser -Force | Out-Null

module/PowerShellEditorServices/Start-EditorServices.ps1

+7-4
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ try {
349349
"status" = "not started";
350350
"languageServiceTransport" = $PSCmdlet.ParameterSetName;
351351
"debugServiceTransport" = $PSCmdlet.ParameterSetName;
352-
};
352+
}
353353

354354
# Create the Editor Services host
355355
Log "Invoking Start-EditorServicesHost"
@@ -368,7 +368,8 @@ try {
368368
-BundledModulesPath $BundledModulesPath `
369369
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
370370
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
371-
-WaitForDebugger:$WaitForDebugger.IsPresent
371+
-WaitForDebugger:$WaitForDebugger.IsPresent `
372+
-FeatureFlags $FeatureFlags
372373
break
373374
}
374375

@@ -392,7 +393,8 @@ try {
392393
-BundledModulesPath $BundledModulesPath `
393394
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
394395
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
395-
-WaitForDebugger:$WaitForDebugger.IsPresent
396+
-WaitForDebugger:$WaitForDebugger.IsPresent `
397+
-FeatureFlags $FeatureFlags
396398

397399
Set-PipeFileResult $resultDetails "languageServiceReadPipeName" $LanguageServiceInPipeName
398400
Set-PipeFileResult $resultDetails "languageServiceWritePipeName" $LanguageServiceOutPipeName
@@ -417,7 +419,8 @@ try {
417419
-BundledModulesPath $BundledModulesPath `
418420
-EnableConsoleRepl:$EnableConsoleRepl.IsPresent `
419421
-DebugServiceOnly:$DebugServiceOnly.IsPresent `
420-
-WaitForDebugger:$WaitForDebugger.IsPresent
422+
-WaitForDebugger:$WaitForDebugger.IsPresent `
423+
-FeatureFlags $FeatureFlags
421424

422425
Set-PipeFileResult $resultDetails "languageServicePipeName" $LanguageServicePipeName
423426
Set-PipeFileResult $resultDetails "debugServicePipeName" $DebugServicePipeName

modules.json

+5
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@
88
"MinimumVersion":"1.0",
99
"MaximumVersion":"1.99",
1010
"AllowPrerelease":false
11+
},
12+
"PSReadLine":{
13+
"MinimumVersion":"2.0.0-beta3",
14+
"MaximumVersion":"2.1",
15+
"AllowPrerelease":true
1116
}
1217
}

scripts/travis.ps1

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
1+
Get-Module PowerShellGet,PackageManagement | Remove-Module -Force -Verbose
2+
powershell -Command { Install-Module -Name PowershellGet -MinimumVersion 1.6 -Scope CurrentUser -force -confirm:$false -verbose }
3+
powershell -Command { Install-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Scope CurrentUser -Force -Confirm:$false -Verbose }
4+
Import-Module -Name PowerShellGet -MinimumVersion 1.6 -Force
5+
Import-Module -Name PackageManagement -MinimumVersion 1.1.7.0 -Force
6+
Install-PackageProvider -Name NuGet -Force | Out-Null
7+
Import-PackageProvider NuGet -Force | Out-Null
8+
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | Out-Null
29

310
# Install InvokeBuild
411
Install-Module InvokeBuild -MaximumVersion 5.1.0 -Scope CurrentUser -Force

src/PowerShellEditorServices.Host/EditorServicesHost.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ private EditorSession CreateSession(
378378
bool enableConsoleRepl)
379379
{
380380
EditorSession editorSession = new EditorSession(this.logger);
381-
PowerShellContext powerShellContext = new PowerShellContext(this.logger);
381+
PowerShellContext powerShellContext = new PowerShellContext(this.logger, this.featureFlags.Contains("PSReadLine"));
382382

383383
EditorServicesPSHostUserInterface hostUserInterface =
384384
enableConsoleRepl
@@ -418,7 +418,9 @@ private EditorSession CreateDebugSession(
418418
bool enableConsoleRepl)
419419
{
420420
EditorSession editorSession = new EditorSession(this.logger);
421-
PowerShellContext powerShellContext = new PowerShellContext(this.logger);
421+
PowerShellContext powerShellContext = new PowerShellContext(
422+
this.logger,
423+
this.featureFlags.Contains("PSReadLine"));
422424

423425
EditorServicesPSHostUserInterface hostUserInterface =
424426
enableConsoleRepl

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

+26-1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ protected Task LaunchScript(RequestContext<object> requestContext, string script
112112

113113
private async Task OnExecutionCompleted(Task executeTask)
114114
{
115+
try
116+
{
117+
await executeTask;
118+
}
119+
catch (Exception e)
120+
{
121+
Logger.Write(
122+
LogLevel.Error,
123+
"Exception occurred while awaiting debug launch task.\n\n" + e.ToString());
124+
}
125+
115126
Logger.Write(LogLevel.Verbose, "Execution completed, terminating...");
116127

117128
_executionCompleted = true;
@@ -462,7 +473,7 @@ protected async Task HandleDisconnectRequest(
462473
if (_executionCompleted == false)
463474
{
464475
_disconnectRequestContext = requestContext;
465-
_editorSession.PowerShellContext.AbortExecution();
476+
_editorSession.PowerShellContext.AbortExecution(shouldAbortDebugSession: true);
466477

467478
if (_isInteractiveDebugSession)
468479
{
@@ -730,6 +741,20 @@ protected async Task HandleStackTraceRequest(
730741
StackFrameDetails[] stackFrames =
731742
_editorSession.DebugService.GetStackFrames();
732743

744+
// Handle a rare race condition where the adapter requests stack frames before they've
745+
// begun building.
746+
if (stackFrames == null)
747+
{
748+
await requestContext.SendResult(
749+
new StackTraceResponseBody
750+
{
751+
StackFrames = new StackFrame[0],
752+
TotalFrames = 0
753+
});
754+
755+
return;
756+
}
757+
733758
List<StackFrame> newStackFrames = new List<StackFrame>();
734759

735760
int startFrameIndex = stackTraceParams.StartFrame ?? 0;

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

+9
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,15 @@ private static async Task DelayThenInvokeDiagnostics(
16531653
catch (TaskCanceledException)
16541654
{
16551655
// If the task is cancelled, exit directly
1656+
foreach (var script in filesToAnalyze)
1657+
{
1658+
await PublishScriptDiagnostics(
1659+
script,
1660+
script.SyntaxMarkers,
1661+
correctionIndex,
1662+
eventSender);
1663+
}
1664+
16561665
return;
16571666
}
16581667

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.Runtime.InteropServices;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace Microsoft.PowerShell.EditorServices.Console
12+
{
13+
/// <summary>
14+
/// Provides asynchronous implementations of the <see cref="Console" /> API's as well as
15+
/// synchronous implementations that work around platform specific issues.
16+
/// </summary>
17+
internal static class ConsoleProxy
18+
{
19+
private static IConsoleOperations s_consoleProxy;
20+
21+
static ConsoleProxy()
22+
{
23+
// Maybe we should just include the RuntimeInformation package for FullCLR?
24+
#if CoreCLR
25+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
26+
{
27+
s_consoleProxy = new WindowsConsoleOperations();
28+
return;
29+
}
30+
31+
s_consoleProxy = new UnixConsoleOperations();
32+
#else
33+
s_consoleProxy = new WindowsConsoleOperations();
34+
#endif
35+
}
36+
37+
public static Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) =>
38+
s_consoleProxy.ReadKeyAsync(cancellationToken);
39+
40+
public static int GetCursorLeft() =>
41+
s_consoleProxy.GetCursorLeft();
42+
43+
public static int GetCursorLeft(CancellationToken cancellationToken) =>
44+
s_consoleProxy.GetCursorLeft(cancellationToken);
45+
46+
public static Task<int> GetCursorLeftAsync() =>
47+
s_consoleProxy.GetCursorLeftAsync();
48+
49+
public static Task<int> GetCursorLeftAsync(CancellationToken cancellationToken) =>
50+
s_consoleProxy.GetCursorLeftAsync(cancellationToken);
51+
52+
public static int GetCursorTop() =>
53+
s_consoleProxy.GetCursorTop();
54+
55+
public static int GetCursorTop(CancellationToken cancellationToken) =>
56+
s_consoleProxy.GetCursorTop(cancellationToken);
57+
58+
public static Task<int> GetCursorTopAsync() =>
59+
s_consoleProxy.GetCursorTopAsync();
60+
61+
public static Task<int> GetCursorTopAsync(CancellationToken cancellationToken) =>
62+
s_consoleProxy.GetCursorTopAsync(cancellationToken);
63+
64+
/// <summary>
65+
/// On Unix platforms this method is sent to PSReadLine as a work around for issues
66+
/// with the System.Console implementation for that platform. Functionally it is the
67+
/// same as System.Console.ReadKey, with the exception that it will not lock the
68+
/// standard input stream.
69+
/// </summary>
70+
/// <param name="intercept">
71+
/// Determines whether to display the pressed key in the console window.
72+
/// true to not display the pressed key; otherwise, false.
73+
/// </param>
74+
/// <param name="cancellationToken">
75+
/// The <see cref="CancellationToken" /> that can be used to cancel the request.
76+
/// </param>
77+
/// <returns>
78+
/// An object that describes the ConsoleKey constant and Unicode character, if any,
79+
/// that correspond to the pressed console key. The ConsoleKeyInfo object also describes,
80+
/// in a bitwise combination of ConsoleModifiers values, whether one or more Shift, Alt,
81+
/// or Ctrl modifier keys was pressed simultaneously with the console key.
82+
/// </returns>
83+
internal static ConsoleKeyInfo UnixReadKey(bool intercept, CancellationToken cancellationToken)
84+
{
85+
try
86+
{
87+
return ((UnixConsoleOperations)s_consoleProxy).ReadKey(intercept, cancellationToken);
88+
}
89+
catch (OperationCanceledException)
90+
{
91+
return default(ConsoleKeyInfo);
92+
}
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)