Browse Source

Merge branch 'v2_develop' into ansi-parser

Thomas Nind 7 months ago
parent
commit
903a886c38

+ 21 - 5
Terminal.Gui/Application/Application.Initialization.cs

@@ -150,6 +150,7 @@ public static partial class Application // Initialization (Init/Shutdown)
         try
         {
             MainLoop = Driver!.Init ();
+            SubscribeDriverEvents ();
         }
         catch (InvalidOperationException ex)
         {
@@ -163,11 +164,6 @@ public static partial class Application // Initialization (Init/Shutdown)
                                                 );
         }
 
-        Driver.SizeChanged += Driver_SizeChanged;
-        Driver.KeyDown += Driver_KeyDown;
-        Driver.KeyUp += Driver_KeyUp;
-        Driver.MouseEvent += Driver_MouseEvent;
-
         SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
         SupportedCultures = GetSupportedCultures ();
@@ -176,6 +172,26 @@ public static partial class Application // Initialization (Init/Shutdown)
         InitializedChanged?.Invoke (null, new (init));
     }
 
+    internal static void SubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged += Driver_SizeChanged;
+        Driver.KeyDown += Driver_KeyDown;
+        Driver.KeyUp += Driver_KeyUp;
+        Driver.MouseEvent += Driver_MouseEvent;
+    }
+
+    internal static void UnsubscribeDriverEvents ()
+    {
+        ArgumentNullException.ThrowIfNull (Driver);
+
+        Driver.SizeChanged -= Driver_SizeChanged;
+        Driver.KeyDown -= Driver_KeyDown;
+        Driver.KeyUp -= Driver_KeyUp;
+        Driver.MouseEvent -= Driver_MouseEvent;
+    }
+
     private static void Driver_SizeChanged (object? sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
     private static void Driver_KeyDown (object? sender, Key e) { RaiseKeyDownEvent (e); }
     private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }

+ 1 - 4
Terminal.Gui/Application/Application.cs

@@ -177,10 +177,7 @@ public static partial class Application
         // Driver stuff
         if (Driver is { })
         {
-            Driver.SizeChanged -= Driver_SizeChanged;
-            Driver.KeyDown -= Driver_KeyDown;
-            Driver.KeyUp -= Driver_KeyUp;
-            Driver.MouseEvent -= Driver_MouseEvent;
+            UnsubscribeDriverEvents ();
             Driver?.End ();
             Driver = null;
         }

+ 6 - 6
Terminal.Gui/Text/TextFormatter.cs

@@ -1411,7 +1411,7 @@ public class TextFormatter
 
                 if (textFormatter is { Alignment: Alignment.Center })
                 {
-                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
                 }
 
                 return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
@@ -1426,7 +1426,7 @@ public class TextFormatter
 
                 if (textFormatter is { VerticalAlignment: Alignment.Center })
                 {
-                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
+                    return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
                 }
 
                 return GetRangeThatFits (runes, 0, text, width, tabWidth, textDirection);
@@ -1451,7 +1451,7 @@ public class TextFormatter
             }
             else if (textFormatter is { Alignment: Alignment.Center })
             {
-                return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
+                return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
             }
             else if (GetRuneWidth (text, tabWidth, textDirection) > width)
             {
@@ -1470,7 +1470,7 @@ public class TextFormatter
             }
             else if (textFormatter is { VerticalAlignment: Alignment.Center })
             {
-                return GetRangeThatFits (runes, Math.Max ((runes.Count - width) / 2, 0), text, width, tabWidth, textDirection);
+                return GetRangeThatFits (runes, Math.Max ((runes.Count - width - zeroLength) / 2, 0), text, width, tabWidth, textDirection);
             }
             else if (runes.Count - zeroLength > width)
             {
@@ -1526,7 +1526,7 @@ public class TextFormatter
         }
         else
         {
-            textCount = words.Sum (arg => arg.GetRuneCount ());
+            textCount = words.Sum (arg => arg.GetRuneCount ()) - text.EnumerateRunes ().Sum (r => r.GetColumns () == 0 ? 1 : 0);
         }
 
         int spaces = words.Length > 1 ? (width - textCount) / (words.Length - 1) : 0;
@@ -1936,7 +1936,7 @@ public class TextFormatter
 
     private static int GetRuneWidth (Rune rune, int tabWidth, TextDirection textDirection = TextDirection.LeftRight_TopBottom)
     {
-        int runeWidth = IsHorizontalDirection (textDirection) ? rune.GetColumns () : 1;
+        int runeWidth = IsHorizontalDirection (textDirection) ? rune.GetColumns () : rune.GetColumns () == 0 ? 0 : 1;
 
         if (rune.Value == '\t')
         {

+ 5 - 5
Terminal.Gui/View/View.Keyboard.cs

@@ -690,11 +690,11 @@ public partial class View // Keyboard APIs
 
 #if DEBUG
 
-        if (Application.KeyBindings.TryGet (key, out KeyBinding b))
-        {
-            Debug.WriteLine (
-                             $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.");
-        }
+        //if (Application.KeyBindings.TryGet (key, out KeyBinding b))
+        //{
+        //    Debug.WriteLine (
+        //                     $"WARNING: InvokeKeyBindings ({key}) - An Application scope binding exists for this key. The registered view will not invoke Command.");
+        //}
 
         // TODO: This is a "prototype" debug check. It may be too annoying vs. useful.
         // Scour the bindings up our View hierarchy

+ 3 - 2
Terminal.Gui/View/View.Layout.cs

@@ -561,8 +561,9 @@ public partial class View // Layout APIs
             {
                 SuperView?.SetNeedsDraw ();
             }
-            else
+            else if (Application.TopLevels.Count == 1)
             {
+                // If this is the only TopLevel, we need to redraw the screen
                 Application.ClearScreenNextIteration = true;
             }
         }
@@ -801,7 +802,7 @@ public partial class View // Layout APIs
         {
             foreach (Toplevel tl in Application.TopLevels)
             {
-               // tl.SetNeedsDraw ();
+                // tl.SetNeedsDraw ();
             }
         }
 

+ 1 - 1
UICatalog/Properties/launchSettings.json

@@ -42,7 +42,7 @@
     },
     "All Views Tester": {
       "commandName": "Project",
-      "commandLineArgs": "\"All Views Tester\" -b"
+      "commandLineArgs": "\"All Views Tester\" -b -t 5000"
     },
     "Charmap": {
       "commandName": "Project",

+ 10 - 9
UICatalog/Scenario.cs

@@ -148,14 +148,15 @@ public class Scenario : IDisposable
     /// </summary>
     public virtual void Main () { }
 
-    private const uint MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys
-    private const uint ABORT_TIMEOUT_MS = 2500;
-    private const int DEMO_KEY_PACING_MS = 1; // Must be non-zero
+    private const uint BENCHMARK_MAX_NATURAL_ITERATIONS = 500; // not including needed for demo keys
+    private const int BENCHMARK_KEY_PACING = 1; // Must be non-zero
+
+    public static uint BenchmarkTimeout { get; set; } = 2500;
 
     private readonly object _timeoutLock = new ();
     private object? _timeout;
     private Stopwatch? _stopwatch;
-    private readonly BenchmarkResults _benchmarkResults = new BenchmarkResults ();
+    private readonly BenchmarkResults _benchmarkResults = new ();
 
     public void StartBenchmark ()
     {
@@ -178,7 +179,7 @@ public class Scenario : IDisposable
         return _benchmarkResults;
     }
 
-    private List<Key> _demoKeys;
+    private List<Key>? _demoKeys;
     private int _currentDemoKey = 0;
 
     private void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
@@ -187,7 +188,7 @@ public class Scenario : IDisposable
         {
             lock (_timeoutLock!)
             {
-                _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (ABORT_TIMEOUT_MS), ForceCloseCallback);
+                _timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (BenchmarkTimeout), ForceCloseCallback);
             }
 
             Application.Iteration += OnApplicationOnIteration;
@@ -218,7 +219,7 @@ public class Scenario : IDisposable
     private void OnApplicationOnIteration (object? s, IterationEventArgs a)
     {
         BenchmarkResults.IterationCount++;
-        if (BenchmarkResults.IterationCount > MAX_NATURAL_ITERATIONS + (_demoKeys.Count* DEMO_KEY_PACING_MS))
+        if (BenchmarkResults.IterationCount > BENCHMARK_MAX_NATURAL_ITERATIONS + (_demoKeys.Count * BENCHMARK_KEY_PACING))
         {
             Application.RequestStop ();
         }
@@ -232,7 +233,7 @@ public class Scenario : IDisposable
         _demoKeys = GetDemoKeyStrokes ();
 
         Application.AddTimeout (
-                                new TimeSpan (0, 0, 0, 0, DEMO_KEY_PACING_MS),
+                                new TimeSpan (0, 0, 0, 0, BENCHMARK_KEY_PACING),
                                 () =>
                                 {
                                     if (_currentDemoKey >= _demoKeys.Count)
@@ -271,7 +272,7 @@ public class Scenario : IDisposable
             }
         }
 
-        Debug.WriteLine ($@"  Failed to Quit with {Application.QuitKey} after {ABORT_TIMEOUT_MS}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
+        Debug.WriteLine ($@"  Failed to Quit with {Application.QuitKey} after {BenchmarkTimeout}ms and {BenchmarkResults.IterationCount} iterations. Force quit.");
 
         Application.RequestStop ();
 

+ 15 - 3
UICatalog/UICatalog.cs

@@ -148,6 +148,10 @@ public class UICatalogApp
         benchmarkFlag.AddAlias ("-b");
         benchmarkFlag.AddAlias ("--b");
 
+        Option<uint> benchmarkTimeout = new Option<uint> ("--timeout", getDefaultValue: () => Scenario.BenchmarkTimeout, $"The maximum time in milliseconds to run a benchmark for. Default is {Scenario.BenchmarkTimeout}ms.");
+        benchmarkTimeout.AddAlias ("-t");
+        benchmarkTimeout.AddAlias ("--t");
+
         Option<string> resultsFile = new Option<string> ("--file", "The file to save benchmark results to. If not specified, the results will be displayed in a TableView.");
         resultsFile.AddAlias ("-f");
         resultsFile.AddAlias ("--f");
@@ -165,7 +169,7 @@ public class UICatalogApp
 
         var rootCommand = new RootCommand ("A comprehensive sample library for Terminal.Gui")
         {
-            scenarioArgument, benchmarkFlag, resultsFile, driverOption,
+            scenarioArgument, benchmarkFlag, benchmarkTimeout, resultsFile, driverOption,
         };
 
         rootCommand.SetHandler (
@@ -176,6 +180,7 @@ public class UICatalogApp
                                         Scenario = context.ParseResult.GetValueForArgument (scenarioArgument),
                                         Driver = context.ParseResult.GetValueForOption (driverOption) ?? string.Empty,
                                         Benchmark = context.ParseResult.GetValueForOption (benchmarkFlag),
+                                        BenchmarkTimeout = context.ParseResult.GetValueForOption (benchmarkTimeout),
                                         ResultsFile = context.ParseResult.GetValueForOption (resultsFile) ?? string.Empty,
                                         /* etc. */
                                     };
@@ -197,6 +202,8 @@ public class UICatalogApp
             return 0;
         }
 
+        Scenario.BenchmarkTimeout = _options.BenchmarkTimeout;
+
         UICatalogMain (_options);
 
         return 0;
@@ -332,6 +339,7 @@ public class UICatalogApp
         // regardless of what's in a config file.
         Application.ForceDriver = _forceDriver = options.Driver;
 
+
         // If a Scenario name has been provided on the commandline
         // run it and exit when done.
         if (options.Scenario != "none")
@@ -788,7 +796,8 @@ public class UICatalogApp
                                              {
                                                  if (_statusBar.NeedsLayout)
                                                  {
-                                                   //  throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+                                                       throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+                                                     //_statusBar.Layout ();
                                                  }
                                                  return _statusBar.Frame.Height;
                                              })),
@@ -817,7 +826,8 @@ public class UICatalogApp
                                              {
                                                  if (_statusBar.NeedsLayout)
                                                  {
-                                                    // throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+                                                     throw new LayoutException ("DimFunc.Fn aborted because dependent View needs layout.");
+                                                     //_statusBar.Layout ();
                                                  }
                                                  return _statusBar.Frame.Height;
                                              })),
@@ -1378,6 +1388,8 @@ public class UICatalogApp
 
         public string Scenario;
 
+        public uint BenchmarkTimeout;
+
         public bool Benchmark;
 
         public string ResultsFile;

+ 20 - 0
UnitTests/Application/ApplicationScreenTests.cs

@@ -65,4 +65,24 @@ public class ApplicationScreenTests (ITestOutputHelper output)
         Application.Top = null;
         Application.Shutdown ();
     }
+
+    [Fact]
+    public void Screen_Changes_OnSizeChanged_Without_Call_Application_Init ()
+    {
+        // Arrange
+        Application.ResetState (true);
+        Assert.Null (Application.Driver);
+        Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
+        Application.SubscribeDriverEvents ();
+        Assert.Equal (new (0, 0, 25, 25), Application.Screen);
+
+        // Act
+        (((FakeDriver)Application.Driver)!).SetBufferSize (120, 30);
+
+        // Assert
+        Assert.Equal (new (0, 0, 120, 30), Application.Screen);
+
+        // Cleanup
+        Application.ResetState (true);
+    }
 }

+ 6 - 0
UnitTests/Application/ApplicationTests.cs

@@ -641,6 +641,12 @@ public class ApplicationTests
         Application.Shutdown ();
     }
 
+    [Fact]
+    public void InitState_Throws_If_Driver_Is_Null ()
+    {
+        Assert.Throws<ArgumentNullException> (static () => Application.SubscribeDriverEvents ());
+    }
+
     private void Init ()
     {
         Application.Init (new FakeDriver ());

+ 6 - 8
UnitTests/TestHelpers.cs

@@ -197,14 +197,9 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         // Turn off diagnostic flags in case some test left them on
         View.Diagnostics = ViewDiagnosticFlags.Off;
 
-        if (Application.Driver is { })
-        {
-            ((FakeDriver)Application.Driver).Rows = 25;
-            ((FakeDriver)Application.Driver).Cols = 25;
-            ((FakeDriver)Application.Driver).End ();
-        }
-
-        Application.Driver = null;
+        Application.ResetState (true);
+        Assert.Null (Application.Driver);
+        Assert.Equal (new (0, 0, 2048, 2048), Application.Screen);
         base.After (methodUnderTest);
     }
 
@@ -215,6 +210,9 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute
         Application.ResetState (true);
         Assert.Null (Application.Driver);
         Application.Driver = new FakeDriver { Rows = 25, Cols = 25 };
+        Assert.Equal (new (0, 0, 25, 25), Application.Screen);
+        // Ensures subscribing events, at least for the SizeChanged event
+        Application.SubscribeDriverEvents ();
 
         base.Before (methodUnderTest);
     }

+ 84 - 0
UnitTests/Text/TextFormatterTests.cs

@@ -4629,6 +4629,90 @@ ssb
         Assert.Equal (expectedWrappedText, wrappedText);
     }
 
+    [Theory]
+    [InlineData (
+                    "Les Mise\u0301rables",
+                    14,
+                    -1,
+                    false,
+                    new [] { "Les Misérables" },
+                    "Les Misérables"
+                )]
+    [InlineData (
+                    "Les Mise\u0328\u0301rables",
+                    14,
+                    -2,
+                    false,
+                    new [] { "Les Misę́rables" },
+                    "Les Misę́rables"
+                )]
+    public void Format_Combining_Marks_Alignments (
+        string text,
+        int maxWidth,
+        int widthOffset,
+        bool wrap,
+        IEnumerable<string> resultLines,
+        string expectedText
+    )
+    {
+        Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+
+        // Horizontal text direction
+        foreach (Alignment alignment in Enum.GetValues (typeof (Alignment)))
+        {
+            TextFormatter tf = new () { Text = text, ConstrainToSize = new (maxWidth, 1), WordWrap = wrap, Alignment = alignment };
+
+            List<string> list = TextFormatter.Format (
+                                                      text,
+                                                      maxWidth,
+                                                      alignment,
+                                                      wrap,
+                                                      tf.PreserveTrailingSpaces,
+                                                      tf.TabWidth,
+                                                      tf.Direction,
+                                                      tf.MultiLine,
+                                                      tf);
+            Assert.Equal (list.Count, resultLines.Count ());
+            Assert.Equal (resultLines, list);
+            var formattedText = string.Empty;
+
+            foreach (string txt in list)
+            {
+                formattedText += txt;
+            }
+
+            Assert.Equal (expectedText, formattedText);
+        }
+
+        // Vertical text direction
+        foreach (Alignment alignment in Enum.GetValues (typeof (Alignment)))
+        {
+            TextFormatter tf = new ()
+                { Text = text, ConstrainToSize = new (1, maxWidth), WordWrap = wrap, VerticalAlignment = alignment, Direction = TextDirection.TopBottom_LeftRight };
+
+            List<string> list = TextFormatter.Format (
+                                                      text,
+                                                      maxWidth,
+                                                      alignment,
+                                                      wrap,
+                                                      tf.PreserveTrailingSpaces,
+                                                      tf.TabWidth,
+                                                      tf.Direction,
+                                                      tf.MultiLine,
+                                                      tf);
+            Assert.Equal (list.Count, resultLines.Count ());
+            Assert.Equal (resultLines, list);
+            var formattedText = string.Empty;
+
+            foreach (string txt in list)
+            {
+                formattedText += txt;
+            }
+
+            Assert.Equal (expectedText, formattedText);
+        }
+    }
+
     public static IEnumerable<object []> FormatEnvironmentNewLine =>
         new List<object []>
         {