mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
Merge branch 'v2_develop' of tig:tig/Terminal.Gui into v2_develop
This commit is contained in:
@@ -42,6 +42,12 @@
|
||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL: UICatalog --driver v2unix": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2unix",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL: UICatalog --driver v2net": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
@@ -63,13 +69,19 @@
|
||||
"WSL-Gnome: UICatalog --driver v2": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
|
||||
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2; exec bash\"'",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL-Gnome: UICatalog --driver v2unix": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2unix; exec bash\"'",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL-Gnome: UICatalog --driver v2net": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "bash -c 'while [ ! -e \"$XDG_RUNTIME_DIR/bus\" ]; do sleep 0.1; done; gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
|
||||
"commandLineArgs": "bash -c 'dbus-run-session -- gnome-terminal --wait -- bash -l -c \"dotnet UICatalog.dll --driver v2net; exec bash\"'",
|
||||
"distributionName": ""
|
||||
},
|
||||
"Benchmark All": {
|
||||
@@ -94,6 +106,24 @@
|
||||
"commandLineArgs": "dotnet UICatalog.dll --benchmark",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL: Benchmark All --driver v2": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2 --benchmark",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL: Benchmark All --driver v2unix": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2unix --benchmark",
|
||||
"distributionName": ""
|
||||
},
|
||||
"WSL: Benchmark All --driver v2net": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "wsl",
|
||||
"commandLineArgs": "dotnet UICatalog.dll --driver v2net --benchmark",
|
||||
"distributionName": ""
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker"
|
||||
},
|
||||
|
||||
@@ -217,7 +217,7 @@ public static partial class Application // Initialization (Init/Shutdown)
|
||||
List<string?> driverTypeNames = driverTypes
|
||||
.Where (d => !typeof (IConsoleDriverFacade).IsAssignableFrom (d))
|
||||
.Select (d => d!.Name)
|
||||
.Union (["v2", "v2win", "v2net"])
|
||||
.Union (["v2", "v2win", "v2net", "v2unix"])
|
||||
.ToList ()!;
|
||||
|
||||
return (driverTypes, driverTypeNames);
|
||||
|
||||
@@ -8,7 +8,7 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
|
||||
public EscAsAltPattern () { IsLastMinute = true; }
|
||||
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
private static readonly Regex _pattern = new (@"^\u001b([a-zA-Z0-9_])$");
|
||||
private static readonly Regex _pattern = new (@"^\u001b([\u0001-\u001a\u001fa-zA-Z0-9_])$");
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
|
||||
@@ -22,7 +22,14 @@ internal class EscAsAltPattern : AnsiKeyboardParserPattern
|
||||
return null;
|
||||
}
|
||||
|
||||
char key = match.Groups [1].Value [0];
|
||||
char ch = match.Groups [1].Value [0];
|
||||
|
||||
Key key = ch switch
|
||||
{
|
||||
>= '\u0001' and <= '\u001a' => ((Key)(ch + 96)).WithCtrl,
|
||||
'\u001f' => Key.D7.WithCtrl.WithShift,
|
||||
_ => ch
|
||||
};
|
||||
|
||||
return new Key (key).WithAlt;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Terminal.Gui.Drivers;
|
||||
public class Ss3Pattern : AnsiKeyboardParserPattern
|
||||
{
|
||||
#pragma warning disable IDE1006 // Naming Styles
|
||||
private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCAB])$");
|
||||
private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCABOHFwqysu])$");
|
||||
#pragma warning restore IDE1006 // Naming Styles
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -41,6 +41,13 @@ public class Ss3Pattern : AnsiKeyboardParserPattern
|
||||
'C' => Key.CursorRight,
|
||||
'A' => Key.CursorUp,
|
||||
'B' => Key.CursorDown,
|
||||
'H' => Key.Home,
|
||||
'F' => Key.End,
|
||||
'w' => Key.Home,
|
||||
'q' => Key.End,
|
||||
'y' => Key.PageUp,
|
||||
's' => Key.PageDown,
|
||||
'u' => Key.Clear,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1029,6 +1029,16 @@ public static class EscSeqUtils
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to set the Control key states based on the char.
|
||||
/// </summary>
|
||||
/// <param name="ch">The char value.</param>
|
||||
/// <returns></returns>
|
||||
public static ConsoleKeyInfo MapChar (char ch)
|
||||
{
|
||||
return MapConsoleKeyInfo (new (ch, ConsoleKey.None, false, false, false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
|
||||
/// </summary>
|
||||
@@ -1131,6 +1141,17 @@ public static class EscSeqUtils
|
||||
true);
|
||||
}
|
||||
|
||||
break;
|
||||
case uint n when n is >= '\u001c' and <= '\u001f':
|
||||
key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + 24);
|
||||
|
||||
newConsoleKeyInfo = new (
|
||||
(char)key,
|
||||
key,
|
||||
(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
|
||||
(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
|
||||
true);
|
||||
|
||||
break;
|
||||
case 127: // DEL
|
||||
key = ConsoleKey.Backspace;
|
||||
@@ -1375,6 +1396,12 @@ public static class EscSeqUtils
|
||||
{
|
||||
switch (keyInfo.Key)
|
||||
{
|
||||
case ConsoleKey.Multiply:
|
||||
case ConsoleKey.Add:
|
||||
case ConsoleKey.Separator:
|
||||
case ConsoleKey.Subtract:
|
||||
case ConsoleKey.Decimal:
|
||||
case ConsoleKey.Divide:
|
||||
case ConsoleKey.OemPeriod:
|
||||
case ConsoleKey.OemComma:
|
||||
case ConsoleKey.OemPlus:
|
||||
@@ -1391,8 +1418,31 @@ public static class EscSeqUtils
|
||||
case ConsoleKey.Oem102:
|
||||
if (keyInfo.KeyChar == 0)
|
||||
{
|
||||
// If the keyChar is 0, keyInfo.Key value is not a printable character.
|
||||
System.Diagnostics.Debug.Assert (keyInfo.Key == 0);
|
||||
// All Oem* produce a valid KeyChar and is not guaranteed to be printable ASCII, but it’s never just '\0' (null).
|
||||
// If that happens it's because Console.ReadKey is misreporting for AltGr + non-character keys
|
||||
// or if it's a combine key waiting for the next input which will determine the respective KeyChar.
|
||||
// This behavior only happens on Windows and not on Unix-like systems.
|
||||
if (keyInfo.Key != ConsoleKey.Multiply
|
||||
&& keyInfo.Key != ConsoleKey.Add
|
||||
&& keyInfo.Key != ConsoleKey.Decimal
|
||||
&& keyInfo.Key != ConsoleKey.Subtract
|
||||
&& keyInfo.Key != ConsoleKey.Divide
|
||||
&& keyInfo.Key != ConsoleKey.OemPeriod
|
||||
&& keyInfo.Key != ConsoleKey.OemComma
|
||||
&& keyInfo.Key != ConsoleKey.OemPlus
|
||||
&& keyInfo.Key != ConsoleKey.OemMinus
|
||||
&& keyInfo.Key != ConsoleKey.Oem1
|
||||
&& keyInfo.Key != ConsoleKey.Oem2
|
||||
&& keyInfo.Key != ConsoleKey.Oem3
|
||||
&& keyInfo.Key != ConsoleKey.Oem4
|
||||
&& keyInfo.Key != ConsoleKey.Oem5
|
||||
&& keyInfo.Key != ConsoleKey.Oem6
|
||||
&& keyInfo.Key != ConsoleKey.Oem7
|
||||
&& keyInfo.Key != ConsoleKey.Oem102)
|
||||
{
|
||||
// If the keyChar is 0, keyInfo.Key value is not a printable character.
|
||||
System.Diagnostics.Debug.Assert (keyInfo.Key == 0);
|
||||
}
|
||||
|
||||
return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
|
||||
}
|
||||
@@ -1411,7 +1461,7 @@ public static class EscSeqUtils
|
||||
// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
|
||||
if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
|
||||
{
|
||||
if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control) && keyInfo.Key == ConsoleKey.I)
|
||||
if (keyInfo is { Modifiers: ConsoleModifiers.Control, Key: ConsoleKey.I })
|
||||
{
|
||||
return KeyCode.Tab;
|
||||
}
|
||||
|
||||
@@ -81,24 +81,29 @@ public class ApplicationV2 : ApplicationImpl
|
||||
{
|
||||
PlatformID p = Environment.OSVersion.Platform;
|
||||
|
||||
bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
|
||||
bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
||||
bool definetlyWin = (driverName?.Contains ("win") ?? false) || _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
|
||||
bool definetlyNet = (driverName?.Contains ("net") ?? false) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
|
||||
bool definetlyUnix = (driverName?.Contains ("unix") ?? false) || _componentFactory is IComponentFactory<char>;
|
||||
|
||||
if (definetlyWin)
|
||||
{
|
||||
_coordinator = CreateWindowsSubcomponents ();
|
||||
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
||||
}
|
||||
else if (definetlyNet)
|
||||
{
|
||||
_coordinator = CreateNetSubcomponents ();
|
||||
_coordinator = CreateSubcomponents (() => new NetComponentFactory ());
|
||||
}
|
||||
else if (definetlyUnix)
|
||||
{
|
||||
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
|
||||
}
|
||||
else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
|
||||
{
|
||||
_coordinator = CreateWindowsSubcomponents ();
|
||||
_coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
|
||||
}
|
||||
else
|
||||
{
|
||||
_coordinator = CreateNetSubcomponents ();
|
||||
_coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
|
||||
}
|
||||
|
||||
_coordinator.StartAsync ().Wait ();
|
||||
@@ -109,49 +114,23 @@ public class ApplicationV2 : ApplicationImpl
|
||||
}
|
||||
}
|
||||
|
||||
private IMainLoopCoordinator CreateWindowsSubcomponents ()
|
||||
private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
|
||||
{
|
||||
ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
|
||||
MainLoop<WindowsConsole.InputRecord> loop = new ();
|
||||
ConcurrentQueue<T> inputBuffer = new ();
|
||||
MainLoop<T> loop = new ();
|
||||
|
||||
IComponentFactory<WindowsConsole.InputRecord> cf;
|
||||
IComponentFactory<T> cf;
|
||||
|
||||
if (_componentFactory != null)
|
||||
if (_componentFactory is IComponentFactory<T> typedFactory)
|
||||
{
|
||||
cf = (IComponentFactory<WindowsConsole.InputRecord>)_componentFactory;
|
||||
cf = typedFactory;
|
||||
}
|
||||
else
|
||||
{
|
||||
cf = new WindowsComponentFactory ();
|
||||
cf = fallbackFactory ();
|
||||
}
|
||||
|
||||
return new MainLoopCoordinator<WindowsConsole.InputRecord> (_timedEvents,
|
||||
inputBuffer,
|
||||
loop,
|
||||
cf);
|
||||
}
|
||||
|
||||
private IMainLoopCoordinator CreateNetSubcomponents ()
|
||||
{
|
||||
ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
|
||||
MainLoop<ConsoleKeyInfo> loop = new ();
|
||||
|
||||
IComponentFactory<ConsoleKeyInfo> cf;
|
||||
|
||||
if (_componentFactory != null)
|
||||
{
|
||||
cf = (IComponentFactory<ConsoleKeyInfo>)_componentFactory;
|
||||
}
|
||||
else
|
||||
{
|
||||
cf = new NetComponentFactory ();
|
||||
}
|
||||
|
||||
return new MainLoopCoordinator<ConsoleKeyInfo> (
|
||||
_timedEvents,
|
||||
inputBuffer,
|
||||
loop,
|
||||
cf);
|
||||
return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -260,16 +260,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
|
||||
/// <inheritdoc/>
|
||||
public virtual string GetVersionInfo ()
|
||||
{
|
||||
var type = "";
|
||||
|
||||
if (InputProcessor is WindowsInputProcessor)
|
||||
{
|
||||
type = "win";
|
||||
}
|
||||
else if (InputProcessor is NetInputProcessor)
|
||||
{
|
||||
type = "net";
|
||||
}
|
||||
string type = InputProcessor.DriverName ?? throw new ArgumentNullException (nameof (InputProcessor.DriverName));
|
||||
|
||||
return "v2" + type;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ public interface IInputProcessor
|
||||
/// <summary>Event fired when a mouse event occurs.</summary>
|
||||
event EventHandler<MouseEventArgs>? MouseEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the driver associated with this input processor.
|
||||
/// </summary>
|
||||
string DriverName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
|
||||
/// <see cref="OnKeyUp"/>.
|
||||
|
||||
3
Terminal.Gui/Drivers/V2/IUnixInput.cs
Normal file
3
Terminal.Gui/Drivers/V2/IUnixInput.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
internal interface IUnixInput : IConsoleInput<char>;
|
||||
@@ -30,6 +30,9 @@ public abstract class InputProcessor<T> : IInputProcessor
|
||||
/// </summary>
|
||||
public ConcurrentQueue<T> InputBuffer { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DriverName { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAnsiResponseParser GetParser () { return Parser; }
|
||||
|
||||
|
||||
@@ -70,10 +70,23 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushConsoleInput ()
|
||||
{
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
Console.ReadKey (intercept: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Dispose ()
|
||||
{
|
||||
base.Dispose ();
|
||||
|
||||
// Disable mouse events first
|
||||
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
|
||||
|
||||
//Disable alternative screen buffer.
|
||||
@@ -83,5 +96,8 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
|
||||
Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
|
||||
|
||||
_adjustConsole?.Cleanup ();
|
||||
|
||||
// Flush any pending input so no stray events appear
|
||||
FlushConsoleInput ();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
|
||||
#pragma warning restore CA2211
|
||||
|
||||
/// <inheritdoc/>
|
||||
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ()) { }
|
||||
public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ())
|
||||
{
|
||||
DriverName = "net";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Process (ConsoleKeyInfo consoleKeyInfo)
|
||||
|
||||
@@ -54,24 +54,31 @@ public class NetOutput : OutputBase, IConsoleOutput
|
||||
/// <inheritdoc/>
|
||||
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||
{
|
||||
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||
output,
|
||||
attr.Foreground.R,
|
||||
attr.Foreground.G,
|
||||
attr.Foreground.B
|
||||
);
|
||||
if (Application.Force16Colors)
|
||||
{
|
||||
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
|
||||
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||
output,
|
||||
attr.Foreground.R,
|
||||
attr.Foreground.G,
|
||||
attr.Foreground.B
|
||||
);
|
||||
|
||||
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||
output,
|
||||
attr.Background.R,
|
||||
attr.Background.G,
|
||||
attr.Background.B
|
||||
);
|
||||
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||
output,
|
||||
attr.Background.R,
|
||||
attr.Background.G,
|
||||
attr.Background.B
|
||||
);
|
||||
}
|
||||
|
||||
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Write (StringBuilder output)
|
||||
{
|
||||
@@ -116,9 +123,25 @@ public class NetOutput : OutputBase, IConsoleOutput
|
||||
}
|
||||
|
||||
|
||||
private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
|
||||
|
||||
/// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
|
||||
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||
{
|
||||
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
|
||||
if (visibility != CursorVisibility.Invisible)
|
||||
{
|
||||
if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
|
||||
{
|
||||
_currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
|
||||
|
||||
Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
|
||||
}
|
||||
|
||||
Write (EscSeqUtils.CSI_ShowCursor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write (EscSeqUtils.CSI_HideCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
Terminal.Gui/Drivers/V2/UnixComponentFactory.cs
Normal file
29
Terminal.Gui/Drivers/V2/UnixComponentFactory.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IComponentFactory{T}"/> implementation for native unix console I/O i.e. v2unix.
|
||||
/// This factory creates instances of internal classes <see cref="UnixInput"/>, <see cref="UnixOutput"/> etc.
|
||||
/// </summary>
|
||||
public class UnixComponentFactory : ComponentFactory<char>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override IConsoleInput<char> CreateInput ()
|
||||
{
|
||||
return new UnixInput ();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IInputProcessor CreateInputProcessor (ConcurrentQueue<char> inputBuffer)
|
||||
{
|
||||
return new UnixInputProcessor (inputBuffer);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IConsoleOutput CreateOutput ()
|
||||
{
|
||||
return new UnixOutput ();
|
||||
}
|
||||
}
|
||||
266
Terminal.Gui/Drivers/V2/UnixInput.cs
Normal file
266
Terminal.Gui/Drivers/V2/UnixInput.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
internal class UnixInput : ConsoleInput<char>, IUnixInput
|
||||
{
|
||||
private const int STDIN_FILENO = 0;
|
||||
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
private struct Termios
|
||||
{
|
||||
public uint c_iflag;
|
||||
public uint c_oflag;
|
||||
public uint c_cflag;
|
||||
public uint c_lflag;
|
||||
|
||||
[MarshalAs (UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
public byte [] c_cc;
|
||||
|
||||
public uint c_ispeed;
|
||||
public uint c_ospeed;
|
||||
}
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int tcgetattr (int fd, out Termios termios);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int tcsetattr (int fd, int optional_actions, ref Termios termios);
|
||||
|
||||
// try cfmakeraw (glibc and macOS usually export it)
|
||||
[DllImport ("libc", EntryPoint = "cfmakeraw", SetLastError = false)]
|
||||
private static extern void cfmakeraw_ref (ref Termios termios);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern nint strerror (int err);
|
||||
|
||||
private const int TCSANOW = 0;
|
||||
|
||||
private const ulong BRKINT = 0x00000002;
|
||||
private const ulong ICRNL = 0x00000100;
|
||||
private const ulong INPCK = 0x00000010;
|
||||
private const ulong ISTRIP = 0x00000020;
|
||||
private const ulong IXON = 0x00000400;
|
||||
|
||||
private const ulong OPOST = 0x00000001;
|
||||
|
||||
private const ulong ECHO = 0x00000008;
|
||||
private const ulong ICANON = 0x00000100;
|
||||
private const ulong IEXTEN = 0x00008000;
|
||||
private const ulong ISIG = 0x00000001;
|
||||
|
||||
private const ulong CS8 = 0x00000030;
|
||||
|
||||
private Termios _original;
|
||||
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
private struct Pollfd
|
||||
{
|
||||
public int fd;
|
||||
public short events;
|
||||
public readonly short revents; // readonly signals "don't touch this in managed code"
|
||||
}
|
||||
|
||||
/// <summary>Condition on which to wake up from file descriptor activity. These match the Linux/BSD poll definitions.</summary>
|
||||
[Flags]
|
||||
private enum Condition : short
|
||||
{
|
||||
/// <summary>There is data to read</summary>
|
||||
PollIn = 1,
|
||||
|
||||
/// <summary>There is urgent data to read</summary>
|
||||
PollPri = 2,
|
||||
|
||||
/// <summary>Writing to the specified descriptor will not block</summary>
|
||||
PollOut = 4,
|
||||
|
||||
/// <summary>Error condition on output</summary>
|
||||
PollErr = 8,
|
||||
|
||||
/// <summary>Hang-up on output</summary>
|
||||
PollHup = 16,
|
||||
|
||||
/// <summary>File descriptor is not open.</summary>
|
||||
PollNval = 32
|
||||
}
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int poll ([In][Out] Pollfd [] ufds, uint nfds, int timeout);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int read (int fd, byte [] buf, int count);
|
||||
|
||||
// File descriptor for stdout
|
||||
private const int STDOUT_FILENO = 1;
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int write (int fd, byte [] buf, int count);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int tcflush (int fd, int queueSelector);
|
||||
|
||||
private const int TCIFLUSH = 0; // flush data received but not read
|
||||
|
||||
private Pollfd [] _pollMap;
|
||||
|
||||
public UnixInput ()
|
||||
{
|
||||
Logging.Logger.LogInformation ($"Creating {nameof (UnixInput)}");
|
||||
|
||||
if (ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pollMap = new Pollfd [1];
|
||||
_pollMap [0].fd = STDIN_FILENO; // stdin
|
||||
_pollMap [0].events = (short)Condition.PollIn;
|
||||
|
||||
EnableRawModeAndTreatControlCAsInput ();
|
||||
|
||||
//Enable alternative screen buffer.
|
||||
WriteRaw (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
|
||||
|
||||
//Set cursor key to application.
|
||||
WriteRaw (EscSeqUtils.CSI_HideCursor);
|
||||
|
||||
WriteRaw (EscSeqUtils.CSI_EnableMouseEvents);
|
||||
}
|
||||
|
||||
private void EnableRawModeAndTreatControlCAsInput ()
|
||||
{
|
||||
if (tcgetattr (STDIN_FILENO, out _original) != 0)
|
||||
{
|
||||
var e = Marshal.GetLastWin32Error ();
|
||||
throw new InvalidOperationException ($"tcgetattr failed errno={e} ({StrError (e)})");
|
||||
}
|
||||
|
||||
var raw = _original;
|
||||
|
||||
// Prefer cfmakeraw if available
|
||||
try
|
||||
{
|
||||
cfmakeraw_ref (ref raw);
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
// fallback: roughly cfmakeraw equivalent
|
||||
raw.c_iflag &= ~((uint)BRKINT | (uint)ICRNL | (uint)INPCK | (uint)ISTRIP | (uint)IXON);
|
||||
raw.c_oflag &= ~(uint)OPOST;
|
||||
raw.c_cflag |= (uint)CS8;
|
||||
raw.c_lflag &= ~((uint)ECHO | (uint)ICANON | (uint)IEXTEN | (uint)ISIG);
|
||||
}
|
||||
|
||||
if (tcsetattr (STDIN_FILENO, TCSANOW, ref raw) != 0)
|
||||
{
|
||||
var e = Marshal.GetLastWin32Error ();
|
||||
throw new InvalidOperationException ($"tcsetattr failed errno={e} ({StrError (e)})");
|
||||
}
|
||||
}
|
||||
|
||||
private string StrError (int err)
|
||||
{
|
||||
var p = strerror (err);
|
||||
return p == nint.Zero ? $"errno={err}" : Marshal.PtrToStringAnsi (p) ?? $"errno={err}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool Peek ()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int n = poll (_pollMap!, (uint)_pollMap!.Length, 0);
|
||||
|
||||
if (n != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Optionally log the exception
|
||||
Logging.Logger.LogError ($"Error in Peek: {ex.Message}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private void WriteRaw (string text)
|
||||
{
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
byte [] utf8 = Encoding.UTF8.GetBytes (text);
|
||||
// Write to stdout (fd 1)
|
||||
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IEnumerable<char> Read ()
|
||||
{
|
||||
while (poll (_pollMap!, (uint)_pollMap!.Length, 0) != 0)
|
||||
{
|
||||
// Check if stdin has data
|
||||
if ((_pollMap [0].revents & (int)Condition.PollIn) != 0)
|
||||
{
|
||||
var buf = new byte [256];
|
||||
int bytesRead = read (0, buf, buf.Length); // Read from stdin
|
||||
string input = Encoding.UTF8.GetString (buf, 0, bytesRead);
|
||||
|
||||
foreach (char ch in input)
|
||||
{
|
||||
yield return ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushConsoleInput ()
|
||||
{
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
var fds = new Pollfd [1];
|
||||
fds [0].fd = STDIN_FILENO;
|
||||
fds [0].events = (short)Condition.PollIn;
|
||||
var buf = new byte [256];
|
||||
while (poll (fds, 1, 0) > 0)
|
||||
{
|
||||
read (STDIN_FILENO, buf, buf.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dispose ()
|
||||
{
|
||||
base.Dispose ();
|
||||
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
// Disable mouse events first
|
||||
WriteRaw (EscSeqUtils.CSI_DisableMouseEvents);
|
||||
|
||||
// Drain any pending input already queued by the terminal
|
||||
FlushConsoleInput ();
|
||||
|
||||
// Flush kernel input buffer
|
||||
tcflush (STDIN_FILENO, TCIFLUSH);
|
||||
|
||||
//Disable alternative screen buffer.
|
||||
WriteRaw (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
|
||||
|
||||
//Set cursor key to cursor.
|
||||
WriteRaw (EscSeqUtils.CSI_ShowCursor);
|
||||
|
||||
// Restore terminal to original state
|
||||
tcsetattr (STDIN_FILENO, TCSANOW, ref _original);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Terminal.Gui/Drivers/V2/UnixInputProcessor.cs
Normal file
38
Terminal.Gui/Drivers/V2/UnixInputProcessor.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// Input processor for <see cref="UnixInput"/>, deals in <see cref="char"/> stream.
|
||||
/// </summary>
|
||||
internal class UnixInputProcessor : InputProcessor<char>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public UnixInputProcessor (ConcurrentQueue<char> inputBuffer) : base (inputBuffer, new UnixKeyConverter ())
|
||||
{
|
||||
DriverName = "unix";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Process (char input)
|
||||
{
|
||||
foreach (Tuple<char, char> released in Parser.ProcessInput (Tuple.Create (input, input)))
|
||||
{
|
||||
ProcessAfterParsing (released.Item2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ProcessAfterParsing (char input)
|
||||
{
|
||||
var key = KeyConverter.ToKey (input);
|
||||
|
||||
// If the key is not valid, we don't want to raise any events.
|
||||
if (IsValidInput (key, out key))
|
||||
{
|
||||
OnKeyDown (key);
|
||||
OnKeyUp (key);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Terminal.Gui/Drivers/V2/UnixKeyConverter.cs
Normal file
20
Terminal.Gui/Drivers/V2/UnixKeyConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IKeyConverter{T}"/> capable of converting the
|
||||
/// unix native <see cref="char"/> class
|
||||
/// into Terminal.Gui shared <see cref="Key"/> representation
|
||||
/// (used by <see cref="View"/> etc).
|
||||
/// </summary>
|
||||
internal class UnixKeyConverter : IKeyConverter<char>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Key ToKey (char value)
|
||||
{
|
||||
ConsoleKeyInfo adjustedInput = EscSeqUtils.MapChar (value);
|
||||
|
||||
return EscSeqUtils.MapKey (adjustedInput);
|
||||
}
|
||||
}
|
||||
175
Terminal.Gui/Drivers/V2/UnixOutput.cs
Normal file
175
Terminal.Gui/Drivers/V2/UnixOutput.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
||||
namespace Terminal.Gui.Drivers;
|
||||
|
||||
internal class UnixOutput : OutputBase, IConsoleOutput
|
||||
{
|
||||
[StructLayout (LayoutKind.Sequential)]
|
||||
private struct WinSize
|
||||
{
|
||||
public ushort ws_row;
|
||||
public ushort ws_col;
|
||||
public ushort ws_xpixel;
|
||||
public ushort ws_ypixel;
|
||||
}
|
||||
|
||||
private static readonly uint TIOCGWINSZ =
|
||||
RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ||
|
||||
RuntimeInformation.IsOSPlatform (OSPlatform.FreeBSD)
|
||||
? 0x40087468u // Darwin/BSD
|
||||
: 0x5413u; // Linux
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int ioctl (int fd, uint request, out WinSize ws);
|
||||
|
||||
// File descriptor for stdout
|
||||
private const int STDOUT_FILENO = 1;
|
||||
|
||||
[DllImport ("libc")]
|
||||
private static extern int write (int fd, byte [] buf, int n);
|
||||
|
||||
[DllImport ("libc", SetLastError = true)]
|
||||
private static extern int dup (int fd);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle)
|
||||
{
|
||||
if (Application.Force16Colors)
|
||||
{
|
||||
output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ()));
|
||||
output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
EscSeqUtils.CSI_AppendForegroundColorRGB (
|
||||
output,
|
||||
attr.Foreground.R,
|
||||
attr.Foreground.G,
|
||||
attr.Foreground.B
|
||||
);
|
||||
|
||||
EscSeqUtils.CSI_AppendBackgroundColorRGB (
|
||||
output,
|
||||
attr.Background.R,
|
||||
attr.Background.G,
|
||||
attr.Background.B
|
||||
);
|
||||
EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Write (StringBuilder output)
|
||||
{
|
||||
byte [] utf8 = Encoding.UTF8.GetBytes (output.ToString ());
|
||||
// Write to stdout (fd 1)
|
||||
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||
}
|
||||
|
||||
private Point? _lastCursorPosition;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY)
|
||||
{
|
||||
if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == screenPositionX && _lastCursorPosition.Value.Y == screenPositionY)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_lastCursorPosition = new (screenPositionX, screenPositionY);
|
||||
|
||||
using var writer = CreateUnixStdoutWriter ();
|
||||
|
||||
// + 1 is needed because Unix is based on 1 instead of 0 and
|
||||
EscSeqUtils.CSI_WriteCursorPosition (writer, screenPositionY + 1, screenPositionX + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private TextWriter CreateUnixStdoutWriter ()
|
||||
{
|
||||
// duplicate stdout so we don’t mess with Console.Out’s FD
|
||||
int fdCopy = dup (STDOUT_FILENO);
|
||||
|
||||
if (fdCopy == -1)
|
||||
{
|
||||
throw new IOException ("Failed to dup STDOUT_FILENO");
|
||||
}
|
||||
|
||||
// wrap the raw fd into a SafeFileHandle
|
||||
var handle = new SafeFileHandle (fdCopy, ownsHandle: true);
|
||||
|
||||
// create FileStream from the safe handle
|
||||
var stream = new FileStream (handle, FileAccess.Write);
|
||||
|
||||
return new StreamWriter (stream)
|
||||
{
|
||||
AutoFlush = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write (ReadOnlySpan<char> text)
|
||||
{
|
||||
if (!ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
byte [] utf8 = Encoding.UTF8.GetBytes (text.ToArray ());
|
||||
// Write to stdout (fd 1)
|
||||
write (STDOUT_FILENO, utf8, utf8.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Size GetWindowSize ()
|
||||
{
|
||||
if (ConsoleDriver.RunningUnitTests)
|
||||
{
|
||||
// For unit tests, we return a default size.
|
||||
return Size.Empty;
|
||||
}
|
||||
|
||||
if (ioctl (1, TIOCGWINSZ, out WinSize ws) == 0)
|
||||
{
|
||||
if (ws.ws_col > 0 && ws.ws_row > 0)
|
||||
{
|
||||
return new (ws.ws_col, ws.ws_row);
|
||||
}
|
||||
}
|
||||
|
||||
return Size.Empty; // fallback
|
||||
}
|
||||
|
||||
private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle;
|
||||
|
||||
/// <inheritdoc cref="IConsoleOutput.SetCursorVisibility"/>
|
||||
public override void SetCursorVisibility (CursorVisibility visibility)
|
||||
{
|
||||
if (visibility != CursorVisibility.Invisible)
|
||||
{
|
||||
if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF))
|
||||
{
|
||||
_currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF);
|
||||
|
||||
Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle));
|
||||
}
|
||||
|
||||
Write (EscSeqUtils.CSI_ShowCursor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write (EscSeqUtils.CSI_HideCursor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCursorPosition (int col, int row)
|
||||
{
|
||||
SetCursorPositionImpl (col, row);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose ()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,10 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
|
||||
private readonly bool [] _lastWasPressed = new bool[4];
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ()) { }
|
||||
public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ())
|
||||
{
|
||||
DriverName = "win";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Process (InputRecord inputEvent)
|
||||
|
||||
@@ -30,6 +30,7 @@ public class SyncrhonizationContextTests
|
||||
[InlineData (typeof (CursesDriver))]
|
||||
[InlineData (typeof (ConsoleDriverFacade<WindowsConsole.InputRecord>), "v2win")]
|
||||
[InlineData (typeof (ConsoleDriverFacade<ConsoleKeyInfo>), "v2net")]
|
||||
[InlineData (typeof (ConsoleDriverFacade<char>), "v2unix")]
|
||||
public void SynchronizationContext_Post (Type driverType, string driverName = null)
|
||||
{
|
||||
lock (_lockPost)
|
||||
|
||||
@@ -103,16 +103,27 @@ public class AnsiKeyboardParserTests
|
||||
yield return new object [] { "\u001b[1;2P", Key.F1.WithShift };
|
||||
yield return new object [] { "\u001b[1;3Q", Key.F2.WithAlt };
|
||||
yield return new object [] { "\u001b[1;5R", Key.F3.WithCtrl };
|
||||
|
||||
|
||||
// Keys with Alt modifiers
|
||||
yield return new object [] { "\u001ba", Key.A.WithAlt, true };
|
||||
yield return new object [] { "\u001bA", Key.A.WithShift.WithAlt, true };
|
||||
yield return new object [] { "\u001b1", Key.D1.WithAlt, true };
|
||||
|
||||
// Keys with Ctrl and Alt modifiers
|
||||
yield return new object [] { "\u001b\u0001", Key.A.WithCtrl.WithAlt, true };
|
||||
yield return new object [] { "\u001b\u001a", Key.Z.WithCtrl.WithAlt, true };
|
||||
|
||||
// Keys with Ctrl, Shift and Alt modifiers
|
||||
yield return new object [] { "\u001b\u001f", Key.D7.WithCtrl.WithShift.WithAlt, true };
|
||||
}
|
||||
|
||||
// Consolidated test for all keyboard events (e.g., arrow keys)
|
||||
[Theory]
|
||||
[MemberData (nameof (GetKeyboardTestData))]
|
||||
public void ProcessKeyboardInput_ReturnsCorrectKey (string? input, Key? expectedKey)
|
||||
public void ProcessKeyboardInput_ReturnsCorrectKey (string? input, Key? expectedKey, bool isLastMinute = false)
|
||||
{
|
||||
// Act
|
||||
Key? result = _parser.IsKeyboard (input)?.GetKey (input);
|
||||
Key? result = _parser.IsKeyboard (input, isLastMinute)?.GetKey (input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
|
||||
|
||||
@@ -1538,6 +1538,22 @@ public class EscSeqUtilsTests
|
||||
Assert.Equal (expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ('\u001B', KeyCode.Esc)]
|
||||
[InlineData ('\r', KeyCode.Enter)]
|
||||
[InlineData ('1', KeyCode.D1)]
|
||||
[InlineData ('!', (KeyCode)'!')]
|
||||
[InlineData ('a', KeyCode.A)]
|
||||
[InlineData ('A', KeyCode.A | KeyCode.ShiftMask)]
|
||||
public void MapChar_Returns_Modifiers_If_Needed (char ch, KeyCode keyCode)
|
||||
{
|
||||
ConsoleKeyInfo cki = EscSeqUtils.MapChar (ch);
|
||||
Key key = EscSeqUtils.MapKey (cki);
|
||||
Key expectedKey = keyCode;
|
||||
|
||||
Assert.Equal (key, expectedKey);
|
||||
}
|
||||
|
||||
private void ClearAll ()
|
||||
{
|
||||
EscSeqRequests.Clear ();
|
||||
|
||||
Reference in New Issue
Block a user