Skip to content

Commit 1c73f11

Browse files
committed
Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop
2 parents 03f9ba5 + 8f199cd commit 1c73f11

File tree

6 files changed

+214
-10
lines changed

6 files changed

+214
-10
lines changed

Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ static class Mono {
256256
/// to avoid the dependency on libc-dev Linux.
257257
/// </summary>
258258
static class CoreCLR {
259-
#if NET6_0
259+
#if NET7_0
260260
// Custom resolver to support true single-file apps
261261
// (those which run directly from bundle; in-memory).
262262
// -1 on Unix means self-referencing binary (libcoreclr.so)
@@ -266,7 +266,6 @@ static CoreCLR() => NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly
266266
(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
267267
libraryName == "libcoreclr.so" ? (IntPtr)(-1) : IntPtr.Zero);
268268
#endif
269-
270269
[DllImport ("libcoreclr.so")]
271270
internal static extern IntPtr dlopen (string filename, int flags);
272271

Terminal.Gui/Views/ITreeViewFilter.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Terminal.Gui {
2+
3+
/// <summary>
4+
/// Provides filtering for a <see cref="TreeView"/>.
5+
/// </summary>
6+
public interface ITreeViewFilter<T> where T : class {
7+
8+
/// <summary>
9+
/// Return <see langword="true"/> if the <paramref name="model"/> should
10+
/// be included in the tree.
11+
/// </summary>
12+
bool IsMatch (T model);
13+
}
14+
}

Terminal.Gui/Views/TreeView.cs

+46-7
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ public int ScrollOffsetHorizontal {
214214

215215
CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
216216

217+
/// <summary>
218+
/// Interface for filtering which lines of the tree are displayed
219+
/// e.g. to provide text searching. Defaults to <see langword="null"/>
220+
/// (no filtering).
221+
/// </summary>
222+
public ITreeViewFilter<T> Filter = null;
223+
217224
/// <summary>
218225
/// Get / Set the wished cursor when the tree is focused.
219226
/// Only applies when <see cref="MultiSelect"/> is true.
@@ -541,7 +548,12 @@ private IReadOnlyCollection<Branch<T>> BuildLineMap ()
541548
List<Branch<T>> toReturn = new List<Branch<T>> ();
542549

543550
foreach (var root in roots.Values) {
544-
toReturn.AddRange (AddToLineMap (root));
551+
552+
var toAdd = AddToLineMap (root, false, out var isMatch);
553+
if(isMatch)
554+
{
555+
toReturn.AddRange (toAdd);
556+
}
545557
}
546558

547559
cachedLineMap = new ReadOnlyCollection<Branch<T>> (toReturn);
@@ -551,17 +563,44 @@ private IReadOnlyCollection<Branch<T>> BuildLineMap ()
551563
return cachedLineMap;
552564
}
553565

554-
private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch)
566+
private bool IsFilterMatch (Branch<T> branch)
567+
{
568+
return Filter?.IsMatch(branch.Model) ?? true;
569+
}
570+
571+
private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch,bool parentMatches, out bool match)
555572
{
556-
yield return currentBranch;
573+
bool weMatch = IsFilterMatch(currentBranch);
574+
bool anyChildMatches = false;
575+
576+
var toReturn = new List<Branch<T>>();
577+
var children = new List<Branch<T>>();
557578

558579
if (currentBranch.IsExpanded) {
559580
foreach (var subBranch in currentBranch.ChildBranches.Values) {
560-
foreach (var sub in AddToLineMap (subBranch)) {
561-
yield return sub;
581+
582+
foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {
583+
584+
if(childMatch)
585+
{
586+
children.Add(sub);
587+
anyChildMatches = true;
588+
}
562589
}
563590
}
564591
}
592+
593+
if(parentMatches || weMatch || anyChildMatches)
594+
{
595+
match = true;
596+
toReturn.Add(currentBranch);
597+
}
598+
else{
599+
match = false;
600+
}
601+
602+
toReturn.AddRange(children);
603+
return toReturn;
565604
}
566605

567606
/// <summary>
@@ -1289,9 +1328,9 @@ protected void CollapseImpl (T toCollapse, bool all)
12891328
}
12901329

12911330
/// <summary>
1292-
/// Clears any cached results of <see cref="BuildLineMap"/>
1331+
/// Clears any cached results of the tree state.
12931332
/// </summary>
1294-
protected void InvalidateLineMap ()
1333+
public void InvalidateLineMap ()
12951334
{
12961335
cachedLineMap = null;
12971336
}
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System;
2+
3+
namespace Terminal.Gui {
4+
5+
/// <summary>
6+
/// <see cref="ITreeViewFilter{T}"/> implementation which searches the
7+
/// <see cref="TreeView{T}.AspectGetter"/> of the model for the given
8+
/// <see cref="Text"/>.
9+
/// </summary>
10+
/// <typeparam name="T"></typeparam>
11+
public class TreeViewTextFilter<T> : ITreeViewFilter<T> where T : class {
12+
readonly TreeView<T> _forTree;
13+
14+
/// <summary>
15+
/// Creates a new instance of the filter for use with <paramref name="forTree"/>.
16+
/// Set <see cref="Text"/> to begin filtering.
17+
/// </summary>
18+
/// <param name="forTree"></param>
19+
/// <exception cref="ArgumentNullException"></exception>
20+
public TreeViewTextFilter (TreeView<T> forTree)
21+
{
22+
_forTree = forTree ?? throw new ArgumentNullException (nameof (forTree));
23+
}
24+
25+
/// <summary>
26+
/// The case sensitivity of the search match.
27+
/// Defaults to <see cref="StringComparison.OrdinalIgnoreCase"/>.
28+
/// </summary>
29+
public StringComparison Comparer { get; set; } = StringComparison.OrdinalIgnoreCase;
30+
private string text;
31+
32+
/// <summary>
33+
/// The text that will be searched for in the <see cref="TreeView{T}"/>
34+
/// </summary>
35+
public string Text {
36+
get { return text; }
37+
set {
38+
text = value;
39+
RefreshTreeView ();
40+
}
41+
}
42+
43+
private void RefreshTreeView ()
44+
{
45+
_forTree.InvalidateLineMap ();
46+
_forTree.SetNeedsDisplay ();
47+
}
48+
49+
/// <summary>
50+
/// Returns <typeparamref name="T"/> if there is no <see cref="Text"/> or
51+
/// the text matches the <see cref="TreeView{T}.AspectGetter"/> of the
52+
/// <paramref name="model"/>.
53+
/// </summary>
54+
/// <param name="model"></param>
55+
/// <returns></returns>
56+
public bool IsMatch (T model)
57+
{
58+
if (string.IsNullOrWhiteSpace (Text)) {
59+
return true;
60+
}
61+
62+
return _forTree.AspectGetter (model)?.IndexOf (Text, Comparer) != -1;
63+
}
64+
}
65+
}

UICatalog/Scenarios/ClassExplorer.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,30 @@ public override void Setup ()
8484

8585
treeView = new TreeView<object> () {
8686
X = 0,
87-
Y = 0,
87+
Y = 1,
8888
Width = Dim.Percent (50),
8989
Height = Dim.Fill (),
9090
};
9191

92+
var lblSearch = new Label("Search");
93+
var tfSearch = new TextField(){
94+
Width = 20,
95+
X = Pos.Right(lblSearch),
96+
};
97+
98+
Win.Add(lblSearch);
99+
Win.Add(tfSearch);
100+
101+
var filter = new TreeViewTextFilter<object>(treeView);
102+
treeView.Filter = filter;
103+
tfSearch.TextChanged += (s)=>{
104+
filter.Text = tfSearch.Text.ToString();
105+
if(treeView.SelectedObject != null)
106+
{
107+
treeView.EnsureVisible(treeView.SelectedObject);
108+
}
109+
};
110+
92111
treeView.AddObjects (AppDomain.CurrentDomain.GetAssemblies ());
93112
treeView.AspectGetter = GetRepresentation;
94113
treeView.TreeBuilder = new DelegateTreeBuilder<object> (ChildGetter, CanExpand);

UnitTests/Views/TreeViewTests.cs

+68
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,74 @@ public void TestTreeViewColor ()
908908
new [] { tv.ColorScheme.Normal, pink });
909909
}
910910

911+
[Fact, AutoInitShutdown]
912+
public void TestTreeView_Filter ()
913+
{
914+
var tv = new TreeView { Width = 20, Height = 10 };
915+
916+
var n1 = new TreeNode ("root one");
917+
var n1_1 = new TreeNode ("leaf 1");
918+
var n1_2 = new TreeNode ("leaf 2");
919+
n1.Children.Add (n1_1);
920+
n1.Children.Add (n1_2);
921+
922+
var n2 = new TreeNode ("root two");
923+
tv.AddObject (n1);
924+
tv.AddObject (n2);
925+
tv.Expand (n1);
926+
927+
tv.ColorScheme = new ColorScheme ();
928+
tv.Redraw (tv.Bounds);
929+
930+
// Normal drawing of the tree view
931+
TestHelpers.AssertDriverContentsAre (
932+
@"
933+
├-root one
934+
│ ├─leaf 1
935+
│ └─leaf 2
936+
└─root two
937+
", output);
938+
var filter = new TreeViewTextFilter<ITreeNode> (tv);
939+
tv.Filter = filter;
940+
941+
// matches nothing
942+
filter.Text = "asdfjhasdf";
943+
tv.Redraw (tv.Bounds);
944+
// Normal drawing of the tree view
945+
TestHelpers.AssertDriverContentsAre (
946+
@"", output);
947+
948+
949+
// Matches everything
950+
filter.Text = "root";
951+
tv.Redraw (tv.Bounds);
952+
TestHelpers.AssertDriverContentsAre (
953+
@"
954+
├-root one
955+
│ ├─leaf 1
956+
│ └─leaf 2
957+
└─root two
958+
", output);
959+
// Matches 2 leaf nodes
960+
filter.Text = "leaf";
961+
tv.Redraw (tv.Bounds);
962+
TestHelpers.AssertDriverContentsAre (
963+
@"
964+
├-root one
965+
│ ├─leaf 1
966+
│ └─leaf 2
967+
", output);
968+
969+
// Matches 1 leaf nodes
970+
filter.Text = "leaf 1";
971+
tv.Redraw (tv.Bounds);
972+
TestHelpers.AssertDriverContentsAre (
973+
@"
974+
├-root one
975+
│ ├─leaf 1
976+
", output);
977+
}
978+
911979
[Fact, AutoInitShutdown]
912980
public void DesiredCursorVisibility_MultiSelect ()
913981
{

0 commit comments

Comments
 (0)