Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 48 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
module/
out/
bin/
obj/
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/

# Rider
.idea

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn

publish/
*.sln

#Module build
module/

# Visual Studio 2015
.vs/

# ingore downloaded .NET
.dotnet

# Ignore package
Microsoft.PowerShell.GraphicalTools.zip
Microsoft.PowerShell.ConsoleGuiTools.zip

# git artifacts
*.orig
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
19 changes: 11 additions & 8 deletions ConsoleGuiTools.build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ task Build {

Push-Location src/Microsoft.PowerShell.ConsoleGuiTools
Invoke-BuildExec { & dotnet publish --configuration $Configuration --output publish }
$Assets = $(
"./publish/Microsoft.PowerShell.ConsoleGuiTools.dll",
"./publish/Microsoft.PowerShell.ConsoleGuiTools.psd1",
"./publish/Microsoft.PowerShell.OutGridView.Models.dll",
"./publish/Terminal.Gui.dll",
"./publish/NStack.dll")
$Assets | ForEach-Object {
Copy-Item -Force -Path $_ -Destination ../../module

# Copy all DLLs except PowerShell SDK dependencies (those are provided by PowerShell itself)
Get-ChildItem "./publish/*.dll" | Where-Object {
$_.Name -notlike "System.Management.Automation.dll" -and
$_.Name -notlike "Microsoft.PowerShell.Commands.Diagnostics.dll" -and
$_.Name -notlike "Microsoft.Management.Infrastructure.CimCmdlets.dll"
} | ForEach-Object {
Copy-Item -Force -Path $_.FullName -Destination ../../module
}

# Copy the module manifest
Copy-Item -Force -Path "./publish/Microsoft.PowerShell.ConsoleGuiTools.psd1" -Destination ../../module
Pop-Location

$Assets = $(
Expand Down
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<ItemGroup>
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.4.7" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Nstack.Core" Version="1.1.1" />
<PackageVersion Include="Terminal.Gui" Version="1.17.1" />
<PackageVersion Include="System.Management.Automation" Version="7.4.7" />
<PackageVersion Include="Terminal.Gui" Version="2.0.0-develop.4636" />
</ItemGroup>
</Project>
</Project>
36 changes: 36 additions & 0 deletions GraphicalTools.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.3.11206.111 d18.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.ConsoleGuiTools", "src\Microsoft.PowerShell.ConsoleGuiTools\Microsoft.PowerShell.ConsoleGuiTools.csproj", "{C0749375-3F76-9F36-9A4D-6857B5504C9A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.PowerShell.OutGridView.Models", "src\Microsoft.PowerShell.OutGridView.Models\Microsoft.PowerShell.OutGridView.Models.csproj", "{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C0749375-3F76-9F36-9A4D-6857B5504C9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C0749375-3F76-9F36-9A4D-6857B5504C9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C0749375-3F76-9F36-9A4D-6857B5504C9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C0749375-3F76-9F36-9A4D-6857B5504C9A}.Release|Any CPU.Build.0 = Release|Any CPU
{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C0749375-3F76-9F36-9A4D-6857B5504C9A} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{D5EDF10B-A646-FC2C-FF3C-B7F2C1814863} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7EA28E35-5572-47D2-B03E-5DB79D82BEBC}
EndGlobalSection
EndGlobal
7 changes: 7 additions & 0 deletions GraphicalTools.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PS/@EntryIndexedValue">PS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UI/@EntryIndexedValue">UI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="CONSTANT_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BUGBUG/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ocgv/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
152 changes: 152 additions & 0 deletions src/Microsoft.PowerShell.ConsoleGuiTools/CachedMemberResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace Microsoft.PowerShell.ConsoleGuiTools;

/// <summary>
/// Represents a cached reflection result for a property or field member, including its value and collection details.
/// </summary>
internal sealed class CachedMemberResult
{
#region Fields

private readonly string? _representation;
private List<CachedMemberResultElement>? _valueAsList;

#endregion

#region Properties

/// <summary>
/// Gets or sets the member information (property or field) that was accessed.
/// </summary>
public MemberInfo Member { get; set; }

/// <summary>
/// Gets or sets the value retrieved from the member.
/// </summary>
public object? Value { get; set; }

/// <summary>
/// Gets or sets the parent object that contains this member.
/// </summary>
public object Parent { get; set; }

/// <summary>
/// Gets a value indicating whether this member's value is a collection.
/// </summary>
public bool IsCollection => _valueAsList != null;

/// <summary>
/// Gets the collection elements if this member's value is a collection; otherwise, <see langword="null" />.
/// </summary>
public IReadOnlyCollection<CachedMemberResultElement>? Elements => _valueAsList?.AsReadOnly();

#endregion

#region Constructor

/// <summary>
/// Initializes a new instance of the <see cref="CachedMemberResult" /> class by reflecting on the specified member.
/// </summary>
/// <param name="parent">The parent object containing the member.</param>
/// <param name="mem">The member information to retrieve the value from.</param>
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";
}
}

#endregion

#region Overrides

/// <summary>
/// Returns a string representation of this member in the format "MemberName: value".
/// </summary>
/// <returns>A formatted string showing the member name and value.</returns>
public override string ToString() => Member.Name + ": " + _representation;

#endregion

#region Private Methods

/// <summary>
/// Converts the member's value to a string representation, detecting collections and formatting them appropriately.
/// </summary>
/// <returns>A string representation of the value.</returns>
private string? ValueToString()
{
if (Value == null)
return "Null";

try
{
if (IsCollectionOfKnownTypeAndSize(out var elementType, out var size))
return $"{elementType!.Name}[{size}]";
}
catch (Exception)
{
return Value?.ToString();
}

return Value?.ToString();
}

/// <summary>
/// Determines whether the value is a collection of a known type and caches the collection elements.
/// </summary>
/// <param name="elementType">When this method returns, contains the element type if the value is a homogeneous collection; otherwise, <see langword="null" />.</param>
/// <param name="size">When this method returns, contains the size of the collection if applicable; otherwise, 0.</param>
/// <returns><see langword="true" /> if the value is a collection of a single known type; otherwise, <see langword="false" />.</returns>
private bool IsCollectionOfKnownTypeAndSize(out Type? elementType, out int size)
{
elementType = null;
size = 0;

if (Value is null or string)
return false;

if (Value is IEnumerable enumerable)
{
var list = enumerable.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;
}

#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;

namespace Microsoft.PowerShell.ConsoleGuiTools;

/// <summary>
/// Represents an element within a collection member result, providing indexed access to collection items.
/// </summary>
internal sealed class CachedMemberResultElement
{
#region Fields

/// <summary>
/// The index of this element within the collection.
/// </summary>
public int Index;

/// <summary>
/// The value of this collection element.
/// </summary>
public object? Value;

private readonly string _representation;

#endregion

#region Constructor

/// <summary>
/// Initializes a new instance of the <see cref="CachedMemberResultElement" /> class with the specified value and index.
/// </summary>
/// <param name="value">The value of the collection element.</param>
/// <param name="index">The zero-based index of this element within the collection.</param>
public CachedMemberResultElement(object? value, int index)
{
Index = index;
Value = value;

try
{
_representation = Value?.ToString() ?? "Null";
}
catch (Exception)
{
Value = _representation = "Unavailable";
}
}

#endregion

#region Overrides

/// <summary>
/// Returns a string representation of this collection element in the format "[index]: value".
/// </summary>
/// <returns>A formatted string showing the index and value.</returns>
public override string ToString() => $"[{Index}]: {_representation}]";

#endregion
}
Loading
Loading