Browse Source

Merge branch 'develop'

Tig Kindel 2 years ago
parent
commit
8fe38cc675

+ 2 - 2
ReactiveExample/ReactiveExample.csproj

@@ -11,8 +11,8 @@
     <InformationalVersion>1.10.1+6.Branch.main.Sha.f7ee66ddbf8dbcfb0d96af7d63789879091670ec</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="18.4.26" />
-    <PackageReference Include="ReactiveUI" Version="18.4.26" />
+    <PackageReference Include="ReactiveUI.Fody" Version="18.4.44" />
+    <PackageReference Include="ReactiveUI" Version="18.4.44" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.2.3" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>

+ 1 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -256,7 +256,7 @@ namespace Unix.Terminal {
 		/// to avoid the dependency on libc-dev Linux.
 		/// </summary>
 		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 @@ namespace Unix.Terminal {
 				(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);
 

+ 1 - 1
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1494,7 +1494,7 @@ namespace Terminal.Gui {
 					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);
 				}

+ 6 - 6
Terminal.Gui/Terminal.Gui.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>portable</DebugType>
   </PropertyGroup>
@@ -10,10 +10,10 @@
     <!-- Version numbers are automatically updated by gitversion when a release is released -->
     <!-- In the source tree the version will always be 1.0 for all projects. -->
     <!-- Do not modify these. Do NOT commit after manually running `dotnet-gitversion /updateprojectfiles` -->
-    <AssemblyVersion>1.10.1.0</AssemblyVersion>
-    <FileVersion>1.10.1.0</FileVersion>
-    <Version>1.10.1</Version>
-    <InformationalVersion>1.10.1+6.Branch.main.Sha.f7ee66ddbf8dbcfb0d96af7d63789879091670ec</InformationalVersion>
+    <AssemblyVersion>1.11.0.0</AssemblyVersion>
+    <FileVersion>1.11.0.0</FileVersion>
+    <Version>1.11</Version>
+    <InformationalVersion>1.11</InformationalVersion>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
@@ -53,7 +53,7 @@
   <!-- Enable Nuget Source Link for github -->
   <ItemGroup>
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
-    <PackageReference Include="System.Management" Version="7.0.0" />
+    <PackageReference Include="System.Management" Version="7.0.1" />
   </ItemGroup>
   <PropertyGroup>
     <TargetFrameworks>net472;netstandard2.1;net7.0</TargetFrameworks>

+ 14 - 0
Terminal.Gui/Views/ITreeViewFilter.cs

@@ -0,0 +1,14 @@
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// Provides filtering for a <see cref="TreeView"/>.
+	/// </summary>
+	public interface ITreeViewFilter<T> where T : class {
+
+		/// <summary>
+		/// Return <see langword="true"/> if the <paramref name="model"/> should
+		/// be included in the tree.
+		/// </summary>
+		bool IsMatch (T model);
+	}
+}

+ 46 - 7
Terminal.Gui/Views/TreeView.cs

@@ -214,6 +214,13 @@ namespace Terminal.Gui {
 
 		CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
 
+		/// <summary>
+		/// Interface for filtering which lines of the tree are displayed
+		///  e.g. to provide text searching.  Defaults to <see langword="null"/>
+		/// (no filtering).
+		/// </summary>
+		public ITreeViewFilter<T> Filter = null;
+
 		/// <summary>
 		/// Get / Set the wished cursor when the tree is focused.
 		/// Only applies when <see cref="MultiSelect"/> is true.
@@ -541,7 +548,12 @@ namespace Terminal.Gui {
 			List<Branch<T>> toReturn = new List<Branch<T>> ();
 
 			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<Branch<T>> (toReturn);
@@ -551,17 +563,44 @@ namespace Terminal.Gui {
 			return cachedLineMap;
 		}
 
-		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch)
+		private bool IsFilterMatch (Branch<T> branch)
+		{
+			return Filter?.IsMatch(branch.Model) ?? true;
+		}
+
+		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch,bool parentMatches, out bool match)
 		{
-			yield return currentBranch;
+			bool weMatch = IsFilterMatch(currentBranch);
+			bool anyChildMatches = false;
+			
+			var toReturn = new List<Branch<T>>();
+			var children = new List<Branch<T>>();
 
 			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;
 		}
 
 		/// <summary>
@@ -1289,9 +1328,9 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Clears any cached results of <see cref="BuildLineMap"/>
+		/// Clears any cached results of the tree state.
 		/// </summary>
-		protected void InvalidateLineMap ()
+		public void InvalidateLineMap ()
 		{
 			cachedLineMap = null;
 		}

+ 65 - 0
Terminal.Gui/Views/TreeViewTextFilter.cs

@@ -0,0 +1,65 @@
+using System;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// <see cref="ITreeViewFilter{T}"/> implementation which searches the
+	/// <see cref="TreeView{T}.AspectGetter"/> of the model for the given
+	/// <see cref="Text"/>.
+	/// </summary>
+	/// <typeparam name="T"></typeparam>
+	public class TreeViewTextFilter<T> : ITreeViewFilter<T> where T : class {
+		readonly TreeView<T> _forTree;
+
+		/// <summary>
+		/// Creates a new instance of the filter for use with <paramref name="forTree"/>.
+		/// Set <see cref="Text"/> to begin filtering.
+		/// </summary>
+		/// <param name="forTree"></param>
+		/// <exception cref="ArgumentNullException"></exception>
+		public TreeViewTextFilter (TreeView<T> forTree)
+		{
+			_forTree = forTree ?? throw new ArgumentNullException (nameof (forTree));
+		}
+
+		/// <summary>
+		/// The case sensitivity of the search match. 
+		/// Defaults to <see cref="StringComparison.OrdinalIgnoreCase"/>.
+		/// </summary>
+		public StringComparison Comparer { get; set; } = StringComparison.OrdinalIgnoreCase;
+		private string text;
+
+		/// <summary>
+		/// The text that will be searched for in the <see cref="TreeView{T}"/>
+		/// </summary>
+		public string Text {
+			get { return text; }
+			set {
+				text = value;
+				RefreshTreeView ();
+			}
+		}
+
+		private void RefreshTreeView ()
+		{
+			_forTree.InvalidateLineMap ();
+			_forTree.SetNeedsDisplay ();
+		}
+
+		/// <summary>
+		/// Returns <typeparamref name="T"/> if there is no <see cref="Text"/> or
+		/// the text matches the <see cref="TreeView{T}.AspectGetter"/> of the
+		/// <paramref name="model"/>.
+		/// </summary>
+		/// <param name="model"></param>
+		/// <returns></returns>
+		public bool IsMatch (T model)
+		{
+			if (string.IsNullOrWhiteSpace (Text)) {
+				return true;
+			}
+
+			return _forTree.AspectGetter (model)?.IndexOf (Text, Comparer) != -1;
+		}
+	}
+}

+ 20 - 1
UICatalog/Scenarios/ClassExplorer.cs

@@ -84,11 +84,30 @@ namespace UICatalog.Scenarios {
 
 			treeView = new TreeView<object> () {
 				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<object>(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<object> (ChildGetter, CanExpand);

+ 10 - 8
UnitTests/Drivers/ClipboardTests.cs

@@ -102,8 +102,8 @@ namespace Terminal.Gui.DriverTests {
 		[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 @@ else 				Assert.False (Clipboard.IsSupported);
 		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 @@ else 				Assert.NotEqual (clipText, Clipboard.Contents);
 
 			Application.Run ();
 
-			if (!failed) 				Assert.Equal (clipText, getClipText);
+			if (!failed) {
+				Assert.Equal (clipText, getClipText);
+			}
 		}
 
 		[Fact, AutoInitShutdown (useFakeClipboard: false)]
@@ -265,7 +267,7 @@ else 				Assert.NotEqual (clipText, Clipboard.Contents);
 
 			Application.Run ();
 
-			if (!failed) 				Assert.Equal (clipText, clipReadText.TrimEnd ());
+			if (!failed) Assert.Equal (clipText, clipReadText.TrimEnd ());
 
 		}
 

+ 3 - 2
UnitTests/UnitTests.csproj

@@ -21,8 +21,8 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
-    <PackageReference Include="ReportGenerator" Version="5.1.19" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
+    <PackageReference Include="ReportGenerator" Version="5.1.20" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.2" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
@@ -56,5 +56,6 @@
     <IncludeTestAssembly>
       False
     </IncludeTestAssembly>
+    <RunSettingsFilePath>C:\Users\charlie\s\gui-cs\Terminal.Gui\UnitTests\bin\Debug\net7.0\fine-code-coverage\coverage-tool-output\UnitTests-fcc-mscodecoverage-generated.runsettings</RunSettingsFilePath>
   </PropertyGroup>
 </Project>

+ 68 - 0
UnitTests/Views/TreeViewTests.cs

@@ -908,6 +908,74 @@ namespace Terminal.Gui.ViewTests {
 				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<ITreeNode> (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 ()
 		{