Forráskód Böngészése

Merge branch 'develop' of tig:gui-cs/Terminal.Gui into develop

Tigger Kindel 2 éve
szülő
commit
34228cc907

+ 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>

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

@@ -82,6 +82,7 @@ namespace Terminal.Gui {
 		bool poll_dirty = true;
 		int [] wakeupPipes = new int [2];
 		static IntPtr ignore = Marshal.AllocHGlobal (1);
+		static IntPtr readHandle = Marshal.AllocHGlobal (1);
 		MainLoop mainLoop;
 		bool winChanged;
 
@@ -97,7 +98,7 @@ namespace Terminal.Gui {
 			this.mainLoop = mainLoop;
 			pipe (wakeupPipes);
 			AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
-				read (wakeupPipes [0], ignore, (IntPtr)1);
+				read (wakeupPipes [0], ignore, readHandle);
 				return true;
 			});
 		}

+ 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);
 

+ 2 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -873,7 +873,7 @@ namespace Terminal.Gui {
 						keyUpHandler (new KeyEvent (map, keyModifiers));
 					}
 				}
-				if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) {
+				if (!inputEvent.KeyEvent.bKeyDown) {
 					keyModifiers = null;
 				}
 				break;
@@ -908,6 +908,7 @@ namespace Terminal.Gui {
 				break;
 
 			case WindowsConsole.EventType.Focus:
+				keyModifiers = null;
 				break;
 			}
 		}

+ 59 - 45
Terminal.Gui/Core/Border.cs

@@ -168,7 +168,6 @@ namespace Terminal.Gui {
 				if (border == null) {
 					Border = new Border () {
 						BorderStyle = BorderStyle.Single,
-						BorderBrush = ColorScheme.Normal.Background,
 						Title = (ustring)title
 					};
 				} else {
@@ -318,8 +317,8 @@ namespace Terminal.Gui {
 		private BorderStyle borderStyle;
 		private bool drawMarginFrame;
 		private Thickness borderThickness;
-		private Color borderBrush;
-		private Color background;
+		private Color? borderBrush;
+		private Color? background;
 		private Thickness padding;
 		private bool effect3D;
 		private Point effect3DOffset = new Point (1, 1);
@@ -374,7 +373,7 @@ namespace Terminal.Gui {
 		/// Gets or sets the <see cref="Color"/> that draws the outer border color.
 		/// </summary>
 		public Color BorderBrush {
-			get => borderBrush;
+			get => borderBrush != null ? (Color)borderBrush : (Color)(-1);
 			set {
 				borderBrush = value;
 				OnBorderChanged ();
@@ -385,7 +384,7 @@ namespace Terminal.Gui {
 		/// Gets or sets the <see cref="Color"/> that fills the area between the bounds of a <see cref="Border"/>.
 		/// </summary>
 		public Color Background {
-			get => background;
+			get => background != null ? (Color)background : (Color)(-1);
 			set {
 				background = value;
 				OnBorderChanged ();
@@ -439,7 +438,6 @@ namespace Terminal.Gui {
 			set {
 				child = value;
 				if (child != null && Parent != null) {
-					Parent.Initialized += Parent_Initialized;
 					Parent.Removed += Parent_Removed;
 				}
 			}
@@ -452,30 +450,6 @@ namespace Terminal.Gui {
 			child.Removed -= Parent_Removed;
 		}
 
-		private void Parent_Initialized (object s, EventArgs e)
-		{
-			SetMarginFrameTitleBrush ();
-			child.Initialized -= Parent_Initialized;
-		}
-
-		private void SetMarginFrameTitleBrush ()
-		{
-			if (child != null) {
-				var view = Parent?.Border != null ? Parent : child;
-				if (view.ColorScheme != null) {
-					if (borderBrush == default) {
-						BorderBrush = view.GetNormalColor ().Foreground;
-					}
-					if (background == default) {
-						Background = view.GetNormalColor ().Background;
-					}
-					return;
-				}
-			}
-			BorderBrush = default;
-			Background = default;
-		}
-
 		/// <summary>
 		/// Gets the parent <see cref="Child"/> parent if any.
 		/// </summary>
@@ -609,7 +583,8 @@ namespace Terminal.Gui {
 			}
 
 			// Draw border thickness
-			driver.SetAttribute (new Attribute (BorderBrush));
+			SetBorderBrush (driver);
+
 			Child.Clear (borderRect);
 
 			borderRect = new Rect () {
@@ -624,7 +599,7 @@ namespace Terminal.Gui {
 				Child.Clear (borderRect);
 			}
 
-			driver.SetAttribute (new Attribute (BorderBrush, Background));
+			SetBorderBrushBackground (driver);
 
 			// Draw margin frame
 			if (DrawMarginFrame) {
@@ -661,7 +636,7 @@ namespace Terminal.Gui {
 
 			var savedAttribute = driver.GetAttribute ();
 
-			driver.SetAttribute (new Attribute (BorderBrush));
+			SetBorderBrush (driver);
 
 			// Draw the upper BorderThickness
 			for (int r = frame.Y - drawMarginFrame - sumThickness.Top;
@@ -703,7 +678,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			driver.SetAttribute (new Attribute (Background));
+			SetBackground (driver);
 
 			// Draw the upper Padding
 			for (int r = frame.Y - drawMarginFrame - padding.Top;
@@ -745,7 +720,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			driver.SetAttribute (new Attribute (BorderBrush, Background));
+			SetBorderBrushBackground (driver);
 
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
@@ -816,7 +791,7 @@ namespace Terminal.Gui {
 
 			var savedAttribute = driver.GetAttribute ();
 
-			driver.SetAttribute (new Attribute (BorderBrush));
+			SetBorderBrush (driver);
 
 			// Draw the upper BorderThickness
 			for (int r = frame.Y;
@@ -858,7 +833,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			driver.SetAttribute (new Attribute (Background));
+			SetBackground (driver);
 
 			// Draw the upper Padding
 			for (int r = frame.Y + borderThickness.Top;
@@ -900,7 +875,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			driver.SetAttribute (new Attribute (BorderBrush, Background));
+			SetBorderBrushBackground (driver);
 
 			// Draw the MarginFrame
 			if (DrawMarginFrame) {
@@ -962,6 +937,37 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 		}
 
+		private void SetBorderBrushBackground (ConsoleDriver driver)
+		{
+			if (borderBrush != null && background != null) {
+				driver.SetAttribute (new Attribute (BorderBrush, Background));
+			} else if (borderBrush != null && background == null) {
+				driver.SetAttribute (new Attribute (BorderBrush, Parent.ColorScheme.Normal.Background));
+			} else if (borderBrush == null && background != null) {
+				driver.SetAttribute (new Attribute (Parent.ColorScheme.Normal.Foreground, Background));
+			} else {
+				driver.SetAttribute (Parent.ColorScheme.Normal);
+			}
+		}
+
+		private void SetBackground (ConsoleDriver driver)
+		{
+			if (background != null) {
+				driver.SetAttribute (new Attribute (Background));
+			} else {
+				driver.SetAttribute (new Attribute (Parent.ColorScheme.Normal.Background));
+			}
+		}
+
+		private void SetBorderBrush (ConsoleDriver driver)
+		{
+			if (borderBrush != null) {
+				driver.SetAttribute (new Attribute (BorderBrush));
+			} else {
+				driver.SetAttribute (new Attribute (Parent.ColorScheme.Normal.Foreground));
+			}
+		}
+
 		private Attribute GetEffect3DBrush ()
 		{
 			return Effect3DBrush == null
@@ -989,9 +995,8 @@ namespace Terminal.Gui {
 		{
 			var driver = Application.Driver;
 			if (DrawMarginFrame) {
-				driver.SetAttribute (new Attribute (BorderBrush, Background));
-				if (view.HasFocus)
-					driver.SetAttribute (new Attribute (Child.ColorScheme.HotNormal.Foreground, Background));
+				SetBorderBrushBackground (driver);
+				SetHotNormalBackground (view, driver);
 				var padding = view.Border.GetSumThickness ();
 				Rect scrRect;
 				if (view == Child) {
@@ -1007,6 +1012,17 @@ namespace Terminal.Gui {
 			driver.SetAttribute (Child.GetNormalColor ());
 		}
 
+		private void SetHotNormalBackground (View view, ConsoleDriver driver)
+		{
+			if (view.HasFocus) {
+				if (background != null) {
+					driver.SetAttribute (new Attribute (Child.ColorScheme.HotNormal.Foreground, Background));
+				} else {
+					driver.SetAttribute (Child.ColorScheme.HotNormal);
+				}
+			}
+		}
+
 		/// <summary>
 		/// Draws the <see cref="View.Text"/> to the screen.
 		/// </summary>
@@ -1016,10 +1032,8 @@ namespace Terminal.Gui {
 		{
 			var driver = Application.Driver;
 			if (DrawMarginFrame) {
-				driver.SetAttribute (new Attribute (BorderBrush, Background));
-				if (view.HasFocus) {
-					driver.SetAttribute (new Attribute (view.ColorScheme.HotNormal.Foreground, Background));
-				}
+				SetBorderBrushBackground (driver);
+				SetHotNormalBackground (view, driver);
 				var padding = Parent.Border.GetSumThickness ();
 				var scrRect = Parent.ViewToScreen (new Rect (0, 0, rect.Width, rect.Height));
 				driver.DrawWindowTitle (scrRect, view.Text,

+ 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);
 				}

+ 8 - 1
Terminal.Gui/Core/Trees/Branch.cs

@@ -61,8 +61,15 @@ namespace Terminal.Gui.Trees {
 				return;
 			}
 
-			var children = tree.TreeBuilder.GetChildren (this.Model) ?? Enumerable.Empty<T> ();
+			IEnumerable<T> children;
 
+			if (Depth >= tree.MaxDepth) {
+				children = Enumerable.Empty<T> ();
+			}
+			else {
+				children = tree.TreeBuilder.GetChildren (this.Model) ?? Enumerable.Empty<T> ();
+			}
+			
 			this.ChildBranches = children.ToDictionary (k => k, val => new Branch<T> (tree, this, val));
 		}
 

+ 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.0</AssemblyVersion>
+    <FileVersion>1.0</FileVersion>
+    <Version>1.0</Version>
+    <InformationalVersion>1.0</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);
+	}
+}

+ 3 - 1
Terminal.Gui/Views/TextView.cs

@@ -2693,8 +2693,9 @@ namespace Terminal.Gui {
 			} else if (currentRow - topRow + BottomOffset >= Frame.Height + offB.height) {
 				topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1 + BottomOffset, 0), currentRow);
 				need = true;
-			} else if (topRow > 0 && currentRow == topRow) {
+			} else if (topRow > 0 && currentRow < topRow) {
 				topRow = Math.Max (topRow - 1, 0);
+				need = true;
 			}
 			if (need) {
 				if (wrapNeeded) {
@@ -4107,6 +4108,7 @@ namespace Terminal.Gui {
 			leftColumn = 0;
 			TrackColumn ();
 			PositionCursor ();
+			SetNeedsDisplay ();
 		}
 
 		bool MoveNext (ref int col, ref int row, out Rune rune)

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

@@ -85,6 +85,11 @@ namespace Terminal.Gui {
 		/// <value></value>
 		public bool MultiSelect { get; set; } = true;
 
+		/// <summary>
+		/// Maximum number of nodes that can be expanded in any given branch.
+		/// </summary>
+		public int MaxDepth { get; set; } = 100;
+
 		/// <summary>
 		/// True makes a letter key press navigate to the next visible branch that begins with
 		/// that letter/digit.
@@ -214,6 +219,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 +553,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 +568,44 @@ namespace Terminal.Gui {
 			return cachedLineMap;
 		}
 
-		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch)
+		private bool IsFilterMatch (Branch<T> branch)
 		{
-			yield return currentBranch;
+			return Filter?.IsMatch(branch.Model) ?? true;
+		}
+
+		private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch,bool parentMatches, out bool match)
+		{
+			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 +1333,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);

+ 3 - 3
UnitTests/Core/BorderTests.cs

@@ -20,8 +20,8 @@ namespace Terminal.Gui.CoreTests {
 			Assert.Equal (BorderStyle.None, b.BorderStyle);
 			Assert.False (b.DrawMarginFrame);
 			Assert.Equal (default, b.BorderThickness);
-			Assert.Equal (default, b.BorderBrush);
-			Assert.Equal (default, b.Background);
+			Assert.Equal ((Color)(-1), b.BorderBrush);
+			Assert.Equal ((Color)(-1), b.Background);
 			Assert.Equal (default, b.Padding);
 			Assert.Equal (0, b.ActualWidth);
 			Assert.Equal (0, b.ActualHeight);
@@ -574,7 +574,7 @@ namespace Terminal.Gui.CoreTests {
 			var lblTop = new Label ("At 0,0");
 			var lblFrame = new Label ("Centered") { X = Pos.Center (), Y = Pos.Center () };
 			var frame = new FrameView () { Y = 1, Width = 20, Height = 3 };
-			var lblFill = new Label () { Width = Dim.Fill(),Height = Dim.Fill(), Visible = false };
+			var lblFill = new Label () { Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
 			var fillText = new System.Text.StringBuilder ();
 			for (int i = 0; i < frame.Bounds.Height; i++) {
 				if (i > 0) {

+ 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 ());
 
 		}
 

+ 2 - 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">

+ 161 - 0
UnitTests/Views/TreeViewTests.cs

@@ -908,6 +908,167 @@ namespace Terminal.Gui.ViewTests {
 				new [] { tv.ColorScheme.Normal, pink });
 		}
 
+		[Fact, AutoInitShutdown]
+		public void TestBottomlessTreeView_MaxDepth_5 ()
+		{
+			var tv = new TreeView<string> () { Width = 20, Height = 10 };
+
+			tv.TreeBuilder = new DelegateTreeBuilder<string> (
+				(s) => new [] { (int.Parse (s) + 1).ToString () }
+				);
+
+			tv.AddObject ("1");
+			tv.ColorScheme = new ColorScheme ();
+
+			tv.LayoutSubviews ();
+			tv.Redraw (tv.Bounds);
+
+			// Nothing expanded
+			TestHelpers.AssertDriverContentsAre (
+@"└+1
+", output);
+			tv.MaxDepth = 5;
+			tv.ExpandAll ();
+
+			tv.Redraw (tv.Bounds);
+
+			// Normal drawing of the tree view
+			TestHelpers.AssertDriverContentsAre (
+@"    
+└-1
+  └-2
+    └-3
+      └-4
+        └-5
+          └─6
+", output);
+			Assert.False (tv.CanExpand ("6"));
+			Assert.False (tv.IsExpanded ("6"));
+
+			tv.Collapse("6");
+
+			Assert.False (tv.CanExpand ("6"));
+			Assert.False (tv.IsExpanded ("6"));
+
+			tv.Collapse ("5");
+
+			Assert.True (tv.CanExpand ("5"));
+			Assert.False (tv.IsExpanded ("5"));
+
+			tv.Redraw (tv.Bounds);
+
+			// Normal drawing of the tree view
+			TestHelpers.AssertDriverContentsAre (
+@"    
+└-1
+  └-2
+    └-3
+      └-4
+        └+5
+", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TestBottomlessTreeView_MaxDepth_3 ()
+		{
+			var tv = new TreeView<string> () { Width = 20, Height = 10 };
+
+			tv.TreeBuilder = new DelegateTreeBuilder<string> (
+				(s) => new [] { (int.Parse (s) + 1).ToString () }
+				);
+
+			tv.AddObject ("1");
+			tv.ColorScheme = new ColorScheme ();
+
+			tv.LayoutSubviews ();
+			tv.Redraw (tv.Bounds);
+
+			// Nothing expanded
+			TestHelpers.AssertDriverContentsAre (
+@"└+1
+", output);
+			tv.MaxDepth = 3;
+			tv.ExpandAll ();
+			tv.Redraw (tv.Bounds);
+
+			// Normal drawing of the tree view
+			TestHelpers.AssertDriverContentsAre (
+@"    
+└-1
+  └-2
+    └-3
+      └─4
+", output);
+		}
+
+		[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 ()
 		{