Skip to content

Commit 9669da3

Browse files
committed
Introduce OutputEvent and command eval in debugger
This change introduces the new OutputEvent which allows the the language and debugging services to send script output text to the client to be displayed to the user. Currently this is used in VS Code's Debug Console but could be used in other contexts as well. This change also enables the user to evaluate PowerShell commands while stopped in the debugger to aid in debugging. In the future this will be expanded to allow commands to be executed at any time.
1 parent 746c6fa commit 9669da3

File tree

7 files changed

+81
-35
lines changed

7 files changed

+81
-35
lines changed

src/PowerShellEditorServices.Host/MessageLoop.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,22 +183,17 @@ await this.messageWriter.WriteMessage(
183183
}, null);
184184
}
185185

186-
void PowerShellSession_OutputWritten(object sender, OutputWrittenEventArgs e)
186+
async void PowerShellSession_OutputWritten(object sender, OutputWrittenEventArgs e)
187187
{
188-
// TODO: change this to use the OutputEvent!
189-
190-
//await this.messageWriter.WriteMessage(
191-
// new ReplWriteOutputEvent
192-
// {
193-
// Body = new ReplWriteOutputEventBody
194-
// {
195-
// LineContents = e.OutputText,
196-
// LineType = e.OutputType,
197-
// IncludeNewLine = e.IncludeNewLine,
198-
// ForegroundColor = e.ForegroundColor,
199-
// BackgroundColor = e.BackgroundColor
200-
// }
201-
// });
188+
await this.messageWriter.WriteMessage(
189+
new OutputEvent
190+
{
191+
Body = new OutputEventBody
192+
{
193+
Output = e.OutputText + (e.IncludeNewLine ? "\r\n" : string.Empty),
194+
Category = (e.OutputType == OutputType.Error) ? "stderr" : "stdout"
195+
}
196+
});
202197
}
203198

204199
#endregion
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 Microsoft.PowerShell.EditorServices.Transport.Stdio.Message;
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using System.Text;
11+
using System.Threading.Tasks;
12+
13+
namespace Microsoft.PowerShell.EditorServices.Transport.Stdio.Event
14+
{
15+
[MessageTypeName("output")]
16+
public class OutputEvent : EventBase<OutputEventBody>
17+
{
18+
}
19+
20+
public class OutputEventBody
21+
{
22+
public string Category { get; set; }
23+
24+
public string Output { get; set; }
25+
}
26+
}
27+

src/PowerShellEditorServices.Transport.Stdio/PowerShellEditorServices.Transport.Stdio.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<Compile Include="Event\EventBase.cs" />
6565
<Compile Include="Event\ExitedEvent.cs" />
6666
<Compile Include="Event\InitializedEvent.cs" />
67+
<Compile Include="Event\OutputEvent.cs" />
6768
<Compile Include="Event\ReplPromptChoiceEvent.cs">
6869
<SubType>Code</SubType>
6970
</Compile>

src/PowerShellEditorServices.Transport.Stdio/Request/EvaluateRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public override async Task ProcessMessage(
1818
MessageWriter messageWriter)
1919
{
2020
VariableDetails result =
21-
editorSession.DebugService.EvaluateExpression(
21+
await editorSession.DebugService.EvaluateExpression(
2222
this.Arguments.Expression,
2323
this.Arguments.FrameId);
2424

src/PowerShellEditorServices/Debugging/DebugService.cs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,17 @@ public VariableDetails[] GetVariables(int variableReferenceId)
193193
}
194194

195195
/// <summary>
196-
/// Evaluates an expression in the context of the stopped
197-
/// debugger. For now, this just does simple evaluation of
198-
/// a variable in the session. In the future it will execute
199-
/// commands in the PowerShellSession.
196+
/// Evaluates a variable expression in the context of the stopped
197+
/// debugger. This method decomposes the variable expression to
198+
/// walk the cached variable data for the specified stack frame.
200199
/// </summary>
201-
/// <param name="expressionString">The expression string to execute.</param>
202-
/// <param name="stackFrameId">The ID of the stack frame in which the expression should be executed.</param>
200+
/// <param name="variableExpression">The variable expression string to evaluate.</param>
201+
/// <param name="stackFrameId">The ID of the stack frame in which the expression should be evaluated.</param>
203202
/// <returns>A VariableDetails object containing the result.</returns>
204-
public VariableDetails EvaluateExpression(string expressionString, int stackFrameId)
203+
public VariableDetails GetVariableFromExpression(string variableExpression, int stackFrameId)
205204
{
206205
// Break up the variable path
207-
string[] variablePathParts = expressionString.Split('.');
206+
string[] variablePathParts = variableExpression.Split('.');
208207

209208
VariableDetails resolvedVariable = null;
210209
IEnumerable<VariableDetails> variableList = this.currentVariables;
@@ -222,10 +221,10 @@ public VariableDetails EvaluateExpression(string expressionString, int stackFram
222221
v =>
223222
string.Equals(
224223
v.Name,
225-
expressionString,
224+
variableExpression,
226225
StringComparison.InvariantCultureIgnoreCase));
227226

228-
if (resolvedVariable != null &&
227+
if (resolvedVariable != null &&
229228
resolvedVariable.IsExpandable)
230229
{
231230
// Continue by searching in this variable's children
@@ -236,6 +235,29 @@ public VariableDetails EvaluateExpression(string expressionString, int stackFram
236235
return resolvedVariable;
237236
}
238237

238+
/// <summary>
239+
/// Evaluates an expression in the context of the stopped
240+
/// debugger. This method will execute the specified expression
241+
/// PowerShellSession.
242+
/// </summary>
243+
/// <param name="expressionString">The expression string to execute.</param>
244+
/// <param name="stackFrameId">The ID of the stack frame in which the expression should be executed.</param>
245+
/// <returns>A VariableDetails object containing the result.</returns>
246+
public async Task<VariableDetails> EvaluateExpression(string expressionString, int stackFrameId)
247+
{
248+
var results =
249+
await this.powerShellSession.ExecuteScriptString(
250+
expressionString);
251+
252+
// Since this method should only be getting invoked in the debugger,
253+
// we can assume that Out-String will be getting used to format results
254+
// of command executions into string output.
255+
256+
return new VariableDetails(
257+
expressionString,
258+
string.Join(Environment.NewLine, results));
259+
}
260+
239261
/// <summary>
240262
/// Gets the list of stack frames at the point where the
241263
/// debugger sf stopped.

src/PowerShellEditorServices/Session/PowerShellSession.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,12 @@ public Task ExecuteCommand(PSCommand psCommand)
310310
/// </summary>
311311
/// <param name="scriptString">The script string to execute.</param>
312312
/// <returns>A Task that can be awaited for the script completion.</returns>
313-
public async Task ExecuteScriptString(string scriptString)
313+
public async Task<IEnumerable<object>> ExecuteScriptString(string scriptString)
314314
{
315315
PSCommand psCommand = new PSCommand();
316316
psCommand.AddScript(scriptString);
317317

318-
await this.ExecuteCommand<object>(psCommand, true);
318+
return await this.ExecuteCommand<object>(psCommand, true);
319319
}
320320

321321
/// <summary>
@@ -328,7 +328,7 @@ public async Task ExecuteScriptAtPath(string scriptPath)
328328
PSCommand command = new PSCommand();
329329
command.AddCommand(scriptPath);
330330

331-
await this.ExecuteCommand<object>(command);
331+
await this.ExecuteCommand<object>(command, true);
332332
}
333333

334334
/// <summary>

test/PowerShellEditorServices.Test.Host/ScenarioTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,20 +371,21 @@ await this.MessageWriter.WriteMessage(
371371
Assert.Equal(sigHelp.Body.ArgumentCount, 1);
372372
}
373373

374-
[Fact(Skip = "Console output events are disabled until we migrate to the updated debug protocol.")]
374+
[Fact]
375375
public async Task ServiceExecutesReplCommandAndReceivesOutput()
376376
{
377377
await this.MessageWriter.WriteMessage(
378-
new ReplExecuteRequest
378+
new EvaluateRequest
379379
{
380-
Arguments = new ReplExecuteArgs
380+
Arguments = new EvaluateRequestArguments
381381
{
382-
CommandString = "1 + 2"
382+
Expression = "1 + 2"
383383
}
384384
});
385385

386-
ReplWriteOutputEvent replWriteLineEvent = this.WaitForMessage<ReplWriteOutputEvent>();
387-
Assert.Equal("3", replWriteLineEvent.Body.LineContents);
386+
OutputEvent outputEvent = this.WaitForMessage<OutputEvent>();
387+
Assert.Equal("3\r\n", outputEvent.Body.Output);
388+
Assert.Equal("stdout", outputEvent.Body.Category);
388389
}
389390

390391
[Fact(Skip = "Choice prompt functionality is currently in transition to a new model.")]

0 commit comments

Comments
 (0)