From 36876b989900f6531e06ba89b3420777f37e0819 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 11 Nov 2024 01:10:18 +0000 Subject: [PATCH] Change TryWriteAnsiRequest method to virtual which use common code for all drivers. --- Terminal.Gui/Application/Application.Run.cs | 3 +- Terminal.Gui/Application/MainLoop.cs | 10 +- Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs | 69 ++++- .../CursesDriver/CursesDriver.cs | 70 ----- .../CursesDriver/UnixMainLoop.cs | 16 +- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 2 +- .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs | 2 + .../ConsoleDrivers/NetDriver/NetDriver.cs | 77 ----- .../ConsoleDrivers/NetDriver/NetEvents.cs | 282 +++++++++--------- .../ConsoleDrivers/NetDriver/NetMainLoop.cs | 6 +- .../WindowsDriver/WindowsDriver.cs | 62 ---- .../WindowsDriver/WindowsMainLoop.cs | 10 +- .../Scenarios/AnsiEscapeSequenceRequests.cs | 5 +- UnitTests/Application/MainLoopTests.cs | 4 + 14 files changed, 238 insertions(+), 380 deletions(-) diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs index 898fc935b..7414054b5 100644 --- a/Terminal.Gui/Application/Application.Run.cs +++ b/Terminal.Gui/Application/Application.Run.cs @@ -1,7 +1,6 @@ #nullable enable using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; namespace Terminal.Gui; @@ -522,7 +521,7 @@ public static partial class Application // Run (Begin, Run, End, Stop) /// The driver for the application /// The main loop. - internal static MainLoop? MainLoop { get; private set; } + public static MainLoop? MainLoop { get; private set; } /// /// Set to true to cause to be called after the first iteration. Set to false (the default) to diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs index ee4bba220..d3cce5efe 100644 --- a/Terminal.Gui/Application/MainLoop.cs +++ b/Terminal.Gui/Application/MainLoop.cs @@ -10,7 +10,7 @@ using System.Collections.ObjectModel; namespace Terminal.Gui; /// Interface to create a platform specific driver. -internal interface IMainLoopDriver +public interface IMainLoopDriver { /// Must report whether there are any events pending, or even block waiting for events. /// true, if there were pending events, false otherwise. @@ -29,6 +29,10 @@ internal interface IMainLoopDriver /// Wakes up the that might be waiting on input, must be thread safe. void Wakeup (); + + bool _forceRead { get; set; } + + ManualResetEventSlim _waitForInput { get; set; } } /// The MainLoop monitors timers and idle handlers. @@ -36,7 +40,7 @@ internal interface IMainLoopDriver /// Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this /// on Windows. /// -internal class MainLoop : IDisposable +public class MainLoop : IDisposable { internal List> _idleHandlers = new (); internal SortedList _timeouts = new (); @@ -72,7 +76,7 @@ internal class MainLoop : IDisposable /// The current in use. /// The main loop driver. - internal IMainLoopDriver MainLoopDriver { get; private set; } + public IMainLoopDriver MainLoopDriver { get; private set; } /// Used for unit tests. internal bool Running { get; set; } diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index e9e2af5ce..6caf6948f 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -32,6 +32,9 @@ public abstract class ConsoleDriver #region ANSI Esc Sequence Handling + private readonly ManualResetEventSlim _waitAnsiResponse = new (false); + private CancellationTokenSource? _ansiResponseTokenSource; + // QUESTION: Should this be virtual with a default implementation that does the common stuff? // QUESTION: Looking at the implementations of this method, there is TONs of duplicated code. // QUESTION: We should figure out how to find just the things that are unique to each driver and @@ -39,9 +42,73 @@ public abstract class ConsoleDriver /// /// Provide handling for the terminal write ANSI escape sequence request. /// + /// The object. /// The object. /// The request response. - public abstract bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); + public virtual bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) + { + if (mainLoopDriver is null) + { + return false; + } + + _ansiResponseTokenSource ??= new (); + + try + { + lock (ansiRequest._responseLock) + { + AnsiEscapeSequenceRequest? request = ansiRequest; + + ansiRequest.ResponseFromInput += (s, e) => + { + Debug.Assert (s == request); + Debug.Assert (e == request.AnsiEscapeSequenceResponse); + + _waitAnsiResponse.Set (); + }; + + AnsiEscapeSequenceRequests.Add (ansiRequest); + + WriteRaw (ansiRequest.Request); + + mainLoopDriver._forceRead = true; + } + + if (!_ansiResponseTokenSource.IsCancellationRequested) + { + mainLoopDriver._waitForInput.Set (); + + _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); + } + } + catch (OperationCanceledException) + { + return false; + } + + lock (ansiRequest._responseLock) + { + mainLoopDriver._forceRead = false; + + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) + { + if (AnsiEscapeSequenceRequests.Statuses.Count > 0 + && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (request.AnsiRequest._responseLock) + { + // Bad request or no response at all + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + } + } + } + + _waitAnsiResponse.Reset (); + + return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; + } + } // QUESTION: This appears to be an API to help in debugging. It's only implemented in CursesDriver and WindowsDriver. // QUESTION: Can it be factored such that it does not contaminate the ConsoleDriver API? diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 2f56f44be..84c74fa20 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -727,9 +727,6 @@ internal class CursesDriver : ConsoleDriver internal override void End () { - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse.Dispose (); StopReportingMouseMoves (); SetCursorVisibility (CursorVisibility.Default); @@ -765,73 +762,6 @@ internal class CursesDriver : ConsoleDriver return false; } - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return false; - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - _mainLoopDriver._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - _mainLoopDriver._waitForInput.Set (); - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - /// internal override void WriteRaw (string ansi) { _mainLoopDriver?.WriteRaw (ansi); } } diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs index c7134e96e..84fa67ad3 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs @@ -43,7 +43,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver private Pollfd []? _pollMap; private readonly ConcurrentQueue _pollDataQueue = new (); private readonly ManualResetEventSlim _eventReady = new (false); - internal readonly ManualResetEventSlim _waitForInput = new (false); + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); private readonly ManualResetEventSlim _windowSizeChange = new (false); private readonly CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -152,7 +152,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver } } - internal bool _forceRead; + bool IMainLoopDriver._forceRead { get; set; } private int _retries; private void CursesInputHandler () @@ -161,9 +161,9 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) { - _waitForInput.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } } catch (OperationCanceledException) @@ -174,11 +174,11 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver { if (!_inputHandlerTokenSource.IsCancellationRequested) { - _waitForInput.Reset (); + ((IMainLoopDriver)this)._waitForInput.Reset (); } } - if (_pollDataQueue?.Count == 0 || _forceRead) + if (_pollDataQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) { while (!_inputHandlerTokenSource.IsCancellationRequested) { @@ -340,7 +340,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForInput.Set (); + ((IMainLoopDriver)this)._waitForInput.Set (); _windowSizeChange.Set (); if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) @@ -390,7 +390,7 @@ internal class UnixMainLoop (ConsoleDriver consoleDriver) : IMainLoopDriver _inputHandlerTokenSource?.Cancel (); _inputHandlerTokenSource?.Dispose (); - _waitForInput?.Dispose (); + ((IMainLoopDriver)this)._waitForInput?.Dispose (); _windowSizeChange.Dispose(); diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 4ebb4029e..64a3415da 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -393,7 +393,7 @@ public class FakeDriver : ConsoleDriver } /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } + public override bool TryWriteAnsiRequest (IMainLoopDriver mainLoopDriver, ref AnsiEscapeSequenceRequest ansiRequest) { throw new NotImplementedException (); } /// internal override void WriteRaw (string ansi) { throw new NotImplementedException (); } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs index d90caace7..16a1a76b7 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs @@ -3,6 +3,8 @@ internal class FakeMainLoop : IMainLoopDriver { public Action MockKeyPressed; + public bool _forceRead { get; set; } + public ManualResetEventSlim _waitForInput { get; set; } = new (); public FakeMainLoop (ConsoleDriver consoleDriver = null) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 7aa943310..f66d921bf 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -359,10 +359,7 @@ internal class NetDriver : ConsoleDriver StopReportingMouseMoves (); - _ansiResponseTokenSource?.Cancel (); - _ansiResponseTokenSource?.Dispose (); - _waitAnsiResponse.Dispose (); if (!RunningUnitTests) { @@ -721,80 +718,6 @@ internal class NetDriver : ConsoleDriver #region Low-Level DotNet tuff - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - lock (ansiRequest._responseLock) - { - if (_mainLoopDriver is null) - { - return false; - } - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - _mainLoopDriver._netEvents!._forceRead = true; - } - - if (!_ansiResponseTokenSource.IsCancellationRequested) - { - lock (ansiRequest._responseLock) - { - _mainLoopDriver._waitForProbe.Set (); - _mainLoopDriver._netEvents._waitForStart.Set (); - - WriteRaw (ansiRequest.Request); - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._netEvents._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - /// internal override void WriteRaw (string ansi) { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs index 76258758a..eb4ecf0d3 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs @@ -7,7 +7,6 @@ internal class NetEvents : IDisposable { private readonly ManualResetEventSlim _inputReady = new (false); private CancellationTokenSource? _inputReadyCancellationTokenSource; - internal readonly ManualResetEventSlim _waitForStart = new (false); private readonly Queue _inputQueue = new (); private readonly ConsoleDriver _consoleDriver; private ConsoleKeyInfo []? _cki; @@ -29,8 +28,6 @@ internal class NetEvents : IDisposable { while (_inputReadyCancellationTokenSource is { Token.IsCancellationRequested: false }) { - _waitForStart.Set (); - try { if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) @@ -81,32 +78,7 @@ internal class NetEvents : IDisposable // In WSL it takes longer for keys to be available. Task.Delay (100, cancellationToken).Wait (cancellationToken); - if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) - { - if (_retries > 1) - { - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) - && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (seqReqStatus.AnsiRequest._responseLock) - { - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - - seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); - } - } - - _retries = 0; - } - else - { - _retries++; - } - } - else - { - _retries = 0; - } + ProcessResponse (); } cancellationToken.ThrowIfCancellationRequested (); @@ -114,152 +86,169 @@ internal class NetEvents : IDisposable return default (ConsoleKeyInfo); } - internal bool _forceRead; + //internal bool _forceRead; private int _retries; + private void ProcessResponse () + { + if (!Console.KeyAvailable && AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is null && AnsiEscapeSequenceRequests.Statuses.Count > 0) + { + if (_retries > 1) + { + if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? seqReqStatus) + && string.IsNullOrEmpty (seqReqStatus.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) + { + lock (seqReqStatus.AnsiRequest._responseLock) + { + AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); + + seqReqStatus.AnsiRequest.RaiseResponseFromInput (null); + } + } + + _retries = 0; + } + else + { + _retries++; + } + } + else + { + _retries = 0; + } + } + private void ProcessInputQueue () { while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { try { - if (!_forceRead) - { - _waitForStart.Wait (_inputReadyCancellationTokenSource.Token); - } - } - catch (OperationCanceledException) - { - return; - } - finally - { - if (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) - { - _waitForStart.Reset (); - } - } + ConsoleKey key = 0; + ConsoleModifiers mod = 0; + ConsoleKeyInfo newConsoleKeyInfo = default; - try - { - if (_inputQueue.Count == 0 || _forceRead) + while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) { - ConsoleKey key = 0; - ConsoleModifiers mod = 0; - ConsoleKeyInfo newConsoleKeyInfo = default; + ConsoleKeyInfo consoleKeyInfo; - while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false }) + try { - ConsoleKeyInfo consoleKeyInfo; + consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); + } + catch (OperationCanceledException) + { + return; + } - try + var ckiAlreadyResized = false; + + if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) + { + ckiAlreadyResized = true; + + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); + AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; + + if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') { - consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token); - } - catch (OperationCanceledException) - { - return; - } - - var ckiAlreadyResized = false; - - if (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos is { }) - { - ckiAlreadyResized = true; - - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - _cki = AnsiEscapeSequenceRequestUtils.InsertArray (AnsiEscapeSequenceRequestUtils.IncompleteCkInfos, _cki); - AnsiEscapeSequenceRequestUtils.IncompleteCkInfos = null; - - if (_cki.Length > 1 && _cki [0].KeyChar == '\u001B') - { - _isEscSeq = true; - } - } - - if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) - || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) - { - if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( - new ( - (char)KeyCode.Esc, - 0, - false, - false, - false - ), - _cki - ); - } - _isEscSeq = true; + } + } - if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) - || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) - || (_cki is { } && char.IsLetter (_cki [^1].KeyChar) && char.IsLower (consoleKeyInfo.KeyChar) && char.IsLetter (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar)) - || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar))) - { - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - } - else - { - newConsoleKeyInfo = consoleKeyInfo; - - if (!ckiAlreadyResized) - { - _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); - } - - if (Console.KeyAvailable) - { - continue; - } - - ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); - _cki = null; - _isEscSeq = false; - } - - break; + if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq) + || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) + { + if (_cki is null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray ( + new ( + (char)KeyCode.Esc, + 0, + false, + false, + false + ), + _cki + ); } - if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + _isEscSeq = true; + + if ((_cki is { } && _cki [^1].KeyChar != Key.Esc && consoleKeyInfo.KeyChar != Key.Esc && consoleKeyInfo.KeyChar <= Key.Space) + || (_cki is { } && _cki [^1].KeyChar != '\u001B' && consoleKeyInfo.KeyChar == 127) + || (_cki is { } + && char.IsLetter (_cki [^1].KeyChar) + && char.IsLower (consoleKeyInfo.KeyChar) + && char.IsLetter (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsLetterOrDigit (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsPunctuation (consoleKeyInfo.KeyChar)) + || (_cki is { Length: > 2 } && char.IsLetter (_cki [^1].KeyChar) && char.IsSymbol (consoleKeyInfo.KeyChar))) { ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); _cki = null; + _isEscSeq = false; + ProcessResponse (); - if (Console.KeyAvailable) + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + else + { + newConsoleKeyInfo = consoleKeyInfo; + + if (!ckiAlreadyResized) { _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); } - else + + if (Console.KeyAvailable) { - ProcessMapConsoleKeyInfo (consoleKeyInfo); + continue; } - break; - } - - ProcessMapConsoleKeyInfo (consoleKeyInfo); - - if (_retries > 0) - { - _retries = 0; + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + _isEscSeq = false; + ProcessResponse (); } break; } + + if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki is { }) + { + ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod); + _cki = null; + ProcessResponse (); + + if (Console.KeyAvailable) + { + _cki = AnsiEscapeSequenceRequestUtils.ResizeArray (consoleKeyInfo, _cki); + } + else + { + ProcessMapConsoleKeyInfo (consoleKeyInfo); + } + + break; + } + + ProcessMapConsoleKeyInfo (consoleKeyInfo); + + if (_retries > 0) + { + _retries = 0; + } + + break; } - _inputReady.Set (); + if (!_inputReady.IsSet) + { + _inputReady.Set (); + } } catch (OperationCanceledException) { @@ -394,12 +383,12 @@ internal class NetEvents : IDisposable return; } - //if (seqReqStatus is { }) - //{ - // HandleRequestResponseEvent (c1Control, code, values, terminating); + if (seqReqStatus is { }) + { + //HandleRequestResponseEvent (c1Control, code, values, terminating); - // return; - //} + return; + } if (newConsoleKeyInfo != default) { @@ -731,7 +720,6 @@ internal class NetEvents : IDisposable _inputReadyCancellationTokenSource = null; _inputReady.Dispose (); - _waitForStart.Dispose (); try { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 36490b32f..03c940106 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -20,6 +20,8 @@ internal class NetMainLoop : IMainLoopDriver private readonly CancellationTokenSource _inputHandlerTokenSource = new (); private readonly Queue _resultQueue = new (); private MainLoop? _mainLoop; + bool IMainLoopDriver._forceRead { get; set; } + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); /// Initializes the class with the console driver. /// Passing a consoleDriver is provided to capture windows resizing. @@ -116,12 +118,12 @@ internal class NetMainLoop : IMainLoopDriver { try { - if (!_inputHandlerTokenSource.IsCancellationRequested && !_netEvents!._forceRead) + if (!_inputHandlerTokenSource.IsCancellationRequested && !((IMainLoopDriver)this)._forceRead) { _waitForProbe.Wait (_inputHandlerTokenSource.Token); } - if (_resultQueue?.Count == 0 || _netEvents!._forceRead) + if (_resultQueue?.Count == 0 || ((IMainLoopDriver)this)._forceRead) { NetEvents.InputResult? result = _netEvents!.DequeueInput (); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index 34afbc056..5eaefd618 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -187,68 +187,6 @@ internal class WindowsDriver : ConsoleDriver } } - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); - private CancellationTokenSource? _ansiResponseTokenSource; - - /// - public override bool TryWriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest) - { - if (_mainLoopDriver is null) - { - return false; - } - - _ansiResponseTokenSource ??= new (); - - try - { - lock (ansiRequest._responseLock) - { - ansiRequest.ResponseFromInput += (s, e) => - { - Debug.Assert (s == ansiRequest); - Debug.Assert (e == ansiRequest.AnsiEscapeSequenceResponse); - - _waitAnsiResponse.Set (); - }; - - AnsiEscapeSequenceRequests.Add (ansiRequest); - - WriteRaw (ansiRequest.Request); - - _mainLoopDriver._forceRead = true; - } - - _waitAnsiResponse.Wait (_ansiResponseTokenSource.Token); - } - catch (OperationCanceledException) - { - return false; - } - - lock (ansiRequest._responseLock) - { - _mainLoopDriver._forceRead = false; - - if (AnsiEscapeSequenceRequests.Statuses.TryPeek (out AnsiEscapeSequenceRequestStatus? request)) - { - if (AnsiEscapeSequenceRequests.Statuses.Count > 0 - && string.IsNullOrEmpty (request.AnsiRequest.AnsiEscapeSequenceResponse?.Response)) - { - lock (request.AnsiRequest._responseLock) - { - // Bad request or no response at all - AnsiEscapeSequenceRequests.Statuses.TryDequeue (out _); - } - } - } - - _waitAnsiResponse.Reset (); - - return ansiRequest.AnsiEscapeSequenceResponse is { Valid: true }; - } - } - internal override void WriteRaw (string ansi) { WinConsole?.WriteANSI (ansi); } #region Not Implemented diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs index ca10a693f..ce23aa864 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs @@ -21,7 +21,7 @@ internal class WindowsMainLoop : IMainLoopDriver // The records that we keep fetching private readonly Queue _resultQueue = new (); - private readonly ManualResetEventSlim _waitForProbe = new (false); + ManualResetEventSlim IMainLoopDriver._waitForInput { get; set; } = new (false); private readonly WindowsConsole? _winConsole; private CancellationTokenSource _eventReadyTokenSource = new (); private readonly CancellationTokenSource _inputHandlerTokenSource = new (); @@ -57,7 +57,7 @@ internal class WindowsMainLoop : IMainLoopDriver bool IMainLoopDriver.EventsPending () { - _waitForProbe.Set (); + ((IMainLoopDriver)this)._waitForInput.Set (); #if HACK_CHECK_WINCHANGED _winChange.Set (); #endif @@ -131,7 +131,7 @@ internal class WindowsMainLoop : IMainLoopDriver } } - _waitForProbe?.Dispose (); + ((IMainLoopDriver)this)._waitForInput?.Dispose (); _resultQueue.Clear (); @@ -146,7 +146,7 @@ internal class WindowsMainLoop : IMainLoopDriver _mainLoop = null; } - internal bool _forceRead; + public bool _forceRead { get; set; } private void WindowsInputHandler () { @@ -156,7 +156,7 @@ internal class WindowsMainLoop : IMainLoopDriver { if (_inputHandlerTokenSource.IsCancellationRequested && !_forceRead) { - _waitForProbe.Wait (_inputHandlerTokenSource.Token); + ((IMainLoopDriver)this)._waitForInput.Wait (_inputHandlerTokenSource.Token); } if (_resultQueue?.Count == 0 || _forceRead) diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index f281dc461..8c140d0ee 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -293,7 +293,8 @@ public sealed class AnsiEscapeSequenceRequests : Scenario }; bool success = Application.Driver!.TryWriteAnsiRequest ( - ansiEscapeSequenceRequest + Application.MainLoop.MainLoopDriver, + ref ansiEscapeSequenceRequest ); tvResponse.Text = ansiEscapeSequenceRequest.AnsiEscapeSequenceResponse?.Response ?? ""; @@ -369,7 +370,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario { _sends.Add (DateTime.Now); AnsiEscapeSequenceRequest ansiRequest = AnsiEscapeSequenceRequestUtils.CSI_SendDeviceAttributes; - if (Application.Driver!.TryWriteAnsiRequest (ansiRequest)) + if (Application.Driver!.TryWriteAnsiRequest (Application.MainLoop.MainLoopDriver, ref ansiRequest)) { HandleResponse (ansiRequest.AnsiEscapeSequenceResponse?.Response); } diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs index 47812926d..3b77dd775 100644 --- a/UnitTests/Application/MainLoopTests.cs +++ b/UnitTests/Application/MainLoopTests.cs @@ -946,10 +946,14 @@ public class MainLoopTests private class TestMainloop : IMainLoopDriver { private MainLoop mainLoop; + private bool _forceRead1; + private ManualResetEventSlim _waitForInput1; public bool EventsPending () { throw new NotImplementedException (); } public void Iteration () { throw new NotImplementedException (); } public void TearDown () { throw new NotImplementedException (); } public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; } public void Wakeup () { throw new NotImplementedException (); } + public bool _forceRead { get; set; } + public ManualResetEventSlim _waitForInput { get; set; } } }