Skip to content

Commit 3a48280

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 5e60d72 commit 3a48280

File tree

16 files changed

+779
-395
lines changed

16 files changed

+779
-395
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: 65 additions & 23 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
{
@@ -103,6 +109,12 @@ private async Task OnExecutionCompleted(Task executeTask)
103109

104110
this.executionCompleted = true;
105111

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

166+
// Make sure remaining output is flushed before exiting
167+
if (this.outputDebouncer != null)
168+
{
169+
this.outputDebouncer.Flush().Wait();
170+
}
171+
154172
if (this.editorSession != null)
155173
{
156174
this.editorSession.PowerShellContext.RunspaceChanged -= this.powerShellContext_RunspaceChanged;
@@ -195,8 +213,8 @@ protected async Task HandleLaunchRequest(
195213
LaunchRequestArguments launchParams,
196214
RequestContext<object> requestContext)
197215
{
198-
// Set the working directory for the PowerShell runspace to the cwd passed in via launch.json.
199-
// In case that is null, use the the folder of the script to be executed. If the resulting
216+
// Set the working directory for the PowerShell runspace to the cwd passed in via launch.json.
217+
// In case that is null, use the the folder of the script to be executed. If the resulting
200218
// working dir path is a file path then extract the directory and use that.
201219
string workingDir =
202220
launchParams.Cwd ??
@@ -289,7 +307,7 @@ protected async Task HandleAttachRequest(
289307

290308
// If there are no host processes to attach to or the user cancels selection, we get a null for the process id.
291309
// This is not an error, just a request to stop the original "attach to" request.
292-
// Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading
310+
// Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading
293311
// to cancel on the VSCode side without sending an attachRequest with processId set to "undefined".
294312
if (string.IsNullOrEmpty(attachParams.ProcessId) || (attachParams.ProcessId == "undefined"))
295313
{
@@ -427,7 +445,7 @@ protected async Task HandleSetBreakpointsRequest(
427445
catch (Exception e) when (e is FileNotFoundException || e is DirectoryNotFoundException)
428446
{
429447
Logger.Write(
430-
LogLevel.Warning,
448+
LogLevel.Warning,
431449
$"Attempted to set breakpoints on a non-existing file: {setBreakpointsParams.Source.Path}");
432450

433451
string message = this.noDebug ? string.Empty : "Source does not exist, breakpoint not set.";
@@ -450,9 +468,9 @@ await requestContext.SendResult(
450468
{
451469
SourceBreakpoint srcBreakpoint = setBreakpointsParams.Breakpoints[i];
452470
breakpointDetails[i] = BreakpointDetails.Create(
453-
scriptFile.FilePath,
454-
srcBreakpoint.Line,
455-
srcBreakpoint.Column,
471+
scriptFile.FilePath,
472+
srcBreakpoint.Line,
473+
srcBreakpoint.Column,
456474
srcBreakpoint.Condition,
457475
srcBreakpoint.HitCondition);
458476
}
@@ -604,7 +622,7 @@ protected async Task HandleStackTraceRequest(
604622
// be referenced back to the current list of stack frames
605623
newStackFrames.Add(
606624
StackFrame.Create(
607-
stackFrames[i],
625+
stackFrames[i],
608626
i));
609627
}
610628

@@ -720,13 +738,34 @@ protected async Task HandleEvaluateRequest(
720738
"repl",
721739
StringComparison.CurrentCultureIgnoreCase);
722740

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

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

746779
await requestContext.SendResult(
747780
new EvaluateResponseBody
@@ -755,13 +788,22 @@ await requestContext.SendResult(
755788

756789
#region Event Handlers
757790

791+
private async void powerShellContext_OutputWritten(object sender, OutputWrittenEventArgs e)
792+
{
793+
if (this.outputDebouncer != null)
794+
{
795+
// Queue the output for writing
796+
await this.outputDebouncer.Invoke(e);
797+
}
798+
}
799+
758800
async void DebugService_DebuggerStopped(object sender, DebuggerStoppedEventArgs e)
759801
{
760802
// Provide the reason for why the debugger has stopped script execution.
761803
// See https://github.com/Microsoft/vscode/issues/3648
762-
// The reason is displayed in the breakpoints viewlet. Some recommended reasons are:
804+
// The reason is displayed in the breakpoints viewlet. Some recommended reasons are:
763805
// "step", "breakpoint", "function breakpoint", "exception" and "pause".
764-
// We don't support exception breakpoints and for "pause", we can't distinguish
806+
// We don't support exception breakpoints and for "pause", we can't distinguish
765807
// between stepping and the user pressing the pause/break button in the debug toolbar.
766808
string debuggerStoppedReason = "step";
767809
if (e.OriginalEvent.Breakpoints.Count > 0)
@@ -799,7 +841,7 @@ async void powerShellContext_RunspaceChanged(object sender, RunspaceChangedEvent
799841
await this.SendEvent(InitializedEvent.Type, null);
800842
}
801843
else if (
802-
e.ChangeAction == RunspaceChangeAction.Exit &&
844+
e.ChangeAction == RunspaceChangeAction.Exit &&
803845
(this.editorSession == null ||
804846
this.editorSession.PowerShellContext.IsDebuggerStopped))
805847
{

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);

0 commit comments

Comments
 (0)