From 31042aa95604418f25fc8b0f88fcbc0936982849 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Sun, 6 Dec 2015 21:54:22 -0700 Subject: [PATCH 1/3] Initial checkin for issue #56 - filter variables into a way to make the user's variables more front-and-center. --- .../Debugging/DebugService.cs | 103 +++++++++++++++--- .../Debugging/VariableContainerDetails.cs | 25 ++++- 2 files changed, 110 insertions(+), 18 deletions(-) diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 01301eb19..6504d71ea 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -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,13 @@ 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; + 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(localStackFrameVariableId, VariableContainerDetails.LocalScopeName), + new VariableScope(this.scriptScopeVariables.Id, VariableContainerDetails.ScriptScopeName), + new VariableScope(this.globalScopeVariables.Id, VariableContainerDetails.GlobalScopeName), }; } @@ -311,15 +312,17 @@ 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); + this.scriptScopeVariables = await FetchVariableContainer(VariableContainerDetails.ScriptScopeName); } private async Task FetchVariableContainer(string scope) @@ -328,18 +331,89 @@ private async Task FetchVariableContainer(string scope 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); + + + // Add a container node to this variable container that will "hide" the built-in, + // automatic variables that we usually aren't interested in. Do this for local and script scope. + VariableContainerDetails automaticVariableContainer = null; + if (scope != VariableContainerDetails.GlobalScopeName) + { + automaticVariableContainer = + new VariableContainerDetails(this.nextVariableId++, "Automatic Variables"); + this.variables.Add(automaticVariableContainer); + scopeVariableContainer.Children.Add(automaticVariableContainer.Name, automaticVariableContainer); + } 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); + + if ((automaticVariableContainer == null) || ShouldAlwaysDisplayVariable(psvariable, scope)) + { + scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); + } + else + { + automaticVariableContainer.Children.Add(variableDetails.Name, variableDetails); + } + } + + return scopeVariableContainer; + } + + private bool ShouldAlwaysDisplayVariable(PSVariable psvariable, string scope) + { + ScopedItemOptions constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant; + ScopedItemOptions readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly; + + if (scope == VariableContainerDetails.GlobalScopeName) + { + // We don't A) have a good way of distinguishing automatic from user created variabbles + // and B) globalScopeVariables.Children.ContainsKey() doesn't work for automatic variables + // stored in a child variable container within the globals variable container. + return true; + } + + // 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)) + { + if (this.globalScopeVariables.Children.ContainsKey(VariableDetails.DollarPrefix + psvariable.Name)) + { + return false; + } + } + + if ((psvariable.Value != null) && (psvariable.Value.GetType() == typeof(PSDebugContext))) + { + return false; } - return variableContainerDetails; + return true; } private async Task FetchStackFrames() @@ -354,7 +428,8 @@ private async Task FetchStackFrames() for (int i = 0; i < callStackFrames.Length; i++) { - VariableContainerDetails localVariables = await FetchVariableContainer(i.ToString()); + VariableContainerDetails localVariables = + await FetchVariableContainer(i.ToString()); this.stackFrameDetails[i] = StackFrameDetails.Create(callStackFrames[i], localVariables); } } diff --git a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs index 2e1b38350..6998558a6 100644 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs @@ -20,7 +20,22 @@ 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 GlobalScopeName = "Global"; + + /// + /// Provides a constant for the name of the Script scope. + /// + public const string ScriptScopeName = "Script"; + + /// + /// Provides a constant for the name of the Local scope. + /// + public const string LocalScopeName = "Local"; + + private readonly Dictionary children; /// /// Instantiates an instance of VariableScopeDetails. @@ -36,13 +51,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 +68,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; } } } From 03d38182b51959afabb0eba4f0bed73cb41325c9 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Thu, 10 Dec 2015 21:23:26 -0700 Subject: [PATCH 2/3] First pass implementation of an Auto variables node that attempts to display just the interesting variables. Starting out with the variables it appears the user has defined. --- .../Debugging/DebugService.cs | 65 +++++++++---------- .../Debugging/StackFrameDetails.cs | 7 ++ .../Debugging/VariableContainerDetails.cs | 11 +++- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 6504d71ea..400d72829 100644 --- a/src/PowerShellEditorServices/Debugging/DebugService.cs +++ b/src/PowerShellEditorServices/Debugging/DebugService.cs @@ -270,9 +270,11 @@ public StackFrameDetails[] GetStackFrames() public VariableScope[] GetVariableScopes(int stackFrameId) { int localStackFrameVariableId = this.stackFrameDetails[stackFrameId].LocalVariables.Id; + int autoVariablesId = this.stackFrameDetails[stackFrameId].AutoVariables.Id; return new VariableScope[] { + new VariableScope(autoVariablesId, VariableContainerDetails.AutoVariablesName), new VariableScope(localStackFrameVariableId, VariableContainerDetails.LocalScopeName), new VariableScope(this.scriptScopeVariables.Id, VariableContainerDetails.ScriptScopeName), new VariableScope(this.globalScopeVariables.Id, VariableContainerDetails.GlobalScopeName), @@ -321,11 +323,13 @@ private async Task FetchStackFramesAndVariables() private async Task FetchGlobalAndScriptVariables() { // Retrieve globals first as script variable retrieval needs to search globals. - this.globalScopeVariables = await FetchVariableContainer(VariableContainerDetails.GlobalScopeName); - this.scriptScopeVariables = await FetchVariableContainer(VariableContainerDetails.ScriptScopeName); + 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"); @@ -335,52 +339,38 @@ private async Task FetchVariableContainer(string scope new VariableContainerDetails(this.nextVariableId++, "Scope: " + scope); this.variables.Add(scopeVariableContainer); - - // Add a container node to this variable container that will "hide" the built-in, - // automatic variables that we usually aren't interested in. Do this for local and script scope. - VariableContainerDetails automaticVariableContainer = null; - if (scope != VariableContainerDetails.GlobalScopeName) - { - automaticVariableContainer = - new VariableContainerDetails(this.nextVariableId++, "Automatic Variables"); - this.variables.Add(automaticVariableContainer); - scopeVariableContainer.Children.Add(automaticVariableContainer.Name, automaticVariableContainer); - } - var results = await this.powerShellContext.ExecuteCommand(psCommand); foreach (PSVariable psvariable in results) { var variableDetails = new VariableDetails(psvariable) { Id = this.nextVariableId++ }; this.variables.Add(variableDetails); + scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); - if ((automaticVariableContainer == null) || ShouldAlwaysDisplayVariable(psvariable, scope)) - { - scopeVariableContainer.Children.Add(variableDetails.Name, variableDetails); - } - else + if ((autoVariables != null) && AddToAutoVariables(psvariable, scope)) { - automaticVariableContainer.Children.Add(variableDetails.Name, variableDetails); + autoVariables.Children.Add(variableDetails.Name, variableDetails); } } return scopeVariableContainer; } - private bool ShouldAlwaysDisplayVariable(PSVariable psvariable, string scope) + private bool AddToAutoVariables(PSVariable psvariable, string scope) { - ScopedItemOptions constantAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.Constant; - ScopedItemOptions readonlyAllScope = ScopedItemOptions.AllScope | ScopedItemOptions.ReadOnly; - - if (scope == VariableContainerDetails.GlobalScopeName) + if ((scope == VariableContainerDetails.GlobalScopeName) || + (scope == VariableContainerDetails.ScriptScopeName)) { - // We don't A) have a good way of distinguishing automatic from user created variabbles - // and B) globalScopeVariables.Children.ContainsKey() doesn't work for automatic variables + // 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 true; + 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.GetType().Name == "LocalVariable") { if (psvariable.Name.Equals("_")) { @@ -428,9 +418,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 27bd4caa0..1940334e7 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. /// @@ -50,6 +55,7 @@ public class StackFrameDetails /// A new instance of the StackFrameDetails class. static internal StackFrameDetails Create( CallStackFrame callStackFrame, + VariableContainerDetails autoVariables, VariableContainerDetails localVariables) { return new StackFrameDetails @@ -58,6 +64,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 6998558a6..6b98c53c9 100644 --- a/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs +++ b/src/PowerShellEditorServices/Debugging/VariableContainerDetails.cs @@ -23,18 +23,23 @@ public class VariableContainerDetails : VariableDetailsBase /// /// Provides a constant for the name of the Global scope. /// - public const string GlobalScopeName = "Global"; + public const string AutoVariablesName = "Auto"; /// - /// Provides a constant for the name of the Script scope. + /// Provides a constant for the name of the Global scope. /// - public const string ScriptScopeName = "Script"; + 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; /// From bc141fd2340a6d9872a8a985ce9d5556da108095 Mon Sep 17 00:00:00 2001 From: Keith Hill Date: Thu, 10 Dec 2015 22:25:30 -0700 Subject: [PATCH 3/3] Cleaned up some build issues in Release config and cleaned up my coding style a bit. --- .../Debugging/DebugService.cs | 12 ++++++++---- .../Debugging/StackFrameDetails.cs | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/PowerShellEditorServices/Debugging/DebugService.cs b/src/PowerShellEditorServices/Debugging/DebugService.cs index 400d72829..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 { @@ -323,8 +323,11 @@ private async Task FetchStackFramesAndVariables() private async Task FetchGlobalAndScriptVariables() { // 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); + this.globalScopeVariables = + await FetchVariableContainer(VariableContainerDetails.GlobalScopeName, null); + + this.scriptScopeVariables = + await FetchVariableContainer(VariableContainerDetails.ScriptScopeName, null); } private async Task FetchVariableContainer( @@ -392,7 +395,8 @@ private bool AddToAutoVariables(PSVariable psvariable, string scope) if (((psvariable.Options | constantAllScope) == constantAllScope) || ((psvariable.Options | readonlyAllScope) == readonlyAllScope)) { - if (this.globalScopeVariables.Children.ContainsKey(VariableDetails.DollarPrefix + psvariable.Name)) + string prefixedVariableName = VariableDetails.DollarPrefix + psvariable.Name; + if (this.globalScopeVariables.Children.ContainsKey(prefixedVariableName)) { return false; } diff --git a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs index 2512af2a4..d228a32bf 100644 --- a/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs +++ b/src/PowerShellEditorServices/Debugging/StackFrameDetails.cs @@ -50,6 +50,9 @@ 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. ///