Skip to content

Commit db8942d

Browse files
committed
AnsiResponseParser now handles net event keyboard inputs for cursor keys
1 parent 617e9d9 commit db8942d

File tree

5 files changed

+102
-9
lines changed

5 files changed

+102
-9
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Text.RegularExpressions;
2+
3+
namespace Terminal.Gui;
4+
5+
public class AnsiKeyboardParser
6+
{
7+
// Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
8+
private readonly Regex _arrowKeyPattern = new (@"\u001b\[(A|B|C|D)", RegexOptions.Compiled);
9+
10+
/// <summary>
11+
/// Parses an ANSI escape sequence into a keyboard event. Returns null if input
12+
/// is not a recognized keyboard event or its syntax is not understood.
13+
/// </summary>
14+
/// <param name="input"></param>
15+
/// <returns></returns>
16+
public Key ProcessKeyboardInput (string input)
17+
{
18+
// Match arrow key events
19+
Match match = _arrowKeyPattern.Match (input);
20+
21+
if (match.Success)
22+
{
23+
char direction = match.Groups [1].Value [0];
24+
25+
return direction switch
26+
{
27+
'A' => Key.CursorUp,
28+
'B' => Key.CursorDown,
29+
'C' => Key.CursorRight,
30+
'D' => Key.CursorDown,
31+
_ => default(Key)
32+
};
33+
34+
}
35+
36+
// It's an unrecognized keyboard event
37+
return null;
38+
}
39+
40+
public bool IsKeyboard (string cur)
41+
{
42+
return _arrowKeyPattern.IsMatch (cur);
43+
}
44+
}

Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ public class AnsiMouseParser
1313
// Regex patterns for button press/release, wheel scroll, and mouse position reporting
1414
private readonly Regex _mouseEventPattern = new (@"\u001b\[<(\d+);(\d+);(\d+)(M|m)", RegexOptions.Compiled);
1515

16+
/// <summary>
17+
/// Returns true if it is a mouse event
18+
/// </summary>
19+
/// <param name="cur"></param>
20+
/// <returns></returns>
21+
public bool IsMouse (string cur)
22+
{
23+
// Typically in this format
24+
// ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
25+
return cur.EndsWith ('M') || cur.EndsWith ('m');
26+
}
27+
1628
/// <summary>
1729
/// Parses a mouse ansi escape sequence into a mouse event. Returns null if input
1830
/// is not a mouse event or its syntax is not understood.

Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace Terminal.Gui;
77
internal abstract class AnsiResponseParserBase : IAnsiResponseParser
88
{
99
private readonly AnsiMouseParser _mouseParser = new ();
10+
private readonly AnsiKeyboardParser _keyboardParser = new ();
1011
protected object _lockExpectedResponses = new();
1112

1213
protected object _lockState = new ();
@@ -16,12 +17,22 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
1617
/// </summary>
1718
public event EventHandler<MouseEventArgs> Mouse;
1819

20+
/// <summary>
21+
/// Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
22+
/// </summary>
23+
public event Action<object, Key> Keyboard;
24+
1925
/// <summary>
2026
/// True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
2127
/// Defaults to <see langword="false"/>
2228
/// </summary>
2329
public bool HandleMouse { get; set; } = false;
2430

31+
/// <summary>
32+
/// True to explicitly handle keyboard escape sequences (such as cursor keys) by passing them to <see cref="Keyboard"/> event
33+
/// </summary>
34+
public bool HandleKeyboard { get; set; } = false;
35+
2536
/// <summary>
2637
/// Responses we are expecting to come in.
2738
/// </summary>
@@ -208,6 +219,15 @@ protected bool ShouldReleaseHeldContent ()
208219
return false;
209220
}
210221

222+
if (HandleKeyboard && IsKeyboard (cur))
223+
{
224+
RaiseKeyboardEvent (cur);
225+
ResetState ();
226+
227+
Logging.Logger.LogTrace ($"AnsiResponseParser handled as keyboard '{cur}'");
228+
return false;
229+
}
230+
211231
lock (_lockExpectedResponses)
212232
{
213233
// Look for an expected response for what is accumulated so far (since Esc)
@@ -281,11 +301,20 @@ private void RaiseMouseEvent (string cur)
281301

282302
private bool IsMouse (string cur)
283303
{
284-
// Typically in this format
285-
// ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
286-
return cur.EndsWith ('M') || cur.EndsWith ('m');
304+
return _mouseParser.IsMouse (cur);
305+
}
306+
private void RaiseKeyboardEvent (string cur)
307+
{
308+
var k = _keyboardParser.ProcessKeyboardInput (cur);
309+
Keyboard?.Invoke (this, k);
310+
}
311+
private bool IsKeyboard (string cur)
312+
{
313+
return _keyboardParser.IsKeyboard (cur);
287314
}
288315

316+
317+
289318
/// <summary>
290319
/// <para>
291320
/// When overriden in a derived class, indicates whether the unexpected response
@@ -390,6 +419,7 @@ public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
390419
/// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
391420
public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
392421

422+
393423
public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
394424
{
395425
List<Tuple<char, T>> output = new ();
@@ -451,6 +481,7 @@ public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T
451481

452482
/// <inheritdoc/>
453483
protected override bool ShouldSwallowUnexpectedResponse () { return UnexpectedResponseHandler.Invoke (HeldToEnumerable ()); }
484+
454485
}
455486

456487
internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ())

Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ public InputProcessor (ConcurrentQueue<T> inputBuffer)
6363
InputBuffer = inputBuffer;
6464
Parser.HandleMouse = true;
6565
Parser.Mouse += (s, e) => OnMouseEvent (e);
66+
67+
Parser.HandleKeyboard = true;
68+
Parser.Keyboard += (s,k)=>
69+
{
70+
OnKeyDown (k);
71+
OnKeyUp (k);
72+
};
73+
6674
// TODO: For now handle all other escape codes with ignore
6775
Parser.UnexpectedResponseHandler = str => { return true; };
6876
}

UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,18 @@ public class MouseInterpreterTests
88
public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, MouseFlags expected)
99
{
1010
// Arrange: Mock dependencies and set up the interpreter
11-
var viewFinder = Mock.Of<IViewFinder> ();
12-
var interpreter = new MouseInterpreter (null, viewFinder);
11+
var interpreter = new MouseInterpreter (null);
1312

1413
// Act and Assert: Process all but the last event and ensure they yield no results
1514
for (int i = 0; i < events.Count - 1; i++)
1615
{
1716
var intermediateResult = interpreter.Process (events [i]);
18-
Assert.Empty (intermediateResult);
17+
Assert.Equal (events [i].Flags,intermediateResult.Flags);
1918
}
2019

2120
// Process the final event and verify the expected result
22-
var finalResult = interpreter.Process (events [^1]).ToArray (); // ^1 is the last item in the list
23-
var singleResult = Assert.Single (finalResult); // Ensure only one result is produced
24-
Assert.Equal (expected, singleResult.Flags);
21+
var finalResult = interpreter.Process (events [^1]); // ^1 is the last item in the list
22+
Assert.Equal (expected, finalResult.Flags);
2523
}
2624

2725

0 commit comments

Comments
 (0)