Skip to content
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

Fix #242 - Adds support for PSCustomObject #243

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
156 changes: 25 additions & 131 deletions src/Microsoft.PowerShell.ConsoleGuiTools/ShowObjectView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

using OutGridView.Models;

using Terminal.Gui;
using Terminal.Gui.Trees;
using OutGridView.Cmdlet.TreeNodeCaching;

namespace OutGridView.Cmdlet
{
Expand Down Expand Up @@ -147,7 +146,7 @@ private void SelectionChanged(object sender, SelectionChangedEventArgs<object> e
{
var selectedValue = e.NewValue;

if (selectedValue is CachedMemberResult cmr)
if (selectedValue is ICachedMemberResult cmr)
{
selectedValue = cmr.Value;
}
Expand Down Expand Up @@ -189,7 +188,7 @@ private bool IsRootObject(object o)

public bool CanExpand(object toExpand)
{
if (toExpand is CachedMemberResult p)
if (toExpand is ICachedMemberResult p)
{
return IsBasicType(p?.Value);
}
Expand All @@ -210,7 +209,7 @@ public IEnumerable<object> GetChildren(object forObject)
return Enumerable.Empty<object>();
}

if (forObject is CachedMemberResult p)
if (forObject is ICachedMemberResult p)
{
if (p.IsCollection)
{
Expand All @@ -225,6 +224,11 @@ public IEnumerable<object> GetChildren(object forObject)
return GetChildren(e.Value);
}

if(forObject is PSObject pso)
{
return GetPSObjectChildren(pso);
}

List<object> children = new List<object>();

foreach (var member in forObject.GetType().GetMembers(BindingFlags.Instance | BindingFlags.Public).OrderBy(m => m.Name))
Expand All @@ -251,6 +255,20 @@ public IEnumerable<object> GetChildren(object forObject)
return children;
}

/// <summary>
/// We only deal with PSObject when there is no native type (e.g. Process).
/// For example when the PSObject.BaseObject is a PSCustomObject.
/// </summary>
/// <param name="pso"></param>
/// <returns></returns>
public IEnumerable<object> GetPSObjectChildren(PSObject pso)
{
foreach(var m in pso.Members.Where(PsoHelper.IsDisplayableMember))
{
yield return new CachedPSObjectMemberResult(pso, m);
}
}

private static IEnumerable<object> GetExtraChildren(object forObject)
{
if (forObject is DirectoryInfo dir)
Expand All @@ -272,7 +290,7 @@ internal static void Run(List<PSObject> objects, ApplicationData applicationData

try
{
window = new ShowObjectView(objects.Select(p => p.BaseObject).ToList(), applicationData);
window = new ShowObjectView(objects.Select(PsoHelper.MaybeUnwrap).ToList(), applicationData);
Application.Top.Add(window);
Application.Run();
}
Expand All @@ -283,131 +301,7 @@ internal static void Run(List<PSObject> objects, ApplicationData applicationData
}
}

sealed class CachedMemberResultElement
{
public int Index;
public object Value;

private string representation;

public CachedMemberResultElement(object value, int index)
{
Index = index;
Value = value;

try
{
representation = Value?.ToString() ?? "Null";
}
catch (Exception)
{
Value = representation = "Unavailable";
}
}
public override string ToString()
{
return $"[{Index}]: {representation}]";
}
}

sealed class CachedMemberResult
{
public MemberInfo Member;
public object Value;
public object Parent;
private string representation;
private List<CachedMemberResultElement> valueAsList;


public bool IsCollection => valueAsList != null;
public IReadOnlyCollection<CachedMemberResultElement> Elements => valueAsList?.AsReadOnly();

public CachedMemberResult(object parent, MemberInfo mem)
{
Parent = parent;
Member = mem;

try
{
if (mem is PropertyInfo p)
{
Value = p.GetValue(parent);
}
else if (mem is FieldInfo f)
{
Value = f.GetValue(parent);
}
else
{
throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type");
}

representation = ValueToString();

}
catch (Exception)
{
Value = representation = "Unavailable";
}
}

private string ValueToString()
{
if (Value == null)
{
return "Null";
}
try
{
if (IsCollectionOfKnownTypeAndSize(out Type elementType, out int size))
{
return $"{elementType.Name}[{size}]";
}
}
catch (Exception)
{
return Value?.ToString();
}


return Value?.ToString();
}

private bool IsCollectionOfKnownTypeAndSize(out Type elementType, out int size)
{
elementType = null;
size = 0;

if (Value == null || Value is string)
{

return false;
}

if (Value is IEnumerable ienumerable)
{
var list = ienumerable.Cast<object>().ToList();

var types = list.Where(v => v != null).Select(v => v.GetType()).Distinct().ToArray();

if (types.Length == 1)
{
elementType = types[0];
size = list.Count;

valueAsList = list.Select((e, i) => new CachedMemberResultElement(e, i)).ToList();
return true;
}
}

return false;
}

public override string ToString()
{
return Member.Name + ": " + representation;
}
}
private sealed class RegexTreeViewTextFilter : ITreeViewFilter<object>
{
private readonly ShowObjectView parent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Reflection;

namespace OutGridView.Cmdlet.TreeNodeCaching
{

sealed class CachedMemberResult : CachedMemberResultBase
{
MemberInfo Member {get;}

protected override string GetMemberName()
{
return Member.Name;
}

public CachedMemberResult(object parent, MemberInfo mem)
{
Parent = parent;
Member = mem;

try
{
if (mem is PropertyInfo p)
{
Value = p.GetValue(parent);
}
else if (mem is FieldInfo f)
{
Value = f.GetValue(parent);
}
else
{
throw new NotSupportedException($"Unknown {nameof(MemberInfo)} Type");
}

Representation = ValueToString();

}
catch (Exception)
{
Value = Representation = "Unavailable";
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace OutGridView.Cmdlet.TreeNodeCaching
{
abstract class CachedMemberResultBase : ICachedMemberResult
{
public object Value {get; protected set;}
public object Parent;
protected string Representation;
private List<CachedMemberResultElement> valueAsList;


public bool IsCollection => valueAsList != null;
public IReadOnlyCollection<CachedMemberResultElement> Elements => valueAsList?.AsReadOnly();


protected string ValueToString()
{
if (Value == null)
{
return "Null";
}
try
{
if (IsCollectionOfKnownTypeAndSize(out Type elementType, out int size))
{
return $"{elementType.Name}[{size}]";
}
}
catch (Exception)
{
return Value?.ToString();
}


return Value?.ToString();
}

private bool IsCollectionOfKnownTypeAndSize(out Type elementType, out int size)
{
elementType = null;
size = 0;

if (Value == null || Value is string)
{

return false;
}

if (Value is IEnumerable ienumerable)
{
var list = ienumerable.Cast<object>().ToList();

var types = list.Where(v => v != null).Select(v => v.GetType()).Distinct().ToArray();

if (types.Length == 1)
{
elementType = types[0];
size = list.Count;

valueAsList = list.Select((e, i) => new CachedMemberResultElement(e, i)).ToList();
return true;
}
}

return false;
}

public override string ToString()
{
return GetMemberName() + ": " + Representation;
}

protected abstract string GetMemberName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace OutGridView.Cmdlet.TreeNodeCaching
{
sealed class CachedMemberResultElement
{
public int Index;
public object Value;

private string representation;

public CachedMemberResultElement(object value, int index)
{
Index = index;
Value = value;

try
{
representation = Value?.ToString() ?? "Null";
}
catch (Exception)
{
Value = representation = "Unavailable";
}
}
public override string ToString()
{
return $"[{Index}]: {representation}";
}
}
}
Loading