Skip to content

Commit aa11be0

Browse files
committed
First pass at normalizing the difference between a collection of variables in a scope versus a normal, expandable variable. This also implements support for locals per stack frame.
1 parent b83a7a8 commit aa11be0

File tree

12 files changed

+205
-105
lines changed

12 files changed

+205
-105
lines changed

src/PowerShellEditorServices.Host/DebugAdapter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ protected async Task HandleStackTraceRequest(
276276
newStackFrames.Add(
277277
StackFrame.Create(
278278
stackFrames[i],
279-
i + 1));
279+
i));
280280
}
281281

282282
await requestContext.SendResult(
@@ -310,7 +310,7 @@ protected async Task HandleVariablesRequest(
310310
EditorSession editorSession,
311311
RequestContext<VariablesResponseBody, object> requestContext)
312312
{
313-
VariableDetails[] variables =
313+
VariableDetailsBase[] variables =
314314
editorSession.DebugService.GetVariables(
315315
variablesParams.VariablesReference);
316316

src/PowerShellEditorServices.Protocol/DebugAdapter/ScopesRequest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System.Diagnostics;
67
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
78

89
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
@@ -14,6 +15,7 @@ public static readonly
1415
RequestType<ScopesRequestArguments, ScopesResponseBody, object>.Create("scopes");
1516
}
1617

18+
[DebuggerDisplay("FrameId = {FrameId}")]
1719
public class ScopesRequestArguments
1820
{
1921
public int FrameId { get; set; }

src/PowerShellEditorServices.Protocol/DebugAdapter/StackTraceRequest.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System.Diagnostics;
67
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
78

89
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
@@ -14,6 +15,7 @@ public static readonly
1415
RequestType<StackTraceRequestArguments, StackTraceResponseBody, object>.Create("stackTrace");
1516
}
1617

18+
[DebuggerDisplay("ThreadId = {ThreadId}, Levels = {Levels}")]
1719
public class StackTraceRequestArguments
1820
{
1921
public int ThreadId { get; private set; }
@@ -27,4 +29,3 @@ public class StackTraceResponseBody
2729
public StackFrame[] StackFrames { get; set; }
2830
}
2931
}
30-

src/PowerShellEditorServices.Protocol/DebugAdapter/Variable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class Variable
1515
// /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */
1616
public int VariablesReference { get; set; }
1717

18-
public static Variable Create(VariableDetails variable)
18+
public static Variable Create(VariableDetailsBase variable)
1919
{
2020
return new Variable
2121
{

src/PowerShellEditorServices.Protocol/DebugAdapter/VariablesRequest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44
//
55

6+
using System.Diagnostics;
67
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
78

89
namespace Microsoft.PowerShell.EditorServices.Protocol.DebugAdapter
@@ -14,6 +15,7 @@ public static readonly
1415
RequestType<VariablesRequestArguments, VariablesResponseBody, object>.Create("variables");
1516
}
1617

18+
[DebuggerDisplay("VariablesReference = {VariablesReference}")]
1719
public class VariablesRequestArguments
1820
{
1921
public int VariablesReference { get; set; }

src/PowerShellEditorServices/Debugging/DebugService.cs

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Linq;
1010
using System.Management.Automation;
1111
using System.Threading.Tasks;
12+
using Microsoft.PowerShell.EditorServices.Debugging;
1213

1314
namespace Microsoft.PowerShell.EditorServices
1415
{
@@ -27,8 +28,10 @@ public class DebugService
2728
new Dictionary<string, List<Breakpoint>>();
2829

2930
private int nextVariableId;
30-
private List<VariableDetails> currentVariables;
31-
private StackFrameDetails[] callStackFrames;
31+
private List<VariableDetailsBase> variables;
32+
private VariableContainerDetails globalScopeVariables;
33+
private VariableContainerDetails scriptScopeVariables;
34+
private StackFrameDetails[] stackFrameDetails;
3235

3336
#endregion
3437

@@ -155,38 +158,28 @@ public void Abort()
155158
/// </summary>
156159
/// <param name="variableReferenceId"></param>
157160
/// <returns>An array of VariableDetails instances which describe the requested variables.</returns>
158-
public VariableDetails[] GetVariables(int variableReferenceId)
161+
public VariableDetailsBase[] GetVariables(int variableReferenceId)
159162
{
160-
VariableDetails[] childVariables = null;
163+
VariableDetailsBase[] childVariables;
161164

162-
if (variableReferenceId >= VariableDetails.FirstVariableId)
165+
VariableDetailsBase parentVariable = this.variables[variableReferenceId];
166+
if (parentVariable.IsExpandable)
163167
{
164-
int correctedId =
165-
(variableReferenceId - VariableDetails.FirstVariableId);
168+
childVariables = parentVariable.GetChildren();
166169

167-
VariableDetails parentVariable =
168-
this.currentVariables[correctedId];
169-
170-
if (parentVariable.IsExpandable)
170+
foreach (var child in childVariables)
171171
{
172-
childVariables = parentVariable.GetChildren();
173-
174-
foreach (var child in childVariables)
172+
// Only add child if it hasn't already been added.
173+
if (child.Id < 0)
175174
{
176-
this.currentVariables.Add(child);
177-
child.Id = this.nextVariableId;
178-
this.nextVariableId++;
175+
child.Id = this.nextVariableId++;
176+
this.variables.Add(child);
179177
}
180178
}
181-
else
182-
{
183-
childVariables = new VariableDetails[0];
184-
}
185179
}
186180
else
187181
{
188-
// TODO: Get variables for the desired scope ID
189-
childVariables = this.currentVariables.ToArray();
182+
childVariables = new VariableDetailsBase[0];
190183
}
191184

192185
return childVariables;
@@ -200,13 +193,13 @@ public VariableDetails[] GetVariables(int variableReferenceId)
200193
/// <param name="variableExpression">The variable expression string to evaluate.</param>
201194
/// <param name="stackFrameId">The ID of the stack frame in which the expression should be evaluated.</param>
202195
/// <returns>A VariableDetails object containing the result.</returns>
203-
public VariableDetails GetVariableFromExpression(string variableExpression, int stackFrameId)
196+
public VariableDetailsBase GetVariableFromExpression(string variableExpression, int stackFrameId)
204197
{
205198
// Break up the variable path
206199
string[] variablePathParts = variableExpression.Split('.');
207200

208-
VariableDetails resolvedVariable = null;
209-
IEnumerable<VariableDetails> variableList = this.currentVariables;
201+
VariableDetailsBase resolvedVariable = null;
202+
IEnumerable<VariableDetailsBase> variableList = this.variables;
210203

211204
foreach (var variableName in variablePathParts)
212205
{
@@ -267,7 +260,7 @@ await this.powerShellContext.ExecuteScriptString(
267260
/// </returns>
268261
public StackFrameDetails[] GetStackFrames()
269262
{
270-
return this.callStackFrames;
263+
return this.stackFrameDetails;
271264
}
272265

273266
/// <summary>
@@ -278,10 +271,11 @@ public StackFrameDetails[] GetStackFrames()
278271
/// <returns>The list of VariableScope instances which describe the available variable scopes.</returns>
279272
public VariableScope[] GetVariableScopes(int stackFrameId)
280273
{
281-
// TODO: Return different scopes based on PowerShell scoping mechanics
282274
return new VariableScope[]
283275
{
284-
new VariableScope(1, "Locals")
276+
new VariableScope(this.stackFrameDetails[stackFrameId].LocalVariables.Id, "Local"),
277+
new VariableScope(this.scriptScopeVariables.Id, "Script"),
278+
new VariableScope(this.globalScopeVariables.Id, "Global"),
285279
};
286280
}
287281

@@ -310,25 +304,44 @@ private async Task ClearBreakpointsInFile(ScriptFile scriptFile)
310304
}
311305
}
312306

313-
private async Task FetchVariables()
307+
private async Task FetchStackFramesAndVariables()
314308
{
315-
this.nextVariableId = VariableDetails.FirstVariableId;
316-
this.currentVariables = new List<VariableDetails>();
309+
// Avoid using 0 as it indicates a variable node with no children.
310+
this.nextVariableId = 1;
311+
this.variables = new List<VariableDetailsBase>();
312+
313+
// Create a dummy variable for index 0, should never see this.
314+
this.variables.Add(new VariableDetails("Dummy", null));
317315

316+
await FetchGlobalAndScriptVariables();
317+
await FetchStackFrames();
318+
319+
}
320+
321+
private async Task FetchGlobalAndScriptVariables()
322+
{
323+
this.scriptScopeVariables = await FetchVariableContainer("Script");
324+
this.globalScopeVariables = await FetchVariableContainer("Global");
325+
}
326+
327+
private async Task<VariableContainerDetails> FetchVariableContainer(string scope)
328+
{
318329
PSCommand psCommand = new PSCommand();
319330
psCommand.AddCommand("Get-Variable");
320-
psCommand.AddParameter("Scope", "Local");
331+
psCommand.AddParameter("Scope", scope);
321332

322-
var results = await this.powerShellContext.ExecuteCommand<PSVariable>(psCommand);
333+
var variableContainerDetails = new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope);
334+
this.variables.Add(variableContainerDetails);
323335

324-
foreach (var variable in results)
336+
var results = await this.powerShellContext.ExecuteCommand<PSVariable>(psCommand);
337+
foreach (PSVariable variable in results)
325338
{
326-
var details = new VariableDetails(variable);
327-
details.Id = this.nextVariableId;
328-
this.currentVariables.Add(details);
329-
330-
this.nextVariableId++;
339+
var variableDetails = new VariableDetails(variable) { Id = this.nextVariableId++ };
340+
this.variables.Add(variableDetails);
341+
variableContainerDetails.Children.Add(variableDetails);
331342
}
343+
344+
return variableContainerDetails;
332345
}
333346

334347
private async Task FetchStackFrames()
@@ -338,10 +351,14 @@ private async Task FetchStackFrames()
338351

339352
var results = await this.powerShellContext.ExecuteCommand<CallStackFrame>(psCommand);
340353

341-
this.callStackFrames =
342-
results
343-
.Select(StackFrameDetails.Create)
344-
.ToArray();
354+
var callStackFrames = results.ToArray();
355+
this.stackFrameDetails = new StackFrameDetails[callStackFrames.Length];
356+
357+
for (int i = 0; i < callStackFrames.Length; i++)
358+
{
359+
VariableContainerDetails localVariables = await FetchVariableContainer(i.ToString());
360+
this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], localVariables);
361+
}
345362
}
346363

347364
#endregion
@@ -355,9 +372,8 @@ private async Task FetchStackFrames()
355372

356373
private async void OnDebuggerStop(object sender, DebuggerStopEventArgs e)
357374
{
358-
// Get the call stack and local variables
359-
await this.FetchStackFrames();
360-
await this.FetchVariables();
375+
// Get call stack and variables.
376+
await this.FetchStackFramesAndVariables();
361377

362378
// Notify the host that the debugger is stopped
363379
if (this.DebuggerStopped != null)

src/PowerShellEditorServices/Debugging/StackFrameDetails.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using System.Collections.Generic;
77
using System.Management.Automation;
8+
using Microsoft.PowerShell.EditorServices.Debugging;
89

910
namespace Microsoft.PowerShell.EditorServices
1011
{
@@ -34,26 +35,32 @@ public class StackFrameDetails
3435
/// </summary>
3536
public int ColumnNumber { get; private set; }
3637

38+
/// <summary>
39+
/// Gets or sets the VariableContainerDetails that contains the local variables.
40+
/// </summary>
41+
public VariableContainerDetails LocalVariables { get; private set; }
42+
3743
/// <summary>
3844
/// Creates an instance of the StackFrameDetails class from a
3945
/// CallStackFrame instance provided by the PowerShell engine.
4046
/// </summary>
4147
/// <param name="callStackFrame">
4248
/// The original CallStackFrame instance from which details will be obtained.
4349
/// </param>
50+
/// <param name="localVariables">
51+
/// A variable container with all the local variables for this stack frame.
4452
/// <returns>A new instance of the StackFrameDetails class.</returns>
4553
static internal StackFrameDetails Create(
46-
CallStackFrame callStackFrame)
54+
CallStackFrame callStackFrame,
55+
VariableContainerDetails localVariables)
4756
{
48-
Dictionary<string, PSVariable> localVariables =
49-
callStackFrame.GetFrameVariables();
50-
5157
return new StackFrameDetails
5258
{
5359
ScriptPath = callStackFrame.ScriptName,
5460
FunctionName = callStackFrame.FunctionName,
5561
LineNumber = callStackFrame.Position.StartLineNumber,
56-
ColumnNumber = callStackFrame.Position.StartColumnNumber
62+
ColumnNumber = callStackFrame.Position.StartColumnNumber,
63+
LocalVariables = localVariables
5764
};
5865
}
5966
}
Lines changed: 59 additions & 0 deletions
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+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using Microsoft.PowerShell.EditorServices.Utility;
9+
10+
namespace Microsoft.PowerShell.EditorServices.Debugging
11+
{
12+
/// <summary>
13+
/// Container for variables that is not itself a variable per se. However given how
14+
/// VSCode uses an integer variable reference id for every node under the "Variables" tool
15+
/// window, it is useful to treat containers, typically scope containers, as a variable.
16+
/// Note that these containers are not necessarily always a scope container. Consider a
17+
/// container such as "Auto" or "My". These aren't scope related but serve as just another
18+
/// way to organize variables into a useful UI structure.
19+
/// </summary>
20+
[DebuggerDisplay("Name = {Name}, Id = {Id}, Count = {Children.Count}")]
21+
public class VariableContainerDetails : VariableDetailsBase
22+
{
23+
private readonly List<VariableDetailsBase> children;
24+
25+
/// <summary>
26+
/// Instantiates an instance of VariableScopeDetails.
27+
/// </summary>
28+
/// <param name="id">The variable reference id for this scope.</param>
29+
/// <param name="name">The name of the variable scope.</param>
30+
public VariableContainerDetails(int id, string name)
31+
{
32+
Validate.IsNotNull(name, "name");
33+
34+
this.Id = id;
35+
this.Name = name;
36+
this.IsExpandable = true;
37+
this.ValueString = " "; // An empty string isn't enough due to a temporary bug in VS Code.
38+
39+
this.children = new List<VariableDetailsBase>();
40+
}
41+
42+
/// <summary>
43+
/// Gets the collection of child variables.
44+
/// </summary>
45+
public List<VariableDetailsBase> Children
46+
{
47+
get { return this.children; }
48+
}
49+
50+
/// <summary>
51+
/// Returns the details of the variable container's children. If empty, returns an empty array.
52+
/// </summary>
53+
/// <returns></returns>
54+
public override VariableDetailsBase[] GetChildren()
55+
{
56+
return this.children.ToArray();
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)