Add support for sending other types of requests

This commit is contained in:
tznind
2024-11-02 08:38:02 +00:00
parent 0275f9aed8
commit 395bee419a
11 changed files with 249 additions and 51 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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