Skip to content

Commit d156a73

Browse files
SeeminglyScienceTylerLeonhardt
authored andcommitted
Use public InternalHost from origin runspace (#874)
* Use public InternalHost from origin runspace `ConsoleHost` from `powershell.exe`/`pwsh` still exists within the runspace created at process start. This change grabs the public reference to that host while initializing EditorServicesHost. We can then leverage that host so we can have a much closer to "default" experience. This change adds support for the following host members ## $Host.UI - WriteProgress (including `Write-Progress`) ## $Host.UI.RawUI - CursorSize (still doesn't work in xterm.js though) - WindowTitle - MaxPhysicalWindowSize - MaxWindowSize - ReadKey - GetBufferContents - ScrollBufferContents - SetBufferContents ## TODO [ ] Test RawUI members [ ] Maybe write sync verison of ReadKey [ ] Maybe avoid TerminalPSHost* breaking changes (constructors) * Fix up RawUI.ReadKey - Add the XML documentation comments to ConsoleProxy because it's more likely to be used than the interface itself. - Add a synchronous implementation of ReadKey to ConsoleProxy and use it in RawUI.ReadKey - Fix Ctrl + C not returning as input in VSCode's terminal - Use the exception message from ConsoleHost when ReadKeyOptions do not include either IncludeKeyUp nor IncludeKeyDown * Pass $Host in the start up script * Address feedback * Address feedback and also add doc comments * Make progress cache thread safe * Added comment about null
1 parent 029a6cc commit d156a73

11 files changed

+506
-116
lines changed

module/PowerShellEditorServices/PowerShellEditorServices.psm1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ function Start-EditorServicesHost {
109109
$EnableConsoleRepl.IsPresent,
110110
$WaitForDebugger.IsPresent,
111111
$AdditionalModules,
112-
$FeatureFlags)
112+
$FeatureFlags,
113+
$Host)
113114

114115
# Build the profile paths using the root paths of the current $profile variable
115116
$profilePaths =

src/PowerShellEditorServices.Host/EditorServicesHost.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
using System.Collections.Generic;
1717
using System.Diagnostics;
1818
using System.IO;
19+
using System.Linq;
20+
using System.Management.Automation;
1921
using System.Management.Automation.Runspaces;
22+
using System.Management.Automation.Host;
2023
using System.Reflection;
21-
using System.Threading.Tasks;
2224
using System.Runtime.InteropServices;
25+
using System.Threading.Tasks;
2326

2427
namespace Microsoft.PowerShell.EditorServices.Host
2528
{
@@ -61,6 +64,7 @@ public class EditorServicesHost
6164
{
6265
#region Private Fields
6366

67+
private readonly PSHost internalHost;
6468
private string[] additionalModules;
6569
private string bundledModulesPath;
6670
private DebugAdapter debugAdapter;
@@ -93,33 +97,66 @@ public class EditorServicesHost
9397
/// <param name="hostDetails">The details of the host which is launching PowerShell Editor Services.</param>
9498
/// <param name="bundledModulesPath">Provides a path to PowerShell modules bundled with the host, if any. Null otherwise.</param>
9599
/// <param name="waitForDebugger">If true, causes the host to wait for the debugger to attach before proceeding.</param>
100+
/// <param name="additionalModules">Modules to be loaded when initializing the new runspace.</param>
101+
/// <param name="featureFlags">Features to enable for this instance.</param>
96102
public EditorServicesHost(
97103
HostDetails hostDetails,
98104
string bundledModulesPath,
99105
bool enableConsoleRepl,
100106
bool waitForDebugger,
101107
string[] additionalModules,
102108
string[] featureFlags)
109+
: this(
110+
hostDetails,
111+
bundledModulesPath,
112+
enableConsoleRepl,
113+
waitForDebugger,
114+
additionalModules,
115+
featureFlags,
116+
GetInternalHostFromDefaultRunspace())
117+
{
118+
}
119+
120+
/// <summary>
121+
/// Initializes a new instance of the EditorServicesHost class and waits for
122+
/// the debugger to attach if waitForDebugger is true.
123+
/// </summary>
124+
/// <param name="hostDetails">The details of the host which is launching PowerShell Editor Services.</param>
125+
/// <param name="bundledModulesPath">Provides a path to PowerShell modules bundled with the host, if any. Null otherwise.</param>
126+
/// <param name="waitForDebugger">If true, causes the host to wait for the debugger to attach before proceeding.</param>
127+
/// <param name="additionalModules">Modules to be loaded when initializing the new runspace.</param>
128+
/// <param name="featureFlags">Features to enable for this instance.</param>
129+
/// <param name="internalHost">The value of the $Host variable in the original runspace.</param>
130+
public EditorServicesHost(
131+
HostDetails hostDetails,
132+
string bundledModulesPath,
133+
bool enableConsoleRepl,
134+
bool waitForDebugger,
135+
string[] additionalModules,
136+
string[] featureFlags,
137+
PSHost internalHost)
103138
{
104139
Validate.IsNotNull(nameof(hostDetails), hostDetails);
140+
Validate.IsNotNull(nameof(internalHost), internalHost);
105141

106142
this.hostDetails = hostDetails;
107143
this.enableConsoleRepl = enableConsoleRepl;
108144
this.bundledModulesPath = bundledModulesPath;
109145
this.additionalModules = additionalModules ?? new string[0];
110146
this.featureFlags = new HashSet<string>(featureFlags ?? new string[0]);
111147
this.serverCompletedTask = new TaskCompletionSource<bool>();
148+
this.internalHost = internalHost;
112149

113150
#if DEBUG
114151
if (waitForDebugger)
115152
{
116-
if (Debugger.IsAttached)
153+
if (System.Diagnostics.Debugger.IsAttached)
117154
{
118-
Debugger.Break();
155+
System.Diagnostics.Debugger.Break();
119156
}
120157
else
121158
{
122-
Debugger.Launch();
159+
System.Diagnostics.Debugger.Launch();
123160
}
124161
}
125162
#endif
@@ -365,6 +402,14 @@ public void WaitForCompletion()
365402

366403
#region Private Methods
367404

405+
private static PSHost GetInternalHostFromDefaultRunspace()
406+
{
407+
using (var pwsh = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace))
408+
{
409+
return pwsh.AddScript("$Host").Invoke<PSHost>().First();
410+
}
411+
}
412+
368413
private EditorSession CreateSession(
369414
HostDetails hostDetails,
370415
ProfilePaths profilePaths,
@@ -377,7 +422,7 @@ private EditorSession CreateSession(
377422

378423
EditorServicesPSHostUserInterface hostUserInterface =
379424
enableConsoleRepl
380-
? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger)
425+
? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger, this.internalHost)
381426
: new ProtocolPSHostUserInterface(powerShellContext, messageSender, this.logger);
382427

383428
EditorServicesPSHost psHost =
@@ -419,7 +464,7 @@ private EditorSession CreateDebugSession(
419464

420465
EditorServicesPSHostUserInterface hostUserInterface =
421466
enableConsoleRepl
422-
? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger)
467+
? (EditorServicesPSHostUserInterface) new TerminalPSHostUserInterface(powerShellContext, this.logger, this.internalHost)
423468
: new ProtocolPSHostUserInterface(powerShellContext, messageSender, this.logger);
424469

425470
EditorServicesPSHost psHost =

src/PowerShellEditorServices/Console/ConsoleProxy.cs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,30 +29,136 @@ static ConsoleProxy()
2929
s_consoleProxy = new UnixConsoleOperations();
3030
}
3131

32-
public static Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken) =>
33-
s_consoleProxy.ReadKeyAsync(cancellationToken);
32+
/// <summary>
33+
/// Obtains the next character or function key pressed by the user asynchronously.
34+
/// Does not block when other console API's are called.
35+
/// </summary>
36+
/// <param name="intercept">
37+
/// Determines whether to display the pressed key in the console window. <see langword="true" />
38+
/// to not display the pressed key; otherwise, <see langword="false" />.
39+
/// </param>
40+
/// <param name="cancellationToken">The CancellationToken to observe.</param>
41+
/// <returns>
42+
/// An object that describes the <see cref="ConsoleKey" /> constant and Unicode character, if any,
43+
/// that correspond to the pressed console key. The <see cref="ConsoleKeyInfo" /> object also
44+
/// describes, in a bitwise combination of <see cref="ConsoleModifiers" /> values, whether
45+
/// one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously with the console key.
46+
/// </returns>
47+
public static ConsoleKeyInfo ReadKey(bool intercept, CancellationToken cancellationToken) =>
48+
s_consoleProxy.ReadKey(intercept, cancellationToken);
3449

50+
/// <summary>
51+
/// Obtains the next character or function key pressed by the user asynchronously.
52+
/// Does not block when other console API's are called.
53+
/// </summary>
54+
/// <param name="intercept">
55+
/// Determines whether to display the pressed key in the console window. <see langword="true" />
56+
/// to not display the pressed key; otherwise, <see langword="false" />.
57+
/// </param>
58+
/// <param name="cancellationToken">The CancellationToken to observe.</param>
59+
/// <returns>
60+
/// A task that will complete with a result of the key pressed by the user.
61+
/// </returns>
62+
public static Task<ConsoleKeyInfo> ReadKeyAsync(bool intercept, CancellationToken cancellationToken) =>
63+
s_consoleProxy.ReadKeyAsync(intercept, cancellationToken);
64+
65+
/// <summary>
66+
/// Obtains the horizontal position of the console cursor. Use this method
67+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
68+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
69+
/// on Unix platforms.
70+
/// </summary>
71+
/// <returns>The horizontal position of the console cursor.</returns>
3572
public static int GetCursorLeft() =>
3673
s_consoleProxy.GetCursorLeft();
3774

75+
/// <summary>
76+
/// Obtains the horizontal position of the console cursor. Use this method
77+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
78+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
79+
/// on Unix platforms.
80+
/// </summary>
81+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
82+
/// <returns>The horizontal position of the console cursor.</returns>
3883
public static int GetCursorLeft(CancellationToken cancellationToken) =>
3984
s_consoleProxy.GetCursorLeft(cancellationToken);
4085

86+
/// <summary>
87+
/// Obtains the horizontal position of the console cursor. Use this method
88+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
89+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
90+
/// on Unix platforms.
91+
/// </summary>
92+
/// <returns>
93+
/// A <see cref="Task{T}" /> representing the asynchronous operation. The
94+
/// <see cref="Task{T}.Result" /> property will return the horizontal position
95+
/// of the console cursor.
96+
/// </returns>
4197
public static Task<int> GetCursorLeftAsync() =>
4298
s_consoleProxy.GetCursorLeftAsync();
4399

100+
/// <summary>
101+
/// Obtains the horizontal position of the console cursor. Use this method
102+
/// instead of <see cref="System.Console.CursorLeft" /> to avoid triggering
103+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
104+
/// on Unix platforms.
105+
/// </summary>
106+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
107+
/// <returns>
108+
/// A <see cref="Task{T}" /> representing the asynchronous operation. The
109+
/// <see cref="Task{T}.Result" /> property will return the horizontal position
110+
/// of the console cursor.
111+
/// </returns>
44112
public static Task<int> GetCursorLeftAsync(CancellationToken cancellationToken) =>
45113
s_consoleProxy.GetCursorLeftAsync(cancellationToken);
46114

115+
/// <summary>
116+
/// Obtains the vertical position of the console cursor. Use this method
117+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
118+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
119+
/// on Unix platforms.
120+
/// </summary>
121+
/// <returns>The vertical position of the console cursor.</returns>
47122
public static int GetCursorTop() =>
48123
s_consoleProxy.GetCursorTop();
49124

125+
/// <summary>
126+
/// Obtains the vertical position of the console cursor. Use this method
127+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
128+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
129+
/// on Unix platforms.
130+
/// </summary>
131+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
132+
/// <returns>The vertical position of the console cursor.</returns>
50133
public static int GetCursorTop(CancellationToken cancellationToken) =>
51134
s_consoleProxy.GetCursorTop(cancellationToken);
52135

136+
/// <summary>
137+
/// Obtains the vertical position of the console cursor. Use this method
138+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
139+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
140+
/// on Unix platforms.
141+
/// </summary>
142+
/// <returns>
143+
/// A <see cref="Task{T}" /> representing the asynchronous operation. The
144+
/// <see cref="Task{T}.Result" /> property will return the vertical position
145+
/// of the console cursor.
146+
/// </returns>
53147
public static Task<int> GetCursorTopAsync() =>
54148
s_consoleProxy.GetCursorTopAsync();
55149

150+
/// <summary>
151+
/// Obtains the vertical position of the console cursor. Use this method
152+
/// instead of <see cref="System.Console.CursorTop" /> to avoid triggering
153+
/// pending calls to <see cref="IConsoleOperations.ReadKeyAsync(bool, CancellationToken)" />
154+
/// on Unix platforms.
155+
/// </summary>
156+
/// <param name="cancellationToken">The <see cref="CancellationToken" /> to observe.</param>
157+
/// <returns>
158+
/// A <see cref="Task{T}" /> representing the asynchronous operation. The
159+
/// <see cref="Task{T}.Result" /> property will return the vertical position
160+
/// of the console cursor.
161+
/// </returns>
56162
public static Task<int> GetCursorTopAsync(CancellationToken cancellationToken) =>
57163
s_consoleProxy.GetCursorTopAsync(cancellationToken);
58164

src/PowerShellEditorServices/Console/ConsoleReadLine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public async Task<SecureString> ReadSecureLineAsync(CancellationToken cancellati
129129

130130
private static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken cancellationToken)
131131
{
132-
return await ConsoleProxy.ReadKeyAsync(cancellationToken);
132+
return await ConsoleProxy.ReadKeyAsync(intercept: true, cancellationToken);
133133
}
134134

135135
private async Task<string> ReadLineAsync(bool isCommandLine, CancellationToken cancellationToken)

0 commit comments

Comments
 (0)