Skip to content

Commit be7f864

Browse files
committed
Merge pull request #55 from PowerShell/rkeithhill/variable-scope
Add support for more variable scopes in debugger
2 parents b83a7a8 + 8f2742e commit be7f864

File tree

12 files changed

+203
-106
lines changed

12 files changed

+203
-106
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: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ public class DebugService
2727
new Dictionary<string, List<Breakpoint>>();
2828

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

3335
#endregion
3436

@@ -155,38 +157,28 @@ public void Abort()
155157
/// </summary>
156158
/// <param name="variableReferenceId"></param>
157159
/// <returns>An array of VariableDetails instances which describe the requested variables.</returns>
158-
public VariableDetails[] GetVariables(int variableReferenceId)
160+
public VariableDetailsBase[] GetVariables(int variableReferenceId)
159161
{
160-
VariableDetails[] childVariables = null;
162+
VariableDetailsBase[] childVariables;
161163

162-
if (variableReferenceId >= VariableDetails.FirstVariableId)
164+
VariableDetailsBase parentVariable = this.variables[variableReferenceId];
165+
if (parentVariable.IsExpandable)
163166
{
164-
int correctedId =
165-
(variableReferenceId - VariableDetails.FirstVariableId);
167+
childVariables = parentVariable.GetChildren();
166168

167-
VariableDetails parentVariable =
168-
this.currentVariables[correctedId];
169-
170-
if (parentVariable.IsExpandable)
169+
foreach (var child in childVariables)
171170
{
172-
childVariables = parentVariable.GetChildren();
173-
174-
foreach (var child in childVariables)
171+
// Only add child if it hasn't already been added.
172+
if (child.Id < 0)
175173
{
176-
this.currentVariables.Add(child);
177-
child.Id = this.nextVariableId;
178-
this.nextVariableId++;
174+
child.Id = this.nextVariableId++;
175+
this.variables.Add(child);
179176
}
180177
}
181-
else
182-
{
183-
childVariables = new VariableDetails[0];
184-
}
185178
}
186179
else
187180
{
188-
// TODO: Get variables for the desired scope ID
189-
childVariables = this.currentVariables.ToArray();
181+
childVariables = new VariableDetailsBase[0];
190182
}
191183

192184
return childVariables;
@@ -200,13 +192,13 @@ public VariableDetails[] GetVariables(int variableReferenceId)
200192
/// <param name="variableExpression">The variable expression string to evaluate.</param>
201193
/// <param name="stackFrameId">The ID of the stack frame in which the expression should be evaluated.</param>
202194
/// <returns>A VariableDetails object containing the result.</returns>
203-
public VariableDetails GetVariableFromExpression(string variableExpression, int stackFrameId)
195+
public VariableDetailsBase GetVariableFromExpression(string variableExpression, int stackFrameId)
204196
{
205197
// Break up the variable path
206198
string[] variablePathParts = variableExpression.Split('.');
207199

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

211203
foreach (var variableName in variablePathParts)
212204
{
@@ -267,7 +259,7 @@ await this.powerShellContext.ExecuteScriptString(
267259
/// </returns>
268260
public StackFrameDetails[] GetStackFrames()
269261
{
270-
return this.callStackFrames;
262+
return this.stackFrameDetails;
271263
}
272264

273265
/// <summary>
@@ -278,10 +270,11 @@ public StackFrameDetails[] GetStackFrames()
278270
/// <returns>The list of VariableScope instances which describe the available variable scopes.</returns>
279271
public VariableScope[] GetVariableScopes(int stackFrameId)
280272
{
281-
// TODO: Return different scopes based on PowerShell scoping mechanics
282273
return new VariableScope[]
283274
{
284-
new VariableScope(1, "Locals")
275+
new VariableScope(this.stackFrameDetails[stackFrameId].LocalVariables.Id, "Local"),
276+
new VariableScope(this.scriptScopeVariables.Id, "Script"),
277+
new VariableScope(this.globalScopeVariables.Id, "Global"),
285278
};
286279
}
287280

@@ -310,25 +303,43 @@ private async Task ClearBreakpointsInFile(ScriptFile scriptFile)
310303
}
311304
}
312305

313-
private async Task FetchVariables()
306+
private async Task FetchStackFramesAndVariables()
314307
{
315-
this.nextVariableId = VariableDetails.FirstVariableId;
316-
this.currentVariables = new List<VariableDetails>();
308+
this.nextVariableId = VariableDetailsBase.FirstVariableId;
309+
this.variables = new List<VariableDetailsBase>();
310+
311+
// Create a dummy variable for index 0, should never see this.
312+
this.variables.Add(new VariableDetails("Dummy", null));
317313

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

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

324-
foreach (var variable in results)
334+
var results = await this.powerShellContext.ExecuteCommand<PSVariable>(psCommand);
335+
foreach (PSVariable variable in results)
325336
{
326-
var details = new VariableDetails(variable);
327-
details.Id = this.nextVariableId;
328-
this.currentVariables.Add(details);
329-
330-
this.nextVariableId++;
337+
var variableDetails = new VariableDetails(variable) { Id = this.nextVariableId++ };
338+
this.variables.Add(variableDetails);
339+
variableContainerDetails.Children.Add(variableDetails);
331340
}
341+
342+
return variableContainerDetails;
332343
}
333344

334345
private async Task FetchStackFrames()
@@ -338,10 +349,14 @@ private async Task FetchStackFrames()
338349

339350
var results = await this.powerShellContext.ExecuteCommand<CallStackFrame>(psCommand);
340351

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

347362
#endregion
@@ -355,9 +370,8 @@ private async Task FetchStackFrames()
355370

356371
private async void OnDebuggerStop(object sender, DebuggerStopEventArgs e)
357372
{
358-
// Get the call stack and local variables
359-
await this.FetchStackFrames();
360-
await this.FetchVariables();
373+
// Get call stack and variables.
374+
await this.FetchStackFramesAndVariables();
361375

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

src/PowerShellEditorServices/Debugging/StackFrameDetails.cs

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

6-
using System.Collections.Generic;
76
using System.Management.Automation;
87

98
namespace Microsoft.PowerShell.EditorServices
@@ -34,26 +33,32 @@ public class StackFrameDetails
3433
/// </summary>
3534
public int ColumnNumber { get; private set; }
3635

36+
/// <summary>
37+
/// Gets or sets the VariableContainerDetails that contains the local variables.
38+
/// </summary>
39+
public VariableContainerDetails LocalVariables { get; private set; }
40+
3741
/// <summary>
3842
/// Creates an instance of the StackFrameDetails class from a
3943
/// CallStackFrame instance provided by the PowerShell engine.
4044
/// </summary>
4145
/// <param name="callStackFrame">
4246
/// The original CallStackFrame instance from which details will be obtained.
4347
/// </param>
48+
/// <param name="localVariables">
49+
/// A variable container with all the local variables for this stack frame.
4450
/// <returns>A new instance of the StackFrameDetails class.</returns>
4551
static internal StackFrameDetails Create(
46-
CallStackFrame callStackFrame)
52+
CallStackFrame callStackFrame,
53+
VariableContainerDetails localVariables)
4754
{
48-
Dictionary<string, PSVariable> localVariables =
49-
callStackFrame.GetFrameVariables();
50-
5155
return new StackFrameDetails
5256
{
5357
ScriptPath = callStackFrame.ScriptName,
5458
FunctionName = callStackFrame.FunctionName,
5559
LineNumber = callStackFrame.Position.StartLineNumber,
56-
ColumnNumber = callStackFrame.Position.StartColumnNumber
60+
ColumnNumber = callStackFrame.Position.StartColumnNumber,
61+
LocalVariables = localVariables
5762
};
5863
}
5964
}
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
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)