mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
Add support for sending other types of requests
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -627,7 +627,7 @@ public abstract class ConsoleDriver
|
||||
GetRequestScheduler ().SendOrSchedule (request);
|
||||
}
|
||||
|
||||
protected abstract IAnsiResponseParser GetParser ();
|
||||
public abstract IAnsiResponseParser GetParser ();
|
||||
|
||||
internal AnsiRequestScheduler GetRequestScheduler ()
|
||||
{
|
||||
|
||||
@@ -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 ()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user