Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes # 3966. TextField crashes app when pasting unicode surrogate pair #3982

Draft
wants to merge 9 commits into
base: v2_develop
Choose a base branch
from
34 changes: 34 additions & 0 deletions Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,40 @@ public void OnMouseEvent (MouseEventArgs a)
/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);

internal char _highSurrogate = '\0';

internal bool IsValidInput (KeyCode keyCode, out KeyCode result)
{
result = keyCode;

if (char.IsHighSurrogate ((char)keyCode))
{
_highSurrogate = (char)keyCode;

return false;
}

if (_highSurrogate > 0 && char.IsLowSurrogate ((char)keyCode))
{
result = (KeyCode)new Rune (_highSurrogate, (char)keyCode).Value;
_highSurrogate = '\0';

return true;
}

if (char.IsSurrogate ((char)keyCode))
{
return false;
}

if (_highSurrogate > 0)
{
_highSurrogate = '\0';
}

return true;
}

#endregion

private AnsiRequestScheduler? _scheduler;
Expand Down
7 changes: 5 additions & 2 deletions Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@
while (wch2 == Curses.KeyMouse)
{
// BUGBUG: Fix this nullable issue.
Key kea = null;

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 732 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Converting null literal or possible null value to non-nullable type.

ConsoleKeyInfo [] cki =
{
Expand All @@ -739,7 +739,7 @@
};
code = 0;
// BUGBUG: Fix this nullable issue.
HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Possible null reference assignment.

Check warning on line 742 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Possible null reference assignment.
}

return;
Expand Down Expand Up @@ -797,7 +797,7 @@
}

// BUGBUG: Fix this nullable issue.
Key key = null;

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Converting null literal or possible null value to non-nullable type.

Check warning on line 800 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Converting null literal or possible null value to non-nullable type.

if (code == 0)
{
Expand Down Expand Up @@ -828,7 +828,7 @@
new ((char)KeyCode.Esc, 0, false, false, false), new ((char)wch2, 0, false, false, false)
];
// BUGBUG: Fix this nullable issue.
HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Possible null reference assignment.

Check warning on line 831 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Possible null reference assignment.

return;
}
Expand Down Expand Up @@ -924,8 +924,11 @@
k &= ~KeyCode.Space;
}

OnKeyDown (new Key (k));
OnKeyUp (new Key (k));
if (IsValidInput (k, out k))
{
OnKeyDown (new (k));
OnKeyUp (new (k));
}
}
}

Expand Down Expand Up @@ -980,7 +983,7 @@
EscSeqUtils.DecodeEscSeq (
ref consoleKeyInfo,
ref ck,
cki,

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.

Check warning on line 986 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Possible null reference argument for parameter 'cki' in 'void EscSeqUtils.DecodeEscSeq(ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo[] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string[] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point>? continuousButtonPressedHandler)'.
ref mod,
out _,
out _,
Expand All @@ -1001,7 +1004,7 @@
}

// BUGBUG: Fix this nullable issue.
cki = null;

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 1007 in Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

if (wch2 == 27)
{
Expand Down
8 changes: 6 additions & 2 deletions Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,12 @@ private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
}

KeyCode map = MapKey (consoleKeyInfo);
OnKeyDown (new Key (map));
OnKeyUp (new Key (map));

if (IsValidInput (map, out map))
{
OnKeyDown (new (map));
OnKeyUp (new (map));
}

//OnKeyPressed (new KeyEventArgs (map));
}
Expand Down
7 changes: 5 additions & 2 deletions Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@

// BUGBUG: Fix this nullable issue.
/// <inheritdoc />
internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_release

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Dereference of a possibly null reference.

Check warning on line 229 in Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Dereference of a possibly null reference.
internal NetMainLoop? _mainLoopDriver;

/// <inheritdoc />
Expand Down Expand Up @@ -321,8 +321,11 @@
break;
}

OnKeyDown (new (map));
OnKeyUp (new (map));
if (IsValidInput (map, out map))
{
OnKeyDown (new (map));
OnKeyUp (new (map));
}

break;
case EventType.Mouse:
Expand Down
9 changes: 6 additions & 3 deletions Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,12 @@ internal void ProcessInputAfterParsing (WindowsConsole.InputRecord inputEvent)
break;
}

// This follows convention in NetDriver
OnKeyDown (new Key (map));
OnKeyUp (new Key (map));
if (IsValidInput (map, out map))
{
// This follows convention in NetDriver
OnKeyDown (new (map));
OnKeyUp (new (map));
}

break;

Expand Down
52 changes: 51 additions & 1 deletion Terminal.Gui/Input/Keyboard/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,44 @@ public Key (string str)
KeyCode = key.KeyCode;
}

/// <summary>
/// Constructs a new Key from an integer describing the key.
/// It parses the integer as Key by calling the constructor with a char or calls the constructor with a
/// KeyCode.
/// </summary>
/// <remarks>
/// Don't rely on <paramref name="value"/> passed from <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/> because
/// would not return the expected keys from 'a' to 'z'.
/// </remarks>
/// <param name="value">The integer describing the key.</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="ArgumentException"></exception>
public Key (int value)
{
if (value < 0 || value > RuneExtensions.MaxUnicodeCodePoint)
{
throw new ArgumentOutOfRangeException (@$"Invalid key value: {value}", nameof (value));
}

if (char.IsSurrogate ((char)value))
{
throw new ArgumentException (@$"Surrogate key not allowed: {value}", nameof (value));
}

Key key;

if (((Rune)value).IsBmp)
{
key = new ((char)value);
}
else
{
key = new ((KeyCode)value);
}

KeyCode = key.KeyCode;
}

/// <summary>
/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
/// Useful for determining if a key represents is a printable character.
Expand Down Expand Up @@ -388,6 +426,11 @@ public static Rune ToRune (KeyCode key)
/// <param name="str"></param>
public static implicit operator Key (string str) { return new (str); }

/// <summary>Cast <see langword="int"/> to a <see cref="Key"/>.</summary>
/// <remarks>See <see cref="Key(int)"/> for more information.</remarks>
/// <param name="value"></param>
public static implicit operator Key (int value) { return new (value); }

/// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
/// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
/// <param name="key"></param>
Expand Down Expand Up @@ -550,7 +593,7 @@ private static string TrimEndSeparator (string input, Rune separator)
// "Ctrl+" (trim)
// "Ctrl++" (trim)

if (input.Length > 1 && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
if (input.Length > 1 && !char.IsHighSurrogate (input [^2]) && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
{
return input [..^1];
}
Expand Down Expand Up @@ -640,6 +683,13 @@ public static bool TryParse (string text, out Key key)
return false;
}

if (text.Length == 2 && char.IsHighSurrogate (text [^2]) && char.IsLowSurrogate (text [^1]))
{
// It's a surrogate pair and there is no modifiers
key = new (new Rune (text [^2], text [^1]).Value);
return true;
}

// e.g. "Ctrl++"
if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
{
Expand Down
20 changes: 5 additions & 15 deletions Terminal.Gui/Views/TextField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -730,21 +730,11 @@ public virtual void DeleteCharRight ()
/// <param name="useOldCursorPos">Use the previous cursor position.</param>
public void InsertText (string toAdd, bool useOldCursorPos = true)
{
foreach (char ch in toAdd)
foreach (Rune rune in toAdd.EnumerateRunes ())
{
Key key;

try
{
key = ch;
}
catch (Exception)
{
throw new ArgumentException (
$"Cannot insert character '{ch}' because it does not map to a Key"
);
}

// All rune can be mapped to a Key and no exception will throw here because
// EnumerateRunes will replace a surrogate char with the Rune.ReplacementChar
Key key = rune.Value;
InsertText (key, useOldCursorPos);
}
}
Expand Down Expand Up @@ -1114,7 +1104,7 @@ public virtual void Paste ()
TextModel.SetCol (ref col, Viewport.Width - 1, cols);
}

int pos = _cursorPosition - ScrollOffset + Math.Min (Viewport.X, 0);
int pos = col - ScrollOffset + Math.Min (Viewport.X, 0);
Move (pos, 0);

return new Point (pos, 0);
Expand Down
111 changes: 111 additions & 0 deletions Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,115 @@ public void TerminalResized_Simulation (Type driverType)
// Application.Run (win);
// Application.Shutdown ();
// }

[Theory]
[InlineData ('\ud83d', '\udcc4')] // This seems right sequence but Stack is LIFO
[InlineData ('\ud83d', '\ud83d')]
[InlineData ('\udcc4', '\udcc4')]
public void FakeDriver_IsValidInput_Wrong_Surrogate_Sequence (char c1, char c2)
{
var driver = (IConsoleDriver)Activator.CreateInstance (typeof (FakeDriver));
Application.Init (driver);

Stack<ConsoleKeyInfo> mKeys = new (
[
new ('a', ConsoleKey.A, false, false, false),
new (c1, ConsoleKey.None, false, false, false),
new (c2, ConsoleKey.None, false, false, false)
]);

Console.MockKeyPresses = mKeys;

Toplevel top = new ();
var view = new View { CanFocus = true };
var rText = "";
var idx = 0;

view.KeyDown += (s, e) =>
{
Assert.Equal (new ('a'), e.AsRune);
Assert.Equal ("a", e.AsRune.ToString ());
rText += e.AsRune;
e.Handled = true;
idx++;
};
top.Add (view);

Application.Iteration += (s, a) =>
{
if (mKeys.Count == 0)
{
Application.RequestStop ();
}
};

Application.Run (top);

Assert.Equal ("a", rText);
Assert.Equal (1, idx);
Assert.Equal (0, ((FakeDriver)driver)._highSurrogate);

top.Dispose ();

// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}

[Fact]
public void FakeDriver_IsValidInput_Correct_Surrogate_Sequence ()
{
var driver = (IConsoleDriver)Activator.CreateInstance (typeof (FakeDriver));
Application.Init (driver);

Stack<ConsoleKeyInfo> mKeys = new (
[
new ('a', ConsoleKey.A, false, false, false),
new ('\udcc4', ConsoleKey.None, false, false, false),
new ('\ud83d', ConsoleKey.None, false, false, false)
]);

Console.MockKeyPresses = mKeys;

Toplevel top = new ();
var view = new View { CanFocus = true };
var rText = "";
var idx = 0;

view.KeyDown += (s, e) =>
{
if (idx == 0)
{
Assert.Equal (new (0x1F4C4), e.AsRune);
Assert.Equal ("📄", e.AsRune.ToString ());
}
else
{
Assert.Equal (new ('a'), e.AsRune);
Assert.Equal ("a", e.AsRune.ToString ());
}

rText += e.AsRune;
e.Handled = true;
idx++;
};
top.Add (view);

Application.Iteration += (s, a) =>
{
if (mKeys.Count == 0)
{
Application.RequestStop ();
}
};

Application.Run (top);

Assert.Equal ("📄a", rText);
Assert.Equal (2, idx);

top.Dispose ();

// Shutdown must be called to safely clean up Application if Init has been called
Application.Shutdown ();
}
}
29 changes: 29 additions & 0 deletions Tests/UnitTests/Views/TextFieldTests.cs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As part of this PR, would you please move any parallelizable unit tests for TextField to the parallelizable unit tests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're asking to move to the same unit test type 😃

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I misunderstand. Can you help me to identify how I can check if an unit test can be parallelizable, please? Thanks.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it doesn't use AutoInitShutdown, SetupFakeDriver, Application, MouseGrab, or CM it can probably be moved.

Original file line number Diff line number Diff line change
Expand Up @@ -2178,4 +2178,33 @@ public void Draw_Esc_Rune ()

tf.Dispose ();
}

[Fact]
public void InsertText_Bmp_SurrogatePair_Non_Bmp_Invalid_SurrogatePair ()
{
var tf = new TextField ();

//📄 == \ud83d\udcc4 == \U0001F4C4
// � == Rune.ReplacementChar
tf.InsertText ("aA,;\ud83d\udcc4\U0001F4C4\udcc4\ud83d");
Assert.Equal ("aA,;📄📄��", tf.Text);
}

[Fact]
public void PositionCursor_Respect_GetColumns ()
{
var tf = new TextField { Width = 5 };
tf.BeginInit ();
tf.EndInit ();

tf.NewKeyDownEvent (new ("📄"));
Assert.Equal (1, tf.CursorPosition);
Assert.Equal (new (2, 0), tf.PositionCursor ());
Assert.Equal ("📄", tf.Text);

tf.NewKeyDownEvent (new (KeyCode.A));
Assert.Equal (2, tf.CursorPosition);
Assert.Equal (new (3, 0), tf.PositionCursor ());
Assert.Equal ("📄a", tf.Text);
}
}
Loading
Loading