mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
Merge branch 'v2_develop' into ansi-parser
This commit is contained in:
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
},
|
||||
"All Views Tester": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "\"All Views Tester\" -b"
|
||||
"commandLineArgs": "\"All Views Tester\" -b -t 5000"
|
||||
},
|
||||
"Charmap": {
|
||||
"commandName": "Project",
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 []>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user