2
0
Эх сурвалжийг харах

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

Tig 3 сар өмнө
parent
commit
0e08c13e05

+ 1 - 0
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs

@@ -10,6 +10,7 @@ public class AnsiKeyboardParser
     {
         new Ss3Pattern (),
         new CsiKeyPattern (),
+        new CsiCursorPattern(),
         new EscAsAltPattern { IsLastMinute = true }
     };
 

+ 73 - 0
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiCursorPattern.cs

@@ -0,0 +1,73 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Detects ansi escape sequences in strings that have been read from
+///     the terminal (see <see cref="IAnsiResponseParser"/>).
+///     Handles navigation CSI key parsing such as <c>\x1b[A</c> (Cursor up)
+///     and <c>\x1b[1;5A</c> (Cursor up with Ctrl)
+/// </summary>
+public class CsiCursorPattern : AnsiKeyboardParserPattern
+{
+    private readonly Regex _pattern = new (@"^\u001b\[(?:1;(\d+))?([A-DHF])$");
+
+    private readonly Dictionary<char, Key> _cursorMap = new ()
+    {
+        { 'A', Key.CursorUp },
+        { 'B', Key.CursorDown },
+        { 'C', Key.CursorRight },
+        { 'D', Key.CursorLeft },
+        { 'H', Key.Home },
+        { 'F', Key.End }
+    };
+
+    /// <inheritdoc/>
+    public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
+
+    /// <summary>
+    ///     Called by the base class to determine the key that matches the input.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    protected override Key? GetKeyImpl (string? input)
+    {
+        Match match = _pattern.Match (input!);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        string modifierGroup = match.Groups [1].Value;
+        char terminator = match.Groups [2].Value [0];
+
+        if (!_cursorMap.TryGetValue (terminator, out Key? key))
+        {
+            return null;
+        }
+
+        if (string.IsNullOrEmpty (modifierGroup))
+        {
+            return key;
+        }
+
+        if (int.TryParse (modifierGroup, out int modifier))
+        {
+            key = modifier switch
+                  {
+                      2 => key.WithShift,
+                      3 => key.WithAlt,
+                      4 => key.WithAlt.WithShift,
+                      5 => key.WithCtrl,
+                      6 => key.WithCtrl.WithShift,
+                      7 => key.WithCtrl.WithAlt,
+                      8 => key.WithCtrl.WithAlt.WithShift,
+                      _ => key
+                  };
+        }
+
+        return key;
+    }
+}

+ 50 - 58
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs

@@ -5,58 +5,39 @@ namespace Terminal.Gui;
 
 /// <summary>
 ///     Detects ansi escape sequences in strings that have been read from
-///     the terminal (see <see cref="IAnsiResponseParser"/>). This pattern
-///     handles keys that begin <c>Esc[</c> e.g. <c>Esc[A</c> - cursor up
+///     the terminal (see <see cref="IAnsiResponseParser"/>).
+///     Handles CSI key parsing such as <c>\x1b[3;5~</c> (Delete with Ctrl)
 /// </summary>
 public class CsiKeyPattern : AnsiKeyboardParserPattern
 {
-    private readonly Dictionary<string, Key> _terminators = new()
+    private readonly Regex _pattern = new (@"^\u001b\[(\d+)(?:;(\d+))?~$");
+
+    private readonly Dictionary<int, Key> _keyCodeMap = new ()
     {
-        { "A", Key.CursorUp },
-        { "B", Key.CursorDown },
-        { "C", Key.CursorRight },
-        { "D", Key.CursorLeft },
-        { "H", Key.Home }, // Home (older variant)
-        { "F", Key.End }, // End (older variant)
-        { "1~", Key.Home }, // Home (modern variant)
-        { "4~", Key.End }, // End (modern variant)
-        { "5~", Key.PageUp },
-        { "6~", Key.PageDown },
-        { "2~", Key.InsertChar },
-        { "3~", Key.Delete },
-        { "11~", Key.F1 },
-        { "12~", Key.F2 },
-        { "13~", Key.F3 },
-        { "14~", Key.F4 },
-        { "15~", Key.F5 },
-        { "17~", Key.F6 },
-        { "18~", Key.F7 },
-        { "19~", Key.F8 },
-        { "20~", Key.F9 },
-        { "21~", Key.F10 },
-        { "23~", Key.F11 },
-        { "24~", Key.F12 }
+        { 1, Key.Home }, // Home (modern variant)
+        { 4, Key.End }, // End (modern variant)
+        { 5, Key.PageUp },
+        { 6, Key.PageDown },
+        { 2, Key.InsertChar },
+        { 3, Key.Delete },
+        { 11, Key.F1 },
+        { 12, Key.F2 },
+        { 13, Key.F3 },
+        { 14, Key.F4 },
+        { 15, Key.F5 },
+        { 17, Key.F6 },
+        { 18, Key.F7 },
+        { 19, Key.F8 },
+        { 20, Key.F9 },
+        { 21, Key.F10 },
+        { 23, Key.F11 },
+        { 24, Key.F12 }
     };
 
-    private readonly Regex _pattern;
-
     /// <inheritdoc/>
     public override bool IsMatch (string? input) { return _pattern.IsMatch (input!); }
 
-    /// <summary>
-    ///     Creates a new instance of the <see cref="CsiKeyPattern"/> class.
-    /// </summary>
-    public CsiKeyPattern ()
-    {
-        var terms = new string (_terminators.Select (k => k.Key [0]).Where (k => !char.IsDigit (k)).ToArray ());
-        _pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}]|\d+~)$");
-    }
-
-    /// <summary>
-    ///     Called by the base class to determine the key that matches the input.
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
+    /// <inheritdoc/>
     protected override Key? GetKeyImpl (string? input)
     {
         Match match = _pattern.Match (input!);
@@ -66,26 +47,37 @@ public class CsiKeyPattern : AnsiKeyboardParserPattern
             return null;
         }
 
-        string terminator = match.Groups [3].Value;
-        string modifierGroup = match.Groups [2].Value;
+        // Group 1: Key code (e.g. 3, 17, etc.)
+        // Group 2: Optional modifier code (e.g. 2 = Shift, 5 = Ctrl)
+
+        if (!int.TryParse (match.Groups [1].Value, out int keyCode))
+        {
+            return null;
+        }
 
-        Key? key = _terminators.GetValueOrDefault (terminator);
+        if (!_keyCodeMap.TryGetValue (keyCode, out Key? key))
+        {
+            return null;
+        }
 
-        if (key is {} && int.TryParse (modifierGroup, out int modifier))
+        // If there's no modifier, just return the key.
+        if (!int.TryParse (match.Groups [2].Value, out int modifier))
         {
-            key = modifier switch
-                  {
-                      2 => key.WithShift,
-                      3 => key.WithAlt,
-                      4 => key.WithAlt.WithShift,
-                      5 => key.WithCtrl,
-                      6 => key.WithCtrl.WithShift,
-                      7 => key.WithCtrl.WithAlt,
-                      8 => key.WithCtrl.WithAlt.WithShift,
-                      _ => key
-                  };
+            return key;
         }
 
+        key = modifier switch
+              {
+                  2 => key.WithShift,
+                  3 => key.WithAlt,
+                  4 => key.WithAlt.WithShift,
+                  5 => key.WithCtrl,
+                  6 => key.WithCtrl.WithShift,
+                  7 => key.WithCtrl.WithAlt,
+                  8 => key.WithCtrl.WithAlt.WithShift,
+                  _ => key
+              };
+
         return key;
     }
 }

+ 53 - 0
Tests/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs

@@ -52,6 +52,59 @@ public class AnsiKeyboardParserTests
         yield return new object [] { "\u001b[1", null! };
         yield return new object [] { "\u001b[AB", null! };
         yield return new object [] { "\u001b[;A", null! };
+
+
+        // Test data for various ANSI escape sequences and their expected Key values
+        yield return new object [] { "\u001b[3;5~", Key.Delete.WithCtrl };
+
+        // Additional special keys
+        yield return new object [] { "\u001b[H", Key.Home };
+        yield return new object [] { "\u001b[F", Key.End };
+        yield return new object [] { "\u001b[2~", Key.InsertChar };
+        yield return new object [] { "\u001b[5~", Key.PageUp };
+        yield return new object [] { "\u001b[6~", Key.PageDown };
+
+        // Home, End with modifiers
+        yield return new object [] { "\u001b[1;2H", Key.Home.WithShift };
+        yield return new object [] { "\u001b[1;3H", Key.Home.WithAlt };
+        yield return new object [] { "\u001b[1;5F", Key.End.WithCtrl };
+
+        // Insert with modifiers
+        yield return new object [] { "\u001b[2;2~", Key.InsertChar.WithShift };
+        yield return new object [] { "\u001b[2;3~", Key.InsertChar.WithAlt };
+        yield return new object [] { "\u001b[2;5~", Key.InsertChar.WithCtrl };
+
+        // PageUp/PageDown with modifiers
+        yield return new object [] { "\u001b[5;2~", Key.PageUp.WithShift };
+        yield return new object [] { "\u001b[6;3~", Key.PageDown.WithAlt };
+        yield return new object [] { "\u001b[6;5~", Key.PageDown.WithCtrl };
+
+        // Function keys F1-F4 (common ESC O sequences)
+        yield return new object [] { "\u001bOP", Key.F1 };
+        yield return new object [] { "\u001bOQ", Key.F2 };
+        yield return new object [] { "\u001bOR", Key.F3 };
+        yield return new object [] { "\u001bOS", Key.F4 };
+
+        // Extended function keys F1-F12 with CSI sequences
+        yield return new object [] { "\u001b[11~", Key.F1 };
+        yield return new object [] { "\u001b[12~", Key.F2 };
+        yield return new object [] { "\u001b[13~", Key.F3 };
+        yield return new object [] { "\u001b[14~", Key.F4 };
+        yield return new object [] { "\u001b[15~", Key.F5 };
+        yield return new object [] { "\u001b[17~", Key.F6 };
+        yield return new object [] { "\u001b[18~", Key.F7 };
+        yield return new object [] { "\u001b[19~", Key.F8 };
+        yield return new object [] { "\u001b[20~", Key.F9 };
+        yield return new object [] { "\u001b[21~", Key.F10 };
+        yield return new object [] { "\u001b[23~", Key.F11 };
+        yield return new object [] { "\u001b[24~", Key.F12 };
+
+        // Function keys with modifiers
+        /*  Not currently supported
+        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 };
+        */
     }
 
     // Consolidated test for all keyboard events (e.g., arrow keys)