#nullable enable
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
namespace Terminal.Gui;
///
/// Processes the queued input buffer contents - which must be of Type .
/// Is responsible for and translating into common Terminal.Gui
/// events and data models.
///
public abstract class InputProcessor : IInputProcessor
{
///
/// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
///
private readonly TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
internal AnsiResponseParser Parser { get; } = new ();
///
/// Class responsible for translating the driver specific native input class e.g.
/// into the Terminal.Gui class (used for all
/// internal library representations of Keys).
///
public IKeyConverter KeyConverter { get; }
///
/// Input buffer which will be drained from by this class.
///
public ConcurrentQueue InputBuffer { get; }
///
public IAnsiResponseParser GetParser () { return Parser; }
private readonly MouseInterpreter _mouseInterpreter = new ();
/// Event fired when a key is pressed down. This is a precursor to .
public event EventHandler? KeyDown;
/// Event fired when a terminal sequence read from input is not recognized and therefore ignored.
public event EventHandler? AnsiSequenceSwallowed;
///
/// Called when a key is pressed down. Fires the event. This is a precursor to
/// .
///
///
public void OnKeyDown (Key a)
{
Logging.Trace ($"{nameof (InputProcessor)} raised {a}");
KeyDown?.Invoke (this, a);
}
/// Event fired when a key is released.
///
/// Drivers that do not support key release events will fire this event after processing is
/// complete.
///
public event EventHandler? KeyUp;
/// Called when a key is released. Fires the event.
///
/// Drivers that do not support key release events will call this method after processing
/// is complete.
///
///
public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
/// Event fired when a mouse event occurs.
public event EventHandler? MouseEvent;
/// Called when a mouse event occurs. Fires the event.
///
public void OnMouseEvent (MouseEventArgs a)
{
// Ensure ScreenPosition is set
a.ScreenPosition = a.Position;
foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
{
Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
// Pass on
MouseEvent?.Invoke (this, e);
}
}
///
/// Constructs base instance including wiring all relevant
/// parser events and setting to
/// the provided thread safe input collection.
///
/// The collection that will be populated with new input (see )
///
/// Key converter for translating driver specific
/// class into Terminal.Gui .
///
protected InputProcessor (ConcurrentQueue inputBuffer, IKeyConverter keyConverter)
{
InputBuffer = inputBuffer;
Parser.HandleMouse = true;
Parser.Mouse += (s, e) => OnMouseEvent (e);
Parser.HandleKeyboard = true;
Parser.Keyboard += (s, k) =>
{
OnKeyDown (k);
OnKeyUp (k);
};
// TODO: For now handle all other escape codes with ignore
Parser.UnexpectedResponseHandler = str =>
{
var cur = new string (str.Select (k => k.Item1).ToArray ());
Logging.Logger.LogInformation ($"{nameof (InputProcessor)} ignored unrecognized response '{cur}'");
AnsiSequenceSwallowed?.Invoke (this, cur);
return true;
};
KeyConverter = keyConverter;
}
///
/// Drains the buffer, processing all available keystrokes
///
public void ProcessQueue ()
{
while (InputBuffer.TryDequeue (out T? input))
{
Process (input);
}
foreach (T input in ReleaseParserHeldKeysIfStale ())
{
ProcessAfterParsing (input);
}
}
private IEnumerable ReleaseParserHeldKeysIfStale ()
{
if (Parser.State is AnsiResponseParserState.ExpectingEscapeSequence or AnsiResponseParserState.InResponse
&& DateTime.Now - Parser.StateChangedAt > _escTimeout)
{
return Parser.Release ().Select (o => o.Item2);
}
return [];
}
///
/// Process the provided single input element . This method
/// is called sequentially for each value read from .
///
///
protected abstract void Process (T input);
///
/// Process the provided single input element - short-circuiting the
/// stage of the processing.
///
///
protected abstract void ProcessAfterParsing (T input);
}