Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop

This commit is contained in:
Tig
2025-06-12 12:03:41 -06:00
18 changed files with 258 additions and 48 deletions

View File

@@ -100,7 +100,7 @@ public class LoginView : Window, IViewFor<LoginViewModel>
ViewModel
.WhenAnyValue (x => x.IsValid)
.Select (valid => valid ? SchemeManager.GetScheme ("Base") : SchemeManager.GetScheme ("Error"))
.BindTo (validation, x => x.GetScheme ())
.Subscribe (scheme => validation.SetScheme (scheme))
.DisposeWith (_disposable);
})
.AddControlAfter<Button> ((previous, login) =>

View File

@@ -12,6 +12,10 @@
"commandName": "Project",
"commandLineArgs": "--driver WindowsDriver"
},
"UICatalog --driver v2": {
"commandName": "Project",
"commandLineArgs": "--driver v2 -dl Trace"
},
"UICatalog --driver v2win": {
"commandName": "Project",
"commandLineArgs": "--driver v2win -dl Trace"
@@ -32,6 +36,18 @@
"commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
"distributionName": ""
},
"WSL: UICatalog --driver v2": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2",
"distributionName": ""
},
"WSL: UICatalog --driver v2net": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet UICatalog.dll --driver v2net",
"distributionName": ""
},
"Benchmark All": {
"commandName": "Project",
"commandLineArgs": "--benchmark"

View File

@@ -54,7 +54,7 @@ namespace UICatalog;
/// </remarks>
public class UICatalog
{
private static string _forceDriver = string.Empty;
private static string? _forceDriver = null;
public static string LogFilePath { get; set; } = string.Empty;
public static LoggingLevelSwitch LogLevelSwitch { get; } = new ();
@@ -77,11 +77,7 @@ public class UICatalog
// If no driver is provided, the default driver is used.
Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
Application.GetDriverTypes ()
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
.Select (d => d!.Name)
.Union (["v2", "v2win", "v2net"])
.ToArray ()
Application.GetDriverTypes ().Item2.ToArray ()!
);
driverOption.AddAlias ("-d");
driverOption.AddAlias ("--d");

View File

@@ -40,11 +40,6 @@ public static partial class Application // Initialization (Init/Shutdown)
[RequiresDynamicCode ("AOT")]
public static void Init (IConsoleDriver? driver = null, string? driverName = null)
{
if (driverName?.StartsWith ("v2") ?? false)
{
ApplicationImpl.ChangeInstance (new ApplicationV2 ());
}
ApplicationImpl.Instance.Init (driver, driverName);
}
@@ -83,12 +78,6 @@ public static partial class Application // Initialization (Init/Shutdown)
ResetState (ignoreDisposed: true);
}
Debug.Assert (Navigation is null);
Navigation = new ();
Debug.Assert(Popover is null);
Popover = new ();
// For UnitTests
if (driver is { })
{
@@ -105,8 +94,6 @@ public static partial class Application // Initialization (Init/Shutdown)
}
}
AddKeyBindings ();
// Ignore Configuration for ForceDriver if driverName is specified
if (!string.IsNullOrEmpty (driverName))
{
@@ -130,13 +117,21 @@ public static partial class Application // Initialization (Init/Shutdown)
}
else
{
List<Type?> drivers = GetDriverTypes ();
(List<Type?> drivers, List<string?> driverTypeNames) = GetDriverTypes ();
Type? driverType = drivers.FirstOrDefault (t => t!.Name.Equals (ForceDriver, StringComparison.InvariantCultureIgnoreCase));
if (driverType is { })
{
Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
}
else if (ForceDriver?.StartsWith ("v2") ?? false)
{
ApplicationImpl.ChangeInstance (new ApplicationV2 ());
ApplicationImpl.Instance.Init (driver, ForceDriver);
Debug.Assert (Driver is { });
return;
}
else
{
throw new ArgumentException (
@@ -146,6 +141,14 @@ public static partial class Application // Initialization (Init/Shutdown)
}
}
Debug.Assert (Navigation is null);
Navigation = new ();
Debug.Assert (Popover is null);
Popover = new ();
AddKeyBindings ();
try
{
MainLoop = Driver!.Init ();
@@ -201,10 +204,10 @@ public static partial class Application // Initialization (Init/Shutdown)
private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
/// <summary>Gets of list of <see cref="IConsoleDriver"/> types that are available.</summary>
/// <summary>Gets of list of <see cref="IConsoleDriver"/> types and type names that are available.</summary>
/// <returns></returns>
[RequiresUnreferencedCode ("AOT")]
public static List<Type?> GetDriverTypes ()
public static (List<Type?>, List<string?>) GetDriverTypes ()
{
// use reflection to get the list of drivers
List<Type?> driverTypes = new ();
@@ -220,7 +223,13 @@ public static partial class Application // Initialization (Init/Shutdown)
}
}
return driverTypes;
List<string?> driverTypeNames = driverTypes
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
.Select (d => d!.Name)
.Union (["v2", "v2win", "v2net"])
.ToList ()!;
return (driverTypes, driverTypeNames);
}
/// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>

View File

@@ -34,7 +34,7 @@ public class ApplicationImpl : IApplication
[RequiresDynamicCode ("AOT")]
public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
{
Application.InternalInit (driver, driverName);
Application.InternalInit (driver, string.IsNullOrWhiteSpace (driverName) ? Application.ForceDriver : driverName);
}
/// <summary>
@@ -85,7 +85,12 @@ public class ApplicationImpl : IApplication
if (!Application.Initialized)
{
// Init() has NOT been called.
Application.InternalInit (driver, null, true);
Application.InternalInit (driver, Application.ForceDriver, true);
}
if (Instance is ApplicationV2)
{
return Instance.Run<T> (errorHandler, driver);
}
var top = new T ();
@@ -227,6 +232,8 @@ public class ApplicationImpl : IApplication
Application.OnInitializedChanged (this, new (in init));
}
_lazyInstance = new (() => new ApplicationImpl ());
}
/// <inheritdoc />

View File

@@ -19,6 +19,11 @@ internal class NetEvents : IDisposable
{
_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
if (ConsoleDriver.RunningUnitTests)
{
return;
}
Task.Run (() =>
{
try
@@ -29,7 +34,8 @@ internal class NetEvents : IDisposable
{ }
}, _netEventsDisposed.Token);
Task.Run (() => {
Task.Run (() =>
{
try
{
CheckWindowSizeChange ();

View File

@@ -64,7 +64,19 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
}
/// <summary>Gets the location and size of the terminal screen.</summary>
public Rectangle Screen => new (new (0, 0), _output.GetWindowSize ());
public Rectangle Screen
{
get
{
if (ConsoleDriver.RunningUnitTests)
{
// In unit tests, we don't have a real output, so we return an empty rectangle.
return Rectangle.Empty;
}
return new (new (0, 0), _output.GetWindowSize ());
}
}
/// <summary>
/// Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject

View File

@@ -17,6 +17,12 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
public NetInput ()
{
Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
if (ConsoleDriver.RunningUnitTests)
{
return;
}
PlatformID p = Environment.OSVersion.Platform;
if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
@@ -39,7 +45,15 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
}
/// <inheritdoc/>
protected override bool Peek () { return Console.KeyAvailable; }
protected override bool Peek ()
{
if (ConsoleDriver.RunningUnitTests)
{
return false;
}
return Console.KeyAvailable;
}
/// <inheritdoc/>
protected override IEnumerable<ConsoleKeyInfo> Read ()

View File

@@ -47,6 +47,11 @@ public class NetOutput : IConsoleOutput
/// <inheritdoc/>
public void Write (IOutputBuffer buffer)
{
if (ConsoleDriver.RunningUnitTests)
{
return;
}
if (Console.WindowHeight < 1
|| buffer.Contents.Length != buffer.Rows * buffer.Cols
|| buffer.Rows != Console.WindowHeight)
@@ -197,7 +202,16 @@ public class NetOutput : IConsoleOutput
}
/// <inheritdoc/>
public Size GetWindowSize () { return new (Console.WindowWidth, Console.WindowHeight); }
public Size GetWindowSize ()
{
if (ConsoleDriver.RunningUnitTests)
{
// For unit tests, we return a default size.
return Size.Empty;
}
return new (Console.WindowWidth, Console.WindowHeight);
}
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
{

View File

@@ -20,6 +20,11 @@ internal class WindowSizeMonitor : IWindowSizeMonitor
/// <inheritdoc/>
public bool Poll ()
{
if (ConsoleDriver.RunningUnitTests)
{
return false;
}
Size size = _consoleOut.GetWindowSize ();
if (size != _lastSize)

View File

@@ -38,6 +38,12 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
public WindowsInput ()
{
Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
if (ConsoleDriver.RunningUnitTests)
{
return;
}
_inputHandle = GetStdHandle (STD_INPUT_HANDLE);
GetConsoleMode (_inputHandle, out uint v);
@@ -110,5 +116,13 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
}
}
public override void Dispose () { SetConsoleMode (_inputHandle, _originalConsoleMode); }
public override void Dispose ()
{
if (ConsoleDriver.RunningUnitTests)
{
return;
}
SetConsoleMode (_inputHandle, _originalConsoleMode);
}
}

View File

@@ -68,6 +68,11 @@ internal partial class WindowsOutput : IConsoleOutput
{
Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
if (ConsoleDriver.RunningUnitTests)
{
return;
}
_screenBuffer = CreateConsoleScreenBuffer (
DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
ShareMode.FileShareRead | ShareMode.FileShareWrite,
@@ -171,12 +176,13 @@ internal partial class WindowsOutput : IConsoleOutput
};
//size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
if (!WriteToConsole (
new (buffer.Cols, buffer.Rows),
outputBuffer,
bufferCoords,
damageRegion,
Application.Driver!.Force16Colors))
if (!ConsoleDriver.RunningUnitTests
&& !WriteToConsole (
new (buffer.Cols, buffer.Rows),
outputBuffer,
bufferCoords,
damageRegion,
Application.Driver!.Force16Colors))
{
int err = Marshal.GetLastWin32Error ();
@@ -312,6 +318,11 @@ internal partial class WindowsOutput : IConsoleOutput
/// <inheritdoc/>
public void SetCursorVisibility (CursorVisibility visibility)
{
if (ConsoleDriver.RunningUnitTests)
{
return;
}
if (Application.Driver!.Force16Colors)
{
var info = new WindowsConsole.ConsoleCursorInfo

View File

@@ -1,4 +1,5 @@
using System.IO.Abstractions;
using System.Globalization;
using System.IO.Abstractions;
using System.IO.Abstractions.TestingHelpers;
using System.Runtime.InteropServices;
using TerminalGuiFluentTesting;
@@ -12,6 +13,7 @@ public class FileDialogFluentTests
public FileDialogFluentTests (ITestOutputHelper outputHelper)
{
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
_out = new TestOutputWriter (outputHelper);
}

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Reflection;
using TerminalGuiFluentTesting;
using Xunit.Abstractions;
@@ -13,6 +14,7 @@ public class MenuBarv2Tests
public MenuBarv2Tests (ITestOutputHelper outputHelper)
{
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
_out = new TestOutputWriter (outputHelper);
}

View File

@@ -1,4 +1,4 @@
using System.Reflection;
using System.Globalization;
using TerminalGuiFluentTesting;
using Xunit.Abstractions;
@@ -7,9 +7,15 @@ namespace IntegrationTests.FluentTests;
/// <summary>
/// Tests for the PopoverMenu class
/// </summary>
public class PopoverMenuTests (ITestOutputHelper outputHelper)
public class PopoverMenuTests
{
private readonly TextWriter _out = new TestOutputWriter (outputHelper);
private readonly TextWriter _out;
public PopoverMenuTests (ITestOutputHelper outputHelper)
{
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
_out = new TestOutputWriter (outputHelper);
}
[Theory]
[ClassData (typeof (V2TestDrivers))]

View File

@@ -655,7 +655,17 @@ public class ApplicationTests
Assert.NotNull (SynchronizationContext.Current);
}
private void Shutdown () { Application.Shutdown (); }
private void Shutdown ()
{
if (ApplicationImpl.Instance is ApplicationV2)
{
ApplicationImpl.Instance.Shutdown ();
}
else
{
Application.Shutdown ();
}
}
#region RunTests
@@ -1104,6 +1114,89 @@ public class ApplicationTests
Assert.Null (Application.Top);
}
private class TestToplevel : Toplevel { }
[Theory]
[InlineData ("v2win", typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>))]
[InlineData ("v2net", typeof (ConsoleDriverFacade<ConsoleKeyInfo>))]
[InlineData ("FakeDriver", typeof (FakeDriver))]
[InlineData ("NetDriver", typeof (NetDriver))]
[InlineData ("WindowsDriver", typeof (WindowsDriver))]
[InlineData ("CursesDriver", typeof (CursesDriver))]
public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string driverName, Type expectedType)
{
Assert.True (ConsoleDriver.RunningUnitTests);
var result = false;
Task.Run (() =>
{
Task.Delay (300).Wait ();
}).ContinueWith (
(t, _) =>
{
// no longer loading
Application.Invoke (() =>
{
result = true;
Application.RequestStop ();
});
},
TaskScheduler.FromCurrentSynchronizationContext ());
Application.ForceDriver = driverName;
Application.Run<TestToplevel> ();
Assert.NotNull (Application.Driver);
Assert.Equal (expectedType, Application.Driver?.GetType ());
Assert.NotNull (Application.Top);
Assert.False (Application.Top!.Running);
Application.Top!.Dispose ();
Shutdown ();
Assert.True (result);
}
[Fact]
public void Run_T_With_Legacy_Driver_Does_Not_Call_ResetState_After_Init ()
{
Assert.False (Application.Initialized);
Application.Init ();
Assert.True (Application.Initialized);
Application.Iteration += (_, _) => Application.RequestStop ();
Application.Run<TestToplevel> ();
Assert.NotNull (Application.Driver);
Assert.NotNull (Application.Top);
Assert.False (Application.Top!.Running);
Application.Top!.Dispose ();
Shutdown ();
}
[Fact]
public void Run_T_With_V2_Driver_Does_Not_Call_ResetState_After_Init ()
{
Assert.False (Application.Initialized);
Application.Init (null, "v2net");
Assert.True (Application.Initialized);
Task.Run (() =>
{
Task.Delay (300).Wait ();
}).ContinueWith (
(t, _) =>
{
// no longer loading
Application.Invoke (() =>
{
Application.RequestStop ();
});
},
TaskScheduler.FromCurrentSynchronizationContext ());
Application.Run<TestToplevel> ();
Assert.NotNull (Application.Driver);
Assert.NotNull (Application.Top);
Assert.False (Application.Top!.Running);
Application.Top!.Dispose ();
Shutdown ();
}
// TODO: Add tests for Run that test errorHandler
#endregion

View File

@@ -7,6 +7,10 @@ using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class ApplicationV2Tests
{
public ApplicationV2Tests ()
{
ConsoleDriver.RunningUnitTests = true;
}
private ApplicationV2 NewApplicationV2 ()
{
@@ -362,7 +366,6 @@ public class ApplicationV2Tests
if (Application.Top != null)
{
Application.RaiseKeyDownEvent (Application.QuitKey);
return false;
}
return false;
@@ -575,8 +578,6 @@ public class ApplicationV2Tests
{
b.NewKeyDownEvent (Key.Enter);
b.NewKeyUpEvent (Key.Enter);
return false;
}
return false;

View File

@@ -3,6 +3,11 @@
namespace UnitTests.ConsoleDrivers.V2;
public class WindowSizeMonitorTests
{
public WindowSizeMonitorTests ()
{
ConsoleDriver.RunningUnitTests = false;
}
[Fact]
public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
{
@@ -70,7 +75,4 @@ public class WindowSizeMonitorTests
Assert.Single (result);
Assert.Equal (new Size (30, 20), result [0].Size);
}
}