Skip to content

Add support for more variable scopes in debugger #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/PowerShellEditorServices.Host/DebugAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ protected async Task HandleStackTraceRequest(
newStackFrames.Add(
StackFrame.Create(
stackFrames[i],
i + 1));
i));
}

await requestContext.SendResult(
Expand Down Expand Up @@ -310,7 +310,7 @@ protected async Task HandleVariablesRequest(
EditorSession editorSession,
RequestContext<VariablesResponseBody, object> requestContext)
{
VariableDetails[] variables =
VariableDetailsBase[] variables =
editorSession.DebugService.GetVariables(
variablesParams.VariablesReference);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Diagnostics;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;

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

[DebuggerDisplay("FrameId = {FrameId}")]
public class ScopesRequestArguments
{
public int FrameId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Diagnostics;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;

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

[DebuggerDisplay("ThreadId = {ThreadId}, Levels = {Levels}")]
public class StackTraceRequestArguments
{
public int ThreadId { get; private set; }
Expand All @@ -27,4 +29,3 @@ public class StackTraceResponseBody
public StackFrame[] StackFrames { get; set; }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Variable
// /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */
public int VariablesReference { get; set; }

public static Variable Create(VariableDetails variable)
public static Variable Create(VariableDetailsBase variable)
{
return new Variable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Diagnostics;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;

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

[DebuggerDisplay("VariablesReference = {VariablesReference}")]
public class VariablesRequestArguments
{
public int VariablesReference { get; set; }
Expand Down
108 changes: 61 additions & 47 deletions src/PowerShellEditorServices/Debugging/DebugService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ public class DebugService
new Dictionary<string, List<Breakpoint>>();

private int nextVariableId;
private List<VariableDetails> currentVariables;
private StackFrameDetails[] callStackFrames;
private List<VariableDetailsBase> variables;
private VariableContainerDetails globalScopeVariables;
private VariableContainerDetails scriptScopeVariables;
private StackFrameDetails[] stackFrameDetails;

#endregion

Expand Down Expand Up @@ -155,38 +157,28 @@ public void Abort()
/// </summary>
/// <param name="variableReferenceId"></param>
/// <returns>An array of VariableDetails instances which describe the requested variables.</returns>
public VariableDetails[] GetVariables(int variableReferenceId)
public VariableDetailsBase[] GetVariables(int variableReferenceId)
{
VariableDetails[] childVariables = null;
VariableDetailsBase[] childVariables;

if (variableReferenceId >= VariableDetails.FirstVariableId)
VariableDetailsBase parentVariable = this.variables[variableReferenceId];
if (parentVariable.IsExpandable)
{
int correctedId =
(variableReferenceId - VariableDetails.FirstVariableId);
childVariables = parentVariable.GetChildren();

VariableDetails parentVariable =
this.currentVariables[correctedId];

if (parentVariable.IsExpandable)
foreach (var child in childVariables)
{
childVariables = parentVariable.GetChildren();

foreach (var child in childVariables)
// Only add child if it hasn't already been added.
if (child.Id < 0)
{
this.currentVariables.Add(child);
child.Id = this.nextVariableId;
this.nextVariableId++;
child.Id = this.nextVariableId++;
this.variables.Add(child);
}
}
else
{
childVariables = new VariableDetails[0];
}
}
else
{
// TODO: Get variables for the desired scope ID
childVariables = this.currentVariables.ToArray();
childVariables = new VariableDetailsBase[0];
}

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

VariableDetails resolvedVariable = null;
IEnumerable<VariableDetails> variableList = this.currentVariables;
VariableDetailsBase resolvedVariable = null;
IEnumerable<VariableDetailsBase> variableList = this.variables;

foreach (var variableName in variablePathParts)
{
Expand Down Expand Up @@ -267,7 +259,7 @@ await this.powerShellContext.ExecuteScriptString(
/// </returns>
public StackFrameDetails[] GetStackFrames()
{
return this.callStackFrames;
return this.stackFrameDetails;
}

/// <summary>
Expand All @@ -278,10 +270,11 @@ public StackFrameDetails[] GetStackFrames()
/// <returns>The list of VariableScope instances which describe the available variable scopes.</returns>
public VariableScope[] GetVariableScopes(int stackFrameId)
{
// TODO: Return different scopes based on PowerShell scoping mechanics
return new VariableScope[]
{
new VariableScope(1, "Locals")
new VariableScope(this.stackFrameDetails[stackFrameId].LocalVariables.Id, "Local"),
new VariableScope(this.scriptScopeVariables.Id, "Script"),
new VariableScope(this.globalScopeVariables.Id, "Global"),
};
}

Expand Down Expand Up @@ -310,25 +303,43 @@ private async Task ClearBreakpointsInFile(ScriptFile scriptFile)
}
}

private async Task FetchVariables()
private async Task FetchStackFramesAndVariables()
{
this.nextVariableId = VariableDetails.FirstVariableId;
this.currentVariables = new List<VariableDetails>();
this.nextVariableId = VariableDetailsBase.FirstVariableId;
this.variables = new List<VariableDetailsBase>();

// Create a dummy variable for index 0, should never see this.
this.variables.Add(new VariableDetails("Dummy", null));

await FetchGlobalAndScriptVariables();
await FetchStackFrames();

}

private async Task FetchGlobalAndScriptVariables()
{
this.scriptScopeVariables = await FetchVariableContainer("Script");
this.globalScopeVariables = await FetchVariableContainer("Global");
}

private async Task<VariableContainerDetails> FetchVariableContainer(string scope)
{
PSCommand psCommand = new PSCommand();
psCommand.AddCommand("Get-Variable");
psCommand.AddParameter("Scope", "Local");
psCommand.AddParameter("Scope", scope);

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

foreach (var variable in results)
var results = await this.powerShellContext.ExecuteCommand<PSVariable>(psCommand);
foreach (PSVariable variable in results)
{
var details = new VariableDetails(variable);
details.Id = this.nextVariableId;
this.currentVariables.Add(details);

this.nextVariableId++;
var variableDetails = new VariableDetails(variable) { Id = this.nextVariableId++ };
this.variables.Add(variableDetails);
variableContainerDetails.Children.Add(variableDetails);
}

return variableContainerDetails;
}

private async Task FetchStackFrames()
Expand All @@ -338,10 +349,14 @@ private async Task FetchStackFrames()

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

this.callStackFrames =
results
.Select(StackFrameDetails.Create)
.ToArray();
var callStackFrames = results.ToArray();
this.stackFrameDetails = new StackFrameDetails[callStackFrames.Length];

for (int i = 0; i < callStackFrames.Length; i++)
{
VariableContainerDetails localVariables = await FetchVariableContainer(i.ToString());
this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], localVariables);
}
}

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

private async void OnDebuggerStop(object sender, DebuggerStopEventArgs e)
{
// Get the call stack and local variables
await this.FetchStackFrames();
await this.FetchVariables();
// Get call stack and variables.
await this.FetchStackFramesAndVariables();

// Notify the host that the debugger is stopped
if (this.DebuggerStopped != null)
Expand Down
17 changes: 11 additions & 6 deletions src/PowerShellEditorServices/Debugging/StackFrameDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Generic;
using System.Management.Automation;

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

/// <summary>
/// Gets or sets the VariableContainerDetails that contains the local variables.
/// </summary>
public VariableContainerDetails LocalVariables { get; private set; }

/// <summary>
/// Creates an instance of the StackFrameDetails class from a
/// CallStackFrame instance provided by the PowerShell engine.
/// </summary>
/// <param name="callStackFrame">
/// The original CallStackFrame instance from which details will be obtained.
/// </param>
/// <param name="localVariables">
/// A variable container with all the local variables for this stack frame.
/// <returns>A new instance of the StackFrameDetails class.</returns>
static internal StackFrameDetails Create(
CallStackFrame callStackFrame)
CallStackFrame callStackFrame,
VariableContainerDetails localVariables)
{
Dictionary<string, PSVariable> localVariables =
callStackFrame.GetFrameVariables();

return new StackFrameDetails
{
ScriptPath = callStackFrame.ScriptName,
FunctionName = callStackFrame.FunctionName,
LineNumber = callStackFrame.Position.StartLineNumber,
ColumnNumber = callStackFrame.Position.StartColumnNumber
ColumnNumber = callStackFrame.Position.StartColumnNumber,
LocalVariables = localVariables
};
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.PowerShell.EditorServices.Utility;

namespace Microsoft.PowerShell.EditorServices
{
/// <summary>
/// Container for variables that is not itself a variable per se. However given how
/// VSCode uses an integer variable reference id for every node under the "Variables" tool
/// window, it is useful to treat containers, typically scope containers, as a variable.
/// Note that these containers are not necessarily always a scope container. Consider a
/// container such as "Auto" or "My". These aren't scope related but serve as just another
/// way to organize variables into a useful UI structure.
/// </summary>
[DebuggerDisplay("Name = {Name}, Id = {Id}, Count = {Children.Count}")]
public class VariableContainerDetails : VariableDetailsBase
{
private readonly List<VariableDetailsBase> children;

/// <summary>
/// Instantiates an instance of VariableScopeDetails.
/// </summary>
/// <param name="id">The variable reference id for this scope.</param>
/// <param name="name">The name of the variable scope.</param>
public VariableContainerDetails(int id, string name)
{
Validate.IsNotNull(name, "name");

this.Id = id;
this.Name = name;
this.IsExpandable = true;
this.ValueString = " "; // An empty string isn't enough due to a temporary bug in VS Code.

this.children = new List<VariableDetailsBase>();
}

/// <summary>
/// Gets the collection of child variables.
/// </summary>
public List<VariableDetailsBase> Children
{
get { return this.children; }
}

/// <summary>
/// Returns the details of the variable container's children. If empty, returns an empty array.
/// </summary>
/// <returns></returns>
public override VariableDetailsBase[] GetChildren()
{
return this.children.ToArray();
}
}
}
Loading