Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Display IEnumerables and IDictionaries in debugger prettily (with "Raw View" available) #1634

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
andyleejordan merged 14 commits into master from JustinGrote/issue1633
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
0edeed4
Feature: Add "Raw View" to IEnumerables and IDictionaries
JustinGrote Nov 23, 2021
1447e94
Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/V...
JustinGrote Nov 24, 2021
f9612ce
Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/V...
JustinGrote Nov 24, 2021
2f8fee8
Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/V...
JustinGrote Nov 24, 2021
7824c51
Update src/PowerShellEditorServices/Services/DebugAdapter/Debugging/V...
JustinGrote Nov 24, 2021
cfda423
Change VariableObject to property from field since it is now derived
JustinGrote Nov 24, 2021
a92d9d7
Implement Suggestion https://github.com/PowerShell/PowerShellEditorSe...
JustinGrote Nov 24, 2021
7df4dd5
🧪 Initial Test Attempt
JustinGrote Jan 23, 2022
049cb97
fixup! 🧪 Initial Test Attempt
JustinGrote Jan 24, 2022
0a99905
fixup! 🧪 Initial Test Attempt
JustinGrote Jan 24, 2022
37518cb
Simplify Raw View Presentation and more tests
JustinGrote Jan 24, 2022
ee20562
Add Dictionary Test and Consolidate VariableTest File
JustinGrote Jan 24, 2022
7e02666
Add Derived Dictionary Test
JustinGrote Jan 24, 2022
719ad9a
Simplify tests and cleanup
andyleejordan Jan 24, 2022
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
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ internal class VariableDetails : VariableDetailsBase
/// Provides a constant for the dollar sign variable prefix string.
/// </summary>
public const string DollarPrefix = "$";

private object valueObject;
protected object ValueObject { get; }
private VariableDetails[] cachedChildren;

#endregion
Expand Down Expand Up @@ -81,7 +80,7 @@ public VariableDetails(PSPropertyInfo psProperty)
/// <param name="value">The variable's value.</param>
public VariableDetails(string name, object value)
{
this.valueObject = value;
this.ValueObject = value;

this.Id = -1; // Not been assigned a variable reference id yet
this.Name = name;
Expand Down Expand Up @@ -109,7 +108,7 @@ public override VariableDetailsBase[] GetChildren(ILogger logger)
{
if (this.cachedChildren == null)
{
this.cachedChildren = GetChildren(this.valueObject, logger);
this.cachedChildren = GetChildren(this.ValueObject, logger);
}

return this.cachedChildren;
Expand Down Expand Up @@ -175,19 +174,18 @@ private static string GetValueStringAndType(object value, bool isExpandable, out
if (value is bool)
{
// Set to identifier recognized by PowerShell to make setVariable from the debug UI more natural.
valueString = (bool)value ? "$true" : "$false";
valueString = (bool)value ? "$true" : "$false";

// We need to use this "magic value" to highlight in vscode properly
// These "magic values" are analagous to TypeScript and are visible in VSCode here:
// https://github.com/microsoft/vscode/blob/57ca9b99d5b6a59f2d2e0f082ae186559f45f1d8/src/vs/workbench/contrib/debug/browser/baseDebugView.ts#L68-L78
// NOTE: we don't do numbers and strings since they (so far) seem to get detected properly by
//serialization, and the original .NET type can be preserved so it shows up in the variable name
//type hover as the original .NET type.
// NOTE: we don't do numbers and strings since they (so far) seem to get detected properly by
//serialization, and the original .NET type can be preserved so it shows up in the variable name
//type hover as the original .NET type.
typeName = "boolean";
}
else if (isExpandable)
{

// Get the "value" for an expandable object.
if (value is DictionaryEntry)
{
Expand Down Expand Up @@ -367,12 +365,19 @@ private VariableDetails[] GetChildren(object obj, ILogger logger)
return childVariables.ToArray();
}

private static void AddDotNetProperties(object obj, List<VariableDetails> childVariables)
protected static void AddDotNetProperties(object obj, List<VariableDetails> childVariables, bool noRawView = false)
{
Type objectType = obj.GetType();
var properties =
objectType.GetProperties(
BindingFlags.Public | BindingFlags.Instance);

// For certain array or dictionary types, we want to hide additional properties under a "raw view" header
// to reduce noise. This is inspired by the C# vscode extension.
if (!noRawView && obj is IEnumerable)
{
childVariables.Add(new VariableDetailsRawView(obj));
return;
}

var properties = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (var property in properties)
{
Expand Down Expand Up @@ -424,4 +429,25 @@ public override string ToString()
}
}
}

/// <summary>
/// A VariableDetails that only returns the raw view properties of the object, rather than its values.
/// </summary>
internal sealed class VariableDetailsRawView : VariableDetails
{
private const string RawViewName = "Raw View";

public VariableDetailsRawView(object value) : base(RawViewName, value)
{
this.ValueString = "";
this.Type = "";
}

public override VariableDetailsBase[] GetChildren(ILogger logger)
{
List<VariableDetails> childVariables = new();
AddDotNetProperties(ValueObject, childVariables, noRawView: true);
return childVariables.ToArray();
}
}
}
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,30 @@ function Test-Variables {
Test-Variables
# NOTE: If a line is added to the function above, the line numbers in the
# associated unit tests MUST be adjusted accordingly.

$SCRIPT:simpleArray = @(
1
2
'red'
'blue'
)

# This is a dummy function that the test will use to stop and evaluate the debug environment
function __BreakDebuggerEnumerableShowsRawView{}; __BreakDebuggerEnumerableShowsRawView

$SCRIPT:simpleDictionary = @{
item1 = 1
item2 = 2
item3 = 'red'
item4 = 'blue'
}
function __BreakDebuggerDictionaryShowsRawView{}; __BreakDebuggerDictionaryShowsRawView

$SCRIPT:sortedDictionary = [Collections.Generic.SortedDictionary[string, object]]::new()
$sortedDictionary[1] = 1
$sortedDictionary[2] = 2
$sortedDictionary['red'] = 'red'
$sortedDictionary['blue'] = 'red'

# This is a dummy function that the test will use to stop and evaluate the debug environment
function __BreakDebuggerDerivedDictionaryPropertyInRawView{}; __BreakDebuggerDerivedDictionaryPropertyInRawView
122 changes: 116 additions & 6 deletions test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ private void AssertDebuggerPaused()

private void AssertDebuggerStopped(
string scriptPath = "",
int lineNumber = -1)
int lineNumber = -1,
CommandBreakpointDetails commandBreakpointDetails = default)
{
var eventArgs = debuggerStoppedQueue.Take(new CancellationTokenSource(5000).Token);

Expand All @@ -132,6 +133,11 @@ private void AssertDebuggerStopped(
{
Assert.Equal(lineNumber, eventArgs.LineNumber);
}

if (commandBreakpointDetails is not null)
{
Assert.Equal(commandBreakpointDetails.Name, eventArgs.OriginalEvent.InvocationInfo.MyCommand.Name);
}
}

private Task<IReadOnlyList<LineBreakpoint>> GetConfirmedBreakpoints(ScriptFile scriptFile)
Expand Down Expand Up @@ -210,7 +216,8 @@ public async Task DebuggerAcceptsScriptArgs()
Assert.True(var.IsExpandable);

var childVars = debugService.GetVariables(var.Id);
Assert.Equal(9, childVars.Length);
// 2 variables plus "Raw View"
Assert.Equal(3, childVars.Length);
Assert.Equal("\"Bar\"", childVars[0].ValueString);
Assert.Equal("\"Baz\"", childVars[1].ValueString);

Expand All @@ -227,7 +234,7 @@ public async Task DebuggerAcceptsScriptArgs()
Assert.True(var.IsExpandable);

childVars = debugService.GetVariables(var.Id);
Assert.Equal(8, childVars.Length);
Assert.Equal(2, childVars.Length);
Assert.Equal("\"Extra1\"", childVars[0].ValueString);
}

Expand Down Expand Up @@ -532,14 +539,15 @@ await debugService.SetLineBreakpointsAsync(
Assert.True(objVar.IsExpandable);

var objChildren = debugService.GetVariables(objVar.Id);
Assert.Equal(9, objChildren.Length);
// Two variables plus "Raw View"
Assert.Equal(3, objChildren.Length);

var arrVar = Array.Find(variables, v => v.Name == "$arrVar");
Assert.NotNull(arrVar);
Assert.True(arrVar.IsExpandable);

var arrChildren = debugService.GetVariables(arrVar.Id);
Assert.Equal(11, arrChildren.Length);
Assert.Equal(5, arrChildren.Length);

var classVar = Array.Find(variables, v => v.Name == "$classVar");
Assert.NotNull(classVar);
Expand Down Expand Up @@ -709,7 +717,8 @@ await debugService.SetLineBreakpointsAsync(
Assert.True(var.IsExpandable);

VariableDetailsBase[] childVars = debugService.GetVariables(var.Id);
Assert.Equal(9, childVars.Length);
// 2 variables plus "Raw View"
Assert.Equal(3, childVars.Length);
Assert.Equal("[0]", childVars[0].Name);
Assert.Equal("[1]", childVars[1].Name);

Expand Down Expand Up @@ -772,6 +781,107 @@ await debugService.SetLineBreakpointsAsync(
Assert.Equal("\"John\"", childVars["Name"]);
}

[Fact]
public async Task DebuggerEnumerableShowsRawView()
{
CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerEnumerableShowsRawView");
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase simpleArrayVar = Array.Find(
GetVariables(VariableContainerDetails.ScriptScopeName),
v => v.Name == "$simpleArray");
Assert.NotNull(simpleArrayVar);
VariableDetailsBase rawDetailsView = Array.Find(
simpleArrayVar.GetChildren(NullLogger.Instance),
v => v.Name == "Raw View");
Assert.NotNull(rawDetailsView);
Assert.Empty(rawDetailsView.Type);
Assert.Empty(rawDetailsView.ValueString);
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
Assert.Equal(7, rawViewChildren.Length);
Assert.Equal("Length", rawViewChildren[0].Name);
Assert.Equal("4", rawViewChildren[0].ValueString);
Assert.Equal("LongLength", rawViewChildren[1].Name);
Assert.Equal("4", rawViewChildren[1].ValueString);
Assert.Equal("Rank", rawViewChildren[2].Name);
Assert.Equal("1", rawViewChildren[2].ValueString);
Assert.Equal("SyncRoot", rawViewChildren[3].Name);
Assert.Equal("IsReadOnly", rawViewChildren[4].Name);
Assert.Equal("$false", rawViewChildren[4].ValueString);
Assert.Equal("IsFixedSize", rawViewChildren[5].Name);
Assert.Equal("$true", rawViewChildren[5].ValueString);
Assert.Equal("IsSynchronized", rawViewChildren[6].Name);
Assert.Equal("$false", rawViewChildren[6].ValueString);
}

[Fact]
public async Task DebuggerDictionaryShowsRawView()
{
CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerDictionaryShowsRawView");
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase simpleDictionaryVar = Array.Find(
GetVariables(VariableContainerDetails.ScriptScopeName),
v => v.Name == "$simpleDictionary");
Assert.NotNull(simpleDictionaryVar);
VariableDetailsBase rawDetailsView = Array.Find(
simpleDictionaryVar.GetChildren(NullLogger.Instance),
v => v.Name == "Raw View");
Assert.NotNull(rawDetailsView);
Assert.Empty(rawDetailsView.Type);
Assert.Empty(rawDetailsView.ValueString);
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
Assert.Equal(7, rawViewChildren.Length);
Assert.Equal("IsReadOnly", rawViewChildren[0].Name);
Assert.Equal("$false", rawViewChildren[0].ValueString);
Assert.Equal("IsFixedSize", rawViewChildren[1].Name);
Assert.Equal("$false", rawViewChildren[1].ValueString);
Assert.Equal("IsSynchronized", rawViewChildren[2].Name);
Assert.Equal("$false", rawViewChildren[2].ValueString);
Assert.Equal("Keys", rawViewChildren[3].Name);
Assert.Equal("Values", rawViewChildren[4].Name);
Assert.Equal("[ValueCollection: 4]", rawViewChildren[4].ValueString);
Assert.Equal("SyncRoot", rawViewChildren[5].Name);
Assert.Equal("Count", rawViewChildren[6].Name);
Assert.Equal("4", rawViewChildren[6].ValueString);
}

[Fact]
public async Task DebuggerDerivedDictionaryPropertyInRawView()
{
CommandBreakpointDetails breakpoint = CommandBreakpointDetails.Create("__BreakDebuggerDerivedDictionaryPropertyInRawView");
await debugService.SetCommandBreakpointsAsync(new[] { breakpoint }).ConfigureAwait(true);

// Execute the script and wait for the breakpoint to be hit
Task _ = ExecuteVariableScriptFile();
AssertDebuggerStopped(commandBreakpointDetails: breakpoint);

VariableDetailsBase sortedDictionaryVar = Array.Find(
GetVariables(VariableContainerDetails.ScriptScopeName),
v => v.Name == "$sortedDictionary");
Assert.NotNull(sortedDictionaryVar);
VariableDetailsBase[] simpleDictionaryChildren = sortedDictionaryVar.GetChildren(NullLogger.Instance);
// 4 items + Raw View
Assert.Equal(5, simpleDictionaryChildren.Length);
VariableDetailsBase rawDetailsView = Array.Find(
simpleDictionaryChildren,
v => v.Name == "Raw View");
Assert.NotNull(rawDetailsView);
Assert.Empty(rawDetailsView.Type);
Assert.Empty(rawDetailsView.ValueString);
VariableDetailsBase[] rawViewChildren = rawDetailsView.GetChildren(NullLogger.Instance);
Assert.Equal(4, rawViewChildren.Length);
Assert.NotNull(Array.Find(rawViewChildren, v => v .Name == "Comparer"));
}

[Fact]
public async Task DebuggerVariablePSCustomObjectDisplaysCorrectly()
{
Expand Down

AltStyle によって変換されたページ (->オリジナル) /