mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 17:57:57 +01:00
Code review comments and cleanup
This commit is contained in:
@@ -16,6 +16,7 @@ public class AnsiEscapeSequenceRequest
|
||||
/// </summary>
|
||||
public required string Request { get; init; }
|
||||
|
||||
// BUGBUG: Nullable issue
|
||||
/// <summary>
|
||||
/// Invoked when the console responds with an ANSI response code that matches the
|
||||
/// <see cref="Terminator"/>
|
||||
|
||||
@@ -14,8 +14,8 @@ public class AnsiRequestScheduler
|
||||
private readonly IAnsiResponseParser _parser;
|
||||
|
||||
/// <summary>
|
||||
/// Function for returning the current time. Use in unit tests to
|
||||
/// ensure repeatable tests.
|
||||
/// Function for returning the current time. Use in unit tests to
|
||||
/// ensure repeatable tests.
|
||||
/// </summary>
|
||||
internal Func<DateTime> Now { get; set; }
|
||||
|
||||
@@ -54,6 +54,11 @@ public class AnsiRequestScheduler
|
||||
|
||||
private readonly DateTime _lastRun;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance.
|
||||
/// </summary>
|
||||
/// <param name="parser"></param>
|
||||
/// <param name="now"></param>
|
||||
public AnsiRequestScheduler (IAnsiResponseParser parser, Func<DateTime>? now = null)
|
||||
{
|
||||
_parser = parser;
|
||||
@@ -67,11 +72,9 @@ public class AnsiRequestScheduler
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns><see langword="true"/> if request was sent immediately. <see langword="false"/> if it was queued.</returns>
|
||||
public bool SendOrSchedule (AnsiEscapeSequenceRequest request)
|
||||
{
|
||||
return SendOrSchedule (request, true);
|
||||
}
|
||||
private bool SendOrSchedule (AnsiEscapeSequenceRequest request,bool addToQueue)
|
||||
public bool SendOrSchedule (AnsiEscapeSequenceRequest request) { return SendOrSchedule (request, true); }
|
||||
|
||||
private bool SendOrSchedule (AnsiEscapeSequenceRequest request, bool addToQueue)
|
||||
{
|
||||
if (CanSend (request, out ReasonCannotSend reason))
|
||||
{
|
||||
@@ -105,13 +108,13 @@ public class AnsiRequestScheduler
|
||||
|
||||
private void EvictStaleRequests ()
|
||||
{
|
||||
foreach (var stale in _lastSend.Where (v => IsStale (v.Value)).Select (k => k.Key))
|
||||
foreach (string stale in _lastSend.Where (v => IsStale (v.Value)).Select (k => k.Key))
|
||||
{
|
||||
EvictStaleRequests (stale);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsStale (DateTime dt) => Now () - dt > _staleTimeout;
|
||||
private bool IsStale (DateTime dt) { return Now () - dt > _staleTimeout; }
|
||||
|
||||
/// <summary>
|
||||
/// Looks to see if the last time we sent <paramref name="withTerminator"/>
|
||||
@@ -155,7 +158,7 @@ public class AnsiRequestScheduler
|
||||
}
|
||||
|
||||
// Get oldest request
|
||||
Tuple<AnsiEscapeSequenceRequest, DateTime>? opportunity = _queuedRequests.MinBy (r=>r.Item2);
|
||||
Tuple<AnsiEscapeSequenceRequest, DateTime>? opportunity = _queuedRequests.MinBy (r => r.Item2);
|
||||
|
||||
if (opportunity != null)
|
||||
{
|
||||
@@ -163,6 +166,7 @@ public class AnsiRequestScheduler
|
||||
if (SendOrSchedule (opportunity.Item1, false))
|
||||
{
|
||||
_queuedRequests.Remove (opportunity);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +176,6 @@ public class AnsiRequestScheduler
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void Send (AnsiEscapeSequenceRequest r)
|
||||
{
|
||||
_lastSend.AddOrUpdate (r.Terminator, _ => Now (), (_, _) => Now ());
|
||||
@@ -210,23 +213,4 @@ public class AnsiRequestScheduler
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ReasonCannotSend
|
||||
{
|
||||
/// <summary>
|
||||
/// No reason given.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The parser is already waiting for a request to complete with the given terminator.
|
||||
/// </summary>
|
||||
OutstandingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// There have been too many requests sent recently, new requests will be put into
|
||||
/// queue to prevent console becoming unresponsive.
|
||||
/// </summary>
|
||||
TooManyRequests
|
||||
}
|
||||
}
|
||||
@@ -4,29 +4,29 @@ namespace Terminal.Gui;
|
||||
|
||||
internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
protected object lockExpectedResponses = new ();
|
||||
protected object _lockExpectedResponses = new ();
|
||||
|
||||
protected object lockState = new ();
|
||||
protected object _lockState = new ();
|
||||
|
||||
/// <summary>
|
||||
/// Responses we are expecting to come in.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> expectedResponses = new ();
|
||||
protected readonly List<AnsiResponseExpectation> _expectedResponses = [];
|
||||
|
||||
/// <summary>
|
||||
/// Collection of responses that we <see cref="StopExpecting"/>.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> lateResponses = new ();
|
||||
protected readonly List<AnsiResponseExpectation> _lateResponses = [];
|
||||
|
||||
/// <summary>
|
||||
/// Responses that you want to look out for that will come in continuously e.g. mouse events.
|
||||
/// Key is the terminator.
|
||||
/// </summary>
|
||||
protected readonly List<AnsiResponseExpectation> persistentExpectations = new ();
|
||||
protected readonly List<AnsiResponseExpectation> _persistentExpectations = [];
|
||||
|
||||
private AnsiResponseParserState _state = AnsiResponseParserState.Normal;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
public AnsiResponseParserState State
|
||||
{
|
||||
get => _state;
|
||||
@@ -37,7 +37,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly IHeld heldContent;
|
||||
protected readonly IHeld _heldContent;
|
||||
|
||||
/// <summary>
|
||||
/// When <see cref="State"/> was last changed.
|
||||
@@ -48,8 +48,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
// see CSI in https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s
|
||||
// No - N or O
|
||||
protected readonly HashSet<char> _knownTerminators = new (
|
||||
new []
|
||||
{
|
||||
[
|
||||
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
|
||||
|
||||
// No - N or O
|
||||
@@ -58,14 +57,18 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
||||
'l', 'm', 'n',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
|
||||
});
|
||||
]);
|
||||
|
||||
protected AnsiResponseParserBase (IHeld heldContent) { this.heldContent = heldContent; }
|
||||
protected AnsiResponseParserBase (IHeld heldContent) { _heldContent = heldContent; }
|
||||
|
||||
protected void ResetState ()
|
||||
{
|
||||
State = AnsiResponseParserState.Normal;
|
||||
heldContent.ClearHeld ();
|
||||
|
||||
lock (_lockState)
|
||||
{
|
||||
_heldContent.ClearHeld ();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,7 +90,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
int inputLength
|
||||
)
|
||||
{
|
||||
lock (lockState)
|
||||
lock (_lockState)
|
||||
{
|
||||
ProcessInputBaseImpl (getCharAtIndex, getObjectAtIndex, appendOutput, inputLength);
|
||||
}
|
||||
@@ -116,7 +119,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
// Escape character detected, move to ExpectingBracket state
|
||||
State = AnsiResponseParserState.ExpectingBracket;
|
||||
heldContent.AddToHeld (currentObj); // Hold the escape character
|
||||
_heldContent.AddToHeld (currentObj); // Hold the escape character
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -131,13 +134,13 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
// Second escape so we must release first
|
||||
ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingBracket);
|
||||
heldContent.AddToHeld (currentObj); // Hold the new escape
|
||||
_heldContent.AddToHeld (currentObj); // Hold the new escape
|
||||
}
|
||||
else if (currentChar == '[')
|
||||
{
|
||||
// Detected '[', transition to InResponse state
|
||||
State = AnsiResponseParserState.InResponse;
|
||||
heldContent.AddToHeld (currentObj); // Hold the '['
|
||||
_heldContent.AddToHeld (currentObj); // Hold the '['
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,7 +152,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
break;
|
||||
|
||||
case AnsiResponseParserState.InResponse:
|
||||
heldContent.AddToHeld (currentObj);
|
||||
_heldContent.AddToHeld (currentObj);
|
||||
|
||||
// Check if the held content should be released
|
||||
if (ShouldReleaseHeldContent ())
|
||||
@@ -166,73 +169,76 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
|
||||
private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
|
||||
{
|
||||
foreach (object o in heldContent.HeldToObjects ())
|
||||
foreach (object o in _heldContent.HeldToObjects ())
|
||||
{
|
||||
appendOutput (o);
|
||||
}
|
||||
|
||||
State = newState;
|
||||
heldContent.ClearHeld ();
|
||||
_heldContent.ClearHeld ();
|
||||
}
|
||||
|
||||
// Common response handler logic
|
||||
protected bool ShouldReleaseHeldContent ()
|
||||
{
|
||||
string cur = heldContent.HeldToString ();
|
||||
|
||||
lock (lockExpectedResponses)
|
||||
lock (_lockState)
|
||||
{
|
||||
// Look for an expected response for what is accumulated so far (since Esc)
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
expectedResponses,
|
||||
true,
|
||||
true))
|
||||
string cur = _heldContent.HeldToString ();
|
||||
|
||||
lock (_lockExpectedResponses)
|
||||
{
|
||||
return false;
|
||||
// Look for an expected response for what is accumulated so far (since Esc)
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
_expectedResponses,
|
||||
true,
|
||||
true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Also try looking for late requests - in which case we do not invoke but still swallow content to avoid corrupting downstream
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
_lateResponses,
|
||||
false,
|
||||
true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Look for persistent requests
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
_persistentExpectations,
|
||||
true,
|
||||
false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Also try looking for late requests - in which case we do not invoke but still swallow content to avoid corrupting downstream
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
lateResponses,
|
||||
false,
|
||||
true))
|
||||
// Finally if it is a valid ansi response but not one we are expect (e.g. its mouse activity)
|
||||
// then we can release it back to input processing stream
|
||||
if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
|
||||
{
|
||||
return false;
|
||||
// We have found a terminator so bail
|
||||
State = AnsiResponseParserState.Normal;
|
||||
|
||||
// Maybe swallow anyway if user has custom delegate
|
||||
bool swallow = ShouldSwallowUnexpectedResponse ();
|
||||
|
||||
if (swallow)
|
||||
{
|
||||
_heldContent.ClearHeld ();
|
||||
|
||||
// Do not send back to input stream
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do release back to input stream
|
||||
return true;
|
||||
}
|
||||
|
||||
// Look for persistent requests
|
||||
if (MatchResponse (
|
||||
cur,
|
||||
persistentExpectations,
|
||||
true,
|
||||
false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally if it is a valid ansi response but not one we are expect (e.g. its mouse activity)
|
||||
// then we can release it back to input processing stream
|
||||
if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
|
||||
{
|
||||
// We have found a terminator so bail
|
||||
State = AnsiResponseParserState.Normal;
|
||||
|
||||
// Maybe swallow anyway if user has custom delegate
|
||||
bool swallow = ShouldSwallowUnexpectedResponse ();
|
||||
|
||||
if (swallow)
|
||||
{
|
||||
heldContent.ClearHeld ();
|
||||
|
||||
// Do not send back to input stream
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do release back to input stream
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // Continue accumulating
|
||||
@@ -241,7 +247,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// When overriden in a derived class, indicates whether the unexpected response
|
||||
/// currently in <see cref="heldContent"/> should be released or swallowed.
|
||||
/// currently in <see cref="_heldContent"/> should be released or swallowed.
|
||||
/// Use this to enable default event for escape codes.
|
||||
/// </para>
|
||||
/// <remarks>
|
||||
@@ -261,7 +267,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
{
|
||||
if (invokeCallback)
|
||||
{
|
||||
matchingResponse.Response.Invoke (heldContent);
|
||||
matchingResponse.Response.Invoke (_heldContent);
|
||||
}
|
||||
|
||||
ResetState ();
|
||||
@@ -278,17 +284,17 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ExpectResponse (string terminator, Action<string> response,Action? abandoned, bool persistent)
|
||||
public void ExpectResponse (string terminator, Action<string> response, Action? abandoned, bool persistent)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
lock (_lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
{
|
||||
persistentExpectations.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
|
||||
_persistentExpectations.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedResponses.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
|
||||
_expectedResponses.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,36 +302,36 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
/// <inheritdoc/>
|
||||
public bool IsExpecting (string terminator)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
lock (_lockExpectedResponses)
|
||||
{
|
||||
// If any of the new terminator matches any existing terminators characters it's a collision so true.
|
||||
return expectedResponses.Any (r => r.Terminator.Intersect (terminator).Any ());
|
||||
return _expectedResponses.Any (r => r.Terminator.Intersect (terminator).Any ());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopExpecting (string terminator, bool persistent)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
lock (_lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
{
|
||||
AnsiResponseExpectation [] removed = persistentExpectations.Where (r => r.Matches (terminator)).ToArray ();
|
||||
AnsiResponseExpectation [] removed = _persistentExpectations.Where (r => r.Matches (terminator)).ToArray ();
|
||||
|
||||
foreach (var toRemove in removed)
|
||||
foreach (AnsiResponseExpectation toRemove in removed)
|
||||
{
|
||||
persistentExpectations.Remove (toRemove);
|
||||
_persistentExpectations.Remove (toRemove);
|
||||
toRemove.Abandoned?.Invoke ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiResponseExpectation [] removed = expectedResponses.Where (r => r.Terminator == terminator).ToArray ();
|
||||
AnsiResponseExpectation [] removed = _expectedResponses.Where (r => r.Terminator == terminator).ToArray ();
|
||||
|
||||
foreach (AnsiResponseExpectation r in removed)
|
||||
{
|
||||
expectedResponses.Remove (r);
|
||||
lateResponses.Add (r);
|
||||
_expectedResponses.Remove (r);
|
||||
_lateResponses.Add (r);
|
||||
r.Abandoned?.Invoke ();
|
||||
}
|
||||
}
|
||||
@@ -333,10 +339,8 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
|
||||
}
|
||||
}
|
||||
|
||||
internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
internal class AnsiResponseParser<T> () : AnsiResponseParserBase (new GenericHeld<T> ())
|
||||
{
|
||||
public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
|
||||
|
||||
/// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
|
||||
public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
|
||||
|
||||
@@ -353,10 +357,10 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
return output;
|
||||
}
|
||||
|
||||
public Tuple<char, T>[] Release ()
|
||||
public Tuple<char, T> [] Release ()
|
||||
{
|
||||
// Lock in case Release is called from different Thread from parse
|
||||
lock (lockState)
|
||||
lock (_lockState)
|
||||
{
|
||||
Tuple<char, T> [] result = HeldToEnumerable ().ToArray ();
|
||||
|
||||
@@ -366,7 +370,7 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Tuple<char, T>> HeldToEnumerable () { return (IEnumerable<Tuple<char, T>>)heldContent.HeldToObjects (); }
|
||||
private IEnumerable<Tuple<char, T>> HeldToEnumerable () { return (IEnumerable<Tuple<char, T>>)_heldContent.HeldToObjects (); }
|
||||
|
||||
/// <summary>
|
||||
/// 'Overload' for specifying an expectation that requires the metadata as well as characters. Has
|
||||
@@ -376,17 +380,17 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
/// <param name="response"></param>
|
||||
/// <param name="abandoned"></param>
|
||||
/// <param name="persistent"></param>
|
||||
public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response,Action? abandoned, bool persistent)
|
||||
public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response, Action? abandoned, bool persistent)
|
||||
{
|
||||
lock (lockExpectedResponses)
|
||||
lock (_lockExpectedResponses)
|
||||
{
|
||||
if (persistent)
|
||||
{
|
||||
persistentExpectations.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
|
||||
_persistentExpectations.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedResponses.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
|
||||
_expectedResponses.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,7 +399,7 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
|
||||
protected override bool ShouldSwallowUnexpectedResponse () { return UnexpectedResponseHandler.Invoke (HeldToEnumerable ()); }
|
||||
}
|
||||
|
||||
internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ())
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
@@ -410,8 +414,6 @@ internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
/// </summary>
|
||||
public Func<string, bool> UnknownResponseHandler { get; set; } = _ => false;
|
||||
|
||||
public AnsiResponseParser () : base (new StringHeld ()) { }
|
||||
|
||||
public string ProcessInput (string input)
|
||||
{
|
||||
var output = new StringBuilder ();
|
||||
@@ -427,9 +429,9 @@ internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
|
||||
public string Release ()
|
||||
{
|
||||
lock (lockState)
|
||||
lock (_lockState)
|
||||
{
|
||||
string output = heldContent.HeldToString ();
|
||||
string output = _heldContent.HeldToString ();
|
||||
ResetState ();
|
||||
|
||||
return output;
|
||||
@@ -437,5 +439,11 @@ internal class AnsiResponseParser : AnsiResponseParserBase
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool ShouldSwallowUnexpectedResponse () { return UnknownResponseHandler.Invoke (heldContent.HeldToString ()); }
|
||||
protected override bool ShouldSwallowUnexpectedResponse ()
|
||||
{
|
||||
lock (_lockState)
|
||||
{
|
||||
return UnknownResponseHandler.Invoke (_heldContent.HeldToString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public interface IAnsiResponseParser
|
||||
/// that already has one.
|
||||
/// exists.
|
||||
/// </exception>
|
||||
void ExpectResponse (string terminator, Action<string> response,Action? abandoned, bool persistent);
|
||||
void ExpectResponse (string terminator, Action<string> response, Action? abandoned, bool persistent);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is an existing expectation (i.e. we are waiting a response
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
namespace Terminal.Gui;
|
||||
|
||||
internal enum ReasonCannotSend
|
||||
{
|
||||
/// <summary>
|
||||
/// No reason given.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The parser is already waiting for a request to complete with the given terminator.
|
||||
/// </summary>
|
||||
OutstandingRequest,
|
||||
|
||||
/// <summary>
|
||||
/// There have been too many requests sent recently, new requests will be put into
|
||||
/// queue to prevent console becoming unresponsive.
|
||||
/// </summary>
|
||||
TooManyRequests
|
||||
}
|
||||
@@ -6,13 +6,13 @@ namespace Terminal.Gui;
|
||||
/// </summary>
|
||||
internal class StringHeld : IHeld
|
||||
{
|
||||
private readonly StringBuilder held = new ();
|
||||
private readonly StringBuilder _held = new ();
|
||||
|
||||
public void ClearHeld () { held.Clear (); }
|
||||
public void ClearHeld () { _held.Clear (); }
|
||||
|
||||
public string HeldToString () { return held.ToString (); }
|
||||
public string HeldToString () { return _held.ToString (); }
|
||||
|
||||
public IEnumerable<object> HeldToObjects () { return held.ToString ().Select (c => (object)c); }
|
||||
public IEnumerable<object> HeldToObjects () { return _held.ToString ().Select (c => (object)c); }
|
||||
|
||||
public void AddToHeld (object o) { held.Append ((char)o); }
|
||||
public void AddToHeld (object o) { _held.Append ((char)o); }
|
||||
}
|
||||
|
||||
@@ -710,6 +710,10 @@ public abstract class ConsoleDriver : IConsoleDriver
|
||||
|
||||
internal abstract IAnsiResponseParser GetParser ();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AnsiRequestScheduler"/> for this <see cref="ConsoleDriver"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AnsiRequestScheduler GetRequestScheduler ()
|
||||
{
|
||||
// Lazy initialization because GetParser is virtual
|
||||
|
||||
@@ -581,6 +581,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
|
||||
private Curses.Window? _window;
|
||||
private UnixMainLoop? _mainLoopDriver;
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
private object _processInputToken;
|
||||
|
||||
public override MainLoop Init ()
|
||||
@@ -730,6 +731,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
|
||||
while (wch2 == Curses.KeyMouse)
|
||||
{
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
Key kea = null;
|
||||
|
||||
ConsoleKeyInfo [] cki =
|
||||
@@ -739,6 +741,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
new ('<', 0, false, false, false)
|
||||
};
|
||||
code = 0;
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
|
||||
}
|
||||
|
||||
@@ -796,6 +799,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
k = KeyCode.AltMask | MapCursesKey (wch);
|
||||
}
|
||||
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
Key key = null;
|
||||
|
||||
if (code == 0)
|
||||
@@ -826,6 +830,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
[
|
||||
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);
|
||||
|
||||
return;
|
||||
@@ -954,6 +959,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
|
||||
if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse)
|
||||
{
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
EscSeqUtils.DecodeEscSeq (
|
||||
ref consoleKeyInfo,
|
||||
ref ck,
|
||||
@@ -977,6 +983,7 @@ internal class CursesDriver : ConsoleDriver
|
||||
OnMouseEvent (new () { Flags = mf, Position = pos });
|
||||
}
|
||||
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
cki = null;
|
||||
|
||||
if (wch2 == 27)
|
||||
|
||||
@@ -247,6 +247,7 @@ internal class UnixMainLoop : IMainLoopDriver
|
||||
|
||||
private class Watch
|
||||
{
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
public Func<MainLoop, bool> Callback;
|
||||
public Condition Condition;
|
||||
public int File;
|
||||
|
||||
@@ -31,8 +31,9 @@ public interface IConsoleDriver
|
||||
/// <summary>The number of columns visible in the terminal.</summary>
|
||||
int Cols { get; set; }
|
||||
|
||||
// BUGBUG: This should not be publicly settable.
|
||||
/// <summary>
|
||||
/// The contents of the application output. The driver outputs this buffer to the terminal when
|
||||
/// Gets or sets the contents of the application output. The driver outputs this buffer to the terminal when
|
||||
/// <see cref="UpdateScreen"/> is called.
|
||||
/// <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
|
||||
/// </summary>
|
||||
@@ -92,11 +93,13 @@ public interface IConsoleDriver
|
||||
/// </returns>
|
||||
bool IsRuneSupported (Rune rune);
|
||||
|
||||
// BUGBUG: This is not referenced. Can it be removed?
|
||||
/// <summary>Tests whether the specified coordinate are valid for drawing.</summary>
|
||||
/// <param name="col">The column.</param>
|
||||
/// <param name="row">The row.</param>
|
||||
/// <returns>
|
||||
/// <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="ConsoleDriver.Clip"/>.
|
||||
/// <see langword="false"/> if the coordinate is outside the screen bounds or outside of
|
||||
/// <see cref="ConsoleDriver.Clip"/>.
|
||||
/// <see langword="true"/> otherwise.
|
||||
/// </returns>
|
||||
bool IsValidLocation (int col, int row);
|
||||
@@ -106,19 +109,23 @@ public interface IConsoleDriver
|
||||
/// <param name="col">The column.</param>
|
||||
/// <param name="row">The row.</param>
|
||||
/// <returns>
|
||||
/// <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="ConsoleDriver.Clip"/>.
|
||||
/// <see langword="false"/> if the coordinate is outside the screen bounds or outside of
|
||||
/// <see cref="ConsoleDriver.Clip"/>.
|
||||
/// <see langword="true"/> otherwise.
|
||||
/// </returns>
|
||||
bool IsValidLocation (Rune rune, int col, int row);
|
||||
|
||||
/// <summary>
|
||||
/// Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in <see cref="ConsoleDriver.Contents"/>.
|
||||
/// Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.AddStr"/> to determine where to add content.
|
||||
/// Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in
|
||||
/// <see cref="ConsoleDriver.Contents"/>.
|
||||
/// Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.AddStr"/> to determine
|
||||
/// where to add content.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This does not move the cursor on the screen, it only updates the internal state of the driver.</para>
|
||||
/// <para>
|
||||
/// If <paramref name="col"/> or <paramref name="row"/> are negative or beyond <see cref="ConsoleDriver.Cols"/> and
|
||||
/// If <paramref name="col"/> or <paramref name="row"/> are negative or beyond <see cref="ConsoleDriver.Cols"/>
|
||||
/// and
|
||||
/// <see cref="ConsoleDriver.Rows"/>, the method still sets those properties.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
@@ -130,12 +137,15 @@ public interface IConsoleDriver
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
|
||||
/// <paramref name="rune"/> required, even if the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
|
||||
/// <paramref name="rune"/> required, even if the new column value is outside of the
|
||||
/// <see cref="ConsoleDriver.Clip"/> or screen
|
||||
/// dimensions defined by <see cref="ConsoleDriver.Cols"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number of columns
|
||||
/// needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
|
||||
/// If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number
|
||||
/// of columns
|
||||
/// needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement
|
||||
/// character (U+FFFD)
|
||||
/// will be added instead.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
@@ -144,7 +154,8 @@ public interface IConsoleDriver
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
|
||||
/// convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/> constructor.
|
||||
/// convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
|
||||
/// constructor.
|
||||
/// </summary>
|
||||
/// <param name="c">Character to add.</param>
|
||||
void AddRune (char c);
|
||||
@@ -153,7 +164,8 @@ public interface IConsoleDriver
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
|
||||
/// <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
|
||||
/// <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/>
|
||||
/// or screen
|
||||
/// dimensions defined by <see cref="ConsoleDriver.Cols"/>.
|
||||
/// </para>
|
||||
/// <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
|
||||
@@ -161,9 +173,12 @@ public interface IConsoleDriver
|
||||
/// <param name="str">String.</param>
|
||||
void AddStr (string str);
|
||||
|
||||
/// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
|
||||
/// <summary>
|
||||
/// Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
|
||||
/// The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be
|
||||
/// drawn.
|
||||
/// </remarks>
|
||||
/// <param name="rect">The Screen-relative rectangle.</param>
|
||||
/// <param name="rune">The Rune used to fill the rectangle</param>
|
||||
@@ -185,12 +200,14 @@ public interface IConsoleDriver
|
||||
/// </summary>
|
||||
event EventHandler<EventArgs>? ClearedContents;
|
||||
|
||||
// BUGBUG: This is not referenced. Can it be removed?
|
||||
/// <summary>
|
||||
/// Sets <see cref="ConsoleDriver.Contents"/> as dirty for situations where views
|
||||
/// don't need layout and redrawing, but just refresh the screen.
|
||||
/// Sets <see cref="ConsoleDriver.Contents"/> as dirty for situations where views
|
||||
/// don't need layout and redrawing, but just refresh the screen.
|
||||
/// </summary>
|
||||
void SetContentsAsDirty ();
|
||||
|
||||
// BUGBUG: This is not referenced. Can it be removed?
|
||||
/// <summary>Determines if the terminal cursor should be visible or not and sets it accordingly.</summary>
|
||||
/// <returns><see langword="true"/> upon success</returns>
|
||||
bool EnsureCursorVisibility ();
|
||||
@@ -224,7 +241,10 @@ public interface IConsoleDriver
|
||||
/// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
|
||||
void Suspend ();
|
||||
|
||||
/// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
|
||||
/// <summary>
|
||||
/// Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
|
||||
/// <see cref="ConsoleDriver.Row"/>.
|
||||
/// </summary>
|
||||
void UpdateCursor ();
|
||||
|
||||
/// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
|
||||
@@ -263,6 +283,7 @@ public interface IConsoleDriver
|
||||
/// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
|
||||
event EventHandler<Key>? KeyDown;
|
||||
|
||||
// BUGBUG: This is not referenced. Can it be removed?
|
||||
/// <summary>
|
||||
/// Called when a key is pressed down. Fires the <see cref="ConsoleDriver.KeyDown"/> event. This is a precursor to
|
||||
/// <see cref="ConsoleDriver.OnKeyUp"/>.
|
||||
@@ -272,14 +293,17 @@ public interface IConsoleDriver
|
||||
|
||||
/// <summary>Event fired when a key is released.</summary>
|
||||
/// <remarks>
|
||||
/// Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/> processing is
|
||||
/// Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/>
|
||||
/// processing is
|
||||
/// complete.
|
||||
/// </remarks>
|
||||
event EventHandler<Key>? KeyUp;
|
||||
|
||||
// BUGBUG: This is not referenced. Can it be removed?
|
||||
/// <summary>Called when a key is released. Fires the <see cref="ConsoleDriver.KeyUp"/> event.</summary>
|
||||
/// <remarks>
|
||||
/// Drivers that do not support key release events will call this method after <see cref="ConsoleDriver.OnKeyDown"/> processing
|
||||
/// Drivers that do not support key release events will call this method after <see cref="ConsoleDriver.OnKeyDown"/>
|
||||
/// processing
|
||||
/// is complete.
|
||||
/// </remarks>
|
||||
/// <param name="a"></param>
|
||||
@@ -294,15 +318,19 @@ public interface IConsoleDriver
|
||||
void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
|
||||
|
||||
/// <summary>
|
||||
/// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
|
||||
/// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
|
||||
/// </summary>
|
||||
public TimeSpan EscTimeout { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queues the given <paramref name="request"/> for execution
|
||||
/// Queues the given <paramref name="request"/> for execution
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
public void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AnsiRequestScheduler"/> for the driver
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public AnsiRequestScheduler GetRequestScheduler ();
|
||||
}
|
||||
|
||||
@@ -223,6 +223,8 @@ internal class NetDriver : ConsoleDriver
|
||||
return updated;
|
||||
}
|
||||
#region Init/End/MainLoop
|
||||
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
/// <inheritdoc />
|
||||
internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
|
||||
internal NetMainLoop? _mainLoopDriver;
|
||||
|
||||
@@ -41,6 +41,7 @@ internal class WindowsDriver : ConsoleDriver
|
||||
private Point _pointMove;
|
||||
private bool _processButtonClick;
|
||||
|
||||
// BUGBUG: Fix this nullable issue.
|
||||
public WindowsDriver ()
|
||||
{
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
|
||||
@@ -47,11 +47,11 @@ public class SixelEncoder
|
||||
/// <returns></returns>
|
||||
public string EncodeSixel (Color [,] pixels)
|
||||
{
|
||||
const string start = "\u001bP"; // Start sixel sequence
|
||||
const string START = "\u001bP"; // Start sixel sequence
|
||||
|
||||
string defaultRatios = AnyHasAlphaOfZero (pixels) ? "0;1;0" : "0;0;0"; // Defaults for aspect ratio and grid size
|
||||
const string completeStartSequence = "q"; // Signals beginning of sixel image data
|
||||
const string noScaling = "\"1;1;"; // no scaling factors (1x1);
|
||||
const string COMPLETE_START_SEQUENCE = "q"; // Signals beginning of sixel image data
|
||||
const string NO_SCALING = "\"1;1;"; // no scaling factors (1x1);
|
||||
|
||||
string fillArea = GetFillArea (pixels);
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SixelEncoder
|
||||
|
||||
const string terminator = "\u001b\\"; // End sixel sequence
|
||||
|
||||
return start + defaultRatios + completeStartSequence + noScaling + fillArea + pallette + pixelData + terminator;
|
||||
return START + defaultRatios + COMPLETE_START_SEQUENCE + NO_SCALING + fillArea + pallette + pixelData + terminator;
|
||||
}
|
||||
|
||||
private string WriteSixel (Color [,] pixels)
|
||||
@@ -9,12 +9,14 @@ namespace Terminal.Gui;
|
||||
public class SixelSupportDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends Ansi escape sequences to the console to determine whether
|
||||
/// sixel is supported (and <see cref="SixelSupportResult.Resolution"/>
|
||||
/// etc).
|
||||
/// Sends Ansi escape sequences to the console to determine whether
|
||||
/// sixel is supported (and <see cref="SixelSupportResult.Resolution"/>
|
||||
/// etc).
|
||||
/// </summary>
|
||||
/// <returns>Description of sixel support, may include assumptions where
|
||||
/// expected response codes are not returned by console.</returns>
|
||||
/// <returns>
|
||||
/// Description of sixel support, may include assumptions where
|
||||
/// expected response codes are not returned by console.
|
||||
/// </returns>
|
||||
public void Detect (Action<SixelSupportResult> resultCallback)
|
||||
{
|
||||
var result = new SixelSupportResult ();
|
||||
@@ -22,75 +24,76 @@ public class SixelSupportDetector
|
||||
IsSixelSupportedByDar (result, resultCallback);
|
||||
}
|
||||
|
||||
|
||||
private void TryGetResolutionDirectly (SixelSupportResult result, Action<SixelSupportResult> resultCallback)
|
||||
{
|
||||
// Expect something like:
|
||||
//<esc>[6;20;10t
|
||||
QueueRequest (EscSeqUtils.CSI_RequestSixelResolution,
|
||||
(r) =>
|
||||
QueueRequest (
|
||||
EscSeqUtils.CSI_RequestSixelResolution,
|
||||
r =>
|
||||
{
|
||||
// Terminal supports directly responding with resolution
|
||||
var match = Regex.Match (r, @"\[\d+;(\d+);(\d+)t$");
|
||||
Match match = Regex.Match (r, @"\[\d+;(\d+);(\d+)t$");
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
if (int.TryParse (match.Groups [1].Value, out var ry) &&
|
||||
int.TryParse (match.Groups [2].Value, out var rx))
|
||||
if (int.TryParse (match.Groups [1].Value, out int ry) && int.TryParse (match.Groups [2].Value, out int rx))
|
||||
{
|
||||
result.Resolution = new Size (rx, ry);
|
||||
result.Resolution = new (rx, ry);
|
||||
}
|
||||
}
|
||||
|
||||
// Finished
|
||||
resultCallback.Invoke (result);
|
||||
|
||||
},
|
||||
// Request failed, so try to compute instead
|
||||
()=>TryComputeResolution (result,resultCallback));
|
||||
}
|
||||
|
||||
// Request failed, so try to compute instead
|
||||
() => TryComputeResolution (result, resultCallback));
|
||||
}
|
||||
|
||||
private void TryComputeResolution (SixelSupportResult result, Action<SixelSupportResult> resultCallback)
|
||||
{
|
||||
string windowSize;
|
||||
string sizeInChars;
|
||||
|
||||
QueueRequest (EscSeqUtils.CSI_RequestWindowSizeInPixels,
|
||||
(r1)=>
|
||||
QueueRequest (
|
||||
EscSeqUtils.CSI_RequestWindowSizeInPixels,
|
||||
r1 =>
|
||||
{
|
||||
windowSize = r1;
|
||||
|
||||
QueueRequest (EscSeqUtils.CSI_ReportTerminalSizeInChars,
|
||||
(r2) =>
|
||||
QueueRequest (
|
||||
EscSeqUtils.CSI_ReportTerminalSizeInChars,
|
||||
r2 =>
|
||||
{
|
||||
sizeInChars = r2;
|
||||
ComputeResolution (result,windowSize,sizeInChars);
|
||||
ComputeResolution (result, windowSize, sizeInChars);
|
||||
resultCallback (result);
|
||||
|
||||
}, abandoned: () => resultCallback (result));
|
||||
},abandoned: ()=>resultCallback(result));
|
||||
},
|
||||
() => resultCallback (result));
|
||||
},
|
||||
() => resultCallback (result));
|
||||
}
|
||||
|
||||
private void ComputeResolution (SixelSupportResult result, string windowSize, string sizeInChars)
|
||||
{
|
||||
// Fallback to window size in pixels and characters
|
||||
// Example [4;600;1200t
|
||||
var pixelMatch = Regex.Match (windowSize, @"\[\d+;(\d+);(\d+)t$");
|
||||
Match pixelMatch = Regex.Match (windowSize, @"\[\d+;(\d+);(\d+)t$");
|
||||
|
||||
// Example [8;30;120t
|
||||
var charMatch = Regex.Match (sizeInChars, @"\[\d+;(\d+);(\d+)t$");
|
||||
Match charMatch = Regex.Match (sizeInChars, @"\[\d+;(\d+);(\d+)t$");
|
||||
|
||||
if (pixelMatch.Success && charMatch.Success)
|
||||
{
|
||||
// Extract pixel dimensions
|
||||
if (int.TryParse (pixelMatch.Groups [1].Value, out var pixelHeight)
|
||||
&& int.TryParse (pixelMatch.Groups [2].Value, out var pixelWidth)
|
||||
if (int.TryParse (pixelMatch.Groups [1].Value, out int pixelHeight)
|
||||
&& int.TryParse (pixelMatch.Groups [2].Value, out int pixelWidth)
|
||||
&&
|
||||
|
||||
// Extract character dimensions
|
||||
int.TryParse (charMatch.Groups [1].Value, out var charHeight)
|
||||
&& int.TryParse (charMatch.Groups [2].Value, out var charWidth)
|
||||
int.TryParse (charMatch.Groups [1].Value, out int charHeight)
|
||||
&& int.TryParse (charMatch.Groups [2].Value, out int charWidth)
|
||||
&& charWidth != 0
|
||||
&& charHeight != 0) // Avoid divide by zero
|
||||
{
|
||||
@@ -99,31 +102,32 @@ public class SixelSupportDetector
|
||||
var cellHeight = (int)Math.Round ((double)pixelHeight / charHeight);
|
||||
|
||||
// Set the resolution based on the character cell size
|
||||
result.Resolution = new Size (cellWidth, cellHeight);
|
||||
result.Resolution = new (cellWidth, cellHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IsSixelSupportedByDar (SixelSupportResult result,Action<SixelSupportResult> resultCallback)
|
||||
private void IsSixelSupportedByDar (SixelSupportResult result, Action<SixelSupportResult> resultCallback)
|
||||
{
|
||||
QueueRequest (
|
||||
EscSeqUtils.CSI_SendDeviceAttributes,
|
||||
(r) =>
|
||||
{
|
||||
result.IsSupported = ResponseIndicatesSupport (r);
|
||||
EscSeqUtils.CSI_SendDeviceAttributes,
|
||||
r =>
|
||||
{
|
||||
result.IsSupported = ResponseIndicatesSupport (r);
|
||||
|
||||
if (result.IsSupported)
|
||||
{
|
||||
TryGetResolutionDirectly (result, resultCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCallback (result);
|
||||
}
|
||||
},abandoned: () => resultCallback(result));
|
||||
if (result.IsSupported)
|
||||
{
|
||||
TryGetResolutionDirectly (result, resultCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCallback (result);
|
||||
}
|
||||
},
|
||||
() => resultCallback (result));
|
||||
}
|
||||
|
||||
private void QueueRequest (AnsiEscapeSequenceRequest req, Action<string> responseCallback, Action abandoned)
|
||||
private static void QueueRequest (AnsiEscapeSequenceRequest req, Action<string> responseCallback, Action abandoned)
|
||||
{
|
||||
var newRequest = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
@@ -133,29 +137,29 @@ public class SixelSupportDetector
|
||||
Abandoned = abandoned
|
||||
};
|
||||
|
||||
Application.Driver.QueueAnsiRequest (newRequest);
|
||||
Application.Driver?.QueueAnsiRequest (newRequest);
|
||||
}
|
||||
|
||||
private bool ResponseIndicatesSupport (string response)
|
||||
private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); }
|
||||
|
||||
private static bool IsWindowsTerminal ()
|
||||
{
|
||||
return response.Split (';').Contains ("4");
|
||||
return !string.IsNullOrWhiteSpace (Environment.GetEnvironmentVariable ("WT_SESSION"));
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
private bool IsWindowsTerminal ()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable ("WT_SESSION"));;
|
||||
}
|
||||
private bool IsXtermWithTransparency ()
|
||||
private static bool IsXtermWithTransparency ()
|
||||
{
|
||||
// Check if running in real xterm (XTERM_VERSION is more reliable than TERM)
|
||||
var xtermVersionStr = Environment.GetEnvironmentVariable ("XTERM_VERSION");
|
||||
string xtermVersionStr = Environment.GetEnvironmentVariable ("XTERM_VERSION");
|
||||
|
||||
// If XTERM_VERSION exists, we are in a real xterm
|
||||
if (!string.IsNullOrWhiteSpace (xtermVersionStr) && int.TryParse (xtermVersionStr, out var xtermVersion) && xtermVersion >= 370)
|
||||
if (!string.IsNullOrWhiteSpace (xtermVersionStr) && int.TryParse (xtermVersionStr, out int xtermVersion) && xtermVersion >= 370)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// Describes the discovered state of sixel support and ancillary information
|
||||
/// e.g. <see cref="Resolution"/>. You can use any <see cref="ISixelSupportDetector"/>
|
||||
/// e.g. <see cref="Resolution"/>. You can use any <see cref="SixelSupportDetector"/>
|
||||
/// to discover this information.
|
||||
/// </summary>
|
||||
public class SixelSupportResult
|
||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Terminal.Gui;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace UICatalog.Scenarios;
|
||||
|
||||
@@ -13,14 +12,13 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
{
|
||||
private GraphView _graphView;
|
||||
|
||||
private DateTime start = DateTime.Now;
|
||||
private ScatterSeries _sentSeries;
|
||||
private ScatterSeries _answeredSeries;
|
||||
|
||||
private List<DateTime> sends = new ();
|
||||
private readonly List<DateTime> _sends = new ();
|
||||
|
||||
private object lockAnswers = new object ();
|
||||
private Dictionary<DateTime, string> answers = new ();
|
||||
private readonly object _lockAnswers = new object ();
|
||||
private readonly Dictionary<DateTime, string> _answers = new ();
|
||||
private Label _lblSummary;
|
||||
|
||||
public override void Main ()
|
||||
@@ -82,7 +80,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
"CSI_RequestCursorPositionReport",
|
||||
"CSI_SendDeviceAttributes2"
|
||||
};
|
||||
|
||||
// TODO: This UI would be cleaner/less rigid if Pos.Align were used
|
||||
var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
|
||||
w.Add (cbRequests);
|
||||
|
||||
@@ -225,7 +223,7 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
TimeSpan.FromMilliseconds (1000),
|
||||
() =>
|
||||
{
|
||||
lock (lockAnswers)
|
||||
lock (_lockAnswers)
|
||||
{
|
||||
UpdateGraph ();
|
||||
|
||||
@@ -327,15 +325,15 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
|
||||
private string GetSummary ()
|
||||
{
|
||||
if (answers.Count == 0)
|
||||
if (_answers.Count == 0)
|
||||
{
|
||||
return "No requests sent yet";
|
||||
}
|
||||
|
||||
var last = answers.Last ().Value;
|
||||
var last = _answers.Last ().Value;
|
||||
|
||||
var unique = answers.Values.Distinct ().Count ();
|
||||
var total = answers.Count;
|
||||
var unique = _answers.Values.Distinct ().Count ();
|
||||
var total = _answers.Count;
|
||||
|
||||
return $"Last:{last} U:{unique} T:{total}";
|
||||
}
|
||||
@@ -361,12 +359,12 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
|
||||
private void UpdateGraph ()
|
||||
{
|
||||
_sentSeries.Points = sends
|
||||
_sentSeries.Points = _sends
|
||||
.GroupBy (ToSeconds)
|
||||
.Select (g => new PointF (g.Key, g.Count ()))
|
||||
.ToList ();
|
||||
|
||||
_answeredSeries.Points = answers.Keys
|
||||
_answeredSeries.Points = _answers.Keys
|
||||
.GroupBy (ToSeconds)
|
||||
.Select (g => new PointF (g.Key, g.Count ()))
|
||||
.ToList ();
|
||||
@@ -389,14 +387,14 @@ public sealed class AnsiEscapeSequenceRequests : Scenario
|
||||
Terminator = EscSeqUtils.CSI_SendDeviceAttributes.Terminator,
|
||||
ResponseReceived = HandleResponse
|
||||
});
|
||||
sends.Add (DateTime.Now);
|
||||
_sends.Add (DateTime.Now);
|
||||
}
|
||||
|
||||
private void HandleResponse (string response)
|
||||
{
|
||||
lock (lockAnswers)
|
||||
lock (_lockAnswers)
|
||||
{
|
||||
answers.Add (DateTime.Now, response);
|
||||
_answers.Add (DateTime.Now, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public class Images : Scenario
|
||||
private SixelToRender _sixelImage;
|
||||
|
||||
// Start by assuming no support
|
||||
private SixelSupportResult _sixelSupportResult = new SixelSupportResult ();
|
||||
private SixelSupportResult _sixelSupportResult = new ();
|
||||
private CheckBox _cbSupportsSixel;
|
||||
|
||||
public override void Main ()
|
||||
@@ -95,8 +95,8 @@ public class Images : Scenario
|
||||
Text = "supports true color "
|
||||
};
|
||||
_win.Add (cbSupportsTrueColor);
|
||||
|
||||
_cbSupportsSixel = new CheckBox
|
||||
|
||||
_cbSupportsSixel = new()
|
||||
{
|
||||
X = Pos.Right (lblDriverName) + 2,
|
||||
Y = 1,
|
||||
@@ -104,26 +104,24 @@ public class Images : Scenario
|
||||
Text = "Supports Sixel"
|
||||
};
|
||||
|
||||
var lblSupportsSixel = new Label ()
|
||||
var lblSupportsSixel = new Label
|
||||
{
|
||||
|
||||
X = Pos.Right (lblDriverName) + 2,
|
||||
Y = Pos.Bottom (_cbSupportsSixel),
|
||||
Text = "(Check if your terminal supports Sixel)"
|
||||
};
|
||||
|
||||
|
||||
/* CheckedState = _sixelSupportResult.IsSupported
|
||||
? CheckState.Checked
|
||||
: CheckState.UnChecked;*/
|
||||
|
||||
_cbSupportsSixel.CheckedStateChanging += (s, e) =>
|
||||
{
|
||||
_sixelSupportResult.IsSupported = e.NewValue == CheckState.Checked;
|
||||
SetupSixelSupported (e.NewValue == CheckState.Checked);
|
||||
ApplyShowTabViewHack ();
|
||||
};
|
||||
|
||||
{
|
||||
_sixelSupportResult.IsSupported = e.NewValue == CheckState.Checked;
|
||||
SetupSixelSupported (e.NewValue == CheckState.Checked);
|
||||
ApplyShowTabViewHack ();
|
||||
};
|
||||
|
||||
_win.Add (_cbSupportsSixel);
|
||||
|
||||
var cbUseTrueColor = new CheckBox
|
||||
@@ -174,7 +172,6 @@ public class Images : Scenario
|
||||
_cbSupportsSixel.CheckedState = newResult.IsSupported ? CheckState.Checked : CheckState.UnChecked;
|
||||
_pxX.Value = _sixelSupportResult.Resolution.Width;
|
||||
_pxY.Value = _sixelSupportResult.Resolution.Height;
|
||||
|
||||
}
|
||||
|
||||
private void SetupSixelSupported (bool isSupported)
|
||||
@@ -311,7 +308,7 @@ public class Images : Scenario
|
||||
{
|
||||
// TODO HACK: This hack seems to be required to make tabview actually refresh itself
|
||||
_tabView.SetNeedsDraw ();
|
||||
var orig = _tabView.SelectedTab;
|
||||
Tab orig = _tabView.SelectedTab;
|
||||
_tabView.SelectedTab = _tabView.Tabs.Except (new [] { orig }).ElementAt (0);
|
||||
_tabView.SelectedTab = orig;
|
||||
}
|
||||
|
||||
@@ -416,8 +416,6 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
|
||||
[Fact]
|
||||
public void ShouldSwallowUnknownResponses_WhenDelegateSaysSo ()
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
// Swallow all unknown escape codes
|
||||
_parser1.UnexpectedResponseHandler = _ => true;
|
||||
_parser2.UnknownResponseHandler = _ => true;
|
||||
|
||||
Reference in New Issue
Block a user