diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 01301eb19..77289b01d 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -3,12 +3,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using Microsoft.PowerShell.EditorServices.Utility; using System; using System.Collections.Generic; using System.Linq; using System.Management.Automation; using System.Threading.Tasks; +using Microsoft.PowerShell.EditorServices.Utility; namespace Microsoft.PowerShell.EditorServices { @@ -165,7 +165,6 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId) if (parentVariable.IsExpandable) { childVariables = parentVariable.GetChildren(); - foreach (var child in childVariables) { // Only add child if it hasn't already been added. @@ -270,11 +269,15 @@ public StackFrameDetails[] GetStackFrames() /// The list of VariableScope instances which describe the available variable scopes. public VariableScope[] GetVariableScopes(int stackFrameId) { + int localStackFrameVariableId = this.stackFrameDetails[stackFrameId].LocalVariables.Id; + int autoVariablesId = this.stackFrameDetails[stackFrameId].AutoVariables.Id; + return new VariableScope[] { - new VariableScope(this.stackFrameDetails[stackFrameId].LocalVariables.Id, "Local"), - new VariableScope(this.scriptScopeVariables.Id, "Script"), - new VariableScope(this.globalScopeVariables.Id, "Global"), + new VariableScope(autoVariablesId, VariableContainerDetails.AutoVariablesName), + new VariableScope(localStackFrameVariableId, VariableContainerDetails.LocalScopeName), + new VariableScope(this.scriptScopeVariables.Id, VariableContainerDetails.ScriptScopeName), + new VariableScope(this.globalScopeVariables.Id, VariableContainerDetails.GlobalScopeName), }; } @@ -311,35 +314,100 @@ private async Task FetchStackFramesAndVariables() // Create a dummy variable for index 0, should never see this. this.variables.Add(new VariableDetails("Dummy", null)); + // Must retrieve global/script variales before stack frame variables + // as we check stack frame variables against globals. await FetchGlobalAndScriptVariables(); await FetchStackFrames(); - } private async Task FetchGlobalAndScriptVariables() { - this.scriptScopeVariables = await FetchVariableContainer("Script"); - this.globalScopeVariables = await FetchVariableContainer("Global"); + // Retrieve globals first as script variable retrieval needs to search globals. + this.globalScopeVariables = + await FetchVariableContainer(VariableContainerDetails.GlobalScopeName, null); + + this.scriptScopeVariables = + await FetchVariableContainer(VariableContainerDetails.ScriptScopeName, null); } - private async Task FetchVariableContainer(string scope) + private async Task FetchVariableContainer( + string scope, + VariableContainerDetails autoVariables) { PSCommand psCommand = new PSCommand(); psCommand.AddCommand("Get-Variable"); psCommand.AddParameter("Scope", scope); - var variableContainerDetails = new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); - this.variables.Add(variableContainerDetails); + var scopeVariableContainer = + new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); + this.variables.Add(scopeVariableContainer); var results = await this.powerShellContext.ExecuteCommand(psCommand); - foreach (PSVariable variable in results) + foreach (PSVariable psvariable in results) { - var variableDetails = new VariableDetails(variable) { Id = this.nextVariableId++ }; + var variableDetails = new VariableDetails(psvariable) { Id = this.nextVariableId++ }; this.variables.Add(variableDetails); - variableContainerDetails.Children.Add(variableDetails); + scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); + + if ((autoVariables != null) && AddToAutoVariables(psvariable, scope)) + { + autoVariables.Children.Add(variableDetails.Name, variableDetails); + } } - return variableContainerDetails; + return scopeVariableContainer; + } + + private bool AddToAutoVariables(PSVariable psvariable, string scope) + { + if ((scope == VariableContainerDetails.GlobalScopeName) || + (scope == VariableContainerDetails.ScriptScopeName)) + { + // We don't A) have a good way of distinguishing built-in from user created variables + // and B) globalScopeVariables.Children.ContainsKey() doesn't work for built-in variables + // stored in a child variable container within the globals variable container. + return false; + } + + var constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant; + var readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly; + + // Some local variables, if they exist, should be displayed by default + if (psvariable.GetType().Name == "LocalVariable") + { + if (psvariable.Name.Equals("_")) + { + return true; + } + else if (psvariable.Name.Equals("args", StringComparison.OrdinalIgnoreCase)) + { + var array = psvariable.Value as Array; + return array != null ? array.Length > 0 : false; + } + + return false; + } + else if (psvariable.GetType() != typeof(PSVariable)) + { + return false; + } + + if (((psvariable.Options | constantAllScope) == constantAllScope) || + ((psvariable.Options | readonlyAllScope) == readonlyAllScope)) + { + string prefixedVariableName = VariableDetails.DollarPrefix + psvariable.Name; + if (this.globalScopeVariables.Children.ContainsKey(prefixedVariableName)) + { + return false; + } + } + + if ((psvariable.Value != null) && (psvariable.Value.GetType() == typeof(PSDebugContext))) + { + return false; + } + + return true; } private async Task FetchStackFrames() @@ -354,8 +422,18 @@ private async Task FetchStackFrames() for (int i = 0; i < callStackFrames.Length; i++) { - VariableContainerDetails localVariables = await FetchVariableContainer(i.ToString()); - this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], localVariables); + VariableContainerDetails autoVariables = + new VariableContainerDetails( + this.nextVariableId++, + VariableContainerDetails.AutoVariablesName); + + this.variables.Add(autoVariables); + + VariableContainerDetails localVariables = + await FetchVariableContainer(i.ToString(), autoVariables); + + this.stackFrameDetails[i] = + StackFrameDetails.Create(callStackFrames[i], autoVariables, localVariables); } } diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs index 3bacc0ac5..d228a32bf 100644 --- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs +++ b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs @@ -33,6 +33,11 @@ public class StackFrameDetails /// public int ColumnNumber { get; private set; } + /// + /// Gets or sets the VariableContainerDetails that contains the auto variables. + /// + public VariableContainerDetails AutoVariables { get; private set; } + /// /// Gets or sets the VariableContainerDetails that contains the local variables. /// @@ -45,12 +50,16 @@ public class StackFrameDetails /// /// The original CallStackFrame instance from which details will be obtained. /// + /// + /// A variable container with all the filtered, auto variables for this stack frame. + /// /// /// A variable container with all the local variables for this stack frame. /// /// A new instance of the StackFrameDetails class. static internal StackFrameDetails Create( CallStackFrame callStackFrame, + VariableContainerDetails autoVariables, VariableContainerDetails localVariables) { return new StackFrameDetails @@ -59,6 +68,7 @@ static internal StackFrameDetails Create( FunctionName = callStackFrame.FunctionName, LineNumber = callStackFrame.Position.StartLineNumber, ColumnNumber = callStackFrame.Position.StartColumnNumber, + AutoVariables = autoVariables, LocalVariables = localVariables }; } diff --git a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs index 2e1b38350..6b98c53c9 100644 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs @@ -20,7 +20,27 @@ namespace Microsoft.PowerShell.EditorServices [DebuggerDisplay("Name = {Name}, Id = {Id}, Count = {Children.Count}")] public class VariableContainerDetails : VariableDetailsBase { - private readonly List children; + /// + /// Provides a constant for the name of the Global scope. + /// + public const string AutoVariablesName = "Auto"; + + /// + /// Provides a constant for the name of the Global scope. + /// + public const string GlobalScopeName = "Global"; + + /// + /// Provides a constant for the name of the Local scope. + /// + public const string LocalScopeName = "Local"; + + /// + /// Provides a constant for the name of the Script scope. + /// + public const string ScriptScopeName = "Script"; + + private readonly Dictionary children; /// /// Instantiates an instance of VariableScopeDetails. @@ -36,13 +56,13 @@ public VariableContainerDetails(int id, string name) this.IsExpandable = true; this.ValueString = " "; // An empty string isn't enough due to a temporary bug in VS Code. - this.children = new List(); + this.children = new Dictionary(); } /// /// Gets the collection of child variables. /// - public List Children + public IDictionary Children { get { return this.children; } } @@ -53,7 +73,9 @@ public List Children /// public override VariableDetailsBase[] GetChildren() { - return this.children.ToArray(); + var variablesArray = new VariableDetailsBase[this.children.Count]; + this.children.Values.CopyTo(variablesArray, 0); + return variablesArray; } } }