From 7e4253cf28428ea80a4773b137d4bd89cf321746 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 30 Mar 2025 12:16:46 +0100 Subject: [PATCH 01/10] Add class for detecting information about console in extensible way --- .../FeatureDetection/ConsoleFeatureFinder.cs | 59 +++++++++++++++++++ .../ConsoleFeatureFinderResults.cs | 16 +++++ .../FeatureDetection/WindowsFeatureSet.cs | 15 +++++ .../ConsoleDrivers/V2/ApplicationV2.cs | 8 ++- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs create mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs create mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs new file mode 100644 index 0000000000..1ce9c5af34 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Win32; + +namespace Terminal.Gui; + +/// +/// Attempts to determine information about the terminal and what features it +/// does/not support based on runtime operations e.g. registry etc +/// +internal class ConsoleFeatureFinder +{ + public ConsoleFeatureFinderResults GetResults () + { + var results = new ConsoleFeatureFinderResults (); + + PlatformID p = Environment.OSVersion.Platform; + results.IsWindows = p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows; + + if (results.IsWindows) + { + DetectWindowsSpecificFeatures (results.Windows); + } + + return results; + } + + private void DetectWindowsSpecificFeatures (WindowsFeatureSet windowsFeatures) + { + windowsFeatures.ConHostLegacyMode = IsLegacyConsoleEnabled (); + } + + bool IsLegacyConsoleEnabled () + { + try + { + using (RegistryKey key = Registry.CurrentUser.OpenSubKey (@"Console")) + { + if (key != null) + { + object value = key.GetValue ("ForceV2"); + if (value is int intValue) + { + return intValue == 0; // Legacy Mode enabled if ForceV2 is 0 + } + } + } + } + catch (Exception ex) + { + Logging.Warning ("Error reading registry: " + ex.Message); + } + + return false; // Assume new console mode if check fails + } +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs new file mode 100644 index 0000000000..fd9fa6e8e6 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs @@ -0,0 +1,16 @@ +namespace Terminal.Gui; + +/// +/// Results of console feature detection +/// +internal class ConsoleFeatureFinderResults +{ + public WindowsFeatureSet Windows { get; set; } = new WindowsFeatureSet(); + public bool IsWindows { get; set; } + + /// + public override string ToString () + { + return $"{nameof(IsWindows)}:{IsWindows} {nameof(Windows)}:{Windows}"; + } +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs new file mode 100644 index 0000000000..1d54ab5ac7 --- /dev/null +++ b/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs @@ -0,0 +1,15 @@ +using static Unix.Terminal.Curses; + +namespace Terminal.Gui; + +/// +/// Features specific to the windows operating system +/// +internal class WindowsFeatureSet +{ + + public bool ConHostLegacyMode { get; set; } + + + public override string ToString () { return $"{nameof(ConHostLegacyMode)}:{ConHostLegacyMode}"; } +} diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs index 9baeba301f..f4500eef09 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs @@ -85,11 +85,15 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n private void CreateDriver (string? driverName) { - PlatformID p = Environment.OSVersion.Platform; bool definetlyWin = driverName?.Contains ("win") ?? false; bool definetlyNet = driverName?.Contains ("net") ?? false; + var finder = new ConsoleFeatureFinder (); + var result = finder.GetResults (); + + Logging.Logger.LogInformation ($"Feature detection results:{ result}"); + if (definetlyWin) { _coordinator = CreateWindowsSubcomponents (); @@ -98,7 +102,7 @@ private void CreateDriver (string? driverName) { _coordinator = CreateNetSubcomponents (); } - else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) + else if (result.IsWindows) { _coordinator = CreateWindowsSubcomponents (); } From 564e6f1424a847ea2ce8cdcbf6c793ac98d8fff8 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 12:13:07 +0100 Subject: [PATCH 02/10] WIP - Create test for reordering --- TerminalGuiFluentTesting/GuiTestContext.cs | 35 ++++++++- .../FluentTests/BasicFluentAssertionTests.cs | 14 +--- .../FluentTests/TestOutputWriter.cs | 15 ++++ .../FluentTests/TreeViewFluentTests.cs | 77 +++++++++++++++++++ 4 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 Tests/IntegrationTests/FluentTests/TestOutputWriter.cs create mode 100644 Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs diff --git a/TerminalGuiFluentTesting/GuiTestContext.cs b/TerminalGuiFluentTesting/GuiTestContext.cs index 9a1195df81..412fd8ed31 100644 --- a/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/TerminalGuiFluentTesting/GuiTestContext.cs @@ -243,7 +243,18 @@ public GuiTestContext WaitIteration (Action? a = null) /// public GuiTestContext Then (Action doAction) { - doAction (); + try + { + doAction (); + } + catch(Exception) + { + Stop (); + _hardStop.Cancel(); + + throw; + + } return this; } @@ -360,6 +371,7 @@ public GuiTestContext Right () { SendNetKey (k); } + WaitIteration (); break; default: throw new ArgumentOutOfRangeException (); @@ -548,4 +560,25 @@ private void SendWindowsKey (ConsoleKeyMapping.VK specialKey) WaitIteration (); } + + /// + /// Sets the input focus to the given . + /// Throws if focus did not change due to system + /// constraints e.g. + /// is + /// + /// + /// + /// + public GuiTestContext Focus (View toFocus) + { + toFocus.FocusDeepest (NavigationDirection.Forward, TabBehavior.TabStop); + + if (!toFocus.HasFocus) + { + throw new ArgumentException ("Failed to set focus, FocusDeepest did not result in HasFocus becoming true. Ensure view is added and focusable"); + } + + return WaitIteration (); + } } diff --git a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs index 345d7acc19..f79bd1f97b 100644 --- a/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs +++ b/Tests/IntegrationTests/FluentTests/BasicFluentAssertionTests.cs @@ -1,5 +1,4 @@ -using System.Text; -using Terminal.Gui; +using Terminal.Gui; using TerminalGuiFluentTesting; using Xunit.Abstractions; @@ -9,17 +8,6 @@ public class BasicFluentAssertionTests { private readonly TextWriter _out; - public class TestOutputWriter : TextWriter - { - private readonly ITestOutputHelper _output; - - public TestOutputWriter (ITestOutputHelper output) { _output = output; } - - public override void WriteLine (string? value) { _output.WriteLine (value ?? string.Empty); } - - public override Encoding Encoding => Encoding.UTF8; - } - public BasicFluentAssertionTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); } [Theory] diff --git a/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs b/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs new file mode 100644 index 0000000000..62e40e5aea --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/TestOutputWriter.cs @@ -0,0 +1,15 @@ +using System.Text; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; + +public class TestOutputWriter : TextWriter +{ + private readonly ITestOutputHelper _output; + + public TestOutputWriter (ITestOutputHelper output) { _output = output; } + + public override void WriteLine (string? value) { _output.WriteLine (value ?? string.Empty); } + + public override Encoding Encoding => Encoding.UTF8; +} diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs new file mode 100644 index 0000000000..7d521f328f --- /dev/null +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Terminal.Gui; +using TerminalGuiFluentTesting; +using Xunit.Abstractions; + +namespace IntegrationTests.FluentTests; + +public class TreeViewFluentTests +{ + + private readonly TextWriter _out; + + public TreeViewFluentTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); } + + [Theory] + [ClassData (typeof (V2TestDrivers))] + public void TreeView_AllowReOrdering (V2TestDriver d) + { + var tv = new TreeView () + { + Width = Dim.Fill(), + Height = Dim.Fill() + }; + + TreeNode car; + TreeNode lorry; + TreeNode bike; + + var root = new TreeNode ("Root") + { + Children = [ + car = new TreeNode("Car"), + lorry = new TreeNode("Lorry"), + bike = new TreeNode("Bike") + ] + }; + + tv.AddObject (root); + + + using GuiTestContext context = + With.A (40, 10, d) + .Add (tv) + .Focus(tv) + .WaitIteration () + .ScreenShot ("Before expanding",_out) + .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) + .Then (() => Assert.Null (tv.GetObjectOnRow (1))) + .Right () + .ScreenShot ("After expanding",_out) + .Then (()=>Assert.Equal (root,tv.GetObjectOnRow (0))) + .Then (() => Assert.Equal (car, tv.GetObjectOnRow (1))) + .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (2))) + .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (3))) + .Then ( + () => + { + // Re order + root.Children = [bike, car, lorry]; + tv.RefreshObject (root); + }) + .WaitIteration () + .ScreenShot ("After re-order",_out) + .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) + .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (1))) + .Then (() => Assert.Equal (car, tv.GetObjectOnRow (2))) + .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (3))) + .WriteOutLogs (_out); + + context.Stop (); + } + +} From 75a7159a32e099481d9373b1f724f306bde038b9 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 12:28:37 +0100 Subject: [PATCH 03/10] Change Dictionary to List and preserve TreeBuilder order --- Terminal.Gui/Views/TreeView/Branch.cs | 36 ++++++++++++++----------- Terminal.Gui/Views/TreeView/TreeView.cs | 4 +-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index e7a5eb4ca4..96df0b0b92 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -27,7 +27,7 @@ public Branch (TreeView tree, Branch parentBranchIfAny, T model) /// The children of the current branch. This is null until the first call to to avoid /// enumerating the entire underlying hierarchy. /// - public Dictionary> ChildBranches { get; set; } + public List> ChildBranches { get; set; } /// The depth of the current branch. Depth of 0 indicates root level branches. public int Depth { get; } @@ -275,14 +275,14 @@ public virtual void FetchChildren () if (Depth >= tree.MaxDepth) { - children = Enumerable.Empty (); + children = []; } else { - children = tree.TreeBuilder.GetChildren (Model) ?? Enumerable.Empty (); + children = tree.TreeBuilder.GetChildren (Model) ?? []; } - ChildBranches = children.ToDictionary (k => k, val => new Branch (tree, this, val)); + ChildBranches = children.Select (o=>new Branch (tree, this, o)).ToList (); } /// @@ -340,10 +340,10 @@ public void Refresh (bool startAtTop) // we already knew about some children so preserve the state of the old children // first gather the new Children - IEnumerable newChildren = tree.TreeBuilder?.GetChildren (Model) ?? Enumerable.Empty (); + T[] newChildren = tree.TreeBuilder?.GetChildren (Model).ToArray () ?? []; // Children who no longer appear need to go - foreach (T toRemove in ChildBranches.Keys.Except (newChildren).ToArray ()) + foreach (Branch toRemove in ChildBranches.Where (b=>!newChildren.Contains(b.Model)).ToArray ()) { ChildBranches.Remove (toRemove); @@ -357,17 +357,21 @@ public void Refresh (bool startAtTop) // New children need to be added foreach (T newChild in newChildren) { + Branch existingBranch = ChildBranches.FirstOrDefault (b => b.Model.Equals (newChild)); // If we don't know about the child, yet we need a new branch - if (!ChildBranches.ContainsKey (newChild)) + if (existingBranch == null) { - ChildBranches.Add (newChild, new Branch (tree, this, newChild)); + ChildBranches.Add (new (tree, this, newChild)); } else { //we already have this object but update the reference anyway in case Equality match but the references are new - ChildBranches [newChild].Model = newChild; + existingBranch.Model = newChild; } } + + // Order the list + ChildBranches = ChildBranches.OrderBy (b => newChildren.IndexOf (b.Model)).ToList (); } } @@ -381,9 +385,9 @@ internal void CollapseAll () if (ChildBranches is { }) { - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.CollapseAll (); + child.CollapseAll (); } } } @@ -395,9 +399,9 @@ internal void ExpandAll () if (ChildBranches is { }) { - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.ExpandAll (); + child.ExpandAll (); } } } @@ -487,9 +491,9 @@ internal void Rebuild () if (IsExpanded) { // if we are expanded we need to update the visible children - foreach (KeyValuePair> child in ChildBranches) + foreach (Branch child in ChildBranches) { - child.Value.Rebuild (); + child.Rebuild (); } } else @@ -526,7 +530,7 @@ private bool IsLast () return this == tree.roots.Values.LastOrDefault (); } - return Parent.ChildBranches.Values.LastOrDefault () == this; + return Parent.ChildBranches.LastOrDefault () == this; } private static Cell NewCell (Attribute attr, Rune r) { return new Cell { Rune = r, Attribute = new (attr) }; } diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index 4ff0a3c89f..a4b33bfe5f 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -847,7 +847,7 @@ public IEnumerable GetChildren (T o) return new T [0]; } - return branch.ChildBranches?.Values?.Select (b => b.Model)?.ToArray () ?? new T [0]; + return branch.ChildBranches?.Select (b => b.Model)?.ToArray () ?? new T [0]; } /// Returns the maximum width line in the tree including prefix and expansion symbols. @@ -1488,7 +1488,7 @@ private IEnumerable> AddToLineMap (Branch currentBranch, bool paren if (currentBranch.IsExpanded) { - foreach (Branch subBranch in currentBranch.ChildBranches.Values) + foreach (Branch subBranch in currentBranch.ChildBranches) { foreach (Branch sub in AddToLineMap (subBranch, weMatch, out bool childMatch)) { From c3aa947b2a3c2bee2d101bfe858c07ff4ac4a64f Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 12:47:02 +0100 Subject: [PATCH 04/10] Add test to ensure branch expansion/status remains consistent despite reorder --- .../FluentTests/TreeViewFluentTests.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs index 7d521f328f..847d2031b6 100644 --- a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -74,4 +74,91 @@ public void TreeView_AllowReOrdering (V2TestDriver d) context.Stop (); } + [Theory] + [ClassData (typeof (V2TestDrivers))] + public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) + { + var tv = new TreeView () + { + Width = Dim.Fill (), + Height = Dim.Fill () + }; + + TreeNode car; + TreeNode lorry; + TreeNode bike; + + TreeNode mrA; + TreeNode mrB; + + TreeNode mrC; + + TreeNode mrD; + TreeNode mrE; + + var root = new TreeNode ("Root") + { + Children = [ + car = new TreeNode("Car") + { + Children = [ + mrA = new TreeNode("Mr A"), + mrB = new TreeNode("Mr B") + ] + }, + lorry = new TreeNode("Lorry") + { + Children = [ + mrC = new TreeNode("Mr C"), + ] + }, + bike = new TreeNode("Bike") + { + Children = [ + mrD = new TreeNode("Mr D"), + mrE = new TreeNode("Mr E") + ] + } + ] + }; + + tv.AddObject (root); + tv.ExpandAll(); + + using GuiTestContext context = + With.A (40, 13, d) + .Add (tv) + .WaitIteration () + .ScreenShot ("Initial State", _out) + .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) + .Then (() => Assert.Equal (car, tv.GetObjectOnRow (1))) + .Then (() => Assert.Equal (mrA, tv.GetObjectOnRow (2))) + .Then (() => Assert.Equal (mrB, tv.GetObjectOnRow (3))) + .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (4))) + .Then (() => Assert.Equal (mrC, tv.GetObjectOnRow (5))) + .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (6))) + .Then (() => Assert.Equal (mrD, tv.GetObjectOnRow (7))) + .Then (() => Assert.Equal (mrE, tv.GetObjectOnRow (8))) + .Then ( + () => + { + // Re order + root.Children = [bike, car, lorry]; + tv.RefreshObject (root); + }) + .WaitIteration () + .ScreenShot ("After re-order", _out) + .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) + .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (1))) + .Then (() => Assert.Equal (mrD, tv.GetObjectOnRow (2))) + .Then (() => Assert.Equal (mrE, tv.GetObjectOnRow (3))) + .Then (() => Assert.Equal (car, tv.GetObjectOnRow (4))) + .Then (() => Assert.Equal (mrA, tv.GetObjectOnRow (5))) + .Then (() => Assert.Equal (mrB, tv.GetObjectOnRow (6))) + .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (7))) + .Then (() => Assert.Equal (mrC, tv.GetObjectOnRow (8))) + .WriteOutLogs (_out); + + context.Stop (); + } } From 387372b8f36753c840b4df71788d52488af88ee9 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 12:49:01 +0100 Subject: [PATCH 05/10] Cleanup code --- .../FluentTests/TreeViewFluentTests.cs | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs index 847d2031b6..ed931074d7 100644 --- a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Terminal.Gui; +using Terminal.Gui; using TerminalGuiFluentTesting; using Xunit.Abstractions; @@ -11,7 +6,6 @@ namespace IntegrationTests.FluentTests; public class TreeViewFluentTests { - private readonly TextWriter _out; public TreeViewFluentTests (ITestOutputHelper outputHelper) { _out = new TestOutputWriter (outputHelper); } @@ -20,10 +14,10 @@ public class TreeViewFluentTests [ClassData (typeof (V2TestDrivers))] public void TreeView_AllowReOrdering (V2TestDriver d) { - var tv = new TreeView () + var tv = new TreeView { - Width = Dim.Fill(), - Height = Dim.Fill() + Width = Dim.Fill (), + Height = Dim.Fill () }; TreeNode car; @@ -32,27 +26,27 @@ public void TreeView_AllowReOrdering (V2TestDriver d) var root = new TreeNode ("Root") { - Children = [ - car = new TreeNode("Car"), - lorry = new TreeNode("Lorry"), - bike = new TreeNode("Bike") - ] + Children = + [ + car = new ("Car"), + lorry = new ("Lorry"), + bike = new ("Bike") + ] }; tv.AddObject (root); - using GuiTestContext context = With.A (40, 10, d) .Add (tv) - .Focus(tv) + .Focus (tv) .WaitIteration () - .ScreenShot ("Before expanding",_out) + .ScreenShot ("Before expanding", _out) .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) .Then (() => Assert.Null (tv.GetObjectOnRow (1))) .Right () - .ScreenShot ("After expanding",_out) - .Then (()=>Assert.Equal (root,tv.GetObjectOnRow (0))) + .ScreenShot ("After expanding", _out) + .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) .Then (() => Assert.Equal (car, tv.GetObjectOnRow (1))) .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (2))) .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (3))) @@ -64,7 +58,7 @@ public void TreeView_AllowReOrdering (V2TestDriver d) tv.RefreshObject (root); }) .WaitIteration () - .ScreenShot ("After re-order",_out) + .ScreenShot ("After re-order", _out) .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (1))) .Then (() => Assert.Equal (car, tv.GetObjectOnRow (2))) @@ -78,7 +72,7 @@ public void TreeView_AllowReOrdering (V2TestDriver d) [ClassData (typeof (V2TestDrivers))] public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) { - var tv = new TreeView () + var tv = new TreeView { Width = Dim.Fill (), Height = Dim.Fill () @@ -98,32 +92,36 @@ public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) var root = new TreeNode ("Root") { - Children = [ - car = new TreeNode("Car") - { - Children = [ - mrA = new TreeNode("Mr A"), - mrB = new TreeNode("Mr B") - ] - }, - lorry = new TreeNode("Lorry") - { - Children = [ - mrC = new TreeNode("Mr C"), - ] - }, - bike = new TreeNode("Bike") - { - Children = [ - mrD = new TreeNode("Mr D"), - mrE = new TreeNode("Mr E") - ] - } - ] + Children = + [ + car = new ("Car") + { + Children = + [ + mrA = new ("Mr A"), + mrB = new ("Mr B") + ] + }, + lorry = new ("Lorry") + { + Children = + [ + mrC = new ("Mr C") + ] + }, + bike = new ("Bike") + { + Children = + [ + mrD = new ("Mr D"), + mrE = new ("Mr E") + ] + } + ] }; tv.AddObject (root); - tv.ExpandAll(); + tv.ExpandAll (); using GuiTestContext context = With.A (40, 13, d) From 222ff34429a50278e2465b5f3b72c826a4bf3afd Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 12:56:16 +0100 Subject: [PATCH 06/10] Fix regression when removed child was the selected one --- Terminal.Gui/Views/TreeView/Branch.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 96df0b0b92..4bc24af6cf 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -348,7 +348,7 @@ public void Refresh (bool startAtTop) ChildBranches.Remove (toRemove); //also if the user has this node selected (its disappearing) so lets change selection to us (the parent object) to be helpful - if (Equals (tree.SelectedObject, toRemove)) + if (Equals (tree.SelectedObject, toRemove.Model)) { tree.SelectedObject = Model; } From 1bc647d2daac8276d6fed3eace1c7bf83d1ba320 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 3 Apr 2025 13:01:53 +0100 Subject: [PATCH 07/10] Revert "Add class for detecting information about console in extensible way" This reverts commit 7e4253cf28428ea80a4773b137d4bd89cf321746. --- .../FeatureDetection/ConsoleFeatureFinder.cs | 59 ------------------- .../ConsoleFeatureFinderResults.cs | 16 ----- .../FeatureDetection/WindowsFeatureSet.cs | 15 ----- .../ConsoleDrivers/V2/ApplicationV2.cs | 8 +-- 4 files changed, 2 insertions(+), 96 deletions(-) delete mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs delete mode 100644 Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs deleted file mode 100644 index 1ce9c5af34..0000000000 --- a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.Win32; - -namespace Terminal.Gui; - -/// -/// Attempts to determine information about the terminal and what features it -/// does/not support based on runtime operations e.g. registry etc -/// -internal class ConsoleFeatureFinder -{ - public ConsoleFeatureFinderResults GetResults () - { - var results = new ConsoleFeatureFinderResults (); - - PlatformID p = Environment.OSVersion.Platform; - results.IsWindows = p is PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows; - - if (results.IsWindows) - { - DetectWindowsSpecificFeatures (results.Windows); - } - - return results; - } - - private void DetectWindowsSpecificFeatures (WindowsFeatureSet windowsFeatures) - { - windowsFeatures.ConHostLegacyMode = IsLegacyConsoleEnabled (); - } - - bool IsLegacyConsoleEnabled () - { - try - { - using (RegistryKey key = Registry.CurrentUser.OpenSubKey (@"Console")) - { - if (key != null) - { - object value = key.GetValue ("ForceV2"); - if (value is int intValue) - { - return intValue == 0; // Legacy Mode enabled if ForceV2 is 0 - } - } - } - } - catch (Exception ex) - { - Logging.Warning ("Error reading registry: " + ex.Message); - } - - return false; // Assume new console mode if check fails - } -} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs deleted file mode 100644 index fd9fa6e8e6..0000000000 --- a/Terminal.Gui/ConsoleDrivers/FeatureDetection/ConsoleFeatureFinderResults.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Terminal.Gui; - -/// -/// Results of console feature detection -/// -internal class ConsoleFeatureFinderResults -{ - public WindowsFeatureSet Windows { get; set; } = new WindowsFeatureSet(); - public bool IsWindows { get; set; } - - /// - public override string ToString () - { - return $"{nameof(IsWindows)}:{IsWindows} {nameof(Windows)}:{Windows}"; - } -} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs b/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs deleted file mode 100644 index 1d54ab5ac7..0000000000 --- a/Terminal.Gui/ConsoleDrivers/FeatureDetection/WindowsFeatureSet.cs +++ /dev/null @@ -1,15 +0,0 @@ -using static Unix.Terminal.Curses; - -namespace Terminal.Gui; - -/// -/// Features specific to the windows operating system -/// -internal class WindowsFeatureSet -{ - - public bool ConHostLegacyMode { get; set; } - - - public override string ToString () { return $"{nameof(ConHostLegacyMode)}:{ConHostLegacyMode}"; } -} diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs index f4500eef09..9baeba301f 100644 --- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs @@ -85,15 +85,11 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n private void CreateDriver (string? driverName) { + PlatformID p = Environment.OSVersion.Platform; bool definetlyWin = driverName?.Contains ("win") ?? false; bool definetlyNet = driverName?.Contains ("net") ?? false; - var finder = new ConsoleFeatureFinder (); - var result = finder.GetResults (); - - Logging.Logger.LogInformation ($"Feature detection results:{ result}"); - if (definetlyWin) { _coordinator = CreateWindowsSubcomponents (); @@ -102,7 +98,7 @@ private void CreateDriver (string? driverName) { _coordinator = CreateNetSubcomponents (); } - else if (result.IsWindows) + else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { _coordinator = CreateWindowsSubcomponents (); } From bc42fc901e22b16d68e6ec998952a2a5571e36a8 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 4 Apr 2025 08:49:59 +0100 Subject: [PATCH 08/10] Code cleanup and enable nullable on Branch --- Terminal.Gui/Views/TreeView/Branch.cs | 130 +++++++++++++------------- 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 4bc24af6cf..87d216df2d 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -1,8 +1,10 @@ -namespace Terminal.Gui; +#nullable enable + +namespace Terminal.Gui; internal class Branch where T : class { - private readonly TreeView tree; + private readonly TreeView _tree; /// /// Declares a new branch of in which the users object is @@ -11,9 +13,9 @@ internal class Branch where T : class /// The UI control in which the branch resides. /// Pass null for root level branches, otherwise pass the parent. /// The user's object that should be displayed. - public Branch (TreeView tree, Branch parentBranchIfAny, T model) + public Branch (TreeView tree, Branch? parentBranchIfAny, T model) { - this.tree = tree; + _tree = tree; Model = model; if (parentBranchIfAny is { }) @@ -27,7 +29,7 @@ public Branch (TreeView tree, Branch parentBranchIfAny, T model) /// The children of the current branch. This is null until the first call to to avoid /// enumerating the entire underlying hierarchy. /// - public List> ChildBranches { get; set; } + public List>? ChildBranches { get; set; } /// The depth of the current branch. Depth of 0 indicates root level branches. public int Depth { get; } @@ -39,7 +41,7 @@ public Branch (TreeView tree, Branch parentBranchIfAny, T model) public T Model { get; private set; } /// The parent or null if it is a root. - public Branch Parent { get; } + public Branch? Parent { get; } /// /// Returns true if the current branch can be expanded according to the or cached @@ -52,13 +54,13 @@ public bool CanExpand () if (ChildBranches is null) { //if there is a rapid method for determining whether there are children - if (tree.TreeBuilder.SupportsCanExpand) + if (_tree.TreeBuilder.SupportsCanExpand) { - return tree.TreeBuilder.CanExpand (Model); + return _tree.TreeBuilder.CanExpand (Model); } //there is no way of knowing whether we can expand without fetching the children - FetchChildren (); + ChildBranches = FetchChildren (); } //we fetched or already know the children, so return whether we have any @@ -80,21 +82,21 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, int indexOfModelText; // true if the current line of the tree is the selected one and control has focus - bool isSelected = tree.IsSelected (Model); + bool isSelected = _tree.IsSelected (Model); Attribute textColor = - isSelected ? tree.HasFocus ? colorScheme.Focus : colorScheme.HotNormal : colorScheme.Normal; - Attribute symbolColor = tree.Style.HighlightModelTextOnly ? colorScheme.Normal : textColor; + isSelected ? _tree.HasFocus ? colorScheme.Focus : colorScheme.HotNormal : colorScheme.Normal; + Attribute symbolColor = _tree.Style.HighlightModelTextOnly ? colorScheme.Normal : textColor; // Everything on line before the expansion run and branch text Rune [] prefix = GetLinePrefix (driver).ToArray (); Rune expansion = GetExpandableSymbol (driver); - string lineBody = tree.AspectGetter (Model) ?? ""; + string lineBody = _tree.AspectGetter (Model) ?? ""; - tree.Move (0, y); + _tree.Move (0, y); // if we have scrolled to the right then bits of the prefix will have disappeared off the screen - int toSkip = tree.ScrollOffsetHorizontal; + int toSkip = _tree.ScrollOffsetHorizontal; Attribute attr = symbolColor; // Draw the line prefix (all parallel lanes or whitespace and an expand/collapse/leaf symbol) @@ -112,20 +114,20 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, } // pick color for expanded symbol - if (tree.Style.ColorExpandSymbol || tree.Style.InvertExpandSymbolColors) + if (_tree.Style.ColorExpandSymbol || _tree.Style.InvertExpandSymbolColors) { - Attribute color = symbolColor; + Attribute color; - if (tree.Style.ColorExpandSymbol) + if (_tree.Style.ColorExpandSymbol) { if (isSelected) { - color = tree.Style.HighlightModelTextOnly ? colorScheme.HotNormal : - tree.HasFocus ? tree.ColorScheme.HotFocus : tree.ColorScheme.HotNormal; + color = _tree.Style.HighlightModelTextOnly ? colorScheme.HotNormal : + _tree.HasFocus ? _tree.GetHotFocusColor () : _tree.GetHotNormalColor (); } else { - color = tree.ColorScheme.HotNormal; + color = _tree.GetHotNormalColor (); } } else @@ -133,9 +135,9 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, color = symbolColor; } - if (tree.Style.InvertExpandSymbolColors) + if (_tree.Style.InvertExpandSymbolColors) { - color = new Attribute (color.Background, color.Foreground); + color = new (color.Background, color.Foreground); } attr = color; @@ -177,10 +179,10 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, if (lineBody.EnumerateRunes ().Sum (l => l.GetColumns ()) > availableWidth) { // remaining space is zero and truncate the line - lineBody = new string ( - lineBody.TakeWhile (c => (availableWidth -= ((Rune)c).GetColumns ()) >= 0) - .ToArray () - ); + lineBody = new ( + lineBody.TakeWhile (c => (availableWidth -= ((Rune)c).GetColumns ()) >= 0) + .ToArray () + ); availableWidth = 0; } else @@ -194,9 +196,9 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, Attribute modelColor = textColor; // if custom color delegate invoke it - if (tree.ColorGetter is { }) + if (_tree.ColorGetter is { }) { - ColorScheme modelScheme = tree.ColorGetter (Model); + ColorScheme modelScheme = _tree.ColorGetter (Model); // if custom color scheme is defined for this Model if (modelScheme is { }) @@ -206,12 +208,12 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, } else { - modelColor = new Attribute (); + modelColor = new (); } } attr = modelColor; - cells.AddRange (lineBody.Select (r => NewCell (attr, new Rune (r)))); + cells.AddRange (lineBody.Select (r => NewCell (attr, new (r)))); if (availableWidth > 0) { @@ -219,7 +221,7 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, cells.AddRange ( Enumerable.Repeat ( - NewCell (attr, new Rune (' ')), + NewCell (attr, new (' ')), availableWidth ) ); @@ -230,12 +232,12 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, Model = Model, Y = y, Cells = cells, - Tree = tree, + Tree = _tree, IndexOfExpandCollapseSymbol = indexOfExpandCollapseSymbol, IndexOfModelText = indexOfModelText }; - tree.OnDrawLine (e); + _tree.OnDrawLine (e); if (!e.Handled && driver != null) { @@ -252,10 +254,7 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, /// Expands the current branch if possible. public void Expand () { - if (ChildBranches is null) - { - FetchChildren (); - } + ChildBranches ??= FetchChildren (); if (ChildBranches.Any ()) { @@ -264,25 +263,25 @@ public void Expand () } /// Fetch the children of this branch. This method populates . - public virtual void FetchChildren () + private List> FetchChildren () { - if (tree.TreeBuilder is null) + if (_tree.TreeBuilder is null) { - return; + return []; } IEnumerable children; - if (Depth >= tree.MaxDepth) + if (Depth >= _tree.MaxDepth) { children = []; } else { - children = tree.TreeBuilder.GetChildren (Model) ?? []; + children = _tree.TreeBuilder.GetChildren (Model) ?? []; } - ChildBranches = children.Select (o=>new Branch (tree, this, o)).ToList (); + return children.Select (o => new Branch (_tree, this, o)).ToList (); } /// @@ -293,16 +292,16 @@ public virtual void FetchChildren () /// public Rune GetExpandableSymbol (IConsoleDriver driver) { - Rune leafSymbol = tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; + Rune leafSymbol = _tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; if (IsExpanded) { - return tree.Style.CollapseableSymbol ?? leafSymbol; + return _tree.Style.CollapseableSymbol ?? leafSymbol; } if (CanExpand ()) { - return tree.Style.ExpandableSymbol ?? leafSymbol; + return _tree.Style.ExpandableSymbol ?? leafSymbol; } return leafSymbol; @@ -316,7 +315,7 @@ public Rune GetExpandableSymbol (IConsoleDriver driver) public virtual int GetWidth (IConsoleDriver driver) { return - GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (tree.AspectGetter (Model) ?? "").Length; + GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (_tree.AspectGetter (Model) ?? "").Length; } /// Refreshes cached knowledge in this branch e.g. what children an object has. @@ -333,35 +332,36 @@ public void Refresh (bool startAtTop) //if we don't know about any children yet just use the normal method if (ChildBranches is null) { - FetchChildren (); + ChildBranches = FetchChildren (); } else { // we already knew about some children so preserve the state of the old children // first gather the new Children - T[] newChildren = tree.TreeBuilder?.GetChildren (Model).ToArray () ?? []; + T [] newChildren = _tree.TreeBuilder?.GetChildren (Model).ToArray () ?? []; // Children who no longer appear need to go - foreach (Branch toRemove in ChildBranches.Where (b=>!newChildren.Contains(b.Model)).ToArray ()) + foreach (Branch toRemove in ChildBranches.Where (b => !newChildren.Contains (b.Model)).ToArray ()) { ChildBranches.Remove (toRemove); //also if the user has this node selected (its disappearing) so lets change selection to us (the parent object) to be helpful - if (Equals (tree.SelectedObject, toRemove.Model)) + if (Equals (_tree.SelectedObject, toRemove.Model)) { - tree.SelectedObject = Model; + _tree.SelectedObject = Model; } } // New children need to be added foreach (T newChild in newChildren) { - Branch existingBranch = ChildBranches.FirstOrDefault (b => b.Model.Equals (newChild)); + Branch? existingBranch = ChildBranches.FirstOrDefault (b => b.Model.Equals (newChild)); + // If we don't know about the child, yet we need a new branch if (existingBranch == null) { - ChildBranches.Add (new (tree, this, newChild)); + ChildBranches.Add (new (_tree, this, newChild)); } else { @@ -415,11 +415,11 @@ internal void ExpandAll () internal IEnumerable GetLinePrefix (IConsoleDriver driver) { // If not showing line branches or this is a root object. - if (!tree.Style.ShowBranchLines) + if (!_tree.Style.ShowBranchLines) { for (var i = 0; i < Depth; i++) { - yield return new Rune (' '); + yield return new (' '); } yield break; @@ -430,14 +430,14 @@ internal IEnumerable GetLinePrefix (IConsoleDriver driver) { if (cur.IsLast ()) { - yield return new Rune (' '); + yield return new (' '); } else { yield return Glyphs.VLine; } - yield return new Rune (' '); + yield return new (' '); } if (IsLast ()) @@ -466,13 +466,13 @@ internal bool IsHitOnExpandableSymbol (IConsoleDriver driver, int x) } // if we could theoretically expand - if (!IsExpanded && tree.Style.ExpandableSymbol != default (Rune?)) + if (!IsExpanded && _tree.Style.ExpandableSymbol != default (Rune?)) { return x == GetLinePrefix (driver).Count (); } // if we could theoretically collapse - if (IsExpanded && tree.Style.CollapseableSymbol != default (Rune?)) + if (IsExpanded && _tree.Style.CollapseableSymbol != default (Rune?)) { return x == GetLinePrefix (driver).Count (); } @@ -508,7 +508,7 @@ internal void Rebuild () /// private IEnumerable> GetParentBranches () { - Branch cur = Parent; + Branch? cur = Parent; while (cur is { }) { @@ -527,11 +527,13 @@ private bool IsLast () { if (Parent is null) { - return this == tree.roots.Values.LastOrDefault (); + return this == _tree.roots.Values.LastOrDefault (); } + Parent.ChildBranches ??= Parent.FetchChildren (); + return Parent.ChildBranches.LastOrDefault () == this; } - private static Cell NewCell (Attribute attr, Rune r) { return new Cell { Rune = r, Attribute = new (attr) }; } + private static Cell NewCell (Attribute attr, Rune r) { return new() { Rune = r, Attribute = new (attr) }; } } From a3ab90aa52ac46df2c003142caf86127f61a5986 Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 4 Apr 2025 08:55:56 +0100 Subject: [PATCH 09/10] Remove color scheme and driver from Branch draw --- .../Views/TableView/TreeTableSource.cs | 4 +-- Terminal.Gui/Views/TreeView/Branch.cs | 36 +++++++++---------- Terminal.Gui/Views/TreeView/TreeView.cs | 6 ++-- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Views/TableView/TreeTableSource.cs b/Terminal.Gui/Views/TableView/TreeTableSource.cs index 9125c0c953..06b304634a 100644 --- a/Terminal.Gui/Views/TableView/TreeTableSource.cs +++ b/Terminal.Gui/Views/TableView/TreeTableSource.cs @@ -87,8 +87,8 @@ private string GetColumnZeroRepresentationFromTree (int row) Branch branch = RowToBranch (row); // Everything on line before the expansion run and branch text - Rune [] prefix = branch.GetLinePrefix (Application.Driver).ToArray (); - Rune expansion = branch.GetExpandableSymbol (Application.Driver); + Rune [] prefix = branch.GetLinePrefix ().ToArray (); + Rune expansion = branch.GetExpandableSymbol (); string lineBody = _tree.AspectGetter (branch.Model) ?? ""; var sb = new StringBuilder (); diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs index 87d216df2d..4c348eaff6 100644 --- a/Terminal.Gui/Views/TreeView/Branch.cs +++ b/Terminal.Gui/Views/TreeView/Branch.cs @@ -71,11 +71,9 @@ public bool CanExpand () public void Collapse () { IsExpanded = false; } /// Renders the current on the specified line . - /// - /// /// /// - public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth) + public virtual void Draw (int y, int availableWidth) { List cells = new (); int? indexOfExpandCollapseSymbol = null; @@ -85,12 +83,12 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, bool isSelected = _tree.IsSelected (Model); Attribute textColor = - isSelected ? _tree.HasFocus ? colorScheme.Focus : colorScheme.HotNormal : colorScheme.Normal; - Attribute symbolColor = _tree.Style.HighlightModelTextOnly ? colorScheme.Normal : textColor; + isSelected ? _tree.HasFocus ? _tree.GetFocusColor () : _tree.GetHotNormalColor () : _tree.GetNormalColor (); + Attribute symbolColor = _tree.Style.HighlightModelTextOnly ? _tree.GetNormalColor () : textColor; // Everything on line before the expansion run and branch text - Rune [] prefix = GetLinePrefix (driver).ToArray (); - Rune expansion = GetExpandableSymbol (driver); + Rune [] prefix = GetLinePrefix ().ToArray (); + Rune expansion = GetExpandableSymbol (); string lineBody = _tree.AspectGetter (Model) ?? ""; _tree.Move (0, y); @@ -122,7 +120,7 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, { if (isSelected) { - color = _tree.Style.HighlightModelTextOnly ? colorScheme.HotNormal : + color = _tree.Style.HighlightModelTextOnly ? _tree.GetHotNormalColor () : _tree.HasFocus ? _tree.GetHotFocusColor () : _tree.GetHotNormalColor (); } else @@ -239,16 +237,16 @@ public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, }; _tree.OnDrawLine (e); - if (!e.Handled && driver != null) + if (!e.Handled) { foreach (Cell cell in cells) { - driver.SetAttribute ((Attribute)cell.Attribute!); - driver.AddRune (cell.Rune); + _tree.SetAttribute ((Attribute)cell.Attribute!); + _tree.AddRune (cell.Rune); } } - driver?.SetAttribute (colorScheme.Normal); + _tree.SetAttribute (_tree.GetNormalColor()); } /// Expands the current branch if possible. @@ -288,9 +286,8 @@ private List> FetchChildren () /// Returns an appropriate symbol for displaying next to the string representation of the /// object to indicate whether it or not (or it is a leaf). /// - /// /// - public Rune GetExpandableSymbol (IConsoleDriver driver) + public Rune GetExpandableSymbol () { Rune leafSymbol = _tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' '; @@ -312,10 +309,10 @@ public Rune GetExpandableSymbol (IConsoleDriver driver) /// line body). /// /// - public virtual int GetWidth (IConsoleDriver driver) + public virtual int GetWidth () { return - GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (_tree.AspectGetter (Model) ?? "").Length; + GetLinePrefix ().Sum (r => r.GetColumns ()) + GetExpandableSymbol ().GetColumns () + (_tree.AspectGetter (Model) ?? "").Length; } /// Refreshes cached knowledge in this branch e.g. what children an object has. @@ -410,9 +407,8 @@ internal void ExpandAll () /// Gets all characters to render prior to the current branches line. This includes indentation whitespace and /// any tree branches (if enabled). /// - /// /// - internal IEnumerable GetLinePrefix (IConsoleDriver driver) + internal IEnumerable GetLinePrefix () { // If not showing line branches or this is a root object. if (!_tree.Style.ShowBranchLines) @@ -468,13 +464,13 @@ internal bool IsHitOnExpandableSymbol (IConsoleDriver driver, int x) // if we could theoretically expand if (!IsExpanded && _tree.Style.ExpandableSymbol != default (Rune?)) { - return x == GetLinePrefix (driver).Count (); + return x == GetLinePrefix ().Count (); } // if we could theoretically collapse if (IsExpanded && _tree.Style.CollapseableSymbol != default (Rune?)) { - return x == GetLinePrefix (driver).Count (); + return x == GetLinePrefix ().Count (); } return false; diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index a4b33bfe5f..af2245efae 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -879,10 +879,10 @@ public int GetContentWidth (bool visible) return 0; } - return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth (Driver)); + return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth ()); } - return map.Max (b => b.GetWidth (Driver)); + return map.Max (b => b.GetWidth ()); } /// @@ -1171,7 +1171,7 @@ protected override bool OnDrawingContent () if (idxToRender < map.Count) { // Render the line - map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Viewport.Width); + map.ElementAt (idxToRender).Draw (line, Viewport.Width); } else { From b801ff6284870b80523f3f42c6df6e614a1d3daa Mon Sep 17 00:00:00 2001 From: tznind Date: Fri, 4 Apr 2025 10:01:23 +0100 Subject: [PATCH 10/10] Add xunit context extensions --- Terminal.sln | 6 +++ .../TerminalGuiFluentTesting.Xunit.csproj | 14 +++++ .../XunitContextExtensions.cs | 25 +++++++++ .../FluentTests/TreeViewFluentTests.cs | 54 +++++++++---------- .../IntegrationTests/IntegrationTests.csproj | 1 + 5 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj create mode 100644 TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs diff --git a/Terminal.sln b/Terminal.sln index e15d8f3606..b255d94598 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests.Parallelizable", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting", "TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj", "{2DBA7BDC-17AE-474B-A507-00807D087607}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalGuiFluentTesting.Xunit", "TerminalGuiFluentTesting.Xunit\TerminalGuiFluentTesting.Xunit.csproj", "{231B9723-10F3-46DB-8EAE-50C0C0375AD3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -123,6 +125,10 @@ Global {2DBA7BDC-17AE-474B-A507-00807D087607}.Debug|Any CPU.Build.0 = Debug|Any CPU {2DBA7BDC-17AE-474B-A507-00807D087607}.Release|Any CPU.ActiveCfg = Release|Any CPU {2DBA7BDC-17AE-474B-A507-00807D087607}.Release|Any CPU.Build.0 = Release|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {231B9723-10F3-46DB-8EAE-50C0C0375AD3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj b/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj new file mode 100644 index 0000000000..03c8b09d94 --- /dev/null +++ b/TerminalGuiFluentTesting.Xunit/TerminalGuiFluentTesting.Xunit.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs b/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs new file mode 100644 index 0000000000..53f81e37b3 --- /dev/null +++ b/TerminalGuiFluentTesting.Xunit/XunitContextExtensions.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace TerminalGuiFluentTesting; + +public static class XunitContextExtensions +{ + public static GuiTestContext AssertTrue (this GuiTestContext context, bool? condition) + { + context.Then ( + () => + { + Assert.True (condition); + }); + return context; + } + public static GuiTestContext AssertEqual (this GuiTestContext context, object? expected, object? actual) + { + context.Then ( + () => + { + Assert.Equal (expected,actual); + }); + return context; + } +} diff --git a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs index ed931074d7..f735147973 100644 --- a/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/TreeViewFluentTests.cs @@ -42,14 +42,14 @@ public void TreeView_AllowReOrdering (V2TestDriver d) .Focus (tv) .WaitIteration () .ScreenShot ("Before expanding", _out) - .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) + .AssertEqual (root, tv.GetObjectOnRow (0)) .Then (() => Assert.Null (tv.GetObjectOnRow (1))) .Right () .ScreenShot ("After expanding", _out) - .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) - .Then (() => Assert.Equal (car, tv.GetObjectOnRow (1))) - .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (2))) - .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (3))) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (car, tv.GetObjectOnRow (1)) + .AssertEqual (lorry, tv.GetObjectOnRow (2)) + .AssertEqual (bike, tv.GetObjectOnRow (3)) .Then ( () => { @@ -59,10 +59,10 @@ public void TreeView_AllowReOrdering (V2TestDriver d) }) .WaitIteration () .ScreenShot ("After re-order", _out) - .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) - .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (1))) - .Then (() => Assert.Equal (car, tv.GetObjectOnRow (2))) - .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (3))) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (bike, tv.GetObjectOnRow (1)) + .AssertEqual (car, tv.GetObjectOnRow (2)) + .AssertEqual (lorry, tv.GetObjectOnRow (3)) .WriteOutLogs (_out); context.Stop (); @@ -128,15 +128,15 @@ public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) .Add (tv) .WaitIteration () .ScreenShot ("Initial State", _out) - .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) - .Then (() => Assert.Equal (car, tv.GetObjectOnRow (1))) - .Then (() => Assert.Equal (mrA, tv.GetObjectOnRow (2))) - .Then (() => Assert.Equal (mrB, tv.GetObjectOnRow (3))) - .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (4))) - .Then (() => Assert.Equal (mrC, tv.GetObjectOnRow (5))) - .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (6))) - .Then (() => Assert.Equal (mrD, tv.GetObjectOnRow (7))) - .Then (() => Assert.Equal (mrE, tv.GetObjectOnRow (8))) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (car, tv.GetObjectOnRow (1)) + .AssertEqual (mrA, tv.GetObjectOnRow (2)) + .AssertEqual (mrB, tv.GetObjectOnRow (3)) + .AssertEqual (lorry, tv.GetObjectOnRow (4)) + .AssertEqual (mrC, tv.GetObjectOnRow (5)) + .AssertEqual (bike, tv.GetObjectOnRow (6)) + .AssertEqual (mrD, tv.GetObjectOnRow (7)) + .AssertEqual (mrE, tv.GetObjectOnRow (8)) .Then ( () => { @@ -146,15 +146,15 @@ public void TreeViewReOrder_PreservesExpansion (V2TestDriver d) }) .WaitIteration () .ScreenShot ("After re-order", _out) - .Then (() => Assert.Equal (root, tv.GetObjectOnRow (0))) - .Then (() => Assert.Equal (bike, tv.GetObjectOnRow (1))) - .Then (() => Assert.Equal (mrD, tv.GetObjectOnRow (2))) - .Then (() => Assert.Equal (mrE, tv.GetObjectOnRow (3))) - .Then (() => Assert.Equal (car, tv.GetObjectOnRow (4))) - .Then (() => Assert.Equal (mrA, tv.GetObjectOnRow (5))) - .Then (() => Assert.Equal (mrB, tv.GetObjectOnRow (6))) - .Then (() => Assert.Equal (lorry, tv.GetObjectOnRow (7))) - .Then (() => Assert.Equal (mrC, tv.GetObjectOnRow (8))) + .AssertEqual (root, tv.GetObjectOnRow (0)) + .AssertEqual (bike, tv.GetObjectOnRow (1)) + .AssertEqual (mrD, tv.GetObjectOnRow (2)) + .AssertEqual (mrE, tv.GetObjectOnRow (3)) + .AssertEqual (car, tv.GetObjectOnRow (4)) + .AssertEqual (mrA, tv.GetObjectOnRow (5)) + .AssertEqual (mrB, tv.GetObjectOnRow (6)) + .AssertEqual (lorry, tv.GetObjectOnRow (7)) + .AssertEqual (mrC, tv.GetObjectOnRow (8)) .WriteOutLogs (_out); context.Stop (); diff --git a/Tests/IntegrationTests/IntegrationTests.csproj b/Tests/IntegrationTests/IntegrationTests.csproj index f279e21df2..80f067bf7d 100644 --- a/Tests/IntegrationTests/IntegrationTests.csproj +++ b/Tests/IntegrationTests/IntegrationTests.csproj @@ -26,6 +26,7 @@ +