Browse Source

Sixel detection and added Abandoned event

tznind 9 months ago
parent
commit
27f131713f

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

@@ -22,6 +22,11 @@ public class AnsiEscapeSequenceRequest
     /// </summary>
     /// </summary>
     public Action<string> ResponseReceived;
     public Action<string> ResponseReceived;
 
 
+    /// <summary>
+    ///     Invoked if the console fails to responds to the ANSI response code
+    /// </summary>
+    public Action? Abandoned;
+
     /// <summary>
     /// <summary>
     ///     <para>
     ///     <para>
     ///         The terminator that uniquely identifies the type of response as responded
     ///         The terminator that uniquely identifies the type of response as responded

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

@@ -176,7 +176,7 @@ internal class AnsiRequestScheduler
     private void Send (AnsiEscapeSequenceRequest r)
     private void Send (AnsiEscapeSequenceRequest r)
     {
     {
         _lastSend.AddOrUpdate (r.Terminator, _ => Now (), (_, _) => Now ());
         _lastSend.AddOrUpdate (r.Terminator, _ => Now (), (_, _) => Now ());
-        _parser.ExpectResponse (r.Terminator, r.ResponseReceived, false);
+        _parser.ExpectResponse (r.Terminator, r.ResponseReceived, r.Abandoned, false);
         r.Send ();
         r.Send ();
     }
     }
 
 

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

@@ -1,7 +1,7 @@
 #nullable enable
 #nullable enable
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
-internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response)
+internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
 {
 {
     public bool Matches (string cur) { return cur.EndsWith (Terminator); }
     public bool Matches (string cur) { return cur.EndsWith (Terminator); }
 }
 }

+ 15 - 7
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs

@@ -281,17 +281,17 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     }
     }
 
 
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public void ExpectResponse (string terminator, Action<string> response, bool persistent)
+    public void ExpectResponse (string terminator, Action<string> response,Action? abandoned, bool persistent)
     {
     {
         lock (lockExpectedResponses)
         lock (lockExpectedResponses)
         {
         {
             if (persistent)
             if (persistent)
             {
             {
-                persistentExpectations.Add (new (terminator, h => response.Invoke (h.HeldToString ())));
+                persistentExpectations.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
             }
             }
             else
             else
             {
             {
-                expectedResponses.Add (new (terminator, h => response.Invoke (h.HeldToString ())));
+                expectedResponses.Add (new (terminator, h => response.Invoke (h.HeldToString ()), abandoned));
             }
             }
         }
         }
     }
     }
@@ -315,7 +315,13 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
         {
         {
             if (persistent)
             if (persistent)
             {
             {
-                persistentExpectations.RemoveAll (r => r.Matches (terminator));
+                AnsiResponseExpectation [] removed = persistentExpectations.Where (r => r.Matches (terminator)).ToArray ();
+
+                foreach (var toRemove in removed)
+                {
+                    persistentExpectations.Remove (toRemove);
+                    toRemove.Abandoned?.Invoke ();
+                }
             }
             }
             else
             else
             {
             {
@@ -325,6 +331,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
                 {
                 {
                     expectedResponses.Remove (r);
                     expectedResponses.Remove (r);
                     lateResponses.Add (r);
                     lateResponses.Add (r);
+                    r.Abandoned?.Invoke ();
                 }
                 }
             }
             }
         }
         }
@@ -373,18 +380,19 @@ internal class AnsiResponseParser<T> : AnsiResponseParserBase
     /// </summary>
     /// </summary>
     /// <param name="terminator"></param>
     /// <param name="terminator"></param>
     /// <param name="response"></param>
     /// <param name="response"></param>
+    /// <param name="abandoned"></param>
     /// <param name="persistent"></param>
     /// <param name="persistent"></param>
-    public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response, bool persistent)
+    public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response,Action? abandoned, bool persistent)
     {
     {
         lock (lockExpectedResponses)
         lock (lockExpectedResponses)
         {
         {
             if (persistent)
             if (persistent)
             {
             {
-                persistentExpectations.Add (new (terminator, h => response.Invoke (HeldToEnumerable ())));
+                persistentExpectations.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
             }
             }
             else
             else
             {
             {
-                expectedResponses.Add (new (terminator, h => response.Invoke (HeldToEnumerable ())));
+                expectedResponses.Add (new (terminator, h => response.Invoke (HeldToEnumerable ()), abandoned));
             }
             }
         }
         }
     }
     }

+ 3 - 2
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs

@@ -24,17 +24,18 @@ public interface IAnsiResponseParser
     ///     sent an ANSI request out).
     ///     sent an ANSI request out).
     /// </summary>
     /// </summary>
     /// <param name="terminator">The terminator you expect to see on response.</param>
     /// <param name="terminator">The terminator you expect to see on response.</param>
+    /// <param name="response">Callback to invoke when the response is seen in console input.</param>
+    /// <param name="abandoned"></param>
     /// <param name="persistent">
     /// <param name="persistent">
     ///     <see langword="true"/> if you want this to persist permanently
     ///     <see langword="true"/> if you want this to persist permanently
     ///     and be raised for every event matching the <paramref name="terminator"/>.
     ///     and be raised for every event matching the <paramref name="terminator"/>.
     /// </param>
     /// </param>
-    /// <param name="response">Callback to invoke when the response is seen in console input.</param>
     /// <exception cref="ArgumentException">
     /// <exception cref="ArgumentException">
     ///     If trying to register a persistent request for a terminator
     ///     If trying to register a persistent request for a terminator
     ///     that already has one.
     ///     that already has one.
     ///     exists.
     ///     exists.
     /// </exception>
     /// </exception>
-    void ExpectResponse (string terminator, Action<string> response, bool persistent);
+    void ExpectResponse (string terminator, Action<string> response,Action? abandoned, bool persistent);
 
 
     /// <summary>
     /// <summary>
     ///     Returns true if there is an existing expectation (i.e. we are waiting a response
     ///     Returns true if there is an existing expectation (i.e. we are waiting a response

+ 0 - 3
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -1368,8 +1368,6 @@ public static class EscSeqUtils
     /// </summary>
     /// </summary>
     public const string CSI_ReportDeviceAttributes_Terminator = "c";
     public const string CSI_ReportDeviceAttributes_Terminator = "c";
 
 
-    /*
-     TODO: depends on https://github.com/gui-cs/Terminal.Gui/pull/3768
     /// <summary>
     /// <summary>
     ///     CSI 16 t - Request sixel resolution (width and height in pixels)
     ///     CSI 16 t - Request sixel resolution (width and height in pixels)
     /// </summary>
     /// </summary>
@@ -1379,7 +1377,6 @@ public static class EscSeqUtils
     ///     CSI 14 t - Request window size in pixels (width x height)
     ///     CSI 14 t - Request window size in pixels (width x height)
     /// </summary>
     /// </summary>
     public static readonly AnsiEscapeSequenceRequest CSI_RequestWindowSizeInPixels = new () { Request = CSI + "14t", Terminator = "t" };
     public static readonly AnsiEscapeSequenceRequest CSI_RequestWindowSizeInPixels = new () { Request = CSI + "14t", Terminator = "t" };
-    */
 
 
     /// <summary>
     /// <summary>
     ///     CSI 1 8 t  | yes | yes |  yes  | report window size in chars
     ///     CSI 1 8 t  | yes | yes |  yes  | report window size in chars

+ 0 - 20
Terminal.Gui/Drawing/AssumeSupportDetector.cs

@@ -1,20 +0,0 @@
-namespace Terminal.Gui;
-
-/// <summary>
-///     Implementation of <see cref="ISixelSupportDetector"/> that assumes best
-///     case scenario (full support including transparency with 10x20 resolution).
-/// </summary>
-public class AssumeSupportDetector : ISixelSupportDetector
-{
-    /// <inheritdoc/>
-    public SixelSupportResult Detect ()
-    {
-        return new()
-        {
-            IsSupported = true,
-            MaxPaletteColors = 256,
-            Resolution = new (10, 20),
-            SupportsTransparency = true
-        };
-    }
-}

+ 0 - 15
Terminal.Gui/Drawing/ISixelSupportDetector.cs

@@ -1,15 +0,0 @@
-namespace Terminal.Gui;
-
-/// <summary>
-///     Interface for detecting sixel support. Either through
-///     ansi requests to terminal or config file etc.
-/// </summary>
-public interface ISixelSupportDetector
-{
-    /// <summary>
-    ///     Gets the supported sixel state e.g. by sending Ansi escape sequences
-    ///     or from a config file etc.
-    /// </summary>
-    /// <returns>Description of sixel support.</returns>
-    public SixelSupportResult Detect ();
-}

+ 104 - 76
Terminal.Gui/Drawing/SixelSupportDetector.cs

@@ -1,12 +1,12 @@
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
-/* TODO : Depends on https://github.com/gui-cs/Terminal.Gui/pull/3768
+
 /// <summary>
 /// <summary>
 ///     Uses Ansi escape sequences to detect whether sixel is supported
 ///     Uses Ansi escape sequences to detect whether sixel is supported
 ///     by the terminal.
 ///     by the terminal.
 /// </summary>
 /// </summary>
-public class SixelSupportDetector : ISixelSupportDetector
+public class SixelSupportDetector
 {
 {
     /// <summary>
     /// <summary>
     /// Sends Ansi escape sequences to the console to determine whether
     /// Sends Ansi escape sequences to the console to determine whether
@@ -15,102 +15,130 @@ public class SixelSupportDetector : ISixelSupportDetector
     /// </summary>
     /// </summary>
     /// <returns>Description of sixel support, may include assumptions where
     /// <returns>Description of sixel support, may include assumptions where
     /// expected response codes are not returned by console.</returns>
     /// expected response codes are not returned by console.</returns>
-    public SixelSupportResult Detect ()
+    public void Detect (Action<SixelSupportResult> resultCallback)
     {
     {
         var result = new SixelSupportResult ();
         var result = new SixelSupportResult ();
+        result.SupportsTransparency = IsWindowsTerminal () || IsXtermWithTransparency ();
+        IsSixelSupportedByDar (result, resultCallback);
+    }
 
 
-        result.IsSupported = IsSixelSupportedByDar ();
 
 
-        if (result.IsSupported)
-        {
-            if (TryGetResolutionDirectly (out var res))
-            {
-                result.Resolution = res;
-            }
-            else if(TryComputeResolution(out res))
-            {
-                result.Resolution = res;
-            }
+    private void TryGetResolutionDirectly (SixelSupportResult result, Action<SixelSupportResult> resultCallback)
+    {
+        // Expect something like:
+        //<esc>[6;20;10t
+        QueueRequest (EscSeqUtils.CSI_RequestSixelResolution,
+                      (r) =>
+                      {
+                          // Terminal supports directly responding with resolution
+                          var 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))
+                              {
+                                  result.Resolution = new Size (rx, ry);
+                              }
+                          }
+
+                          // Finished
+                          resultCallback.Invoke (result);
+
+                      },
+                      // Request failed, so try to compute instead
+                      ()=>TryComputeResolution (result,resultCallback));
+    }
 
 
-            result.SupportsTransparency = IsWindowsTerminal () || IsXtermWithTransparency ();
-        }
 
 
-        return result;
+    private void TryComputeResolution (SixelSupportResult result, Action<SixelSupportResult> resultCallback)
+    {
+        string windowSize;
+        string sizeInChars;
+
+        QueueRequest (EscSeqUtils.CSI_RequestWindowSizeInPixels,
+                      (r1)=>
+                      {
+                          windowSize = r1;
+
+                          QueueRequest (EscSeqUtils.CSI_ReportTerminalSizeInChars,
+                                        (r2) =>
+                                        {
+                                            sizeInChars = r2;
+                                            ComputeResolution (result,windowSize,sizeInChars);
+                                            resultCallback (result);
+
+                                        }, abandoned: () => resultCallback (result));
+                      },abandoned: ()=>resultCallback(result));
     }
     }
 
 
-
-    private bool TryGetResolutionDirectly (out Size resolution)
+    private void ComputeResolution (SixelSupportResult result, string windowSize, string sizeInChars)
     {
     {
-        // Expect something like:
-        //<esc>[6;20;10t
+        // Fallback to window size in pixels and characters
+        // Example [4;600;1200t
+        var pixelMatch = Regex.Match (windowSize, @"\[\d+;(\d+);(\d+)t$");
 
 
-        if (AnsiEscapeSequenceRequest.TryExecuteAnsiRequest (EscSeqUtils.CSI_RequestSixelResolution, out var response))
-        {
-            // Terminal supports directly responding with resolution
-            var match = Regex.Match (response.Response, @"\[\d+;(\d+);(\d+)t$");
+        // Example [8;30;120t
+        var charMatch = Regex.Match (sizeInChars, @"\[\d+;(\d+);(\d+)t$");
 
 
-            if (match.Success)
+        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)
+                &&
+
+                // Extract character dimensions
+                int.TryParse (charMatch.Groups [1].Value, out var charHeight)
+                && int.TryParse (charMatch.Groups [2].Value, out var charWidth)
+                && charWidth != 0
+                && charHeight != 0) // Avoid divide by zero
             {
             {
-                if (int.TryParse (match.Groups [1].Value, out var ry) &&
-                    int.TryParse (match.Groups [2].Value, out var rx))
-                {
-                    resolution = new Size (rx, ry);
+                // Calculate the character cell size in pixels
+                var cellWidth = (int)Math.Round ((double)pixelWidth / charWidth);
+                var cellHeight = (int)Math.Round ((double)pixelHeight / charHeight);
 
 
-                    return true;
-                }
+                // Set the resolution based on the character cell size
+                result.Resolution = new Size (cellWidth, cellHeight);
             }
             }
         }
         }
-
-        resolution = default;
-        return false;
     }
     }
 
 
+    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));
+    }
 
 
-    private bool TryComputeResolution (out Size resolution)
+    private void QueueRequest (AnsiEscapeSequenceRequest req, Action<string> responseCallback, Action abandoned)
     {
     {
-        // Fallback to window size in pixels and characters
-        if (AnsiEscapeSequenceRequest.TryExecuteAnsiRequest (EscSeqUtils.CSI_RequestWindowSizeInPixels, out var pixelSizeResponse)
-            && AnsiEscapeSequenceRequest.TryExecuteAnsiRequest (EscSeqUtils.CSI_ReportTerminalSizeInChars, out var charSizeResponse))
+        var newRequest = new AnsiEscapeSequenceRequest
         {
         {
-            // Example [4;600;1200t
-            var pixelMatch = Regex.Match (pixelSizeResponse.Response, @"\[\d+;(\d+);(\d+)t$");
+            Request = req.Request,
+            Terminator = req.Terminator,
+            ResponseReceived = responseCallback,
+            Abandoned = abandoned
+        };
 
 
-            // Example [8;30;120t
-            var charMatch = Regex.Match (charSizeResponse.Response, @"\[\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)
-                    &&
-
-                    // Extract character dimensions
-                    int.TryParse (charMatch.Groups [1].Value, out var charHeight)
-                    && int.TryParse (charMatch.Groups [2].Value, out var charWidth)
-                    && charWidth != 0
-                    && charHeight != 0) // Avoid divide by zero
-                {
-                    // Calculate the character cell size in pixels
-                    var cellWidth = (int)Math.Round ((double)pixelWidth / charWidth);
-                    var cellHeight = (int)Math.Round ((double)pixelHeight / charHeight);
-
-                    // Set the resolution based on the character cell size
-                    resolution = new Size (cellWidth, cellHeight);
-
-                    return true;
-                }
-            }
-        }
-
-        resolution = default;
-        return false;
+        Application.Driver.QueueAnsiRequest (newRequest);
     }
     }
-    private bool IsSixelSupportedByDar ()
+
+    private bool ResponseIndicatesSupport (string response)
     {
     {
-        return AnsiEscapeSequenceRequest.TryExecuteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes, out AnsiEscapeSequenceResponse darResponse)
-            ? darResponse.Response.Split (';').Contains ("4")
-            : false;
+        return response.Split (';').Contains ("4");
     }
     }
 
 
     private bool IsWindowsTerminal ()
     private bool IsWindowsTerminal ()
@@ -130,4 +158,4 @@ public class SixelSupportDetector : ISixelSupportDetector
 
 
         return false;
         return false;
     }
     }
-}*/
+}

+ 34 - 19
UICatalog/Scenarios/Images.cs

@@ -60,15 +60,15 @@ public class Images : Scenario
     private RadioGroup _rgDistanceAlgorithm;
     private RadioGroup _rgDistanceAlgorithm;
     private NumericUpDown _popularityThreshold;
     private NumericUpDown _popularityThreshold;
     private SixelToRender _sixelImage;
     private SixelToRender _sixelImage;
-    private SixelSupportResult _sixelSupportResult;
+
+    // Start by assuming no support
+    private SixelSupportResult? _sixelSupportResult = new SixelSupportResult ();
+    private CheckBox _cbSupportsSixel;
 
 
     public override void Main ()
     public override void Main ()
     {
     {
-        // TODO: Change to the one that uses Ansi Requests later
-        var sixelSupportDetector = new AssumeSupportDetector ();
-        _sixelSupportResult = sixelSupportDetector.Detect ();
-
         Application.Init ();
         Application.Init ();
+
         _win = new () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
         _win = new () { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
 
 
         bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
         bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
@@ -95,8 +95,8 @@ public class Images : Scenario
             Text = "supports true color "
             Text = "supports true color "
         };
         };
         _win.Add (cbSupportsTrueColor);
         _win.Add (cbSupportsTrueColor);
-
-        var cbSupportsSixel = new CheckBox
+        
+        _cbSupportsSixel = new CheckBox
         {
         {
             X = Pos.Right (lblDriverName) + 2,
             X = Pos.Right (lblDriverName) + 2,
             Y = 1,
             Y = 1,
@@ -108,22 +108,22 @@ public class Images : Scenario
         {
         {
 
 
             X = Pos.Right (lblDriverName) + 2,
             X = Pos.Right (lblDriverName) + 2,
-            Y = Pos.Bottom (cbSupportsSixel),
+            Y = Pos.Bottom (_cbSupportsSixel),
             Text = "(Check if your terminal supports Sixel)"
             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 ();
-                                                };
+        /*        CheckedState = _sixelSupportResult.IsSupported
+                                   ? CheckState.Checked
+                                   : CheckState.UnChecked;*/
+        _cbSupportsSixel.CheckedStateChanging += (s, e) =>
+                                                 {
+                                                     _sixelSupportResult.IsSupported = e.NewValue == CheckState.Checked;
+                                                     SetupSixelSupported (e.NewValue == CheckState.Checked);
+                                                     ApplyShowTabViewHack ();
+                                                 };
 
 
-        _win.Add (cbSupportsSixel);
+        _win.Add (_cbSupportsSixel);
 
 
         var cbUseTrueColor = new CheckBox
         var cbUseTrueColor = new CheckBox
         {
         {
@@ -150,17 +150,32 @@ public class Images : Scenario
         BuildBasicTab (tabBasic);
         BuildBasicTab (tabBasic);
         BuildSixelTab ();
         BuildSixelTab ();
 
 
-        SetupSixelSupported (cbSupportsSixel.CheckedState == CheckState.Checked);
+        SetupSixelSupported (_cbSupportsSixel.CheckedState == CheckState.Checked);
 
 
         btnOpenImage.Accepting += OpenImage;
         btnOpenImage.Accepting += OpenImage;
 
 
         _win.Add (lblSupportsSixel);
         _win.Add (lblSupportsSixel);
         _win.Add (_tabView);
         _win.Add (_tabView);
+
+        // Start trying to detect sixel support
+        var sixelSupportDetector = new SixelSupportDetector ();
+        sixelSupportDetector.Detect (UpdateSixelSupportState);
+
         Application.Run (_win);
         Application.Run (_win);
         _win.Dispose ();
         _win.Dispose ();
         Application.Shutdown ();
         Application.Shutdown ();
     }
     }
 
 
+    private void UpdateSixelSupportState (SixelSupportResult newResult)
+    {
+        _sixelSupportResult = newResult;
+
+        _cbSupportsSixel.CheckedState = newResult.IsSupported ? CheckState.Checked : CheckState.UnChecked;
+        _pxX.Value = _sixelSupportResult.Resolution.Width;
+        _pxY.Value = _sixelSupportResult.Resolution.Height;
+
+    }
+
     private void SetupSixelSupported (bool isSupported)
     private void SetupSixelSupported (bool isSupported)
     {
     {
         _tabSixel.View = isSupported ? _sixelSupported : _sixelNotSupported;
         _tabSixel.View = isSupported ? _sixelSupported : _sixelNotSupported;

+ 5 - 5
UnitTests/ConsoleDrivers/AnsiRequestSchedulerTests.cs

@@ -32,7 +32,7 @@ public class AnsiRequestSchedulerTests
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable(Times.Once);
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable(Times.Once);
 
 
         // then we should execute our request
         // then we should execute our request
-        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), false)).Verifiable (Times.Once);
+        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Once);
 
 
         // Act
         // Act
         bool result = _scheduler.SendOrSchedule (request);
         bool result = _scheduler.SendOrSchedule (request);
@@ -80,7 +80,7 @@ public class AnsiRequestSchedulerTests
 
 
         // Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
         // Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable(Times.Exactly (2));
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable(Times.Exactly (2));
-        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), false)).Verifiable (Times.Exactly (2));
+        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
 
 
         _scheduler.SendOrSchedule (request);
         _scheduler.SendOrSchedule (request);
 
 
@@ -112,7 +112,7 @@ public class AnsiRequestSchedulerTests
 
 
         // Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
         // Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2));
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2));
-        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), false)).Verifiable (Times.Exactly (2));
+        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
 
 
         _scheduler.SendOrSchedule (request);
         _scheduler.SendOrSchedule (request);
 
 
@@ -158,7 +158,7 @@ public class AnsiRequestSchedulerTests
 
 
         // Send
         // Send
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once);
         _parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once);
-        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), false)).Verifiable (Times.Exactly (2));
+        _parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
 
 
         Assert.True (_scheduler.SendOrSchedule (request1));
         Assert.True (_scheduler.SendOrSchedule (request1));
 
 
@@ -213,7 +213,7 @@ public class AnsiRequestSchedulerTests
 
 
         // 'x' is free
         // 'x' is free
         _parserMock.Setup (p => p.IsExpecting ("x")).Returns (false).Verifiable (Times.Once);
         _parserMock.Setup (p => p.IsExpecting ("x")).Returns (false).Verifiable (Times.Once);
-        _parserMock.Setup (p => p.ExpectResponse ("x", It.IsAny<Action<string>> (), false)).Verifiable (Times.Once);
+        _parserMock.Setup (p => p.ExpectResponse ("x", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Once);
 
 
         // Act
         // Act
         var a = _scheduler.SendOrSchedule (request1);
         var a = _scheduler.SendOrSchedule (request1);

+ 14 - 13
UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs

@@ -30,8 +30,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         int i = 0;
         int i = 0;
 
 
         // Imagine that we are expecting a DAR
         // Imagine that we are expecting a DAR
-        _parser1.ExpectResponse ("c",(s)=> response1 = s, false);
-        _parser2.ExpectResponse ("c", (s) => response2 = s , false);
+        _parser1.ExpectResponse ("c",(s)=> response1 = s,null, false);
+        _parser2.ExpectResponse ("c", (s) => response2 = s , null, false);
 
 
         // First char is Escape which we must consume incase what follows is the DAR
         // First char is Escape which we must consume incase what follows is the DAR
         AssertConsumed (ansiStream, ref i); // Esc
         AssertConsumed (ansiStream, ref i); // Esc
@@ -121,8 +121,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
             string response2 = string.Empty;
             string response2 = string.Empty;
 
 
             // Register the expected response with the given terminator
             // Register the expected response with the given terminator
-            _parser1.ExpectResponse (expectedTerminator, s => response1 = s, false);
-            _parser2.ExpectResponse (expectedTerminator, s => response2 = s, false);
+            _parser1.ExpectResponse (expectedTerminator, s => response1 = s, null, false);
+            _parser2.ExpectResponse (expectedTerminator, s => response2 = s, null, false);
 
 
             // Process the input
             // Process the input
             StringBuilder actualOutput1 = new StringBuilder ();
             StringBuilder actualOutput1 = new StringBuilder ();
@@ -228,7 +228,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
 
 
         if (terminator.HasValue)
         if (terminator.HasValue)
         {
         {
-            parser.ExpectResponse (terminator.Value.ToString (),(s)=> response = s, false);
+            parser.ExpectResponse (terminator.Value.ToString (),(s)=> response = s,null, false);
         }
         }
         foreach (var state in expectedStates)
         foreach (var state in expectedStates)
         {
         {
@@ -329,13 +329,13 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         string? responseA = null;
         string? responseA = null;
         string? responseB = null;
         string? responseB = null;
 
 
-        p.ExpectResponse ("z",(r)=>responseA=r, false);
+        p.ExpectResponse ("z",(r)=>responseA=r, null, false);
 
 
         // Some time goes by without us seeing a response
         // Some time goes by without us seeing a response
         p.StopExpecting ("z", false);
         p.StopExpecting ("z", false);
 
 
         // Send our new request
         // Send our new request
-        p.ExpectResponse ("z", (r) => responseB = r, false);
+        p.ExpectResponse ("z", (r) => responseB = r, null, false);
 
 
         // Because we gave up on getting A, we should expect the response to be to our new request
         // Because we gave up on getting A, we should expect the response to be to our new request
         Assert.Empty(p.ProcessInput ("\u001b[<1;2z"));
         Assert.Empty(p.ProcessInput ("\u001b[<1;2z"));
@@ -362,8 +362,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         int m = 0;
         int m = 0;
         int M = 1;
         int M = 1;
 
 
-        p.ExpectResponse ("m", _ => m++, true);
-        p.ExpectResponse ("M", _ => M++, true);
+        p.ExpectResponse ("m", _ => m++, null, true);
+        p.ExpectResponse ("M", _ => M++, null, true);
 
 
         // Act - Feed input strings containing ANSI sequences
         // Act - Feed input strings containing ANSI sequences
         p.ProcessInput ("\u001b[<0;10;10m");  // Should match and increment `m`
         p.ProcessInput ("\u001b[<0;10;10m");  // Should match and increment `m`
@@ -387,10 +387,11 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
         var result = new List<Tuple<char,int>> ();
         var result = new List<Tuple<char,int>> ();
 
 
         p.ExpectResponseT ("m", (r) =>
         p.ExpectResponseT ("m", (r) =>
-                               {
-                                   result = r.ToList ();
-                                   m++;
-                               }, true);
+                                {
+                                    result = r.ToList ();
+                                    m++;
+                                },
+                           null, true);
 
 
         // Act - Feed input strings containing ANSI sequences
         // Act - Feed input strings containing ANSI sequences
         p.ProcessInput (StringToBatch("\u001b[<0;10;10m"));  // Should match and increment `m`
         p.ProcessInput (StringToBatch("\u001b[<0;10;10m"));  // Should match and increment `m`