Skip to content

Commit 694a571

Browse files
committed
Reintroduce simple host interface to fix existing tests
This change reintroduces the old host interface so that we can maintain our current functional tests until we find a good way to test the new terminal-based UI. This also enables consumers of the old host protocol (like Xamarin Studio) to continue using it.
1 parent a066d2f commit 694a571

File tree

16 files changed

+768
-384
lines changed

16 files changed

+768
-384
lines changed

module/PowerShellEditorServices/PowerShellEditorServices.psm1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ function Start-EditorServicesHost {
4646
[ValidateSet("Normal", "Verbose", "Error")]
4747
$LogLevel = "Normal",
4848

49+
[switch]
50+
$EnableConsoleRepl,
51+
4952
[string]
5053
$DebugServiceOnly,
5154

@@ -61,6 +64,7 @@ function Start-EditorServicesHost {
6164
New-Object Microsoft.PowerShell.EditorServices.Host.EditorServicesHost @(
6265
$hostDetails,
6366
$BundledModulesPath,
67+
$EnableConsoleRepl.IsPresent,
6468
$WaitForDebugger.IsPresent)
6569

6670
# Build the profile paths using the root paths of the current $profile variable

src/PowerShellEditorServices.Host/EditorServicesHost.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class EditorServicesHost
2929
{
3030
#region Private Fields
3131

32+
private bool enableConsoleRepl;
3233
private HostDetails hostDetails;
3334
private string bundledModulesPath;
3435
private DebugAdapter debugAdapter;
@@ -58,11 +59,13 @@ public class EditorServicesHost
5859
public EditorServicesHost(
5960
HostDetails hostDetails,
6061
string bundledModulesPath,
62+
bool enableConsoleRepl,
6163
bool waitForDebugger)
6264
{
6365
Validate.IsNotNull(nameof(hostDetails), hostDetails);
6466

6567
this.hostDetails = hostDetails;
68+
this.enableConsoleRepl = enableConsoleRepl;
6669
this.bundledModulesPath = bundledModulesPath;
6770

6871
#if DEBUG
@@ -143,6 +146,7 @@ public void StartLanguageService(int languageServicePort, ProfilePaths profilePa
143146
new LanguageServer(
144147
hostDetails,
145148
profilePaths,
149+
this.enableConsoleRepl,
146150
new TcpSocketServerChannel(languageServicePort));
147151

148152
this.languageServer.Start().Wait();
@@ -163,7 +167,7 @@ public void StartDebugService(
163167
ProfilePaths profilePaths,
164168
bool useExistingSession)
165169
{
166-
if (useExistingSession)
170+
if (this.enableConsoleRepl && useExistingSession)
167171
{
168172
this.debugAdapter =
169173
new DebugAdapter(
@@ -183,8 +187,9 @@ public void StartDebugService(
183187
this.debugAdapter.SessionEnded +=
184188
(obj, args) =>
185189
{
186-
// Only restart if we're reusing the existing session,
187-
// otherwise the process should terminate
190+
// Only restart if we're reusing the existing session
191+
// or if we're not using the console REPL, otherwise
192+
// the process should terminate
188193
if (useExistingSession)
189194
{
190195
Logger.Write(
@@ -193,6 +198,10 @@ public void StartDebugService(
193198

194199
this.StartDebugService(debugServicePort, profilePaths, true);
195200
}
201+
else if (!this.enableConsoleRepl)
202+
{
203+
this.StartDebugService(debugServicePort, profilePaths, false);
204+
}
196205
};
197206

198207
this.debugAdapter.Start().Wait();
@@ -240,7 +249,7 @@ public void WaitForCompletion()
240249

241250
#if !CoreCLR
242251
static void CurrentDomain_UnhandledException(
243-
object sender,
252+
object sender,
244253
UnhandledExceptionEventArgs e)
245254
{
246255
// Log the exception

src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace Microsoft.PowerShell.EditorServices.Protocol.Server
2323
public class DebugAdapter : DebugAdapterBase
2424
{
2525
private EditorSession editorSession;
26+
private OutputDebouncer outputDebouncer;
2627

2728
private bool noDebug;
2829
private bool isRemoteAttach;
@@ -59,7 +60,12 @@ public DebugAdapter(
5960
this.editorSession.StartDebugSession(hostDetails, profilePaths, editorOperations);
6061
this.editorSession.PowerShellContext.RunspaceChanged += this.powerShellContext_RunspaceChanged;
6162
this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped;
62-
}
63+
64+
// The assumption in this overload is that the debugger
65+
// is running in UI-hosted mode, no terminal interface
66+
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
67+
this.outputDebouncer = new OutputDebouncer(this);
68+
}
6369

6470
protected override void Initialize()
6571
{
@@ -102,6 +108,12 @@ private async Task OnExecutionCompleted(Task executeTask)
102108

103109
this.executionCompleted = true;
104110

111+
// Make sure remaining output is flushed before exiting
112+
if (this.outputDebouncer != null)
113+
{
114+
await this.outputDebouncer.Flush();
115+
}
116+
105117
if (this.isAttachSession)
106118
{
107119
// Ensure the read loop is stopped
@@ -150,6 +162,12 @@ protected override void Shutdown()
150162
{
151163
Logger.Write(LogLevel.Normal, "Debug adapter is shutting down...");
152164

165+
// Make sure remaining output is flushed before exiting
166+
if (this.outputDebouncer != null)
167+
{
168+
this.outputDebouncer.Flush().Wait();
169+
}
170+
153171
if (this.editorSession != null)
154172
{
155173
this.editorSession.PowerShellContext.RunspaceChanged -= this.powerShellContext_RunspaceChanged;
@@ -719,13 +737,34 @@ protected async Task HandleEvaluateRequest(
719737
"repl",
720738
StringComparison.CurrentCultureIgnoreCase);
721739

722-
if (!isFromRepl)
740+
if (isFromRepl)
741+
{
742+
// Check for special commands
743+
if (string.Equals("!ctrlc", evaluateParams.Expression, StringComparison.CurrentCultureIgnoreCase))
744+
{
745+
editorSession.PowerShellContext.AbortExecution();
746+
}
747+
else if (string.Equals("!break", evaluateParams.Expression, StringComparison.CurrentCultureIgnoreCase))
748+
{
749+
editorSession.DebugService.Break();
750+
}
751+
else
752+
{
753+
// Send the input through the console service
754+
var notAwaited =
755+
this.editorSession
756+
.PowerShellContext
757+
.ExecuteScriptString(evaluateParams.Expression, false, true)
758+
.ConfigureAwait(false);
759+
}
760+
}
761+
else
723762
{
724763
VariableDetails result =
725-
await editorSession.DebugService.EvaluateExpression(
726-
evaluateParams.Expression,
727-
evaluateParams.FrameId,
728-
isFromRepl);
764+
await editorSession.DebugService.EvaluateExpression(
765+
evaluateParams.Expression,
766+
evaluateParams.FrameId,
767+
isFromRepl);
729768

730769
if (result != null)
731770
{
@@ -735,12 +774,6 @@ await editorSession.DebugService.EvaluateExpression(
735774
result.Id : 0;
736775
}
737776
}
738-
else
739-
{
740-
Logger.Write(
741-
LogLevel.Verbose,
742-
$"Debug adapter client attempted to evaluate command in REPL: {evaluateParams.Expression}");
743-
}
744777

745778
await requestContext.SendResult(
746779
new EvaluateResponseBody
@@ -754,6 +787,15 @@ await requestContext.SendResult(
754787

755788
#region Event Handlers
756789

790+
private async void powerShellContext_OutputWritten(object sender, OutputWrittenEventArgs e)
791+
{
792+
if (this.outputDebouncer != null)
793+
{
794+
// Queue the output for writing
795+
await this.outputDebouncer.Invoke(e);
796+
}
797+
}
798+
757799
async void DebugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e)
758800
{
759801
// Provide the reason for why the debugger has stopped script execution.

src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,22 @@ public EditorSession EditorSession
5252
/// Provides details about the host application.
5353
/// </param>
5454
public LanguageServer(HostDetails hostDetails, ProfilePaths profilePaths)
55-
: this(hostDetails, profilePaths, new StdioServerChannel())
55+
: this(hostDetails, profilePaths, false, new StdioServerChannel())
5656
{
5757
}
5858

5959
/// <param name="hostDetails">
6060
/// Provides details about the host application.
6161
/// </param>
62-
public LanguageServer(HostDetails hostDetails, ProfilePaths profilePaths, ChannelBase serverChannel)
62+
public LanguageServer(
63+
HostDetails hostDetails,
64+
ProfilePaths profilePaths,
65+
bool enableConsoleRepl,
66+
ChannelBase serverChannel)
6367
: base(serverChannel)
6468
{
6569
this.editorSession = new EditorSession();
66-
this.editorSession.StartSession(hostDetails, profilePaths);
67-
//this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
70+
this.editorSession.StartSession(hostDetails, profilePaths, enableConsoleRepl);
6871
this.editorSession.PowerShellContext.RunspaceChanged += PowerShellContext_RunspaceChanged;
6972

7073
// Attach to ExtensionService events
@@ -80,13 +83,20 @@ public LanguageServer(HostDetails hostDetails, ProfilePaths profilePaths, Channe
8083

8184
this.editorSession.StartDebugService(this.editorOperations);
8285

83-
// Always send console prompts through the UI in the language service
84-
// TODO: This will change later once we have a general REPL available
85-
// in VS Code.
86-
this.editorSession.ConsoleService.PushPromptHandlerContext(
87-
new ProtocolPromptHandlerContext(
88-
this,
89-
this.editorSession.ConsoleService));
86+
if (enableConsoleRepl)
87+
{
88+
this.editorSession.ConsoleService.EnableConsoleRepl = true;
89+
}
90+
else
91+
{
92+
this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten;
93+
94+
// Always send console prompts through the UI in the language service
95+
this.editorSession.ConsoleService.PushPromptHandlerContext(
96+
new ProtocolPromptHandlerContext(
97+
this,
98+
this.editorSession.ConsoleService));
99+
}
90100

91101
// Set up the output debouncer to throttle output event writes
92102
this.outputDebouncer = new OutputDebouncer(this);

src/PowerShellEditorServices/Console/ConsoleService.cs

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ public class ConsoleService : IConsoleHost
2929
private PromptHandler activePromptHandler;
3030
private Stack<IPromptHandlerContext> promptHandlerContextStack =
3131
new Stack<IPromptHandlerContext>();
32+
33+
#endregion
34+
35+
#region Properties
36+
37+
/// <summary>
38+
/// Gets or sets a boolean determining whether the console (terminal)
39+
/// REPL should be used in this session.
40+
/// </summary>
41+
public bool EnableConsoleRepl { get; set; }
3242

3343
#endregion
3444

@@ -91,26 +101,29 @@ public ConsoleService(
91101
/// </summary>
92102
public void StartReadLoop()
93103
{
94-
if (this.readLineCancellationToken == null)
104+
if (this.EnableConsoleRepl)
95105
{
96-
this.readLineCancellationToken = new CancellationTokenSource();
97-
98-
var terminalThreadTask =
99-
Task.Factory.StartNew(
100-
async () =>
101-
{
102-
// Set the thread's name to help with debugging
103-
Thread.CurrentThread.Name = "Terminal Input Loop Thread";
104-
105-
await this.StartReplLoop(this.readLineCancellationToken.Token);
106-
},
107-
CancellationToken.None,
108-
TaskCreationOptions.LongRunning,
109-
TaskScheduler.Default);
110-
}
111-
else
112-
{
113-
Logger.Write(LogLevel.Verbose, "StartReadLoop called while read loop is already running");
106+
if (this.readLineCancellationToken == null)
107+
{
108+
this.readLineCancellationToken = new CancellationTokenSource();
109+
110+
var terminalThreadTask =
111+
Task.Factory.StartNew(
112+
async () =>
113+
{
114+
// Set the thread's name to help with debugging
115+
Thread.CurrentThread.Name = "Terminal Input Loop Thread";
116+
117+
await this.StartReplLoop(this.readLineCancellationToken.Token);
118+
},
119+
CancellationToken.None,
120+
TaskCreationOptions.LongRunning,
121+
TaskScheduler.Default);
122+
}
123+
else
124+
{
125+
Logger.Write(LogLevel.Verbose, "StartReadLoop called while read loop is already running");
126+
}
114127
}
115128
}
116129

@@ -334,20 +347,22 @@ await this.powerShellContext.ExecuteScriptString(
334347

335348
void IConsoleHost.WriteOutput(string outputString, bool includeNewLine, OutputType outputType, ConsoleColor foregroundColor, ConsoleColor backgroundColor)
336349
{
337-
ConsoleColor oldForegroundColor = Console.ForegroundColor;
338-
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
339-
340-
Console.ForegroundColor = foregroundColor;
341-
Console.BackgroundColor = backgroundColor;
350+
if (this.EnableConsoleRepl)
351+
{
352+
ConsoleColor oldForegroundColor = Console.ForegroundColor;
353+
ConsoleColor oldBackgroundColor = Console.BackgroundColor;
342354

343-
Console.Write(outputString + (includeNewLine ? Environment.NewLine : ""));
355+
Console.ForegroundColor = foregroundColor;
356+
Console.BackgroundColor = backgroundColor;
344357

345-
Console.ForegroundColor = oldForegroundColor;
346-
Console.BackgroundColor = oldBackgroundColor;
358+
Console.Write(outputString + (includeNewLine ? Environment.NewLine : ""));
347359

348-
if (this.OutputWritten != null)
360+
Console.ForegroundColor = oldForegroundColor;
361+
Console.BackgroundColor = oldBackgroundColor;
362+
}
363+
else
349364
{
350-
this.OutputWritten(
365+
this.OutputWritten?.Invoke(
351366
this,
352367
new OutputWrittenEventArgs(
353368
outputString,

0 commit comments

Comments
 (0)