33// Licensed under the MIT license. See LICENSE file in the project root for full license information.
44//
55
6- using Microsoft . PowerShell . EditorServices . Utility ;
76using System ;
87using System . Collections . Generic ;
98using System . Linq ;
109using System . Management . Automation ;
1110using System . Threading . Tasks ;
11+ using Microsoft . PowerShell . EditorServices . Utility ;
1212
1313namespace Microsoft . PowerShell . EditorServices
1414{
@@ -165,7 +165,6 @@ public VariableDetailsBase[] GetVariables(int variableReferenceId)
165165 if ( parentVariable . IsExpandable )
166166 {
167167 childVariables = parentVariable . GetChildren ( ) ;
168-
169168 foreach ( var child in childVariables )
170169 {
171170 // Only add child if it hasn't already been added.
@@ -270,11 +269,15 @@ public StackFrameDetails[] GetStackFrames()
270269 /// <returns>The list of VariableScope instances which describe the available variable scopes.</returns>
271270 public VariableScope [ ] GetVariableScopes ( int stackFrameId )
272271 {
272+ int localStackFrameVariableId = this . stackFrameDetails [ stackFrameId ] . LocalVariables . Id ;
273+ int autoVariablesId = this . stackFrameDetails [ stackFrameId ] . AutoVariables . Id ;
274+
273275 return new VariableScope [ ]
274276 {
275- new VariableScope ( this . stackFrameDetails [ stackFrameId ] . LocalVariables . Id , "Local" ) ,
276- new VariableScope ( this . scriptScopeVariables . Id , "Script" ) ,
277- new VariableScope ( this . globalScopeVariables . Id , "Global" ) ,
277+ new VariableScope ( autoVariablesId , VariableContainerDetails . AutoVariablesName ) ,
278+ new VariableScope ( localStackFrameVariableId , VariableContainerDetails . LocalScopeName ) ,
279+ new VariableScope ( this . scriptScopeVariables . Id , VariableContainerDetails . ScriptScopeName ) ,
280+ new VariableScope ( this . globalScopeVariables . Id , VariableContainerDetails . GlobalScopeName ) ,
278281 } ;
279282 }
280283
@@ -311,35 +314,100 @@ private async Task FetchStackFramesAndVariables()
311314 // Create a dummy variable for index 0, should never see this.
312315 this . variables . Add ( new VariableDetails ( "Dummy" , null ) ) ;
313316
317+ // Must retrieve global/script variales before stack frame variables
318+ // as we check stack frame variables against globals.
314319 await FetchGlobalAndScriptVariables ( ) ;
315320 await FetchStackFrames ( ) ;
316-
317321 }
318322
319323 private async Task FetchGlobalAndScriptVariables ( )
320324 {
321- this . scriptScopeVariables = await FetchVariableContainer ( "Script" ) ;
322- this . globalScopeVariables = await FetchVariableContainer ( "Global" ) ;
325+ // Retrieve globals first as script variable retrieval needs to search globals.
326+ this . globalScopeVariables =
327+ await FetchVariableContainer ( VariableContainerDetails . GlobalScopeName , null ) ;
328+
329+ this . scriptScopeVariables =
330+ await FetchVariableContainer ( VariableContainerDetails . ScriptScopeName , null ) ;
323331 }
324332
325- private async Task < VariableContainerDetails > FetchVariableContainer ( string scope )
333+ private async Task < VariableContainerDetails > FetchVariableContainer (
334+ string scope ,
335+ VariableContainerDetails autoVariables )
326336 {
327337 PSCommand psCommand = new PSCommand ( ) ;
328338 psCommand . AddCommand ( "Get-Variable" ) ;
329339 psCommand . AddParameter ( "Scope" , scope ) ;
330340
331- var variableContainerDetails = new VariableContainerDetails ( this . nextVariableId ++ , "Scope: " + scope ) ;
332- this . variables . Add ( variableContainerDetails ) ;
341+ var scopeVariableContainer =
342+ new VariableContainerDetails ( this . nextVariableId ++ , "Scope: " + scope ) ;
343+ this . variables . Add ( scopeVariableContainer ) ;
333344
334345 var results = await this . powerShellContext . ExecuteCommand < PSVariable > ( psCommand ) ;
335- foreach ( PSVariable variable in results )
346+ foreach ( PSVariable psvariable in results )
336347 {
337- var variableDetails = new VariableDetails ( variable ) { Id = this . nextVariableId ++ } ;
348+ var variableDetails = new VariableDetails ( psvariable ) { Id = this . nextVariableId ++ } ;
338349 this . variables . Add ( variableDetails ) ;
339- variableContainerDetails . Children . Add ( variableDetails ) ;
350+ scopeVariableContainer . Children . Add ( variableDetails . Name , variableDetails ) ;
351+
352+ if ( ( autoVariables != null ) && AddToAutoVariables ( psvariable , scope ) )
353+ {
354+ autoVariables . Children . Add ( variableDetails . Name , variableDetails ) ;
355+ }
340356 }
341357
342- return variableContainerDetails ;
358+ return scopeVariableContainer ;
359+ }
360+
361+ private bool AddToAutoVariables ( PSVariable psvariable , string scope )
362+ {
363+ if ( ( scope == VariableContainerDetails . GlobalScopeName ) ||
364+ ( scope == VariableContainerDetails . ScriptScopeName ) )
365+ {
366+ // We don't A) have a good way of distinguishing built-in from user created variables
367+ // and B) globalScopeVariables.Children.ContainsKey() doesn't work for built-in variables
368+ // stored in a child variable container within the globals variable container.
369+ return false ;
370+ }
371+
372+ var constantAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . Constant ;
373+ var readonlyAllScope = ScopedItemOptions . AllScope | ScopedItemOptions . ReadOnly ;
374+
375+ // Some local variables, if they exist, should be displayed by default
376+ if ( psvariable . GetType ( ) . Name == "LocalVariable" )
377+ {
378+ if ( psvariable . Name . Equals ( "_" ) )
379+ {
380+ return true ;
381+ }
382+ else if ( psvariable . Name . Equals ( "args" , StringComparison . OrdinalIgnoreCase ) )
383+ {
384+ var array = psvariable . Value as Array ;
385+ return array != null ? array . Length > 0 : false ;
386+ }
387+
388+ return false ;
389+ }
390+ else if ( psvariable . GetType ( ) != typeof ( PSVariable ) )
391+ {
392+ return false ;
393+ }
394+
395+ if ( ( ( psvariable . Options | constantAllScope ) == constantAllScope ) ||
396+ ( ( psvariable . Options | readonlyAllScope ) == readonlyAllScope ) )
397+ {
398+ string prefixedVariableName = VariableDetails . DollarPrefix + psvariable . Name ;
399+ if ( this . globalScopeVariables . Children . ContainsKey ( prefixedVariableName ) )
400+ {
401+ return false ;
402+ }
403+ }
404+
405+ if ( ( psvariable . Value != null ) && ( psvariable . Value . GetType ( ) == typeof ( PSDebugContext ) ) )
406+ {
407+ return false ;
408+ }
409+
410+ return true ;
343411 }
344412
345413 private async Task FetchStackFrames ( )
@@ -354,8 +422,18 @@ private async Task FetchStackFrames()
354422
355423 for ( int i = 0 ; i < callStackFrames . Length ; i ++ )
356424 {
357- VariableContainerDetails localVariables = await FetchVariableContainer ( i . ToString ( ) ) ;
358- this . stackFrameDetails [ i ] = StackFrameDetails . Create ( callStackFrames [ i ] , localVariables ) ;
425+ VariableContainerDetails autoVariables =
426+ new VariableContainerDetails (
427+ this . nextVariableId ++ ,
428+ VariableContainerDetails . AutoVariablesName ) ;
429+
430+ this . variables . Add ( autoVariables ) ;
431+
432+ VariableContainerDetails localVariables =
433+ await FetchVariableContainer ( i . ToString ( ) , autoVariables ) ;
434+
435+ this . stackFrameDetails [ i ] =
436+ StackFrameDetails . Create ( callStackFrames [ i ] , autoVariables , localVariables ) ;
359437 }
360438 }
361439
0 commit comments