Skip to content

Commit 26f6ada

Browse files
committed
Interrupt integrated console command prompt when commands are executed
This change fixes a general class of problems in the integrated console experience where commands that were executed internally (like extension commands or running Plaster templates) do not interrupt the command prompt, causing future input to be written over previous output lines. The fix is to have the command prompt be interrupted by any commands which write output or errors to the host. Fixes #411.
1 parent 0b2a773 commit 26f6ada

File tree

7 files changed

+273
-55
lines changed

7 files changed

+273
-55
lines changed

Diff for: src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs

-12
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,6 @@ protected override void Initialize()
100100

101101
protected Task LaunchScript(RequestContext<object> requestContext)
102102
{
103-
// Ensure the read loop is stopped
104-
this.editorSession.ConsoleService.CancelReadLoop();
105-
106103
// Is this an untitled script?
107104
Task launchTask = null;
108105

@@ -144,9 +141,6 @@ private async Task OnExecutionCompleted(Task executeTask)
144141

145142
if (this.isAttachSession)
146143
{
147-
// Ensure the read loop is stopped
148-
this.editorSession.ConsoleService.CancelReadLoop();
149-
150144
// Pop the sessions
151145
if (this.editorSession.PowerShellContext.CurrentRunspace.Context == RunspaceContext.EnteredProcess)
152146
{
@@ -165,12 +159,6 @@ private async Task OnExecutionCompleted(Task executeTask)
165159
Logger.WriteException("Caught exception while popping attached process after debugging", e);
166160
}
167161
}
168-
169-
}
170-
171-
if (!this.ownsEditorSession)
172-
{
173-
this.editorSession.ConsoleService.StartReadLoop();
174162
}
175163

176164
if (this.disconnectRequestContext != null)

Diff for: src/PowerShellEditorServices.Protocol/Server/LanguageServer.cs

-3
Original file line numberDiff line numberDiff line change
@@ -1108,9 +1108,6 @@ protected Task HandleEvaluateRequest(
11081108
DebugAdapterMessages.EvaluateRequestArguments evaluateParams,
11091109
RequestContext<DebugAdapterMessages.EvaluateResponseBody> requestContext)
11101110
{
1111-
// Cancel the read loop before executing
1112-
this.editorSession.ConsoleService.CancelReadLoop();
1113-
11141111
// We don't await the result of the execution here because we want
11151112
// to be able to receive further messages while the current script
11161113
// is executing. This important in cases where the pipeline thread

Diff for: src/PowerShellEditorServices/Console/ConsoleService.cs

+42-30
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public ConsoleService(
7979
this.powerShellContext.ConsoleHost = this;
8080
this.powerShellContext.DebuggerStop += PowerShellContext_DebuggerStop;
8181
this.powerShellContext.DebuggerResumed += PowerShellContext_DebuggerResumed;
82+
this.powerShellContext.ExecutionStatusChanged += PowerShellContext_ExecutionStatusChanged;
8283

8384
// Set the default prompt handler factory or create
8485
// a default if one is not provided
@@ -140,31 +141,6 @@ public void CancelReadLoop()
140141
}
141142
}
142143

143-
/// <summary>
144-
/// Called when a command string is received from the user.
145-
/// If a prompt is currently active, the prompt handler is
146-
/// asked to handle the string. Otherwise the string is
147-
/// executed in the PowerShellContext.
148-
/// </summary>
149-
/// <param name="inputString">The input string to evaluate.</param>
150-
/// <param name="echoToConsole">If true, the input will be echoed to the console.</param>
151-
public void ExecuteCommand(string inputString, bool echoToConsole)
152-
{
153-
this.CancelReadLoop();
154-
155-
if (this.activePromptHandler == null)
156-
{
157-
// Execute the script string but don't wait for completion
158-
var executeTask =
159-
this.powerShellContext
160-
.ExecuteScriptString(
161-
inputString,
162-
echoToConsole,
163-
true)
164-
.ConfigureAwait(false);
165-
}
166-
}
167-
168144
/// <summary>
169145
/// Executes a script file at the specified path.
170146
/// </summary>
@@ -338,11 +314,16 @@ await this.consoleReadLine.ReadCommandLine(
338314
{
339315
Console.Write(Environment.NewLine);
340316

341-
await this.powerShellContext.ExecuteScriptString(
342-
commandString,
343-
false,
344-
true,
345-
true);
317+
var unusedTask =
318+
this.powerShellContext
319+
.ExecuteScriptString(
320+
commandString,
321+
false,
322+
true,
323+
true)
324+
.ConfigureAwait(false);
325+
326+
break;
346327
}
347328
}
348329
while (!cancellationToken.IsCancellationRequested);
@@ -459,6 +440,37 @@ private void PowerShellContext_DebuggerResumed(object sender, System.Management.
459440
this.CancelReadLoop();
460441
}
461442

443+
private void PowerShellContext_ExecutionStatusChanged(object sender, ExecutionStatusChangedEventArgs eventArgs)
444+
{
445+
if (this.EnableConsoleRepl)
446+
{
447+
// Any command which writes output to the host will affect
448+
// the display of the prompt
449+
if (eventArgs.ExecutionOptions.WriteOutputToHost ||
450+
eventArgs.ExecutionOptions.InterruptCommandPrompt)
451+
{
452+
if (eventArgs.ExecutionStatus != ExecutionStatus.Running)
453+
{
454+
// Execution has completed, start the input prompt
455+
this.StartReadLoop();
456+
}
457+
else
458+
{
459+
// A new command was started, cancel the input prompt
460+
this.CancelReadLoop();
461+
}
462+
}
463+
else if (
464+
eventArgs.ExecutionOptions.WriteErrorsToHost &&
465+
(eventArgs.ExecutionStatus == ExecutionStatus.Failed ||
466+
eventArgs.HadErrors))
467+
{
468+
this.CancelReadLoop();
469+
this.StartReadLoop();
470+
}
471+
}
472+
}
473+
462474
#endregion
463475
}
464476
}
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
namespace Microsoft.PowerShell.EditorServices
7+
{
8+
/// <summary>
9+
/// Defines options for the execution of a command.
10+
/// </summary>
11+
public class ExecutionOptions
12+
{
13+
#region Properties
14+
15+
/// <summary>
16+
/// Gets or sets a boolean that determines whether command output
17+
/// should be written to the host.
18+
/// </summary>
19+
public bool WriteOutputToHost { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets a boolean that determines whether command errors
23+
/// should be written to the host.
24+
/// </summary>
25+
public bool WriteErrorsToHost { get; set; }
26+
27+
/// <summary>
28+
/// Gets or sets a boolean that determines whether the executed
29+
/// command should be added to the command history.
30+
/// </summary>
31+
public bool AddToHistory { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets a boolean that determines whether the execution
35+
/// of the command should interrupt the command prompt. Should
36+
/// only be set if WriteOutputToHost is false but the command
37+
/// should still interrupt the command prompt.
38+
/// </summary>
39+
public bool InterruptCommandPrompt { get; set; }
40+
41+
#endregion
42+
43+
#region Constructors
44+
45+
/// <summary>
46+
/// Creates an instance of the ExecutionOptions class with
47+
/// default settings configured.
48+
/// </summary>
49+
public ExecutionOptions()
50+
{
51+
this.WriteOutputToHost = true;
52+
this.WriteErrorsToHost = true;
53+
this.AddToHistory = false;
54+
this.InterruptCommandPrompt = false;
55+
}
56+
57+
#endregion
58+
}
59+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
namespace Microsoft.PowerShell.EditorServices
7+
{
8+
/// <summary>
9+
/// Enumerates the possible execution results that can occur after
10+
/// executing a command or script.
11+
/// </summary>
12+
public enum ExecutionStatus
13+
{
14+
/// <summary>
15+
/// Indicates that execution has not yet started.
16+
/// </summary>
17+
Pending,
18+
19+
/// <summary>
20+
/// Indicates that the command is executing.
21+
/// </summary>
22+
Running,
23+
24+
/// <summary>
25+
/// Indicates that execution has failed.
26+
/// </summary>
27+
Failed,
28+
29+
/// <summary>
30+
/// Indicates that execution was aborted by the user.
31+
/// </summary>
32+
Aborted,
33+
34+
/// <summary>
35+
/// Indicates that execution completed successfully.
36+
/// </summary>
37+
Completed
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
namespace Microsoft.PowerShell.EditorServices
7+
{
8+
/// <summary>
9+
/// Contains details about an executed
10+
/// </summary>
11+
public class ExecutionStatusChangedEventArgs
12+
{
13+
#region Properties
14+
15+
/// <summary>
16+
/// Gets the options used when the command was executed.
17+
/// </summary>
18+
public ExecutionOptions ExecutionOptions { get; private set; }
19+
20+
/// <summary>
21+
/// Gets the command execution's current status.
22+
/// </summary>
23+
public ExecutionStatus ExecutionStatus { get; private set; }
24+
25+
/// <summary>
26+
/// If true, the command execution had errors.
27+
/// </summary>
28+
public bool HadErrors { get; private set; }
29+
30+
#endregion
31+
32+
#region Constructors
33+
34+
/// <summary>
35+
/// Creates an instance of the ExecutionStatusChangedEventArgs class.
36+
/// </summary>
37+
/// <param name="executionStatus">The command execution's current status.</param>
38+
/// <param name="executionOptions">The options used when the command was executed.</param>
39+
/// <param name="hadErrors">If execution has completed, indicates whether there were errors.</param>
40+
public ExecutionStatusChangedEventArgs(
41+
ExecutionStatus executionStatus,
42+
ExecutionOptions executionOptions,
43+
bool hadErrors)
44+
{
45+
this.ExecutionStatus = executionStatus;
46+
this.ExecutionOptions = executionOptions;
47+
this.HadErrors = hadErrors || (executionStatus == ExecutionStatus.Failed);
48+
}
49+
50+
#endregion
51+
}
52+
}

0 commit comments

Comments
 (0)