Selaa lähdekoodia

Merge pull request #173 from tig/tznind-ansi-parser

Code review comments and cleanup
Thomas Nind 8 kuukautta sitten
vanhempi
commit
21f0cc1be8
43 muutettua tiedostoa jossa 299 lisäystä ja 245 poistoa
  1. 1 0
      Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
  2. 15 31
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
  3. 102 94
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
  4. 1 1
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs
  5. 21 0
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/ReasonCannotSend.cs
  6. 5 5
      Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
  7. 4 0
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  8. 7 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  9. 1 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  10. 48 20
      Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
  11. 2 0
      Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
  12. 1 0
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
  13. 0 0
      Terminal.Gui/Drawing/Color/AnsiColorCode.cs
  14. 0 0
      Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs
  15. 0 0
      Terminal.Gui/Drawing/Color/Color.ColorName.cs
  16. 0 0
      Terminal.Gui/Drawing/Color/Color.ColorParseException.cs
  17. 0 0
      Terminal.Gui/Drawing/Color/Color.Formatting.cs
  18. 0 0
      Terminal.Gui/Drawing/Color/Color.Operators.cs
  19. 0 0
      Terminal.Gui/Drawing/Color/Color.cs
  20. 0 0
      Terminal.Gui/Drawing/Color/ColorEventArgs.cs
  21. 0 0
      Terminal.Gui/Drawing/Color/ColorModel.cs
  22. 0 0
      Terminal.Gui/Drawing/Color/ColorQuantizer.cs
  23. 0 0
      Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs
  24. 0 0
      Terminal.Gui/Drawing/Color/ColorScheme.cs
  25. 0 0
      Terminal.Gui/Drawing/Color/ColorStrings.cs
  26. 0 0
      Terminal.Gui/Drawing/Color/IColorDistance.cs
  27. 0 0
      Terminal.Gui/Drawing/Color/IColorNameResolver.cs
  28. 0 0
      Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs
  29. 0 0
      Terminal.Gui/Drawing/Color/W3CColors.cs
  30. 0 0
      Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs
  31. 0 0
      Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs
  32. 0 0
      Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs
  33. 0 0
      Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs
  34. 0 0
      Terminal.Gui/Drawing/LineCanvas/LineStyle.cs
  35. 0 0
      Terminal.Gui/Drawing/LineCanvas/StraightLine.cs
  36. 0 0
      Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs
  37. 4 4
      Terminal.Gui/Drawing/Sixel/SixelEncoder.cs
  38. 61 57
      Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
  39. 1 1
      Terminal.Gui/Drawing/Sixel/SixelSupportResult.cs
  40. 0 0
      Terminal.Gui/Drawing/Sixel/SixelToRender.cs
  41. 14 16
      UICatalog/Scenarios/AnsiRequestsScenario.cs
  42. 11 14
      UICatalog/Scenarios/Images.cs
  43. 0 2
      UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

+ 1 - 0
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs

@@ -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"/>

+ 15 - 31
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs

@@ -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
-}
+}

+ 102 - 94
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs

@@ -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))
-            {
-                return false;
-            }
+            string cur = _heldContent.HeldToString ();
 
-            // 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))
+            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;
+                }
             }
 
-            // Look for persistent requests
-            if (MatchResponse (
-                               cur,
-                               persistentExpectations,
-                               true,
-                               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))
             {
-                return false;
-            }
-        }
+                // We have found a terminator so bail
+                State = AnsiResponseParserState.Normal;
 
-        // 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 ();
 
-            // Maybe swallow anyway if user has custom delegate
-            bool swallow = ShouldSwallowUnexpectedResponse ();
+                if (swallow)
+                {
+                    _heldContent.ClearHeld ();
 
-            if (swallow)
-            {
-                heldContent.ClearHeld ();
+                    // Do not send back to input stream
+                    return false;
+                }
 
-                // Do not send back to input stream
-                return false;
+                // Do release back to input stream
+                return true;
             }
-
-            // 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 ());
+        }
+    }
 }

+ 1 - 1
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs

@@ -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

+ 21 - 0
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/ReasonCannotSend.cs

@@ -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
+}

+ 5 - 5
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs

@@ -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); }
 }

+ 4 - 0
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -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

+ 7 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -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)

+ 1 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -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;

+ 48 - 20
Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs

@@ -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 ();
 }

+ 2 - 0
Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs

@@ -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;

+ 1 - 0
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

@@ -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)

+ 0 - 0
Terminal.Gui/Drawing/AnsiColorCode.cs → Terminal.Gui/Drawing/Color/AnsiColorCode.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.ColorExtensions.cs → Terminal.Gui/Drawing/Color/Color.ColorExtensions.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.ColorName.cs → Terminal.Gui/Drawing/Color/Color.ColorName.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.ColorParseException.cs → Terminal.Gui/Drawing/Color/Color.ColorParseException.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.Formatting.cs → Terminal.Gui/Drawing/Color/Color.Formatting.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.Operators.cs → Terminal.Gui/Drawing/Color/Color.Operators.cs


+ 0 - 0
Terminal.Gui/Drawing/Color.cs → Terminal.Gui/Drawing/Color/Color.cs


+ 0 - 0
Terminal.Gui/Drawing/ColorEventArgs.cs → Terminal.Gui/Drawing/Color/ColorEventArgs.cs


+ 0 - 0
Terminal.Gui/Drawing/ColorModel.cs → Terminal.Gui/Drawing/Color/ColorModel.cs


+ 0 - 0
Terminal.Gui/Drawing/Quant/ColorQuantizer.cs → Terminal.Gui/Drawing/Color/ColorQuantizer.cs


+ 0 - 0
Terminal.Gui/Drawing/ColorScheme.Colors.cs → Terminal.Gui/Drawing/Color/ColorScheme.Colors.cs


+ 0 - 0
Terminal.Gui/Drawing/ColorScheme.cs → Terminal.Gui/Drawing/Color/ColorScheme.cs


+ 0 - 0
Terminal.Gui/Drawing/ColorStrings.cs → Terminal.Gui/Drawing/Color/ColorStrings.cs


+ 0 - 0
Terminal.Gui/Drawing/Quant/IColorDistance.cs → Terminal.Gui/Drawing/Color/IColorDistance.cs


+ 0 - 0
Terminal.Gui/Drawing/IColorNameResolver.cs → Terminal.Gui/Drawing/Color/IColorNameResolver.cs


+ 0 - 0
Terminal.Gui/Drawing/ICustomColorFormatter.cs → Terminal.Gui/Drawing/Color/ICustomColorFormatter.cs


+ 0 - 0
Terminal.Gui/Drawing/W3CColors.cs → Terminal.Gui/Drawing/Color/W3CColors.cs


+ 0 - 0
Terminal.Gui/Drawing/IntersectionDefinition.cs → Terminal.Gui/Drawing/LineCanvas/IntersectionDefinition.cs


+ 0 - 0
Terminal.Gui/Drawing/IntersectionRuneType.cs → Terminal.Gui/Drawing/LineCanvas/IntersectionRuneType.cs


+ 0 - 0
Terminal.Gui/Drawing/IntersectionType.cs → Terminal.Gui/Drawing/LineCanvas/IntersectionType.cs


+ 0 - 0
Terminal.Gui/Drawing/LineCanvas.cs → Terminal.Gui/Drawing/LineCanvas/LineCanvas.cs


+ 0 - 0
Terminal.Gui/Drawing/LineStyle.cs → Terminal.Gui/Drawing/LineCanvas/LineStyle.cs


+ 0 - 0
Terminal.Gui/Drawing/StraightLine.cs → Terminal.Gui/Drawing/LineCanvas/StraightLine.cs


+ 0 - 0
Terminal.Gui/Drawing/StraightLineExtensions.cs → Terminal.Gui/Drawing/LineCanvas/StraightLineExtensions.cs


+ 4 - 4
Terminal.Gui/Drawing/SixelEncoder.cs → Terminal.Gui/Drawing/Sixel/SixelEncoder.cs

@@ -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)

+ 61 - 57
Terminal.Gui/Drawing/SixelSupportDetector.cs → Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs

@@ -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));
+                      () => 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);
-
-                  if (result.IsSupported)
-                  {
-                      TryGetResolutionDirectly (result, resultCallback);
-                  }
-                  else
-                  {
-                      resultCallback (result);
-                  }
-              },abandoned: () => resultCallback(result));
+                      EscSeqUtils.CSI_SendDeviceAttributes,
+                      r =>
+                      {
+                          result.IsSupported = ResponseIndicatesSupport (r);
+
+                          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)
-    {
-        return response.Split (';').Contains ("4");
-    }
+    private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); }
 
-    private bool IsWindowsTerminal ()
+    private static bool IsWindowsTerminal ()
     {
-        return  !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable ("WT_SESSION"));;
+        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;
     }
-}
+}

+ 1 - 1
Terminal.Gui/Drawing/SixelSupportResult.cs → Terminal.Gui/Drawing/Sixel/SixelSupportResult.cs

@@ -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

+ 0 - 0
Terminal.Gui/Drawing/SixelToRender.cs → Terminal.Gui/Drawing/Sixel/SixelToRender.cs


+ 14 - 16
UICatalog/Scenarios/AnsiRequestsScenario.cs

@@ -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);
         }
     }
 }

+ 11 - 14
UICatalog/Scenarios/Images.cs

@@ -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;
     }

+ 0 - 2
UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

@@ -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;