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