diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
new file mode 100644
index 0000000000..d6a5d3470b
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
@@ -0,0 +1,186 @@
+#nullable enable
+namespace Terminal.Gui;
+
+///
+/// Describes an ongoing ANSI request sent to the console.
+/// Use to handle the response
+/// when console answers the request.
+///
+public class AnsiEscapeSequenceRequest
+{
+ ///
+ /// Execute an ANSI escape sequence escape which may return a response or error.
+ ///
+ /// The ANSI escape sequence to request.
+ /// A with the response, error, terminator and value.
+ public static AnsiEscapeSequenceResponse ExecuteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest)
+ {
+ var response = new StringBuilder ();
+ var error = new StringBuilder ();
+ var savedIsReportingMouseMoves = false;
+ NetDriver? netDriver = null;
+
+ try
+ {
+ switch (Application.Driver)
+ {
+ case NetDriver:
+ netDriver = Application.Driver as NetDriver;
+ savedIsReportingMouseMoves = netDriver!.IsReportingMouseMoves;
+
+ if (savedIsReportingMouseMoves)
+ {
+ netDriver.StopReportingMouseMoves ();
+ }
+
+ while (Console.KeyAvailable)
+ {
+ netDriver._mainLoopDriver._netEvents._waitForStart.Set ();
+ netDriver._mainLoopDriver._netEvents._waitForStart.Reset ();
+
+ netDriver._mainLoopDriver._netEvents._forceRead = true;
+ }
+
+ netDriver._mainLoopDriver._netEvents._forceRead = false;
+
+ break;
+ case CursesDriver cursesDriver:
+ savedIsReportingMouseMoves = cursesDriver.IsReportingMouseMoves;
+
+ if (savedIsReportingMouseMoves)
+ {
+ cursesDriver.StopReportingMouseMoves ();
+ }
+
+ break;
+ }
+
+ if (netDriver is { })
+ {
+ NetEvents._suspendRead = true;
+ }
+ else
+ {
+ Thread.Sleep (100); // Allow time for mouse stopping and to flush the input buffer
+
+ // Flush the input buffer to avoid reading stale input
+ while (Console.KeyAvailable)
+ {
+ Console.ReadKey (true);
+ }
+ }
+
+ // Send the ANSI escape sequence
+ Console.Write (ansiRequest.Request);
+ Console.Out.Flush (); // Ensure the request is sent
+
+ // Read the response from stdin (response should come back as input)
+ Thread.Sleep (100); // Allow time for the terminal to respond
+
+ // Read input until no more characters are available or the terminator is encountered
+ while (Console.KeyAvailable)
+ {
+ // Peek the next key
+ ConsoleKeyInfo keyInfo = Console.ReadKey (true); // true to not display on the console
+
+ // Append the current key to the response
+ response.Append (keyInfo.KeyChar);
+
+ if (keyInfo.KeyChar == ansiRequest.Terminator [^1]) // Check if the key is terminator (ANSI escape sequence ends)
+ {
+ // Break out of the loop when terminator is found
+ break;
+ }
+ }
+
+ if (!response.ToString ().EndsWith (ansiRequest.Terminator [^1]))
+ {
+ throw new InvalidOperationException ($"Terminator doesn't ends with: '{ansiRequest.Terminator [^1]}'");
+ }
+ }
+ catch (Exception ex)
+ {
+ error.AppendLine ($"Error executing ANSI request: {ex.Message}");
+ }
+ finally
+ {
+ if (savedIsReportingMouseMoves)
+ {
+ switch (Application.Driver)
+ {
+ case NetDriver:
+ NetEvents._suspendRead = false;
+ netDriver!.StartReportingMouseMoves ();
+
+ break;
+ case CursesDriver cursesDriver:
+ cursesDriver.StartReportingMouseMoves ();
+
+ break;
+ }
+ }
+ }
+
+ var values = new string? [] { null };
+
+ if (string.IsNullOrEmpty (error.ToString ()))
+ {
+ (string? c1Control, string? code, values, string? terminator) = EscSeqUtils.GetEscapeResult (response.ToString ().ToCharArray ());
+ }
+
+ AnsiEscapeSequenceResponse ansiResponse = new ()
+ {
+ Response = response.ToString (), Error = error.ToString (),
+ Terminator = string.IsNullOrEmpty (response.ToString ()) ? "" : response.ToString () [^1].ToString (), Value = values [0]
+ };
+
+ // Invoke the event if it's subscribed
+ ansiRequest.ResponseReceived?.Invoke (ansiRequest, ansiResponse);
+
+ return ansiResponse;
+ }
+
+ ///
+ /// Request to send e.g. see
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Request
+ ///
+ ///
+ public required string Request { get; init; }
+
+ ///
+ /// Invoked when the console responds with an ANSI response code that matches the
+ ///
+ ///
+ public event EventHandler? ResponseReceived;
+
+ ///
+ ///
+ /// The terminator that uniquely identifies the type of response as responded
+ /// by the console. e.g. for
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Request
+ ///
+ /// the terminator is
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator
+ ///
+ /// .
+ ///
+ ///
+ /// After sending a request, the first response with matching terminator will be matched
+ /// to the oldest outstanding request.
+ ///
+ ///
+ public required string Terminator { get; init; }
+
+ ///
+ /// The value expected in the response e.g.
+ ///
+ /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value
+ ///
+ /// which will have a 't' as terminator but also other different request may return the same terminator with a
+ /// different value.
+ ///
+ public string? Value { get; init; }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs
new file mode 100644
index 0000000000..df98511558
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceResponse.cs
@@ -0,0 +1,53 @@
+#nullable enable
+namespace Terminal.Gui;
+
+///
+/// Describes a finished ANSI received from the console.
+///
+public class AnsiEscapeSequenceResponse
+{
+ ///
+ /// Error received from e.g. see
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Request
+ ///
+ ///
+ public required string Error { get; init; }
+
+ ///
+ /// Response received from e.g. see
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Request
+ ///
+ /// .
+ ///
+ public required string Response { get; init; }
+
+ ///
+ ///
+ /// The terminator that uniquely identifies the type of response as responded
+ /// by the console. e.g. for
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Request
+ ///
+ /// the terminator is
+ ///
+ /// EscSeqUtils.CSI_SendDeviceAttributes.Terminator
+ ///
+ ///
+ ///
+ /// The received terminator must match to the terminator sent by the request.
+ ///
+ ///
+ public required string Terminator { get; init; }
+
+ ///
+ /// The value expected in the response e.g.
+ ///
+ /// EscSeqUtils.CSI_ReportTerminalSizeInChars.Value
+ ///
+ /// which will have a 't' as terminator but also other different request may return the same terminator with a
+ /// different value.
+ ///
+ public string? Value { get; init; }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 99e560044d..a48881d4b1 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -177,11 +177,15 @@ public override bool SetCursorVisibility (CursorVisibility visibility)
return true;
}
+ public bool IsReportingMouseMoves { get; private set; }
+
public void StartReportingMouseMoves ()
{
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+
+ IsReportingMouseMoves = true;
}
}
@@ -190,6 +194,8 @@ public void StopReportingMouseMoves ()
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+ IsReportingMouseMoves = false;
}
}
diff --git a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
index 1eb63e34aa..d32a8e01fe 100644
--- a/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
+++ b/Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
@@ -1316,13 +1316,9 @@ public enum DECSCUSR_Style
///
/// ESC [ ? 6 n - Request Cursor Position Report (?) (DECXCPR)
/// https://terminalguide.namepad.de/seq/csi_sn__p-6/
+ /// The terminal reply to . ESC [ ? (y) ; (x) ; 1 R
///
- public static readonly string CSI_RequestCursorPositionReport = CSI + "?6n";
-
- ///
- /// The terminal reply to . ESC [ ? (y) ; (x) R
- ///
- public const string CSI_RequestCursorPositionReport_Terminator = "R";
+ public static readonly AnsiEscapeSequenceRequest CSI_RequestCursorPositionReport = new () { Request = CSI + "?6n", Terminator = "R" };
///
/// ESC [ 0 c - Send Device Attributes (Primary DA)
@@ -1341,37 +1337,25 @@ public enum DECSCUSR_Style
/// 28 = Rectangular area operations
/// 32 = Text macros
/// 42 = ISO Latin-2 character set
+ /// The terminator indicating a reply to or
+ ///
///
- public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
+ public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes = new () { Request = CSI + "0c", Terminator = "c" };
///
/// ESC [ > 0 c - Send Device Attributes (Secondary DA)
/// Windows Terminal v1.18+ emits: "\x1b[>0;10;1c" (vt100, firmware version 1.0, vt220)
- ///
- public static readonly string CSI_SendDeviceAttributes2 = CSI + ">0c";
-
- ///
/// The terminator indicating a reply to or
///
///
- public const string CSI_ReportDeviceAttributes_Terminator = "c";
+ public static readonly AnsiEscapeSequenceRequest CSI_SendDeviceAttributes2 = new () { Request = CSI + ">0c", Terminator = "c" };
///
/// CSI 1 8 t | yes | yes | yes | report window size in chars
/// https://terminalguide.namepad.de/seq/csi_st-18/
- ///
- public static readonly string CSI_ReportTerminalSizeInChars = CSI + "18t";
-
- ///
/// The terminator indicating a reply to : ESC [ 8 ; height ; width t
///
- public const string CSI_ReportTerminalSizeInChars_Terminator = "t";
-
- ///
- /// The value of the response to indicating value 1 and 2 are the terminal
- /// size in chars.
- ///
- public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
+ public static readonly AnsiEscapeSequenceRequest CSI_ReportTerminalSizeInChars = new () { Request = CSI + "18t", Terminator = "t", Value = "8" };
#endregion
}
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index b5729406ce..9097b37309 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -136,7 +136,7 @@ internal class NetEvents : IDisposable
{
private readonly ManualResetEventSlim _inputReady = new (false);
private CancellationTokenSource _inputReadyCancellationTokenSource;
- private readonly ManualResetEventSlim _waitForStart = new (false);
+ internal readonly ManualResetEventSlim _waitForStart = new (false);
//CancellationTokenSource _waitForStartCancellationTokenSource;
private readonly ManualResetEventSlim _winChange = new (false);
@@ -202,7 +202,7 @@ private static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellation
{
// if there is a key available, return it without waiting
// (or dispatching work to the thread queue)
- if (Console.KeyAvailable)
+ if (Console.KeyAvailable && !_suspendRead)
{
return Console.ReadKey (intercept);
}
@@ -211,7 +211,7 @@ private static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellation
{
Task.Delay (100, cancellationToken).Wait (cancellationToken);
- if (Console.KeyAvailable)
+ if (Console.KeyAvailable && !_suspendRead)
{
return Console.ReadKey (intercept);
}
@@ -222,6 +222,9 @@ private static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellation
return default (ConsoleKeyInfo);
}
+ internal bool _forceRead;
+ internal static bool _suspendRead;
+
private void ProcessInputQueue ()
{
while (_inputReadyCancellationTokenSource is { IsCancellationRequested: false })
@@ -237,7 +240,7 @@ private void ProcessInputQueue ()
_waitForStart.Reset ();
- if (_inputQueue.Count == 0)
+ if (_inputQueue.Count == 0 || _forceRead)
{
ConsoleKey key = 0;
ConsoleModifiers mod = 0;
@@ -591,55 +594,48 @@ private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
{
- switch (terminating)
- {
- // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
- case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
- var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
-
- if (_lastCursorPosition.Y != point.Y)
- {
- _lastCursorPosition = point;
- var eventType = EventType.WindowPosition;
- var winPositionEv = new WindowPositionEvent { CursorPosition = point };
-
- _inputQueue.Enqueue (
- new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
- );
- }
- else
- {
- return;
- }
-
- break;
+ if (terminating ==
- case EscSeqUtils.CSI_ReportTerminalSizeInChars_Terminator:
- switch (values [0])
- {
- case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue:
- EnqueueWindowSizeEvent (
- Math.Max (int.Parse (values [1]), 0),
- Math.Max (int.Parse (values [2]), 0),
- Math.Max (int.Parse (values [1]), 0),
- Math.Max (int.Parse (values [2]), 0)
- );
-
- break;
- default:
- EnqueueRequestResponseEvent (c1Control, code, values, terminating);
+ // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
+ // The observation is correct because the response isn't immediate and this is useless
+ EscSeqUtils.CSI_RequestCursorPositionReport.Terminator)
+ {
+ var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
- break;
- }
+ if (_lastCursorPosition.Y != point.Y)
+ {
+ _lastCursorPosition = point;
+ var eventType = EventType.WindowPosition;
+ var winPositionEv = new WindowPositionEvent { CursorPosition = point };
- break;
- case EscSeqUtils.CSI_ReportDeviceAttributes_Terminator:
- ConsoleDriver.SupportsSixel = values.Any (v => v == "4");
- break;
- default:
+ _inputQueue.Enqueue (
+ new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
+ );
+ }
+ else
+ {
+ return;
+ }
+ }
+ else if (terminating == EscSeqUtils.CSI_ReportTerminalSizeInChars.Terminator)
+ {
+ if (values [0] == EscSeqUtils.CSI_ReportTerminalSizeInChars.Value)
+ {
+ EnqueueWindowSizeEvent (
+ Math.Max (int.Parse (values [1]), 0),
+ Math.Max (int.Parse (values [2]), 0),
+ Math.Max (int.Parse (values [1]), 0),
+ Math.Max (int.Parse (values [2]), 0)
+ );
+ }
+ else
+ {
EnqueueRequestResponseEvent (c1Control, code, values, terminating);
-
- break;
+ }
+ }
+ else
+ {
+ EnqueueRequestResponseEvent (c1Control, code, values, terminating);
}
_inputReady.Set ();
@@ -819,7 +815,7 @@ internal class NetDriver : ConsoleDriver
private const int COLOR_RED = 31;
private const int COLOR_WHITE = 37;
private const int COLOR_YELLOW = 33;
- private NetMainLoop _mainLoopDriver;
+ internal NetMainLoop _mainLoopDriver;
public bool IsWinPlatform { get; private set; }
public NetWinVTConsole NetWinConsole { get; private set; }
@@ -1141,13 +1137,9 @@ internal override MainLoop Init ()
_mainLoopDriver = new NetMainLoop (this);
_mainLoopDriver.ProcessInput = ProcessInput;
- _mainLoopDriver._netEvents.EscSeqRequests.Add ("c");
- // Determine if sixel is supported
- Console.Out.Write (EscSeqUtils.CSI_SendDeviceAttributes);
-
return new MainLoop (_mainLoopDriver);
}
-
+
private void ProcessInput (InputResult inputEvent)
{
switch (inputEvent.EventType)
@@ -1348,7 +1340,6 @@ private bool SetCursorPosition (int col, int row)
}
private CursorVisibility? _cachedCursorVisibility;
- private static bool _supportsSixel;
public override void UpdateCursor ()
{
@@ -1397,11 +1388,15 @@ public override bool EnsureCursorVisibility ()
#region Mouse Handling
+ public bool IsReportingMouseMoves { get; private set; }
+
public void StartReportingMouseMoves ()
{
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+
+ IsReportingMouseMoves = true;
}
}
@@ -1410,6 +1405,8 @@ public void StopReportingMouseMoves ()
if (!RunningUnitTests)
{
Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+
+ IsReportingMouseMoves = false;
}
}
@@ -1769,7 +1766,13 @@ void IMainLoopDriver.Iteration ()
{
while (_resultQueue.Count > 0)
{
- ProcessInput?.Invoke (_resultQueue.Dequeue ().Value);
+ // Always dequeue even if it's null and invoke if isn't null
+ InputResult? dequeueResult = _resultQueue.Dequeue ();
+
+ if (dequeueResult is { })
+ {
+ ProcessInput?.Invoke (dequeueResult.Value);
+ }
}
}
@@ -1825,10 +1828,16 @@ private void NetInputHandler ()
_resultQueue.Enqueue (_netEvents.DequeueInput ());
}
- while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
+ try
{
- _resultQueue.Dequeue ();
+ while (_resultQueue.Count > 0 && _resultQueue.Peek () is null)
+ {
+ // Dequeue null values
+ _resultQueue.Dequeue ();
+ }
}
+ catch (InvalidOperationException) // Peek can raise an exception
+ { }
if (_resultQueue.Count > 0)
{
diff --git a/Terminal.Gui/Views/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/AutocompleteFilepathContext.cs
index f577e554fe..96ce1dfaef 100644
--- a/Terminal.Gui/Views/AutocompleteFilepathContext.cs
+++ b/Terminal.Gui/Views/AutocompleteFilepathContext.cs
@@ -6,7 +6,7 @@ namespace Terminal.Gui;
internal class AutocompleteFilepathContext : AutocompleteContext
{
public AutocompleteFilepathContext (string currentLine, int cursorPosition, FileDialogState state)
- : base (TextModel.ToRuneCellList (currentLine), cursorPosition)
+ : base (RuneCell.ToRuneCellList (currentLine), cursorPosition)
{
State = state;
}
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index 64e3d0482e..aa4322e2e0 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -546,7 +546,7 @@ public string SelectedText
if (!Secret && !_historyText.IsFromHistory)
{
_historyText.Add (
- new List> { TextModel.ToRuneCellList (oldText) },
+ new List> { RuneCell.ToRuneCellList (oldText) },
new Point (_cursorPosition, 0)
);
@@ -952,7 +952,7 @@ public override void OnDrawContent (Rectangle viewport)
int p = ScrollOffset;
var col = 0;
- int width = Frame.Width + OffSetBackground ();
+ int width = Viewport.Width + OffSetBackground ();
int tcount = _text.Count;
Attribute roc = GetReadOnlyColor ();
@@ -1140,10 +1140,10 @@ public virtual void Paste ()
}
int cols = _text [idx].GetColumns ();
- TextModel.SetCol (ref col, Frame.Width - 1, cols);
+ TextModel.SetCol (ref col, Viewport.Width - 1, cols);
}
- int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
+ int pos = _cursorPosition - ScrollOffset + Math.Min (Viewport.X, 0);
Move (pos, 0);
return new Point (pos, 0);
}
@@ -1225,16 +1225,16 @@ private void Adjust ()
ScrollOffset = _cursorPosition;
need = true;
}
- else if (Frame.Width > 0
- && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
- || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
+ else if (Viewport.Width > 0
+ && (ScrollOffset + _cursorPosition - (Viewport.Width + offB) == 0
+ || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Viewport.Width + offB))
{
ScrollOffset = Math.Max (
TextModel.CalculateLeftColumn (
_text,
ScrollOffset,
_cursorPosition,
- Frame.Width + offB
+ Viewport.Width + offB
),
0
);
@@ -1342,7 +1342,7 @@ private List DeleteSelectedText ()
private void GenerateSuggestions ()
{
- List currentLine = TextModel.ToRuneCellList (Text);
+ List currentLine = RuneCell.ToRuneCellList (Text);
int cursorPosition = Math.Min (CursorPosition, currentLine.Count);
Autocomplete.Context = new AutocompleteContext (
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index b81877b531..5c4a82fd8d 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -42,6 +42,22 @@ public override string ToString ()
return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}";
}
+
+ /// Converts the string into a .
+ /// The string to convert.
+ /// The to use.
+ ///
+ public static List ToRuneCellList (string str, ColorScheme? colorScheme = null)
+ {
+ List cells = new ();
+
+ foreach (Rune rune in str.EnumerateRunes ())
+ {
+ cells.Add (new () { Rune = rune, ColorScheme = colorScheme });
+ }
+
+ return cells;
+ }
}
internal class TextModel
@@ -233,22 +249,6 @@ public static List> StringToLinesOfRuneCells (string content, Col
return SplitNewLines (cells);
}
- /// Converts the string into a .
- /// The string to convert.
- /// The to use.
- ///
- public static List ToRuneCellList (string str, ColorScheme? colorScheme = null)
- {
- List cells = new ();
-
- foreach (Rune rune in str.EnumerateRunes ())
- {
- cells.Add (new () { Rune = rune, ColorScheme = colorScheme });
- }
-
- return cells;
- }
-
public override string ToString ()
{
var sb = new StringBuilder ();
@@ -855,7 +855,7 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth =
found = true;
}
- _lines [i] = ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col));
+ _lines [i] = RuneCell.ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col));
x = _lines [i];
txt = GetText (x);
pos = new (col, i);
@@ -1706,7 +1706,7 @@ public List> ToListRune (List textList)
foreach (string text in textList)
{
- runesList.Add (TextModel.ToRuneCellList (text));
+ runesList.Add (RuneCell.ToRuneCellList (text));
}
return runesList;
@@ -3591,6 +3591,8 @@ public override void OnDrawContent (Rectangle viewport)
else
{
AddRune (col, row, rune);
+ // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
+ cols = Math.Max (cols, 1);
}
if (!TextModel.SetCol (ref col, viewport.Right, cols))
@@ -3713,7 +3715,7 @@ public void Paste ()
if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0)
{
- List runeList = contents is null ? new () : TextModel.ToRuneCellList (contents);
+ List runeList = contents is null ? new () : RuneCell.ToRuneCellList (contents);
List currentLine = GetCurrentLine ();
_historyText.Add (new () { new (currentLine) }, CursorPosition);
@@ -3802,6 +3804,11 @@ public void Paste ()
{
cols += TabWidth + 1;
}
+ else
+ {
+ // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
+ cols = Math.Max (cols, 1);
+ }
if (!TextModel.SetCol (ref col, Viewport.Width, cols))
{
diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs
new file mode 100644
index 0000000000..10f152c17c
--- /dev/null
+++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs
@@ -0,0 +1,118 @@
+using System.Collections.Generic;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("AnsiEscapeSequenceRequest", "Ansi Escape Sequence Request")]
+[ScenarioCategory ("Ansi Escape Sequence")]
+public sealed class AnsiEscapeSequenceRequests : Scenario
+{
+ public override void Main ()
+ {
+ // Init
+ Application.Init ();
+
+ // Setup - Create a top-level application window and configure it.
+ Window appWindow = new ()
+ {
+ Title = GetQuitKeyAndName (),
+ };
+ appWindow.Padding.Thickness = new (1);
+
+ var scrRequests = new List
+ {
+ "CSI_SendDeviceAttributes",
+ "CSI_ReportTerminalSizeInChars",
+ "CSI_RequestCursorPositionReport",
+ "CSI_SendDeviceAttributes2"
+ };
+
+ var cbRequests = new ComboBox () { Width = 40, Height = 5, ReadOnly = true, Source = new ListWrapper (new (scrRequests)) };
+ appWindow.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 };
+ appWindow.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 };
+ appWindow.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 };
+ appWindow.Add (label, tfTerminator);
+
+ cbRequests.SelectedItemChanged += (s, e) =>
+ {
+ 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 };
+ appWindow.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 };
+ appWindow.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 };
+ appWindow.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 };
+ appWindow.Add (label, tvTerminator);
+
+ var btnResponse = new Button { X = Pos.Center (), Y = Pos.Bottom (tvResponse) + 2, Text = "Send Request" };
+ btnResponse.Accept += (s, e) =>
+ {
+ var ansiEscapeSequenceRequest = new AnsiEscapeSequenceRequest
+ {
+ Request = tfRequest.Text,
+ Terminator = tfTerminator.Text,
+ Value = string.IsNullOrEmpty (tfValue.Text) ? null : tfValue.Text
+ };
+
+ var ansiEscapeSequenceResponse = AnsiEscapeSequenceRequest.ExecuteAnsiRequest (
+ ansiEscapeSequenceRequest
+ );
+
+ tvResponse.Text = ansiEscapeSequenceResponse.Response;
+ tvError.Text = ansiEscapeSequenceResponse.Error;
+ tvValue.Text = ansiEscapeSequenceResponse.Value ?? "";
+ tvTerminator.Text = ansiEscapeSequenceResponse.Terminator;
+ };
+ appWindow.Add (btnResponse);
+
+ appWindow.Add (new Label { Y = Pos.Bottom (btnResponse) + 2, Text = "You can send other requests by editing the TextFields." });
+
+ // Run - Start the application.
+ Application.Run (appWindow);
+ appWindow.Dispose ();
+
+ // Shutdown - Calling Application.Shutdown is required.
+ Application.Shutdown ();
+ }
+}
diff --git a/UnitTests/Text/AutocompleteTests.cs b/UnitTests/Text/AutocompleteTests.cs
index c7d907e1a9..1689c70c8d 100644
--- a/UnitTests/Text/AutocompleteTests.cs
+++ b/UnitTests/Text/AutocompleteTests.cs
@@ -254,7 +254,7 @@ public void Test_GenerateSuggestions_Simple ()
ac.GenerateSuggestions (
new (
- TextModel.ToRuneCellList (tv.Text),
+ RuneCell.ToRuneCellList (tv.Text),
2
)
);
diff --git a/UnitTests/View/Mouse/MouseTests.cs b/UnitTests/View/Mouse/MouseTests.cs
index 28613dadfa..8eadb5bbe6 100644
--- a/UnitTests/View/Mouse/MouseTests.cs
+++ b/UnitTests/View/Mouse/MouseTests.cs
@@ -344,6 +344,7 @@ public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_M
me.Handled = false;
view.Dispose ();
+ Application.ResetState (ignoreDisposed: true);
}
[Theory]
diff --git a/UnitTests/Views/TextFieldTests.cs b/UnitTests/Views/TextFieldTests.cs
index a5c6fe4e1e..60a999bff9 100644
--- a/UnitTests/Views/TextFieldTests.cs
+++ b/UnitTests/Views/TextFieldTests.cs
@@ -2047,4 +2047,18 @@ public void Autocomplete_Visible_False_By_Default ()
Assert.True (t.Visible);
Assert.False (t.Autocomplete.Visible);
}
+
+ [Fact]
+ [AutoInitShutdown]
+ public void Draw_Esc_Rune ()
+ {
+ var tf = new TextField { Width = 5, Text = "\u001b" };
+ tf.BeginInit ();
+ tf.EndInit ();
+ tf.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre ("\u241b", output);
+
+ tf.Dispose ();
+ }
}
diff --git a/UnitTests/Views/TextViewTests.cs b/UnitTests/Views/TextViewTests.cs
index 2ab55e7aea..cdbde04480 100644
--- a/UnitTests/Views/TextViewTests.cs
+++ b/UnitTests/Views/TextViewTests.cs
@@ -8660,4 +8660,18 @@ public void Autocomplete_Visible_False_By_Default ()
Assert.True (t.Visible);
Assert.False (t.Autocomplete.Visible);
}
+
+ [Fact]
+ [AutoInitShutdown]
+ public void Draw_Esc_Rune ()
+ {
+ var tv = new TextView { Width = 5, Height = 1, Text = "\u001b" };
+ tv.BeginInit ();
+ tv.EndInit ();
+ tv.Draw ();
+
+ TestHelpers.AssertDriverContentsWithFrameAre ("\u241b", _output);
+
+ tv.Dispose ();
+ }
}