diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7c8439c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: +- package-ecosystem: nuget + directory: "/src/Microsoft.PowerShell.ConsoleGuiTools/" + schedule: + interval: weekly +- package-ecosystem: nuget + directory: "/src/Microsoft.PowerShell.OutGridView.Models/" + schedule: + interval: weekly diff --git a/.vscode/launch.json b/.vscode/launch.json index fcc366c..3858d91 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", + "version": "0.7.0", "configurations": [ { "name": ".NET Core Launch (console)", @@ -14,7 +14,7 @@ "-NoExit", "-NoProfile", "-Command", - "Import-Module ${workspaceFolder}/module/Microsoft.PowerShell.ConsoleGuiTools" + "Import-Module -verbose ${workspaceFolder}/module/Microsoft.PowerShell.ConsoleGuiTools" ], "cwd": "${workspaceFolder}", "console": "integratedTerminal", diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs index 1af21b5..db60d1e 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ConsoleGui.cs @@ -15,14 +15,22 @@ internal class ConsoleGui : IDisposable { private const string FILTER_LABEL = "Filter"; // This adjusts the left margin of all controls - private const int MARGIN_LEFT = 2; + private const int MARGIN_LEFT = 1; // Width of Terminal.Gui ListView selection/check UI elements (old == 4, new == 2) - private const int CHECK_WIDTH = 4; + private const int CHECK_WIDTH = 2; private bool _cancelled; private Label _filterLabel; private TextField _filterField; private ListView _listView; - private GridViewDataSource _itemSource; + // _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; @@ -43,8 +51,9 @@ public HashSet Start(ApplicationData applicationData) List gridHeaders = _applicationData.DataTable.DataColumns.Select((c) => c.Label).ToList(); CalculateColumnWidths(gridHeaders); - // Copy DataTable into the ListView's DataSource - _itemSource = LoadData(); + // 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) { @@ -60,7 +69,9 @@ public HashSet Start(ApplicationData applicationData) // Status bar is where our key-bindings are handled AddStatusBar(!_applicationData.MinUI); - // If -Filter parameter is set, apply it. + // 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(); _listView.SetFocus(); @@ -76,10 +87,8 @@ public HashSet Start(ApplicationData applicationData) return selectedIndexes; } - // Ensure that only items that are marked AND not filtered out - // get returned (See Issue #121) - List itemList = GridViewHelpers.FilterData(_itemSource.GridViewRowList, _applicationData.Filter); - foreach (GridViewRow gvr in itemList) + // Return any items that were selected. + foreach (GridViewRow gvr in _inputSource.GridViewRowList) { if (gvr.IsMarked) { @@ -109,6 +118,7 @@ private GridViewDataSource LoadData() items.Add(new GridViewRow { DisplayString = displayString, + // We use this to keep _inputSource up to date when a filter is applied OriginalIndex = i }); @@ -120,9 +130,22 @@ private GridViewDataSource LoadData() private void ApplyFilter() { - List itemList = GridViewHelpers.FilterData(_itemSource.GridViewRowList, _applicationData.Filter ?? string.Empty); - // Set the ListView to show only the subset defined by the filter - _listView.Source = new GridViewDataSource(itemList); + // 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; + } + + _listViewSource = new GridViewDataSource(GridViewHelpers.FilterData(_inputSource.GridViewRowList, _applicationData.Filter ?? string.Empty)); + _listViewSource.MarkChanged += ListViewSource_MarkChanged; + _listView.Source = _listViewSource; + } + + private void ListViewSource_MarkChanged (object s, GridViewDataSource.RowMarkedEventArgs a) + { + _inputSource.GridViewRowList[a.Row.OriginalIndex].IsMarked = a.Row.IsMarked; } private void Accept() @@ -198,7 +221,7 @@ private void AddStatusBar(bool visible) // 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 && - _itemSource.GridViewRowList.Find(i => i.IsMarked) == null) + _inputSource.GridViewRowList.Find(i => i.IsMarked) == null) { _listView.MarkUnmarkRow(); } @@ -279,7 +302,7 @@ private void AddFilter(Window win) X = Pos.Right(_filterLabel) + 1, Y = Pos.Top(_filterLabel), CanFocus = true, - Width = Dim.Fill() - _filterLabel.Text.Length + Width = Dim.Fill() - 1 }; // TextField captures Ctrl-A (select all text) and Ctrl-D (delete backwards) @@ -315,7 +338,6 @@ private void AddFilter(Window win) filterErrorLabel.Text = ex.Message; filterErrorLabel.ColorScheme = Colors.Error; filterErrorLabel.Redraw(filterErrorLabel.Bounds); - _listView.Source = _itemSource; } }; @@ -370,7 +392,7 @@ private void AddHeaders(Window win, List gridHeaders) private void AddListView(Window win) { - _listView = new ListView(_itemSource); + _listView = new ListView(_inputSource); _listView.X = MARGIN_LEFT; if (!_applicationData.MinUI) { @@ -380,10 +402,11 @@ private void AddListView(Window win) { _listView.Y = 1; // 1 for space, 1 for header, 1 for header underline } - _listView.Width = Dim.Fill(2); + _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); } diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs index d4a24ca..1f7c7e7 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewDataSource.cs @@ -32,9 +32,23 @@ public void Render(ListView container, ConsoleDriver driver, bool selected, int public void SetMark(int item, bool value) { + var oldValue = GridViewRowList[item].IsMarked; GridViewRowList[item].IsMarked = value; + var args = new RowMarkedEventArgs() { + Row = GridViewRowList[item], + OldValue = oldValue + }; + MarkChanged?.Invoke(this, args); } + public class RowMarkedEventArgs : EventArgs { + public GridViewRow Row { get; set;} + public bool OldValue { get ; set;} + + } + + public event EventHandler MarkChanged; + public IList ToList() { return GridViewRowList; diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs index 95e3d76..19ca003 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/GridViewHelpers.cs @@ -14,23 +14,20 @@ namespace OutGridView.Cmdlet { internal class GridViewHelpers { - public static List FilterData(List list, 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 items = new List(); + var filteredList = new List(); if (string.IsNullOrEmpty(filter)) { - filter = ".*"; + return listToFilter; } - foreach (GridViewRow gvr in list) - { - if(Regex.IsMatch(gvr.DisplayString, filter, RegexOptions.IgnoreCase)) - { - items.Add(gvr); - } - } + filteredList.AddRange(listToFilter.Where(gvr => gvr.IsMarked)); + filteredList.AddRange(listToFilter.Where(gvr => !gvr.IsMarked && Regex.IsMatch(gvr.DisplayString, filter, RegexOptions.IgnoreCase))); - return items; + return filteredList; } public static string GetPaddedString(List strings, int offset, int[] listViewColumnWidths) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj index 92854d2..a6270b4 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.csproj @@ -5,9 +5,9 @@ - - - + + + diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/Microsoft.PowerShell.ConsoleGuiTools.psd1 index 2addab4..2f02183 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.2' +ModuleVersion = '0.7.3' # Supported PSEditions CompatiblePSEditions = @( 'Core' ) diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/ModuleLayout.psd1 b/src/Microsoft.PowerShell.ConsoleGuiTools/ModuleLayout.psd1 index 48c9432..b1887f8 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/ModuleLayout.psd1 +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/ModuleLayout.psd1 @@ -2,15 +2,13 @@ RequiredBuildAssets = @{ 'Microsoft.PowerShell.ConsoleGuiTools' = @( 'publish/Microsoft.PowerShell.ConsoleGuiTools.dll', - 'publish/Microsoft.PowerShell.ConsoleGuiTools.pdb', 'publish/Microsoft.PowerShell.ConsoleGuiTools.psd1', 'publish/Terminal.Gui.dll', 'publish/NStack.dll' ) 'Microsoft.PowerShell.OutGridView.Models' = @( - 'publish/Microsoft.PowerShell.OutGridView.Models.dll', - 'publish/Microsoft.PowerShell.OutGridView.Models.pdb' + 'publish/Microsoft.PowerShell.OutGridView.Models.dll' ) } diff --git a/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs b/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs index 4eb3f12..57a0f56 100644 --- a/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs +++ b/src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleGridviewCmdletCommand.cs @@ -109,7 +109,7 @@ baseObject is PSReference || baseObject is PSObject) { ErrorRecord error = new ErrorRecord( - new FormatException("Invalid data type for Out-GridView"), + new FormatException("Invalid data type for Out-ConsoleGridView"), DataNotQualifiedForGridView, ErrorCategory.InvalidType, null); diff --git a/src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj b/src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj index f1760b6..5604e73 100644 --- a/src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj +++ b/src/Microsoft.PowerShell.OutGridView.Models/Microsoft.PowerShell.OutGridView.Models.csproj @@ -4,6 +4,6 @@ net6.0 - +