diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj index d1c8c9d188..6ec77039a2 100644 --- a/ReactiveExample/ReactiveExample.csproj +++ b/ReactiveExample/ReactiveExample.csproj @@ -11,8 +11,8 @@ 1.10.1+6.Branch.main.Sha.f7ee66ddbf8dbcfb0d96af7d63789879091670ec - - + + diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs index cde4337447..40d51be4f3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs @@ -256,7 +256,7 @@ static class Mono { /// to avoid the dependency on libc-dev Linux. /// static class CoreCLR { -#if NET6_0 +#if NET7_0 // Custom resolver to support true single-file apps // (those which run directly from bundle; in-memory). // -1 on Unix means self-referencing binary (libcoreclr.so) @@ -266,7 +266,6 @@ static CoreCLR() => NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly (string libraryName, Assembly assembly, DllImportSearchPath? searchPath) => libraryName == "libcoreclr.so" ? (IntPtr)(-1) : IntPtr.Zero); #endif - [DllImport ("libcoreclr.so")] internal static extern IntPtr dlopen (string filename, int flags); diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 275396d4d4..1a7fed26c6 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -1494,7 +1494,7 @@ public static (int exitCode, string result) Process (string cmd, string argument process.StandardInput.Close (); } - if (!process.WaitForExit (5000)) { + if (!process.WaitForExit (10000)) { var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; throw new TimeoutException (timeoutError); } diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 77a9093eb0..79dfde6fb8 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -1,4 +1,4 @@ - + portable @@ -10,10 +10,10 @@ - 1.10.1.0 - 1.10.1.0 - 1.10.1 - 1.10.1+6.Branch.main.Sha.f7ee66ddbf8dbcfb0d96af7d63789879091670ec + 1.11.0.0 + 1.11.0.0 + 1.11 + 1.11 @@ -53,7 +53,7 @@ - + net472;netstandard2.1;net7.0 diff --git a/Terminal.Gui/Views/ITreeViewFilter.cs b/Terminal.Gui/Views/ITreeViewFilter.cs new file mode 100644 index 0000000000..6f9aa5afca --- /dev/null +++ b/Terminal.Gui/Views/ITreeViewFilter.cs @@ -0,0 +1,14 @@ +namespace Terminal.Gui { + + /// + /// Provides filtering for a . + /// + public interface ITreeViewFilter where T : class { + + /// + /// Return if the should + /// be included in the tree. + /// + bool IsMatch (T model); + } +} \ No newline at end of file diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs index 7b8942c398..b4c0e4ab8b 100644 --- a/Terminal.Gui/Views/TreeView.cs +++ b/Terminal.Gui/Views/TreeView.cs @@ -214,6 +214,13 @@ public int ScrollOffsetHorizontal { CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible; + /// + /// Interface for filtering which lines of the tree are displayed + /// e.g. to provide text searching. Defaults to + /// (no filtering). + /// + public ITreeViewFilter Filter = null; + /// /// Get / Set the wished cursor when the tree is focused. /// Only applies when is true. @@ -541,7 +548,12 @@ private IReadOnlyCollection> BuildLineMap () List> toReturn = new List> (); foreach (var root in roots.Values) { - toReturn.AddRange (AddToLineMap (root)); + + var toAdd = AddToLineMap (root, false, out var isMatch); + if(isMatch) + { + toReturn.AddRange (toAdd); + } } cachedLineMap = new ReadOnlyCollection> (toReturn); @@ -551,17 +563,44 @@ private IReadOnlyCollection> BuildLineMap () return cachedLineMap; } - private IEnumerable> AddToLineMap (Branch currentBranch) + private bool IsFilterMatch (Branch branch) + { + return Filter?.IsMatch(branch.Model) ?? true; + } + + private IEnumerable> AddToLineMap (Branch currentBranch,bool parentMatches, out bool match) { - yield return currentBranch; + bool weMatch = IsFilterMatch(currentBranch); + bool anyChildMatches = false; + + var toReturn = new List>(); + var children = new List>(); if (currentBranch.IsExpanded) { foreach (var subBranch in currentBranch.ChildBranches.Values) { - foreach (var sub in AddToLineMap (subBranch)) { - yield return sub; + + foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) { + + if(childMatch) + { + children.Add(sub); + anyChildMatches = true; + } } } } + + if(parentMatches || weMatch || anyChildMatches) + { + match = true; + toReturn.Add(currentBranch); + } + else{ + match = false; + } + + toReturn.AddRange(children); + return toReturn; } /// @@ -1289,9 +1328,9 @@ protected void CollapseImpl (T toCollapse, bool all) } /// - /// Clears any cached results of + /// Clears any cached results of the tree state. /// - protected void InvalidateLineMap () + public void InvalidateLineMap () { cachedLineMap = null; } diff --git a/Terminal.Gui/Views/TreeViewTextFilter.cs b/Terminal.Gui/Views/TreeViewTextFilter.cs new file mode 100644 index 0000000000..b861f08aec --- /dev/null +++ b/Terminal.Gui/Views/TreeViewTextFilter.cs @@ -0,0 +1,65 @@ +using System; + +namespace Terminal.Gui { + + /// + /// implementation which searches the + /// of the model for the given + /// . + /// + /// + public class TreeViewTextFilter : ITreeViewFilter where T : class { + readonly TreeView _forTree; + + /// + /// Creates a new instance of the filter for use with . + /// Set to begin filtering. + /// + /// + /// + public TreeViewTextFilter (TreeView forTree) + { + _forTree = forTree ?? throw new ArgumentNullException (nameof (forTree)); + } + + /// + /// The case sensitivity of the search match. + /// Defaults to . + /// + public StringComparison Comparer { get; set; } = StringComparison.OrdinalIgnoreCase; + private string text; + + /// + /// The text that will be searched for in the + /// + public string Text { + get { return text; } + set { + text = value; + RefreshTreeView (); + } + } + + private void RefreshTreeView () + { + _forTree.InvalidateLineMap (); + _forTree.SetNeedsDisplay (); + } + + /// + /// Returns if there is no or + /// the text matches the of the + /// . + /// + /// + /// + public bool IsMatch (T model) + { + if (string.IsNullOrWhiteSpace (Text)) { + return true; + } + + return _forTree.AspectGetter (model)?.IndexOf (Text, Comparer) != -1; + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/ClassExplorer.cs b/UICatalog/Scenarios/ClassExplorer.cs index 650bd099fd..4d9f804455 100644 --- a/UICatalog/Scenarios/ClassExplorer.cs +++ b/UICatalog/Scenarios/ClassExplorer.cs @@ -84,11 +84,30 @@ public override void Setup () treeView = new TreeView () { X = 0, - Y = 0, + Y = 1, Width = Dim.Percent (50), Height = Dim.Fill (), }; + var lblSearch = new Label("Search"); + var tfSearch = new TextField(){ + Width = 20, + X = Pos.Right(lblSearch), + }; + + Win.Add(lblSearch); + Win.Add(tfSearch); + + var filter = new TreeViewTextFilter(treeView); + treeView.Filter = filter; + tfSearch.TextChanged += (s)=>{ + filter.Text = tfSearch.Text.ToString(); + if(treeView.SelectedObject != null) + { + treeView.EnsureVisible(treeView.SelectedObject); + } + }; + treeView.AddObjects (AppDomain.CurrentDomain.GetAssemblies ()); treeView.AspectGetter = GetRepresentation; treeView.TreeBuilder = new DelegateTreeBuilder (ChildGetter, CanExpand); diff --git a/UnitTests/Drivers/ClipboardTests.cs b/UnitTests/Drivers/ClipboardTests.cs index fa96f0961c..d35ef8823c 100644 --- a/UnitTests/Drivers/ClipboardTests.cs +++ b/UnitTests/Drivers/ClipboardTests.cs @@ -102,8 +102,8 @@ public void Contents_Fake_Gets_Sets_When_IsSupportedFalse () [Fact, AutoInitShutdown (useFakeClipboard: false)] public void IsSupported_Get () { - if (Clipboard.IsSupported) Assert.True (Clipboard.IsSupported); -else Assert.False (Clipboard.IsSupported); + if (Clipboard.IsSupported) Assert.True (Clipboard.IsSupported); + else Assert.False (Clipboard.IsSupported); } [Fact, AutoInitShutdown (useFakeClipboard: false)] @@ -129,15 +129,15 @@ public void TryGetClipboardData_Gets_From_OS_Clipboard () public void TrySetClipboardData_Sets_The_OS_Clipboard () { var clipText = "The TrySetClipboardData_Sets_The_OS_Clipboard unit test pasted this to the OS clipboard."; - if (Clipboard.IsSupported) Assert.True (Clipboard.TrySetClipboardData (clipText)); -else Assert.False (Clipboard.TrySetClipboardData (clipText)); + if (Clipboard.IsSupported) Assert.True (Clipboard.TrySetClipboardData (clipText)); + else Assert.False (Clipboard.TrySetClipboardData (clipText)); Application.Iteration += () => Application.RequestStop (); Application.Run (); - if (Clipboard.IsSupported) Assert.Equal (clipText, Clipboard.Contents); -else Assert.NotEqual (clipText, Clipboard.Contents); + if (Clipboard.IsSupported) Assert.Equal (clipText, Clipboard.Contents); + else Assert.NotEqual (clipText, Clipboard.Contents); } @@ -209,7 +209,9 @@ public void Contents_Copies_From_OS_Clipboard () Application.Run (); - if (!failed) Assert.Equal (clipText, getClipText); + if (!failed) { + Assert.Equal (clipText, getClipText); + } } [Fact, AutoInitShutdown (useFakeClipboard: false)] @@ -265,7 +267,7 @@ public void Contents_Pastes_To_OS_Clipboard () Application.Run (); - if (!failed) Assert.Equal (clipText, clipReadText.TrimEnd ()); + if (!failed) Assert.Equal (clipText, clipReadText.TrimEnd ()); } diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index c2fe896f96..32a042255b 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -21,8 +21,8 @@ TRACE;DEBUG_IDISPOSABLE - - + + @@ -56,5 +56,6 @@ False + C:\Users\charlie\s\gui-cs\Terminal.Gui\UnitTests\bin\Debug\net7.0\fine-code-coverage\coverage-tool-output\UnitTests-fcc-mscodecoverage-generated.runsettings \ No newline at end of file diff --git a/UnitTests/Views/TreeViewTests.cs b/UnitTests/Views/TreeViewTests.cs index 3390339235..b253e3196c 100644 --- a/UnitTests/Views/TreeViewTests.cs +++ b/UnitTests/Views/TreeViewTests.cs @@ -908,6 +908,74 @@ public void TestTreeViewColor () new [] { tv.ColorScheme.Normal, pink }); } + [Fact, AutoInitShutdown] + public void TestTreeView_Filter () + { + var tv = new TreeView { Width = 20, Height = 10 }; + + var n1 = new TreeNode ("root one"); + var n1_1 = new TreeNode ("leaf 1"); + var n1_2 = new TreeNode ("leaf 2"); + n1.Children.Add (n1_1); + n1.Children.Add (n1_2); + + var n2 = new TreeNode ("root two"); + tv.AddObject (n1); + tv.AddObject (n2); + tv.Expand (n1); + + tv.ColorScheme = new ColorScheme (); + tv.Redraw (tv.Bounds); + + // Normal drawing of the tree view + TestHelpers.AssertDriverContentsAre ( +@" +├-root one +│ ├─leaf 1 +│ └─leaf 2 +└─root two +", output); + var filter = new TreeViewTextFilter (tv); + tv.Filter = filter; + + // matches nothing + filter.Text = "asdfjhasdf"; + tv.Redraw (tv.Bounds); + // Normal drawing of the tree view + TestHelpers.AssertDriverContentsAre ( +@"", output); + + + // Matches everything + filter.Text = "root"; + tv.Redraw (tv.Bounds); + TestHelpers.AssertDriverContentsAre ( +@" +├-root one +│ ├─leaf 1 +│ └─leaf 2 +└─root two +", output); + // Matches 2 leaf nodes + filter.Text = "leaf"; + tv.Redraw (tv.Bounds); + TestHelpers.AssertDriverContentsAre ( +@" +├-root one +│ ├─leaf 1 +│ └─leaf 2 +", output); + + // Matches 1 leaf nodes + filter.Text = "leaf 1"; + tv.Redraw (tv.Bounds); + TestHelpers.AssertDriverContentsAre ( +@" +├-root one +│ ├─leaf 1 +", output); + } + [Fact, AutoInitShutdown] public void DesiredCursorVisibility_MultiSelect () {