Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleans up/Refactors View.Subviews #3962

Merged
merged 26 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Terminal.Gui/Application/Application.Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static Key ArrangeKey
/// <param name="toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
/// <remarks>
/// This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
/// of <see cref="Toplevel"/>s, lays out the Subviews, focuses the first element, and draws the <see cref="Toplevel"/>
/// of <see cref="Toplevel"/>s, lays out the SubViews, focuses the first element, and draws the <see cref="Toplevel"/>
/// in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
/// <see cref="End(RunState)"/> method upon termination which will undo these changes.
/// </remarks>
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Application/ApplicationNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public ApplicationNavigation ()
}

/// <summary>
/// Gets whether <paramref name="view"/> is in the Subview hierarchy of <paramref name="start"/>.
/// Gets whether <paramref name="view"/> is in the SubView hierarchy of <paramref name="start"/>.
/// </summary>
/// <param name="start"></param>
/// <param name="view"></param>
Expand All @@ -50,7 +50,7 @@ public static bool IsInHierarchy (View? start, View? view)
return true;
}

foreach (View subView in start.Subviews)
foreach (View subView in start.SubViews)
{
if (view == subView)
{
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class AnsiEscapeSequence
/// to the oldest outstanding request.
/// </para>
/// </summary>
public required string Terminator { get; init; }
public required string? Terminator { get; init; }



Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class AnsiEscapeSequenceRequest : AnsiEscapeSequence
/// Invoked when the console responds with an ANSI response code that matches the
/// <see cref="AnsiEscapeSequence.Terminator"/>
/// </summary>
public required Action<string> ResponseReceived { get; init; }
public required Action<string?> ResponseReceived { get; init; }

/// <summary>
/// Invoked if the console fails to responds to the ANSI response code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ public class AnsiMouseParser
/// </summary>
/// <param name="cur"></param>
/// <returns></returns>
public bool IsMouse (string cur)
public bool IsMouse (string? cur)
{
// Typically in this format
// ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
return cur.EndsWith ('M') || cur.EndsWith ('m');
return cur!.EndsWith ('M') || cur.EndsWith ('m');
}

/// <summary>
Expand All @@ -30,10 +30,10 @@ public bool IsMouse (string cur)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public MouseEventArgs? ProcessMouseInput (string input)
public MouseEventArgs? ProcessMouseInput (string? input)
{
// Match mouse wheel events first
Match match = _mouseEventPattern.Match (input);
Match match = _mouseEventPattern.Match (input!);

if (match.Success)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private bool SendOrSchedule (AnsiEscapeSequenceRequest request, bool addToQueue)

private void EvictStaleRequests ()
{
foreach (string stale in _lastSend.Where (v => IsStale (v.Value)).Select (k => k.Key))
foreach (string? stale in _lastSend.Where (v => IsStale (v.Value)).Select (k => k.Key))
{
EvictStaleRequests (stale);
}
Expand All @@ -123,9 +123,9 @@ private void EvictStaleRequests ()
/// </summary>
/// <param name="withTerminator"></param>
/// <returns></returns>
private bool EvictStaleRequests (string withTerminator)
private bool EvictStaleRequests (string? withTerminator)
{
if (_lastSend.TryGetValue (withTerminator, out DateTime dt))
if (_lastSend.TryGetValue (withTerminator!, out DateTime dt))
{
if (IsStale (dt))
{
Expand Down Expand Up @@ -178,7 +178,7 @@ public bool RunSchedule (bool force = false)

private void Send (AnsiEscapeSequenceRequest r)
{
_lastSend.AddOrUpdate (r.Terminator, _ => Now (), (_, _) => Now ());
_lastSend.AddOrUpdate (r.Terminator!, _ => Now (), (_, _) => Now ());
_parser.ExpectResponse (r.Terminator, r.ResponseReceived, r.Abandoned, false);
r.Send ();
}
Expand Down Expand Up @@ -206,7 +206,7 @@ private bool CanSend (AnsiEscapeSequenceRequest r, out ReasonCannotSend reason)

private bool ShouldThrottle (AnsiEscapeSequenceRequest r)
{
if (_lastSend.TryGetValue (r.Terminator, out DateTime value))
if (_lastSend.TryGetValue (r.Terminator!, out DateTime value))
{
return Now () - value < _throttle;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#nullable enable
namespace Terminal.Gui;

internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
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!); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,32 @@ namespace Terminal.Gui;

internal abstract class AnsiResponseParserBase : IAnsiResponseParser
{
private const char Escape = '\x1B';
private const char ESCAPE = '\x1B';
private readonly AnsiMouseParser _mouseParser = new ();
#pragma warning disable IDE1006 // Naming Styles
protected readonly AnsiKeyboardParser _keyboardParser = new ();
protected object _lockExpectedResponses = new ();

protected object _lockState = new ();
protected readonly IHeld _heldContent;

/// <summary>
/// Responses we are expecting to come in.
/// </summary>
protected readonly List<AnsiResponseExpectation> _expectedResponses = [];

/// <summary>
/// Collection of responses that we <see cref="StopExpecting"/>.
/// </summary>
protected readonly List<AnsiResponseExpectation> _lateResponses = [];

/// <summary>
/// Responses that you want to look out for that will come in continuously e.g. mouse events.
/// Key is the terminator.
/// </summary>
protected readonly List<AnsiResponseExpectation> _persistentExpectations = [];

#pragma warning restore IDE1006 // Naming Styles

/// <summary>
/// Event raised when mouse events are detected - requires setting <see cref="HandleMouse"/> to true
Expand All @@ -35,22 +55,6 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
/// </summary>
public bool HandleKeyboard { get; set; } = false;

/// <summary>
/// Responses we are expecting to come in.
/// </summary>
protected readonly List<AnsiResponseExpectation> _expectedResponses = [];

/// <summary>
/// Collection of responses that we <see cref="StopExpecting"/>.
/// </summary>
protected readonly List<AnsiResponseExpectation> _lateResponses = [];

/// <summary>
/// Responses that you want to look out for that will come in continuously e.g. mouse events.
/// Key is the terminator.
/// </summary>
protected readonly List<AnsiResponseExpectation> _persistentExpectations = [];

private AnsiResponseParserState _state = AnsiResponseParserState.Normal;

/// <inheritdoc/>
Expand All @@ -64,8 +68,6 @@ protected set
}
}

protected readonly IHeld _heldContent;

/// <summary>
/// When <see cref="State"/> was last changed.
/// </summary>
Expand All @@ -74,17 +76,17 @@ protected set
// These all are valid terminators on ansi responses,
// see CSI in https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s
// No - N or O
protected readonly HashSet<char> _knownTerminators = new (
[
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'G', 'H', 'I', 'J', 'K', 'L', 'M',

// No - N or O
'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Z',
'^', '`', '~',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'l', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
]);
protected readonly HashSet<char> _knownTerminators =
[
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'G', 'H', 'I', 'J', 'K', 'L', 'M',

// No - N or O
'P', 'Q', 'R', 'S', 'T', 'W', 'X', 'Z',
'^', '`', '~',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'l', 'm', 'n',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
];

protected AnsiResponseParserBase (IHeld heldContent) { _heldContent = heldContent; }

Expand Down Expand Up @@ -137,7 +139,7 @@ int inputLength
char currentChar = getCharAtIndex (index);
object currentObj = getObjectAtIndex (index);

bool isEscape = currentChar == Escape;
bool isEscape = currentChar == ESCAPE;

switch (State)
{
Expand Down Expand Up @@ -233,7 +235,7 @@ protected void TryLastMinuteSequences ()
{
lock (_lockState)
{
string cur = _heldContent.HeldToString ();
string? cur = _heldContent.HeldToString ();

if (HandleKeyboard)
{
Expand All @@ -250,7 +252,7 @@ protected void TryLastMinuteSequences ()

// We have something totally unexpected, not a CSI and
// still Esc+<something>. So give last minute swallow chance
if (cur.Length >= 2 && cur [0] == Escape)
if (cur!.Length >= 2 && cur [0] == ESCAPE)
{
// Maybe swallow anyway if user has custom delegate
bool swallow = ShouldSwallowUnexpectedResponse ();
Expand All @@ -270,7 +272,7 @@ protected bool ShouldReleaseHeldContent ()
{
lock (_lockState)
{
string cur = _heldContent.HeldToString ();
string? cur = _heldContent.HeldToString ();

if (HandleMouse && IsMouse (cur))
{
Expand Down Expand Up @@ -328,7 +330,7 @@ protected bool ShouldReleaseHeldContent ()

// Finally if it is a valid ansi response but not one we are expect (e.g. its mouse activity)
// then we can release it back to input processing stream
if (_knownTerminators.Contains (cur.Last ()) && cur.StartsWith (EscSeqUtils.CSI))
if (_knownTerminators.Contains (cur!.Last ()) && cur!.StartsWith (EscSeqUtils.CSI))
{
// We have found a terminator so bail
State = AnsiResponseParserState.Normal;
Expand All @@ -354,7 +356,7 @@ protected bool ShouldReleaseHeldContent ()
return false; // Continue accumulating
}

private void RaiseMouseEvent (string cur)
private void RaiseMouseEvent (string? cur)
{
MouseEventArgs? ev = _mouseParser.ProcessMouseInput (cur);

Expand All @@ -364,9 +366,9 @@ private void RaiseMouseEvent (string cur)
}
}

private bool IsMouse (string cur) { return _mouseParser.IsMouse (cur); }
private bool IsMouse (string? cur) { return _mouseParser.IsMouse (cur); }

protected void RaiseKeyboardEvent (AnsiKeyboardParserPattern pattern, string cur)
protected void RaiseKeyboardEvent (AnsiKeyboardParserPattern pattern, string? cur)
{
Key? k = pattern.GetKey (cur);

Expand Down Expand Up @@ -394,7 +396,7 @@ protected void RaiseKeyboardEvent (AnsiKeyboardParserPattern pattern, string cur
/// <returns></returns>
protected abstract bool ShouldSwallowUnexpectedResponse ();

private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection, bool invokeCallback, bool removeExpectation)
private bool MatchResponse (string? cur, List<AnsiResponseExpectation> collection, bool invokeCallback, bool removeExpectation)
{
// Check for expected responses
AnsiResponseExpectation? matchingResponse = collection.FirstOrDefault (r => r.Matches (cur));
Expand Down Expand Up @@ -422,7 +424,7 @@ private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection
}

/// <inheritdoc/>
public void ExpectResponse (string terminator, Action<string> response, Action? abandoned, bool persistent)
public void ExpectResponse (string? terminator, Action<string?> response, Action? abandoned, bool persistent)
{
lock (_lockExpectedResponses)
{
Expand All @@ -438,17 +440,17 @@ public void ExpectResponse (string terminator, Action<string> response, Action?
}

/// <inheritdoc/>
public bool IsExpecting (string terminator)
public bool IsExpecting (string? terminator)
{
lock (_lockExpectedResponses)
{
// If any of the new terminator matches any existing terminators characters it's a collision so true.
return _expectedResponses.Any (r => r.Terminator.Intersect (terminator).Any ());
return _expectedResponses.Any (r => r.Terminator!.Intersect (terminator!).Any ());
}
}

/// <inheritdoc/>
public void StopExpecting (string terminator, bool persistent)
public void StopExpecting (string? terminator, bool persistent)
{
lock (_lockExpectedResponses)
{
Expand Down Expand Up @@ -530,7 +532,7 @@ public Tuple<char, T> [] Release ()
/// <param name="response"></param>
/// <param name="abandoned"></param>
/// <param name="persistent"></param>
public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T>>> response, Action? abandoned, bool persistent)
public void ExpectResponseT (string? terminator, Action<IEnumerable<Tuple<char, T>>> response, Action? abandoned, bool persistent)
{
lock (_lockExpectedResponses)
{
Expand Down Expand Up @@ -562,7 +564,7 @@ internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ()
/// keystrokes 'swallowed' (i.e. not returned to input stream).
/// </para>
/// </summary>
public Func<string, bool> UnknownResponseHandler { get; set; } = _ => false;
public Func<string?, bool> UnknownResponseHandler { get; set; } = _ => false;

public string ProcessInput (string input)
{
Expand All @@ -583,13 +585,13 @@ private void AppendOutput (StringBuilder output, char c)
output.Append (c);
}

public string Release ()
public string? Release ()
{
lock (_lockState)
{
TryLastMinuteSequences ();

string output = _heldContent.HeldToString ();
string? output = _heldContent.HeldToString ();
ResetState ();

return output;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class GenericHeld<T> : IHeld

public void ClearHeld () { held.Clear (); }

public string HeldToString () { return new (held.Select (h => h.Item1).ToArray ()); }
public string? HeldToString () { return new (held.Select (h => h.Item1).ToArray ()); }

public IEnumerable<object> HeldToObjects () { return held; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ public interface IAnsiResponseParser
/// that already has one.
/// exists.
/// </exception>
void ExpectResponse (string terminator, Action<string> response, Action? abandoned, bool persistent);
void ExpectResponse (string? terminator, Action<string?> response, Action? abandoned, bool persistent);

/// <summary>
/// Returns true if there is an existing expectation (i.e. we are waiting a response
/// from console) for the given <paramref name="terminator"/>.
/// </summary>
/// <param name="terminator"></param>
/// <returns></returns>
bool IsExpecting (string terminator);
bool IsExpecting (string? terminator);

/// <summary>
/// Removes callback and expectation that we will get a response for the
Expand All @@ -50,5 +50,5 @@ public interface IAnsiResponseParser
/// <see langword="true"/> if you want to remove a persistent
/// request listener.
/// </param>
void StopExpecting (string requestTerminator, bool persistent);
void StopExpecting (string? requestTerminator, bool persistent);
}
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal interface IHeld
/// Returns string representation of the held objects
/// </summary>
/// <returns></returns>
string HeldToString ();
string? HeldToString ();

/// <summary>
/// Returns the collection objects directly e.g. <see langword="char"/>
Expand Down
Loading
Loading