From b321382f327519fdaf0c3dfb59b35ae52663b6ff Mon Sep 17 00:00:00 2001 From: Emre Aydinceren Date: Tue, 1 Oct 2024 09:52:05 -0700 Subject: [PATCH 1/4] - Refactor 2 projects into single project - Use Format-Table as formatter rather than built-in logic - Enabling styling via Format-Table's Properties feature - Fixing bug where columns were not aligned to right when appropriate - Reduce code base to maintain. --- ConsoleGuiTools.Common.props | 2 +- .../ConsoleGui.cs | 622 +++++++---------- .../FormatHelper.cs | 88 +++ .../GridViewDataSource.cs | 105 ++- .../GridViewDetails.cs | 19 - .../GridViewHelpers.cs | 90 ++- .../GridViewRow.cs | 13 - ...icrosoft.PowerShell.ConsoleGuiTools.csproj | 7 +- .../Microsoft.PowerShell.ConsoleGuiTools.psd1 | 2 +- .../Models/ApplicationData.cs | 38 + .../Models/GridViewHeader.cs | 11 + .../Models/GridViewRow.cs | 12 + .../Models/OutputModeOptions.cs | 22 + .../Models/Serializers.cs | 41 ++ .../Models/Table.cs | 38 + .../OutConsoleGridviewCmdletCommand.cs | 345 ++++++---- .../ShowObjectTreeCmdletCommand.cs | 241 ++++--- .../ShowObjectView.cs | 649 +++++++++--------- .../TypeGetter.cs | 198 ------ .../ApplicationData.cs | 24 - .../DataTable.cs | 19 - .../DataTableColumn.cs | 40 -- .../DataTableRow.cs | 47 -- ...osoft.PowerShell.OutGridView.Models.csproj | 9 - .../OutputModeOptions.cs | 23 - .../Serializers.cs | 43 -- 26 files changed, 1243 insertions(+), 1505 deletions(-) create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/FormatHelper.cs delete mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDetails.cs delete mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/GridViewRow.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/ApplicationData.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewHeader.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewRow.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/OutputModeOptions.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/Serializers.cs create mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/Models/Table.cs delete mode 100644 src/Microsoft.PowerShell.ConsoleGuiTools/TypeGetter.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/ApplicationData.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/DataTable.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/DataTableColumn.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/DataTableRow.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/OutputModeOptions.cs delete mode 100644 src/Microsoft.PowerShell.OutGridView.Models/Serializers.cs diff --git a/ConsoleGuiTools.Common.props b/ConsoleGuiTools.Common.props index 390d688..92fc67c 100644 --- a/ConsoleGuiTools.Common.props +++ b/ConsoleGuiTools.Common.props @@ -1,6 +1,6 @@ - 0.7.7 + 0.8.0 Microsoft © Microsoft Corporation. diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs index e4385e1..fbffcdc 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs @@ -6,438 +6,334 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Text; -using OutGridView.Models; +using Microsoft.PowerShell.ConsoleGuiTools.Models; using Terminal.Gui; -namespace OutGridView.Cmdlet +namespace Microsoft.PowerShell.ConsoleGuiTools; + +internal sealed class ConsoleGui : IDisposable { - internal sealed class ConsoleGui : IDisposable + private const string FILTER_LABEL = "Filter"; + // This adjusts the left margin of all controls + private const int MARGIN_LEFT = 1; + // Width of Terminal.Gui ListView selection/check UI elements (old == 4, new == 2) + private const int CHECK_WIDTH = 2; + private bool _cancelled; + private Label _filterLabel; + private TextField _filterField; + private ListView _listView; + // _inputSource contains the full set of Input data and tracks any items the user + // marks. When the cmdlet exits, any marked items are returned. When a filter is + // active, the list view shows a copy of _inputSource that includes both the items + // matching the filter AND any items previously marked. + private GridViewDataSource _inputSource; + + // _listViewSource is a filtered copy of _inputSource that ListView.Source is set to. + // Changes to IsMarked are propagated back to _inputSource. + private GridViewDataSource _listViewSource; + private ApplicationData _applicationData; + + public IEnumerable Start(ApplicationData applicationData) { - private const string FILTER_LABEL = "Filter"; - // This adjusts the left margin of all controls - private const int MARGIN_LEFT = 1; - // Width of Terminal.Gui ListView selection/check UI elements (old == 4, new == 2) - private const int CHECK_WIDTH = 2; - private bool _cancelled; - private Label _filterLabel; - private TextField _filterField; - private ListView _listView; - // _inputSource contains the full set of Input data and tracks any items the user - // marks. When the cmdlet exits, any marked items are returned. When a filter is - // active, the list view shows a copy of _inputSource that includes both the items - // matching the filter AND any items previously marked. - private GridViewDataSource _inputSource; - - // _listViewSource is a filtered copy of _inputSource that ListView.Source is set to. - // Changes to IsMarked are propogated back to _inputSource. - private GridViewDataSource _listViewSource; - private ApplicationData _applicationData; - private GridViewDetails _gridViewDetails; - - public HashSet Start(ApplicationData applicationData) + _applicationData = applicationData; + // Note, in Terminal.Gui v2, this property is renamed to Application.UseNetDriver, hence + // using that terminology here. + Application.UseSystemConsole = _applicationData.UseNetDriver; + Application.Init(); + + var (gridViewHeader, gridViewDataSource) = GridViewHelpers.CreateGridViewInputs( + // If OutputMode is Single or Multiple, then we make items selectable. If we make them selectable, + // 2 columns are required for the check/selection indicator and space. + listViewOffset: _applicationData.OutputMode != OutputModeOption.None ? MARGIN_LEFT + CHECK_WIDTH : MARGIN_LEFT, + applicationData: _applicationData, + properties: _applicationData.Properties, + leftMargin: MARGIN_LEFT + ); + + Window win = CreateTopLevelWindow(); + + // Copy the input DataTable into our master ListView source list; upon exit any items + // that are IsMarked are returned (if Outputmode is set) + _inputSource = gridViewDataSource; + + if (!_applicationData.MinUI) { - _applicationData = applicationData; - // Note, in Terminal.Gui v2, this property is renamed to Application.UseNetDriver, hence - // using that terminology here. - Application.UseSystemConsole = _applicationData.UseNetDriver; - Application.Init(); - _gridViewDetails = new GridViewDetails - { - // If OutputMode is Single or Multiple, then we make items selectable. If we make them selectable, - // 2 columns are required for the check/selection indicator and space. - ListViewOffset = _applicationData.OutputMode != OutputModeOption.None ? MARGIN_LEFT + CHECK_WIDTH : MARGIN_LEFT - }; - - Window win = CreateTopLevelWindow(); - - // Create the headers and calculate column widths based on the DataTable - List gridHeaders = _applicationData.DataTable.DataColumns.Select((c) => c.Label).ToList(); - CalculateColumnWidths(gridHeaders); - - // Copy the input DataTable into our master ListView source list; upon exit any items - // that are IsMarked are returned (if Outputmode is set) - _inputSource = LoadData(); - - if (!_applicationData.MinUI) - { - // Add Filter UI - AddFilter(win); - // Add Header UI - AddHeaders(win, gridHeaders); - } + // Add Filter UI + AddFilter(win); + // Add Header UI + AddHeaders(win, gridViewHeader); + } - // Add ListView - AddListView(win); + // Add ListView + AddListView(win); - // Status bar is where our key-bindings are handled - AddStatusBar(!_applicationData.MinUI); + // Status bar is where our key-bindings are handled + AddStatusBar(!_applicationData.MinUI); - // We *always* apply a filter, even if the -Filter parameter is not set or Filtering is not - // available. The ListView always shows a fitlered version of _inputSource even if there is no - // actual fitler. - ApplyFilter(); + // We *always* apply a filter, even if the -Filter parameter is not set or Filtering is not + // available. The ListView always shows a filtered version of _inputSource even if there is no + // actual filter. + ApplyFilter(); - _listView.SetFocus(); + _listView.SetFocus(); - // Run the GUI. - Application.Run(); - Application.Shutdown(); + // Run the GUI. + Application.Run(); + Application.Shutdown(); - // Return results of selection if required. - HashSet selectedIndexes = new HashSet(); - if (_cancelled) - { - return selectedIndexes; - } + // Return results of selection if required. + if (_cancelled) + { + return Enumerable.Empty(); + } - // Return any items that were selected. - foreach (GridViewRow gvr in _inputSource.GridViewRowList) - { - if (gvr.IsMarked) - { - selectedIndexes.Add(gvr.OriginalIndex); - } - } + // Return any items that were selected. + return _inputSource.GridViewRowList + .Where(gvr => gvr.IsMarked) + .Select(gvr => gvr.OriginalIndex); + } - return selectedIndexes; - } + private void ApplyFilter() + { + // The ListView is always filled with a (filtered) copy of _inputSource. + // We listen for `MarkChanged` events on this filtered list and apply those changes up to _inputSource. - private GridViewDataSource LoadData() + if (_listViewSource != null) { - var items = new List(); - int newIndex = 0; - for (int i = 0; i < _applicationData.DataTable.Data.Count; i++) - { - var dataTableRow = _applicationData.DataTable.Data[i]; - var valueList = new List(); - foreach (var dataTableColumn in _applicationData.DataTable.DataColumns) - { - string dataValue = dataTableRow.Values[dataTableColumn.ToString()].DisplayValue; - valueList.Add(dataValue); - } + _listViewSource.MarkChanged -= ListViewSource_MarkChanged; + _listViewSource = null; + } - string displayString = GridViewHelpers.GetPaddedString(valueList, 0, _gridViewDetails.ListViewColumnWidths); + _listViewSource = new GridViewDataSource(GridViewHelpers.FilterData(_inputSource.GridViewRowList, _applicationData.Filter ?? string.Empty)); + _listViewSource.MarkChanged += ListViewSource_MarkChanged; + _listView.Source = _listViewSource; + } - items.Add(new GridViewRow - { - DisplayString = displayString, - // We use this to keep _inputSource up to date when a filter is applied - OriginalIndex = i - }); + private void ListViewSource_MarkChanged(object s, GridViewDataSource.RowMarkedEventArgs a) + { + _inputSource.GridViewRowList[a.Row.OriginalIndex].IsMarked = a.Row.IsMarked; + } - newIndex++; - } + private static void Accept() + { + Application.RequestStop(); + } - return new GridViewDataSource(items); - } + private void Close() + { + _cancelled = true; + Application.RequestStop(); + } - private void ApplyFilter() + private Window CreateTopLevelWindow() + { + // Creates the top-level window to show + var win = new Window(_applicationData.Title) { - // The ListView is always filled with a (filtered) copy of _inputSource. - // We listen for `MarkChanged` events on this filtered list and apply those changes up to _inputSource. - - if (_listViewSource != null) - { - _listViewSource.MarkChanged -= ListViewSource_MarkChanged; - _listViewSource = null; - } + X = _applicationData.MinUI ? -1 : 0, + Y = _applicationData.MinUI ? -1 : 0, - _listViewSource = new GridViewDataSource(GridViewHelpers.FilterData(_inputSource.GridViewRowList, _applicationData.Filter ?? string.Empty)); - _listViewSource.MarkChanged += ListViewSource_MarkChanged; - _listView.Source = _listViewSource; - } + // By using Dim.Fill(), it will automatically resize without manual intervention + Width = Dim.Fill(_applicationData.MinUI ? -1 : 0), + Height = Dim.Fill(_applicationData.MinUI ? -1 : 1) + }; - private void ListViewSource_MarkChanged(object s, GridViewDataSource.RowMarkedEventArgs a) + if (_applicationData.MinUI) { - _inputSource.GridViewRowList[a.Row.OriginalIndex].IsMarked = a.Row.IsMarked; + win.Border.BorderStyle = BorderStyle.None; } - private static void Accept() - { - Application.RequestStop(); - } + Application.Top.Add(win); + return win; + } - private void Close() + private void AddStatusBar(bool visible) + { + var statusItems = new List(); + if (_applicationData.OutputMode != OutputModeOption.None) { - _cancelled = true; - Application.RequestStop(); + // Use Key.Unknown for SPACE with no delegate because ListView already + // handles SPACE + statusItems.Add(new StatusItem(Key.Unknown, "~SPACE~ Select Item", null)); } - private Window CreateTopLevelWindow() + if (_applicationData.OutputMode == OutputModeOption.Multiple) { - // Creates the top-level window to show - var win = new Window(_applicationData.Title) + statusItems.Add(new StatusItem(Key.A | Key.CtrlMask, "~CTRL-A~ Select All", () => { - X = _applicationData.MinUI ? -1 : 0, - Y = _applicationData.MinUI ? -1 : 0, - - // By using Dim.Fill(), it will automatically resize without manual intervention - Width = Dim.Fill(_applicationData.MinUI ? -1 : 0), - Height = Dim.Fill(_applicationData.MinUI ? -1 : 1) - }; - - if (_applicationData.MinUI) + // This selects only the items that match the Filter + var gvds = _listView.Source as GridViewDataSource; + gvds.GridViewRowList.ForEach(i => i.IsMarked = true); + _listView.SetNeedsDisplay(); + })); + + // Ctrl-D is commonly used in GUIs for select-none + statusItems.Add(new StatusItem(Key.D | Key.CtrlMask, "~CTRL-D~ Select None", () => { - win.Border.BorderStyle = BorderStyle.None; - } - - Application.Top.Add(win); - return win; + // This un-selects only the items that match the Filter + var gvds = _listView.Source as GridViewDataSource; + gvds.GridViewRowList.ForEach(i => i.IsMarked = false); + _listView.SetNeedsDisplay(); + })); } - private void AddStatusBar(bool visible) + if (_applicationData.OutputMode != OutputModeOption.None) { - var statusItems = new List(); - if (_applicationData.OutputMode != OutputModeOption.None) + statusItems.Add(new StatusItem(Key.Enter, "~ENTER~ Accept", () => { - // Use Key.Unknown for SPACE with no delegate because ListView already - // handles SPACE - statusItems.Add(new StatusItem(Key.Unknown, "~SPACE~ Select Item", null)); - } - - if (_applicationData.OutputMode == OutputModeOption.Multiple) - { - statusItems.Add(new StatusItem(Key.A | Key.CtrlMask, "~CTRL-A~ Select All", () => - { - // This selects only the items that match the Filter - var gvds = _listView.Source as GridViewDataSource; - gvds.GridViewRowList.ForEach(i => i.IsMarked = true); - _listView.SetNeedsDisplay(); - })); - - // Ctrl-D is commonly used in GUIs for select-none - statusItems.Add(new StatusItem(Key.D | Key.CtrlMask, "~CTRL-D~ Select None", () => - { - // This un-selects only the items that match the Filter - var gvds = _listView.Source as GridViewDataSource; - gvds.GridViewRowList.ForEach(i => i.IsMarked = false); - _listView.SetNeedsDisplay(); - })); - } - - if (_applicationData.OutputMode != OutputModeOption.None) - { - statusItems.Add(new StatusItem(Key.Enter, "~ENTER~ Accept", () => + if (Application.Top.MostFocused == _listView) { - if (Application.Top.MostFocused == _listView) + // If nothing was explicitly marked, we return the item that was selected + // when ENTER is pressed in Single mode. If something was previously selected + // (using SPACE) then honor that as the single item to return + if (_applicationData.OutputMode == OutputModeOption.Single && + _inputSource.GridViewRowList.Find(i => i.IsMarked) == null) { - // If nothing was explicitly marked, we return the item that was selected - // when ENTER is pressed in Single mode. If something was previously selected - // (using SPACE) then honor that as the single item to return - if (_applicationData.OutputMode == OutputModeOption.Single && - _inputSource.GridViewRowList.Find(i => i.IsMarked) == null) - { - _listView.MarkUnmarkRow(); - } - Accept(); + _listView.MarkUnmarkRow(); } - else if (Application.Top.MostFocused == _filterField) - { - _listView.SetFocus(); - } - })); - } - - statusItems.Add(new StatusItem(Key.Esc, "~ESC~ Close", () => Close())); - if (_applicationData.Verbose || _applicationData.Debug) - { - statusItems.Add(new StatusItem(Key.Null, $" v{_applicationData.ModuleVersion}", null)); - statusItems.Add(new StatusItem(Key.Null, - $"{Application.Driver} v{FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(Application)).Location).ProductVersion}", null)); - } + Accept(); + } + else if (Application.Top.MostFocused == _filterField) + { + _listView.SetFocus(); + } + })); + } - var statusBar = new StatusBar(statusItems.ToArray()); - statusBar.Visible = visible; - Application.Top.Add(statusBar); + statusItems.Add(new StatusItem(Key.Esc, "~ESC~ Close", () => Close())); + if (_applicationData.Verbose || _applicationData.Debug) + { + statusItems.Add(new StatusItem(Key.Null, $" v{_applicationData.ModuleVersion}", null)); + statusItems.Add(new StatusItem(Key.Null, + $"{Application.Driver} v{FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(Application)).Location).ProductVersion}", null)); } - private void CalculateColumnWidths(List gridHeaders) + var statusBar = new StatusBar(statusItems.ToArray()) { - _gridViewDetails.ListViewColumnWidths = new int[gridHeaders.Count]; - var listViewColumnWidths = _gridViewDetails.ListViewColumnWidths; + Visible = visible + }; - for (int i = 0; i < gridHeaders.Count; i++) - { - listViewColumnWidths[i] = gridHeaders[i].Length; - } + Application.Top.Add(statusBar); + } - // calculate the width of each column based on longest string in each column for each row - foreach (var row in _applicationData.DataTable.Data) - { - int index = 0; + private void AddFilter(Window win) + { + _filterLabel = new Label(FILTER_LABEL) + { + X = MARGIN_LEFT, + Y = 0 + }; - // use half of the visible buffer height for the number of objects to inspect to calculate widths - foreach (var col in row.Values.Take(Application.Top.Frame.Height / 2)) - { - var len = col.Value.DisplayValue.Length; - if (len > listViewColumnWidths[index]) - { - listViewColumnWidths[index] = len; - } - index++; - } - } + _filterField = new TextField(_applicationData.Filter ?? string.Empty) + { + X = Pos.Right(_filterLabel) + 1, + Y = Pos.Top(_filterLabel), + CanFocus = true, + Width = Dim.Fill() - 1 + }; + + // TextField captures Ctrl-A (select all text) and Ctrl-D (delete backwards) + // In OCGV these are used for select-all/none of items. Selecting items is more + // common than editing the filter field so we turn them off in the filter textview. + // BACKSPACE still works for delete backwards + _filterField.ClearKeybinding(Key.A | Key.CtrlMask); + _filterField.ClearKeybinding(Key.D | Key.CtrlMask); + + var filterErrorLabel = new Label(string.Empty) + { + X = Pos.Right(_filterLabel) + 1, + Y = Pos.Top(_filterLabel) + 1, + ColorScheme = Colors.Base, + Width = Dim.Fill() - _filterLabel.Text.Length + }; - // if the total width is wider than the usable width, remove 1 from widest column until it fits - _gridViewDetails.UsableWidth = Application.Top.Frame.Width - MARGIN_LEFT - listViewColumnWidths.Length - _gridViewDetails.ListViewOffset; - int columnWidthsSum = listViewColumnWidths.Sum(); - while (columnWidthsSum >= _gridViewDetails.UsableWidth) + _filterField.TextChanged += (str) => + { + // str is the OLD value + string filterText = _filterField.Text?.ToString(); + try { - int maxWidth = 0; - int maxIndex = 0; - for (int i = 0; i < listViewColumnWidths.Length; i++) - { - if (listViewColumnWidths[i] > maxWidth) - { - maxWidth = listViewColumnWidths[i]; - maxIndex = i; - } - } + filterErrorLabel.Text = " "; + filterErrorLabel.ColorScheme = Colors.Base; + filterErrorLabel.Redraw(filterErrorLabel.Bounds); + _applicationData.Filter = filterText; + ApplyFilter(); - listViewColumnWidths[maxIndex]--; - columnWidthsSum--; } - } - - private void AddFilter(Window win) - { - _filterLabel = new Label(FILTER_LABEL) + catch (Exception ex) { - X = MARGIN_LEFT, - Y = 0 - }; + filterErrorLabel.Text = ex.Message; + filterErrorLabel.ColorScheme = Colors.Error; + filterErrorLabel.Redraw(filterErrorLabel.Bounds); + } + }; - _filterField = new TextField(_applicationData.Filter ?? string.Empty) - { - X = Pos.Right(_filterLabel) + 1, - Y = Pos.Top(_filterLabel), - CanFocus = true, - Width = Dim.Fill() - 1 - }; - - // TextField captures Ctrl-A (select all text) and Ctrl-D (delete backwards) - // In OCGV these are used for select-all/none of items. Selecting items is more - // common than editing the filter field so we turn them off in the filter textview. - // BACKSPACE still works for delete backwards - _filterField.ClearKeybinding(Key.A | Key.CtrlMask); - _filterField.ClearKeybinding(Key.D | Key.CtrlMask); - - var filterErrorLabel = new Label(string.Empty) - { - X = Pos.Right(_filterLabel) + 1, - Y = Pos.Top(_filterLabel) + 1, - ColorScheme = Colors.Base, - Width = Dim.Fill() - _filterLabel.Text.Length - }; + win.Add(_filterLabel, _filterField, filterErrorLabel); - _filterField.TextChanged += (str) => - { - // str is the OLD value - string filterText = _filterField.Text?.ToString(); - try - { - filterErrorLabel.Text = " "; - filterErrorLabel.ColorScheme = Colors.Base; - filterErrorLabel.Redraw(filterErrorLabel.Bounds); - _applicationData.Filter = filterText; - ApplyFilter(); + _filterField.Text = _applicationData.Filter ?? string.Empty; + _filterField.CursorPosition = _filterField.Text.Length; + } - } - catch (Exception ex) - { - filterErrorLabel.Text = ex.Message; - filterErrorLabel.ColorScheme = Colors.Error; - filterErrorLabel.Redraw(filterErrorLabel.Bounds); - } - }; + private void AddHeaders(Window win, GridViewHeader gridViewHeader) + { + var header = new Label(gridViewHeader.HeaderText) + { + X = 0, + Y = _applicationData.MinUI ? 0 : 2 + }; - win.Add(_filterLabel, _filterField, filterErrorLabel); + win.Add(header); - _filterField.Text = _applicationData.Filter ?? string.Empty; - _filterField.CursorPosition = _filterField.Text.Length; + if (_applicationData.MinUI) + { + return; } - private void AddHeaders(Window win, List gridHeaders) + var headerLine = new Label(gridViewHeader.HeaderUnderLine) { - var header = new Label(GridViewHelpers.GetPaddedString( - gridHeaders, - _gridViewDetails.ListViewOffset, - _gridViewDetails.ListViewColumnWidths)); - header.X = 0; - if (_applicationData.MinUI) - { - header.Y = 0; - } - else - { - header.Y = 2; - } - win.Add(header); + X = 0, + Y = Pos.Bottom(header) + }; - // This renders dashes under the header to make it more clear what is header and what is data - var headerLineText = new StringBuilder(); - foreach (char c in header.Text) - { - if (c.Equals(' ')) - { - headerLineText.Append(' '); - } - else - { - // When gui.cs supports text decorations, should replace this with just underlining the header - headerLineText.Append('-'); - } - } + win.Add(headerLine); + } - if (!_applicationData.MinUI) - { - var headerLine = new Label(headerLineText.ToString()) - { - X = 0, - Y = Pos.Bottom(header) - }; - win.Add(headerLine); - } + private void AddListView(Window win) + { + _listView = new ListView(_inputSource); + _listView.X = MARGIN_LEFT; + if (!_applicationData.MinUI) + { + _listView.Y = Pos.Bottom(_filterLabel) + 3; // 1 for space, 1 for header, 1 for header underline } - - private void AddListView(Window win) + else { - _listView = new ListView(_inputSource); - _listView.X = MARGIN_LEFT; - if (!_applicationData.MinUI) - { - _listView.Y = Pos.Bottom(_filterLabel) + 3; // 1 for space, 1 for header, 1 for header underline - } - else - { - _listView.Y = 1; // 1 for space, 1 for header, 1 for header underline - } - _listView.Width = Dim.Fill(1); - _listView.Height = Dim.Fill(); - _listView.AllowsMarking = _applicationData.OutputMode != OutputModeOption.None; - _listView.AllowsMultipleSelection = _applicationData.OutputMode == OutputModeOption.Multiple; - _listView.AddKeyBinding(Key.Space, Command.ToggleChecked, Command.LineDown); - - win.Add(_listView); + _listView.Y = 1; // 1 for space, 1 for header, 1 for header underline } + _listView.Width = Dim.Fill(1); + _listView.Height = Dim.Fill(); + _listView.AllowsMarking = _applicationData.OutputMode != OutputModeOption.None; + _listView.AllowsMultipleSelection = _applicationData.OutputMode == OutputModeOption.Multiple; + _listView.AddKeyBinding(Key.Space, Command.ToggleChecked, Command.LineDown); + + win.Add(_listView); + } - public void Dispose() + public void Dispose() + { + if (!Console.IsInputRedirected) { - if (!Console.IsInputRedirected) - { - // By emitting this, we fix two issues: - // 1. An issue where arrow keys don't work in the console because .NET - // requires application mode to support Arrow key escape sequences. - // Esc[?1h sets the cursor key to application mode - // See http://ascii-table.com/ansi-escape-sequences-vt-100.php - // 2. An issue where moving the mouse causes characters to show up because - // mouse tracking is still on. Esc[?1003l turns it off. - // See https://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking - Console.Write("\u001b[?1h\u001b[?1003l"); - } + // By emitting this, we fix two issues: + // 1. An issue where arrow keys don't work in the console because .NET + // requires application mode to support Arrow key escape sequences. + // Esc[?1h sets the cursor key to application mode + // See http://ascii-table.com/ansi-escape-sequences-vt-100.php + // 2. An issue where moving the mouse causes characters to show up because + // mouse tracking is still on. Esc[?1003l turns it off. + // See https://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking + Console.Write("\u001b[?1h\u001b[?1003l"); } } } diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/FormatHelper.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/FormatHelper.cs new file mode 100644 index 0000000..706e254 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/FormatHelper.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Linq; +using System.Management.Automation; +using Microsoft.PowerShell.ConsoleGuiTools.Models; + +/// +/// Helper class for formatting objects passed pipeline object into text. +/// +internal static class FormatHelper +{ + /// + /// Formats the output of a command as a table with the selected properties of the object in each column. + /// The object type determines the default layout and properties that are displayed in each column. + /// You can use the Property parameter to select the properties that you want to display. + /// + /// Collection of + /// Specifies the object properties that appear in the display and the order in which they appear. + /// Type one or more property names, separated by commas, or use a hash table to display a calculated property. + /// Wildcards are permitted. + /// data transfer object that contains header and rows in string. + /// + /// Format-Table Powershell command is used to format the inputs objects as a table. + /// + internal static Table FormatTable(IReadOnlyList inputs, bool force, object[] properties = null) + { + if (inputs.Count == 0) + { + return Table.Empty; + } + + using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("Format-Table"); + ps.AddParameter("AutoSize"); + + if (properties != null) + { + ps.AddParameter("Property", properties); + } + + if (force == true) + { + ps.AddParameter("Force"); + } + + ps.AddParameter("InputObject", inputs); + + // Format-Table's output objects are internal to Powershell, + // we cannot use them here, so we need to convert it to a string as workaround. + ps.AddCommand("Out-String"); + + var results = ps.Invoke(); + var text = results.FirstOrDefault()?.BaseObject.ToString(); + + var lines = text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) + .Where(line => string.IsNullOrEmpty(line) == false).ToList(); + + /* + * Format-Table sometimes outputs a label on the top based on input's data type. + * We need to detect and skip the label and extract only header and rows. + * Strategy is to detect the index of the line under the header with dashes and spaces ('---- -- --- '). + */ + + static bool isHeaderLine(string text) => text.Contains('-') && text.All(c => c == '-' || c == ' '); + + var headerLineIndex = lines.FindIndex(isHeaderLine); + + if (headerLineIndex == -1) + { + // unexpected result, return the whole text + headerLineIndex = 1; + } + + return new Table + { + Header = lines.Skip(headerLineIndex - 1).FirstOrDefault(), + HeaderLine = lines.Skip(headerLineIndex).FirstOrDefault(), + Rows = lines.Skip(headerLineIndex + 1) + }; + } +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs index 4d6724a..b57ec88 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs @@ -4,80 +4,77 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; + +using Microsoft.PowerShell.ConsoleGuiTools.Models; using NStack; using Terminal.Gui; -namespace OutGridView.Cmdlet -{ - internal sealed class GridViewDataSource : IListDataSource - { - public List GridViewRowList { get; set; } +namespace Microsoft.PowerShell.ConsoleGuiTools; - public int Count => GridViewRowList.Count; +internal sealed class GridViewDataSource(IEnumerable gridViewRowList) : IListDataSource +{ + internal List GridViewRowList { get; init; } = gridViewRowList.ToList(); - public GridViewDataSource(List itemList) - { - GridViewRowList = itemList; - } + public int Count => GridViewRowList.Count; - public int Length { get; } + public int Length { get; } - public void Render(ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start) - { - container.Move(col, line); - RenderUstr(driver, GridViewRowList[item].DisplayString, col, line, width); - } + public void Render(ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start) + { + container.Move(col, line); + RenderUstr(driver, GridViewRowList[item].DisplayString, width); + } - public bool IsMarked(int item) => GridViewRowList[item].IsMarked; + public bool IsMarked(int item) => GridViewRowList[item].IsMarked; - public void SetMark(int item, bool value) + public void SetMark(int item, bool value) + { + var oldValue = GridViewRowList[item].IsMarked; + GridViewRowList[item].IsMarked = value; + var args = new RowMarkedEventArgs() { - var oldValue = GridViewRowList[item].IsMarked; - GridViewRowList[item].IsMarked = value; - var args = new RowMarkedEventArgs() - { - Row = GridViewRowList[item], - OldValue = oldValue - }; - MarkChanged?.Invoke(this, args); - } + Row = GridViewRowList[item], + OldValue = oldValue + }; + MarkChanged?.Invoke(this, args); + } - public sealed class RowMarkedEventArgs : EventArgs - { - public GridViewRow Row { get; set; } - public bool OldValue { get; set; } + public sealed class RowMarkedEventArgs : EventArgs + { + public GridViewRow Row { get; set; } + public bool OldValue { get; set; } - } + } - public event EventHandler MarkChanged; + public event EventHandler MarkChanged; - public IList ToList() + public IList ToList() + { + return GridViewRowList; + } + + // A slightly adapted method from gui.cs: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 + private static void RenderUstr(ConsoleDriver driver, ustring ustr, int width) + { + int used = 0; + int index = 0; + while (index < ustr.Length) { - return GridViewRowList; + (var rune, var size) = Utf8.DecodeRune(ustr, index, index - ustr.Length); + var count = Rune.ColumnWidth(rune); + if (used + count > width) break; + driver.AddRune(rune); + used += count; + index += size; } - // A slightly adapted method from gui.cs: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 - private static void RenderUstr(ConsoleDriver driver, ustring ustr, int col, int line, int width) + while (used < width) { - int used = 0; - int index = 0; - while (index < ustr.Length) - { - (var rune, var size) = Utf8.DecodeRune(ustr, index, index - ustr.Length); - var count = Rune.ColumnWidth(rune); - if (used + count > width) break; - driver.AddRune(rune); - used += count; - index += size; - } - - while (used < width) - { - driver.AddRune(' '); - used++; - } + driver.AddRune(' '); + used++; } } } diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDetails.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDetails.cs deleted file mode 100644 index 7c660f9..0000000 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDetails.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace OutGridView.Cmdlet -{ - internal sealed class GridViewDetails - { - // Contains the width of each column in the grid view. - public int[] ListViewColumnWidths { get; set; } - - // Dictates where the header should actually start considering - // some offset is needed to factor in the checkboxes - public int ListViewOffset { get; set; } - - // The width that is actually useable on the screen after - // subtracting space needed for a clean UI (spaces between columns, etc). - public int UsableWidth { get; set; } - } -} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs index 482fe1d..6006633 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs @@ -2,65 +2,59 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Data; using System.Linq; -using System.Text; using System.Text.RegularExpressions; -namespace OutGridView.Cmdlet +using Microsoft.PowerShell.ConsoleGuiTools.Models; + +namespace Microsoft.PowerShell.ConsoleGuiTools; + +internal static class GridViewHelpers { - internal sealed class GridViewHelpers + // Add all items already selected plus any that match the filter + // The selected items should be at the top of the list, in their original order + internal static List FilterData(List listToFilter, string filter) { - // Add all items already selected plus any that match the filter - // The selected items should be at the top of the list, in their original order - public static List FilterData(List listToFilter, string filter) + var filteredList = new List(); + if (string.IsNullOrEmpty(filter)) { - var filteredList = new List(); - if (string.IsNullOrEmpty(filter)) - { - return listToFilter; - } - - filteredList.AddRange(listToFilter.Where(gvr => gvr.IsMarked)); - filteredList.AddRange(listToFilter.Where(gvr => !gvr.IsMarked && Regex.IsMatch(gvr.DisplayString, filter, RegexOptions.IgnoreCase))); - - return filteredList; + return listToFilter; } - public static string GetPaddedString(List strings, int offset, int[] listViewColumnWidths) - { - var builder = new StringBuilder(); - if (offset > 0) - { - builder.Append(string.Empty.PadRight(offset)); - } + filteredList.AddRange(listToFilter.Where(gvr => gvr.IsMarked)); + filteredList.AddRange(listToFilter.Where(gvr => !gvr.IsMarked && Regex.IsMatch(gvr.DisplayString, filter, RegexOptions.IgnoreCase))); - for (int i = 0; i < strings.Count; i++) - { - if (i > 0) - { - // Add a space between columns - builder.Append(' '); - } + return filteredList; + } - // Replace any newlines with encoded newline/linefeed (`n or `r) - // Note we can't use Environment.Newline because we don't know that the - // Command honors that. - strings[i] = strings[i].Replace("\r", "`r"); - strings[i] = strings[i].Replace("\n", "`n"); + /// + /// Creates the header and data source for the GridView. + /// + /// Dictates where the header should actually start considering + /// some offset is needed to factor in the checkboxes + /// + /// + /// Dictates where the header should actually start considering some offset is needed to factor in the checkboxes + /// and from commandlet inputs. + internal static (GridViewHeader Header, GridViewDataSource DataSource) CreateGridViewInputs(int listViewOffset, int leftMargin, ApplicationData applicationData, object[] properties) + { + var table = FormatHelper.FormatTable(applicationData.Input, applicationData.Force, properties); - // If the string won't fit in the column, append an ellipsis. - if (strings[i].Length > listViewColumnWidths[i]) - { - builder.Append(strings[i], 0, listViewColumnWidths[i] - 3); - builder.Append("..."); - } - else - { - builder.Append(strings[i].PadRight(listViewColumnWidths[i])); - } - } + var gridViewHeader = new GridViewHeader + { + HeaderText = string.Concat(new string(' ', listViewOffset), table.Header), + HeaderUnderLine = string.Concat(new string(' ', listViewOffset), table.HeaderLine) + }; - return builder.ToString(); - } + var gridViewDataSource = new GridViewDataSource(table.Rows.Select((line, index) => new GridViewRow + { + DisplayString = line, + OriginalIndex = index + })); + + return ( + gridViewHeader, + gridViewDataSource); } } diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewRow.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewRow.cs deleted file mode 100644 index 2ecc80e..0000000 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewRow.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace OutGridView.Cmdlet -{ - public class GridViewRow - { - public string DisplayString { get; set; } - public bool IsMarked { get; set; } - public int OriginalIndex { get; set; } - public override string ToString() => DisplayString; - } -} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj index da77cf2..efe4cc8 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -11,6 +11,7 @@ - Add ';https://api.nuget.org/v3/index.json' to the end of the RestoreSources property group below - Uncomment the RestoreSources property group below --> + @@ -20,10 +21,6 @@ - - - - diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index 80c0fe8..b6602c3 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 @@ -9,7 +9,7 @@ RootModule = 'Microsoft.PowerShell.ConsoleGuiTools.dll' # Version number of this module. -ModuleVersion = '0.7.7' +ModuleVersion = '0.8.0' # Supported PSEditions CompatiblePSEditions = @( 'Core' ) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/ApplicationData.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/ApplicationData.cs new file mode 100644 index 0000000..3f37483 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/ApplicationData.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; + +internal class ApplicationData +{ + public string Title { get; set; } + public OutputModeOption OutputMode { get; set; } + public bool PassThru { get; set; } + public string Filter { get; set; } + public bool MinUI { get; set; } + + public bool UseNetDriver { get; set; } + public bool Verbose { get; set; } + public bool Debug { get; set; } + + public string ModuleVersion { get; set; } + + /// + /// Get's the objects from the pipeline. + /// + public IReadOnlyList Input { get; init; } + + /// + /// Gets the Properties parameter. + /// + public object[] Properties { get; init; } + + /// + /// Gets the Force parameter. + /// + public bool Force { get; init; } +} \ No newline at end of file diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewHeader.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewHeader.cs new file mode 100644 index 0000000..da9c4de --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewHeader.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +internal sealed class GridViewHeader +{ + internal string HeaderText { get; init; } + + internal string HeaderUnderLine { get; init; } +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewRow.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewRow.cs new file mode 100644 index 0000000..f7e0dcc --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/GridViewRow.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +public class GridViewRow +{ + public string DisplayString { get; set; } + public bool IsMarked { get; set; } + public int OriginalIndex { get; set; } + public override string ToString() => DisplayString; +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/OutputModeOptions.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/OutputModeOptions.cs new file mode 100644 index 0000000..85cd531 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/OutputModeOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +public enum OutputModeOption +{ + /// + /// None is the default and it means OK and Cancel will not be present + /// and no objects will be written to the pipeline. + /// The selectionMode of the actual list will still be multiple. + /// + None, + /// + /// Allow selection of one single item to be written to the pipeline. + /// + Single, + /// + ///Allow select of multiple items to be written to the pipeline. + /// + Multiple +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Serializers.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Serializers.cs new file mode 100644 index 0000000..0ca9006 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Serializers.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; +using System; +using System.Text; +//TODO: switch to JSON.NET + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +public class Serializers +{ + private readonly static JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.All + }; + + public static string ObjectToJson(T obj) + { + var jsonString = JsonConvert.SerializeObject(obj, jsonSerializerSettings); + + return ToBase64String(jsonString); + } + + public static T ObjectFromJson(string base64Json) + { + var jsonString = FromBase64String(base64Json); + + return JsonConvert.DeserializeObject(jsonString, jsonSerializerSettings); + } + + private static string FromBase64String(string base64string) + { + return Encoding.UTF8.GetString(Convert.FromBase64String(base64string)); + } + + private static string ToBase64String(string str) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)); + } +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Table.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Table.cs new file mode 100644 index 0000000..4a73813 --- /dev/null +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Models/Table.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.ConsoleGuiTools.Models; + +using System.Collections.Generic; +using System.Linq; + +/// +/// Data transfer object containing the formatted table data. +/// +internal class Table +{ + /// + /// Represents an empty table. + /// + internal static Table Empty { get; } = new Table + { + Header = string.Empty, + HeaderLine = string.Empty, + Rows = Enumerable.Empty() + }; + + /// + /// Gets the header of the table with labels for each column in a line. + /// + internal string Header { get; init; } + + /// + /// Gets the line under the header with dashes and spaces ('---- -- --- '). + /// + internal string HeaderLine { get; init; } + + /// + /// Gets the rows of the table. + /// + internal IEnumerable Rows { get; init; } +} diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs index 94bb7e1..ffd8101 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs @@ -6,182 +6,223 @@ using System.Collections.Generic; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Reflection.Metadata; +using Microsoft.PowerShell.ConsoleGuiTools.Models; -using OutGridView.Models; +namespace Microsoft.PowerShell.ConsoleGuiTools; -namespace OutGridView.Cmdlet +[Cmdlet(VerbsData.Out, "ConsoleGridView")] +[Alias("ocgv")] +public class OutConsoleGridViewCmdletCommand : PSCmdlet, IDisposable { - [Cmdlet(VerbsData.Out, "ConsoleGridView")] - [Alias("ocgv")] - public class OutConsoleGridViewCmdletCommand : PSCmdlet, IDisposable + #region Properties + + private const string DataNotQualifiedForGridView = nameof(DataNotQualifiedForGridView); + private const string EnvironmentNotSupportedForGridView = nameof(EnvironmentNotSupportedForGridView); + + private List _psObjects = new List(); + private ConsoleGui _consoleGui = new ConsoleGui(); + + #endregion Properties + + #region Input Parameters + + /// + /// Positional parameter for properties, property sets and table sets + /// specified on the command line. + /// The parameter is optional, since the defaults + /// will be determined using property sets, etc. + /// + /// + /// This parameter will be passed to Format-Table command. + /// Help message is copied from Format-Table documentation. + /// + [Parameter(Position = 0, + HelpMessage = @"Specifies the object properties that appear in the display and the order in which they appear. Type one or more property names, separated by commas, or use a hash table to display a calculated property. Wildcards are permitted. + +If you omit this parameter, the properties that appear in the display depend on the first object's properties. For example, if the first object has PropertyA and PropertyB but subsequent objects have PropertyA, PropertyB, and PropertyC, then only the PropertyA and PropertyB headers are displayed. + +The value of the Property parameter can be a new calculated property. The calculated property can be a script block or a hash table.Valid key-value pairs are: + + - Name (or Label) - + - Expression - or