Pārlūkot izejas kodu

Add support for sending other types of requests

tznind 9 mēneši atpakaļ
vecāks
revīzija
395bee419a

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

@@ -48,4 +48,15 @@ public class AnsiEscapeSequenceRequest
     ///     sending many requests.
     /// </summary>
     public void Send () { Application.Driver?.RawWrite (Request); }
+
+
+    /// <summary>
+    ///     The value expected in the response e.g.
+    ///     <see>
+    ///         <cref>EscSeqUtils.CSI_ReportTerminalSizeInChars.Value</cref>
+    ///     </see>
+    ///     which will have a 't' as terminator but also other different request may return the same terminator with a
+    ///     different value.
+    /// </summary>
+    public string? Value { get; init; }
 }

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

@@ -50,7 +50,7 @@ internal class AnsiRequestScheduler
     ///     to respond. Only affects when we try to send a new request with the same terminator - at which point
     ///     we tell the parser to stop expecting the old request and start expecting the new request.
     /// </summary>
-    private readonly TimeSpan _staleTimeout = TimeSpan.FromSeconds (5);
+    private readonly TimeSpan _staleTimeout = TimeSpan.FromSeconds (1);
 
     private readonly DateTime _lastRun;
 

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

@@ -26,7 +26,10 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 
     private AnsiResponseParserState _state = AnsiResponseParserState.Normal;
 
-    // Current state of the parser
+    /// <inheritdoc />
+    public event EventHandler<EventArgs<string>>? StoppedExpecting;
+
+    /// <inheritdoc />
     public AnsiResponseParserState State
     {
         get => _state;
@@ -306,6 +309,8 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     /// <inheritdoc/>
     public void StopExpecting (string terminator, bool persistent)
     {
+        StoppedExpecting?.Invoke (this, new (terminator));
+
         lock (lockExpectedResponses)
         {
             if (persistent)

+ 5 - 0
Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs

@@ -7,6 +7,11 @@ namespace Terminal.Gui;
 /// </summary>
 public interface IAnsiResponseParser
 {
+    /// <summary>
+    /// Called when parser is told to <see cref="StopExpecting"/> a response for a given request
+    /// </summary>
+    public event EventHandler<EventArgs<string>> StoppedExpecting;
+
     /// <summary>
     ///     Current state of the parser based on what sequence of characters it has
     ///     read from the console input stream.

+ 1 - 1
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -627,7 +627,7 @@ public abstract class ConsoleDriver
         GetRequestScheduler ().SendOrSchedule (request);
     }
 
-    protected abstract IAnsiResponseParser GetParser ();
+    public abstract IAnsiResponseParser GetParser ();
 
     internal AnsiRequestScheduler GetRequestScheduler ()
     {

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

@@ -592,7 +592,7 @@ internal class CursesDriver : ConsoleDriver
 
     private readonly AnsiResponseParser _parser = new ();
     /// <inheritdoc />
-    protected override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     internal void ProcessInput ()
     {

+ 5 - 4
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -1329,7 +1329,7 @@ public static class EscSeqUtils
     ///     ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR)
     ///     https://terminalguide.namepad.de/seq/csi_sn__p-6/
     /// </summary>
-    public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n";
+    public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" };
 
     /// <summary>
     ///     The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
@@ -1354,13 +1354,13 @@ public static class EscSeqUtils
     ///     32 = Text macros
     ///     42 = ISO Latin-2 character set
     /// </summary>
-    public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
+    public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" };
 
     /// <summary>
     ///     ESC [ > 0 c - Send Device Attributes (Secondary DA)
     ///     Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220)
     /// </summary>
-    public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c";
+    public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" };
 
     /// <summary>
     ///     The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
@@ -1385,7 +1385,8 @@ public static class EscSeqUtils
     ///     CSI 1 8 t  | yes | yes |  yes  | report window size in chars
     ///     https://terminalguide.namepad.de/seq/csi_st-18/
     /// </summary>
-    public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";
+    public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" };
+
 
     /// <summary>
     ///     The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t

+ 1 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -395,7 +395,7 @@ public class FakeDriver : ConsoleDriver
     private AnsiResponseParser _parser = new ();
 
     /// <inheritdoc />
-    protected override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     /// <inheritdoc />
     internal override void RawWrite (string str) { }

+ 1 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -994,7 +994,7 @@ internal class NetDriver : ConsoleDriver
     }
 
     /// <inheritdoc />
-    protected override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
+    public override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
 
     /// <inheritdoc />
     internal override void RawWrite (string str)

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

@@ -1180,7 +1180,7 @@ internal class WindowsDriver : ConsoleDriver
     }
 
     /// <inheritdoc />
-    protected override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     /// <inheritdoc />
     internal override void RawWrite (string str) => WinConsole?.WriteANSI (str);

+ 216 - 40
UICatalog/Scenarios/AnsiRequestsScenario.cs

@@ -2,42 +2,221 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
-using ColorHelper;
 using Terminal.Gui;
+using static System.Runtime.InteropServices.JavaScript.JSType;
 
 namespace UICatalog.Scenarios;
 
-
-
-[ScenarioMetadata ("Ansi Requests", "Demonstration of how to send ansi requests.")]
-[ScenarioCategory ("Colors")]
-[ScenarioCategory ("Drawing")]
-public class AnsiRequestsScenario : Scenario
+[ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")]
+[ScenarioCategory ("Ansi Escape Sequence")]
+public sealed class AnsiEscapeSequenceRequests : Scenario
 {
     private GraphView _graphView;
-    private Window _win;
 
     private DateTime start = DateTime.Now;
     private ScatterSeries _sentSeries;
     private ScatterSeries _answeredSeries;
 
-    private List<DateTime> sends = new  ();
+    private List<DateTime> sends = new ();
 
     private object lockAnswers = new object ();
-    private Dictionary<DateTime,string> answers = new ();
+    private Dictionary<DateTime, string> answers = new ();
     private Label _lblSummary;
 
     public override void Main ()
     {
+        // Init
         Application.Init ();
 
-        _win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
+        TabView tv = new TabView
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+
+        Tab single = new Tab ();
+        single.DisplayText = "Single";
+        single.View = BuildSingleTab ();
+
+        Tab bulk = new ();
+        bulk.DisplayText = "Multi";
+        bulk.View = BuildBulkTab ();
+
+        tv.AddTab (single, true);
+        tv.AddTab (bulk, false);
+
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = GetQuitKeyAndName (),
+        };
+
+        appWindow.Add (tv);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        bulk.View.Dispose ();
+        single.View.Dispose ();
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+    }
+
+    private View BuildSingleTab ()
+    {
+        View w = new View ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
+
+        w.Padding.Thickness = new (1);
+
+        var scrRequests = new List<string>
+        {
+            "CSI_SendDeviceAttributes",
+            "CSI_ReportTerminalSizeInChars",
+            "CSI_RequestCursorPositionReport",
+            "CSI_SendDeviceAttributes2"
+        };
+
+        var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper<string> (new (scrRequests)) };
+        w.Add (cbRequests);
+
+        var label = new Label { Y = Pos.Bottom (cbRequests) + 1, Text = "Request:" };
+        var tfRequest = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 20 };
+        w.Add (label, tfRequest);
+
+        label = new Label { X = Pos.Right (tfRequest) + 1, Y = Pos.Top (tfRequest) - 1, Text = "Value:" };
+        var tfValue = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6 };
+        w.Add (label, tfValue);
+
+        label = new Label { X = Pos.Right (tfValue) + 1, Y = Pos.Top (tfValue) - 1, Text = "Terminator:" };
+        var tfTerminator = new TextField { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4 };
+        w.Add (label, tfTerminator);
+
+        cbRequests.SelectedItemChanged += (s, e) =>
+                                          {
+                                              if (cbRequests.SelectedItem == -1)
+                                              {
+                                                  return;
+                                              }
+
+                                              var selAnsiEscapeSequenceRequestName = scrRequests [cbRequests.SelectedItem];
+                                              AnsiEscapeSequenceRequest selAnsiEscapeSequenceRequest = null;
+
+                                              switch (selAnsiEscapeSequenceRequestName)
+                                              {
+                                                  case "CSI_SendDeviceAttributes":
+                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes;
+
+                                                      break;
+                                                  case "CSI_ReportTerminalSizeInChars":
+                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_ReportTerminalSizeInChars;
+
+                                                      break;
+                                                  case "CSI_RequestCursorPositionReport":
+                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_RequestCursorPositionReport;
+
+                                                      break;
+                                                  case "CSI_SendDeviceAttributes2":
+                                                      selAnsiEscapeSequenceRequest = EscSeqUtils.CSI_SendDeviceAttributes2;
+
+                                                      break;
+                                              }
+
+                                              tfRequest.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Request : "";
+                                              tfValue.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Value ?? "" : "";
+                                              tfTerminator.Text = selAnsiEscapeSequenceRequest is { } ? selAnsiEscapeSequenceRequest.Terminator : "";
+                                          };
+
+        // Forces raise cbRequests.SelectedItemChanged to update TextFields
+        cbRequests.SelectedItem = 0;
+
+        label = new Label { Y = Pos.Bottom (tfRequest) + 2, Text = "Response:" };
+        var tvResponse = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
+        w.Add (label, tvResponse);
+
+        label = new Label { X = Pos.Right (tvResponse) + 1, Y = Pos.Top (tvResponse) - 1, Text = "Error:" };
+        var tvError = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 40, Height = 4, ReadOnly = true };
+        w.Add (label, tvError);
+
+        label = new Label { X = Pos.Right (tvError) + 1, Y = Pos.Top (tvError) - 1, Text = "Value:" };
+        var tvValue = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 6, Height = 4, ReadOnly = true };
+        w.Add (label, tvValue);
+
+        label = new Label { X = Pos.Right (tvValue) + 1, Y = Pos.Top (tvValue) - 1, Text = "Terminator:" };
+        var tvTerminator = new TextView { X = Pos.Left (label), Y = Pos.Bottom (label), Width = 4, Height = 4, ReadOnly = true };
+        w.Add (label, tvTerminator);
+
+        var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request", IsDefault = true };
+
+        var lblSuccess = new Label { X = Pos.Center (), Y = Pos.Bottom (btnResponse) + 1 };
+        w.Add (lblSuccess);
+
+        Application.Driver.GetParser ().StoppedExpecting += (s,e)=>OnFail(e.CurrentValue,tvResponse,tvError,tvValue,tvTerminator, lblSuccess);
+
+        btnResponse.Accepting += (s, e) =>
+                                 {
+                                     var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest
+                                     {
+                                         Request = tfRequest.Text,
+                                         Terminator = tfTerminator.Text,
+                                         Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
+                                     };
+
+                                     Application.Driver.QueueAnsiRequest (
+                                                                          new ()
+                                                                          {
+                                                                              Request = ansiEscapeSequenceRequest.Request,
+                                                                              Terminator = ansiEscapeSequenceRequest.Terminator,
+                                                                              ResponseReceived = (s)=>OnSuccess(s, tvResponse, tvError, tvValue, tvTerminator,lblSuccess)
+                                                                          });
+                                 };
+
+        w.Add (btnResponse);
+
+        w.Add (new Label { Y = Pos.Bottom (lblSuccess) + 2, Text = "You can send other requests by editing the TextFields." });
+
+        return w;
+    }
+
+    private void OnSuccess (string response, TextView tvResponse, TextView tvError, TextView tvValue, TextView tvTerminator,Label lblSuccess)
+    {
+        tvResponse.Text = response;
+        tvError.Text = string.Empty;
+        tvValue.Text = string.Empty;
+        tvTerminator.Text = string.Empty;
+
+            lblSuccess.ColorScheme = Colors.ColorSchemes ["Base"];
+            lblSuccess.Text = "Successful";
+    }
+
+    private void OnFail (string terminator, TextView tvResponse, TextView tvError, TextView tvValue, TextView tvTerminator, Label lblSuccess)
+    {
+        tvResponse.Text = string.Empty;
+        tvError.Text = "No Response";
+        tvValue.Text = string.Empty;
+        tvTerminator.Text = terminator;
+
+        lblSuccess.ColorScheme = Colors.ColorSchemes ["Error"];
+        lblSuccess.Text = "Error";
+    }
+
+    private View BuildBulkTab ()
+    {
+        View w = new View ()
+        {
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        };
 
         var lbl = new Label ()
         {
             Text = "This scenario tests Ansi request/response processing. Use the TextView to ensure regular user interaction continues as normal during sends",
             Height = 2,
-            Width = Dim.Fill()
+            Width = Dim.Fill ()
         };
 
         Application.AddTimeout (
@@ -60,7 +239,7 @@ public class AnsiRequestsScenario : Scenario
         {
             Y = Pos.Bottom (lbl),
             Width = Dim.Percent (50),
-            Height = Dim.Fill()
+            Height = Dim.Fill ()
         };
 
 
@@ -78,13 +257,13 @@ public class AnsiRequestsScenario : Scenario
         };
 
         cbDar.ValueChanging += (s, e) =>
-                               {
-                                   if (e.NewValue < 0 || e.NewValue > 20)
-                                   {
-                                       e.Cancel = true;
-                                   }
-                               };
-        _win.Add (cbDar);
+        {
+            if (e.NewValue < 0 || e.NewValue > 20)
+            {
+                e.Cancel = true;
+            }
+        };
+        w.Add (cbDar);
 
         int lastSendTime = Environment.TickCount;
         object lockObj = new object ();
@@ -116,35 +295,32 @@ public class AnsiRequestsScenario : Scenario
         {
             Y = Pos.Bottom (cbDar),
             X = Pos.Right (tv),
-            Width = Dim.Fill(),
-            Height = Dim.Fill(1)
+            Width = Dim.Fill (),
+            Height = Dim.Fill (1)
         };
 
         _lblSummary = new Label ()
         {
             Y = Pos.Bottom (_graphView),
             X = Pos.Right (tv),
-            Width = Dim.Fill()
+            Width = Dim.Fill ()
         };
 
         SetupGraph ();
 
-        _win.Add (lbl);
-        _win.Add (lblDar);
-        _win.Add (cbDar);
-        _win.Add (tv);
-        _win.Add (_graphView);
-        _win.Add (_lblSummary);
+        w.Add (lbl);
+        w.Add (lblDar);
+        w.Add (cbDar);
+        w.Add (tv);
+        w.Add (_graphView);
+        w.Add (_lblSummary);
 
-        Application.Run (_win);
-        _win.Dispose ();
-        Application.Shutdown ();
+        return w;
     }
-    
     private void UpdateResponses ()
     {
         _lblSummary.Text = GetSummary ();
-        _lblSummary.SetNeedsDisplay();
+        _lblSummary.SetNeedsDisplay ();
     }
 
     private string GetSummary ()
@@ -157,8 +333,8 @@ public class AnsiRequestsScenario : Scenario
         var last = answers.Last ().Value;
 
         var unique = answers.Values.Distinct ().Count ();
-        var total = answers.Count; 
-        
+        var total = answers.Count;
+
         return $"Last:{last} U:{unique} T:{total}";
     }
 
@@ -193,7 +369,7 @@ public class AnsiRequestsScenario : Scenario
                                         .Select (g => new PointF (g.Key, g.Count ()))
                                         .ToList ();
         //  _graphView.ScrollOffset  = new PointF(,0);
-        _graphView.SetNeedsDisplay();
+        _graphView.SetNeedsDisplay ();
 
     }
 
@@ -207,7 +383,7 @@ public class AnsiRequestsScenario : Scenario
         Application.Driver.QueueAnsiRequest (
                                              new ()
                                              {
-                                                 Request = EscSeqUtils.CSI_SendDeviceAttributes,
+                                                 Request = EscSeqUtils.CSI_SendDeviceAttributes.Request,
                                                  Terminator = EscSeqUtils.CSI_ReportDeviceAttributes_Terminator,
                                                  ResponseReceived = HandleResponse
                                              });
@@ -218,7 +394,7 @@ public class AnsiRequestsScenario : Scenario
     {
         lock (lockAnswers)
         {
-            answers.Add (DateTime.Now,response);
+            answers.Add (DateTime.Now, response);
         }
     }
-}
+}