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
-
-
+
+
@@ -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 ()
{