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

Robustness improvements in prep for implementing Virtual Terminal Sequences #3094

Merged
merged 44 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
0436019
Fixes #2616. Support combining sequences that don't normalize
BDisp Oct 24, 2023
f1a8308
Merge branch 'v2_develop' into v2_combining-normalize-fix_2616
tig Oct 25, 2023
475a89e
Decouples Application from ConsoleDriver in TestHelpers
tig Oct 25, 2023
860ceb1
Updates driver tests to match new arch
tig Oct 25, 2023
5e3edce
Start on making all driver tests test all drivers
tig Oct 25, 2023
3867df4
Merge pull request #161 from tig/BDisp-v2_combining-normalize-fix_2616
BDisp Oct 25, 2023
fc26a31
Improves handling if combining marks.
BDisp Oct 25, 2023
b491845
Fix unit tests fails.
BDisp Oct 25, 2023
f555243
Fix unit tests fails.
BDisp Oct 25, 2023
917a84f
Fix merge conflicts.
BDisp Oct 25, 2023
6f99b09
Handling combining mask.
BDisp Oct 25, 2023
cda0f01
Tying to fix this unit test that sometimes fail.
BDisp Oct 25, 2023
0293f6a
Add support for combining mask on NetDriver.
BDisp Oct 25, 2023
41cd364
Enable CombiningMarks as List<Rune>.
BDisp Oct 26, 2023
877190b
Prevents combining marks on invalid runes default and space.
BDisp Oct 26, 2023
abcc5f9
Merge branch 'v2_develop' into v2_combining-normalize-fix_2616
tig Oct 27, 2023
0e9b036
Formatting for CI tests.
BDisp Oct 27, 2023
621c68a
Fix non-normalized combining mark to add 1 to Col.
BDisp Oct 27, 2023
9a9e970
Reformatting for retest the CI.
BDisp Oct 27, 2023
6893af5
Forces non-normalized CMs to be ignored.
tig Oct 27, 2023
b14c652
Initial experiment
tig Oct 29, 2023
e1a963e
merge
tig Oct 29, 2023
6df914a
Created ANSiDriver. Updated UI Catalog command line handling
tig Oct 30, 2023
963a23f
Fixed ForceDriver logic
tig Oct 30, 2023
d6c3a81
Fixed ForceDriver logic
tig Oct 30, 2023
93a3c44
Updating P/Invoke
tig Oct 31, 2023
2eb81a3
Force16 colors WIP
tig Nov 1, 2023
d3c0e94
Fixed 16 colo mode
tig Nov 2, 2023
3c5d5df
Updated unit tests
tig Nov 2, 2023
59322fe
UI catalog tweak
tig Nov 2, 2023
c5477e2
Added chinese scenario from bdisp
tig Nov 2, 2023
3cf52e4
Merged but something is broke
tig Dec 16, 2023
0689a93
Fixed merge
tig Dec 16, 2023
8719b85
Disabled AnsiDriver unit tests for now.
tig Dec 16, 2023
9951a43
Merge branch 'v2_develop' into v2_fixes_2610_WT_VTS
tig Dec 27, 2023
c7a67d2
Merge v2_develop
tig Dec 27, 2023
df4f0a4
Code cleanup
tig Dec 27, 2023
3838d76
Initial commit (fork from v2_fixes_2610_WT_VTS)
tig Dec 27, 2023
f12ce6d
Code cleanup
tig Dec 27, 2023
9d7aebe
Removed nativemethods.txt
tig Dec 27, 2023
7558ec4
Removed not needed native stuff
tig Dec 27, 2023
6affb39
Code cleanup
tig Dec 27, 2023
292f453
Ensures command line handler doesn't eat exceptions
tig Dec 29, 2023
656d6d0
merged v2_develop
tig Jan 5, 2024
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
99 changes: 66 additions & 33 deletions Terminal.Gui/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ namespace Terminal.Gui;
/// </remarks>
public static partial class Application {
/// <summary>
/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="UseSystemConsole"/>.
/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.
/// </summary>
public static ConsoleDriver Driver { get; internal set; }

/// <summary>
/// If <see langword="true"/>, forces the use of the System.Console-based (see <see cref="NetDriver"/>) driver. The default is <see langword="false"/>.
/// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If
/// not specified, the driver is selected based on the platform.
/// </summary>
/// <remarks>
/// Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if
/// called with either `driver` or `driverName` specified.
/// </remarks>
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static bool UseSystemConsole { get; set; } = false;
public static string ForceDriver { get; set; } = string.Empty;

/// <summary>
/// Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in <see cref="ColorName"/>.
Expand Down Expand Up @@ -98,14 +103,13 @@ static List<CultureInfo> GetSupportedCultures ()
/// </para>
/// <para>
/// The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> function
/// combines <see cref="Init(ConsoleDriver)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
/// combines <see cref="Init(ConsoleDriver, string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
/// into a single call. An application cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/>
/// without explicitly calling <see cref="Init(ConsoleDriver)"/>.
/// without explicitly calling <see cref="Init(ConsoleDriver, string)"/>.
/// </para>
/// <param name="driver">
/// The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
/// platform will be used (see <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, and <see cref="NetDriver"/>).</param>
public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver);
/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
/// <param name="driverName">The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (Toplevel.Create, driver, driverName);

internal static bool _initialized = false;
internal static int _mainThreadId = -1;
Expand All @@ -119,7 +123,7 @@ static List<CultureInfo> GetSupportedCultures ()
// Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
//
// calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false)
internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, string driverName = null, bool calledViaRunT = false)
{
if (_initialized && driver == null) {
return;
Expand Down Expand Up @@ -147,15 +151,28 @@ internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver
Load (true);
Apply ();

Driver ??= Environment.OSVersion.Platform switch {
_ when _forceFakeConsole => new FakeDriver (), // for unit testing only
_ when UseSystemConsole => new NetDriver (),
PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (),
_ => new CursesDriver ()
};
// Ignore Configuration for ForceDriver if driverName is specified
if (!string.IsNullOrEmpty (driverName)) {
ForceDriver = driverName;
}

if (Driver == null) {
throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use.");
var p = Environment.OSVersion.Platform;
if (string.IsNullOrEmpty (ForceDriver)) {
if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
Driver = new WindowsDriver ();
} else {
Driver = new CursesDriver ();
}
} else {
var drivers = GetDriverTypes ();
var driverType = drivers.FirstOrDefault (t => t.Name.ToLower () == ForceDriver.ToLower ());
if (driverType != null) {
Driver = (ConsoleDriver)Activator.CreateInstance (driverType);
} else {
throw new ArgumentException ($"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}");
}
}
}

try {
Expand All @@ -168,10 +185,10 @@ internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver
throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
}

Driver.SizeChanged += Driver_SizeChanged;
Driver.KeyDown += Driver_KeyDown;
Driver.KeyUp += Driver_KeyUp;
Driver.MouseEvent += Driver_MouseEvent;
Driver.SizeChanged += (s, args) => OnSizeChanging (args);
Driver.KeyDown += (s, args) => OnKeyDown (args);
Driver.KeyUp += (s, args) => OnKeyUp (args);
Driver.MouseEvent += (s, args) => OnMouseEvent (args);

SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());

Expand All @@ -190,12 +207,29 @@ internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver

static void Driver_MouseEvent (object sender, MouseEventEventArgs e) => OnMouseEvent (e);

/// <summary>
/// Gets of list of <see cref="ConsoleDriver"/> types that are available.
/// </summary>
/// <returns></returns>
public static List<Type> GetDriverTypes ()
{
// use reflection to get the list of drivers
var driverTypes = new List<Type> ();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) {
foreach (var type in asm.GetTypes ()) {
if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) {
driverTypes.Add (type);
}
}
}
return driverTypes;
}

/// <summary>
/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>.
/// Shutdown an application initialized with <see cref="Init"/>.
/// </summary>
/// <remarks>
/// Shutdown must be called for every call to <see cref="Init(ConsoleDriver)"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
/// Shutdown must be called for every call to <see cref="Init"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
/// to ensure all resources are cleaned up (Disposed) and terminal settings are restored.
/// </remarks>
public static void Shutdown ()
Expand Down Expand Up @@ -394,7 +428,7 @@ public static RunState Begin (Toplevel Toplevel)
/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/>
/// with a new instance of the specified <see cref="Toplevel"/>-derived class.
/// <para>
/// Calling <see cref="Init(ConsoleDriver)"/> first is not needed as this function will initialize the application.
/// Calling <see cref="Init"/> first is not needed as this function will initialize the application.
/// </para>
/// <para>
/// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has
Expand All @@ -407,7 +441,7 @@ public static RunState Begin (Toplevel Toplevel)
/// <param name="errorHandler"></param>
/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
/// Must be <see langword="null"/> if <see cref="Init(ConsoleDriver)"/> has already been called.
/// Must be <see langword="null"/> if <see cref="Init"/> has already been called.
/// </param>
public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new ()
{
Expand All @@ -429,7 +463,7 @@ public static RunState Begin (Toplevel Toplevel)
}
} else {
// Init() has NOT been called.
InternalInit (() => new T (), driver, true);
InternalInit (() => new T (), driver, null, true);
Run (Top, errorHandler);
}
}
Expand Down Expand Up @@ -838,13 +872,12 @@ public static void End (RunState runState)
#endregion Run (Begin, Run, End)

#region Toplevel handling

/// <summary>
/// Holds the stack of TopLevel views.
/// </summary>
// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
// about TopLevels that are just a SubView of another View?
static readonly Stack<Toplevel> _topLevels = new ();
static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();

/// <summary>
/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
Expand Down Expand Up @@ -1296,7 +1329,7 @@ bool FrameHandledMouseEvent (Frame frame)
#endregion Mouse handling

#region Keyboard handling
static Key _alternateForwardKey = new (KeyCode.PageDown | KeyCode.CtrlMask);
static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask);

/// <summary>
/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
Expand All @@ -1320,7 +1353,7 @@ static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
}
}

static Key _alternateBackwardKey = new (KeyCode.PageUp | KeyCode.CtrlMask);
static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask);

/// <summary>
/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
Expand All @@ -1344,7 +1377,7 @@ static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
}
}

static Key _quitKey = new (KeyCode.Q | KeyCode.CtrlMask);
static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask);

/// <summary>
/// Gets or sets the key to quit the application.
Expand Down Expand Up @@ -1481,8 +1514,8 @@ public static bool OnKeyUp (Key a)
}
#endregion Keyboard handling
}

/// <summary>
/// Event arguments for the <see cref="Application.Iteration"/> event.
/// </summary>
public class IterationEventArgs { }
public class IterationEventArgs {
}
6 changes: 4 additions & 2 deletions Terminal.Gui/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public static void OnUpdated ()

/// <summary>
/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
/// (e.g. in <see cref="Application.Init(ConsoleDriver)"/> starts. Called by <see cref="Load"/>
/// (e.g. in <see cref="Application.Init"/> starts. Called by <see cref="Load"/>
/// if the <c>reset</c> parameter is <see langword="true"/>.
/// </summary>
/// <remarks>
Expand Down Expand Up @@ -412,7 +412,9 @@ public static void Load (bool reset = false)
{
Debug.WriteLine ($"ConfigurationManager.Load()");

if (reset) Reset ();
if (reset) {
Reset ();
}

// LibraryResources is always loaded by Reset
if (Locations == ConfigLocations.All) {
Expand Down
32 changes: 27 additions & 5 deletions Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,24 @@ public abstract class ConsoleDriver {
/// <summary>
/// The number of columns visible in the terminal.
/// </summary>
public virtual int Cols { get; internal set; }
public virtual int Cols {
get => _cols;
internal set {
_cols = value;
ClearContents();
}
}

/// <summary>
/// The number of rows visible in the terminal.
/// </summary>
public virtual int Rows { get; internal set; }
public virtual int Rows {
get => _rows;
internal set {
_rows = value;
ClearContents();
}
}

/// <summary>
/// The leftmost column in the terminal.
Expand Down Expand Up @@ -152,11 +164,19 @@ public void AddRune (Rune rune)
rune = rune.MakePrintable ();
runeWidth = rune.GetColumns ();
if (runeWidth == 0 && rune.IsCombiningMark ()) {
// AtlasEngine does not support NON-NORMALIZED combining marks in a way
// compatible with the driver architecture. Any CMs (except in the first col)
// are correctly combined with the base char, but are ALSO treated as 1 column
// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
//
// Until this is addressed (see Issue #), we do our best by
// a) Attempting to normalize any CM with the base char to it's left
// b) Ignoring any CMs that don't normalize
if (Col > 0) {
if (Contents [Row, Col - 1].CombiningMarks.Count > 0) {
// Just add this mark to the list
Contents [Row, Col - 1].CombiningMarks.Add (rune);
// Don't move to next column (let the driver figure out what to do).
// Ignore. Don't move to next column (let the driver figure out what to do).
} else {
// Attempt to normalize the cell to our left combined with this mark
string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
Expand All @@ -167,11 +187,11 @@ public void AddRune (Rune rune)
// It normalized! We can just set the Cell to the left with the
// normalized codepoint
Contents [Row, Col - 1].Rune = (Rune)normalized [0];
// Don't move to next column because we're already there
// Ignore. Don't move to next column because we're already there
} else {
// It didn't normalize. Add it to the Cell to left's CM list
Contents [Row, Col - 1].CombiningMarks.Add (rune);
// Don't move to next column (let the driver figure out what to do).
// Ignore. Don't move to next column (let the driver figure out what to do).
}
}
Contents [Row, Col - 1].Attribute = CurrentAttribute;
Expand Down Expand Up @@ -398,6 +418,8 @@ internal virtual bool Force16Colors {
}

Attribute _currentAttribute;
int _cols;
int _rows;

/// <summary>
/// The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/> call.
Expand Down
4 changes: 4 additions & 0 deletions Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ public static uint MapVKtoChar (VK vk)
[DllImport ("user32.dll")]
extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);

/// <summary>
/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
/// </summary>
/// <returns></returns>
public static string GetKeyboardLayoutName ()
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
Expand Down
10 changes: 8 additions & 2 deletions Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ namespace Terminal.Gui;
class CursesDriver : ConsoleDriver {
public override int Cols {
get => Curses.Cols;
internal set => Curses.Cols = value;
internal set {
Curses.Cols = value;
ClearContents();
}
}

public override int Rows {
get => Curses.Lines;
internal set => Curses.Lines = value;
internal set {
Curses.Lines = value;
ClearContents();
}
}

CursorVisibility? _initialCursorVisibility = null;
Expand Down
24 changes: 19 additions & 5 deletions Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ public enum ClearScreenOptions {
/// <summary>
/// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column of the y line
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="row">Origin is (1,1).</param>
/// <param name="col">Origin is (1,1).</param>
/// <returns></returns>
public static string CSI_SetCursorPosition (int y, int x) => $"{CSI}{y};{x}H";
public static string CSI_SetCursorPosition (int row, int col) => $"{CSI}{row};{col}H";


//ESC [ <y> ; <x> f - HVP Horizontal Vertical Position* Cursor moves to<x>; <y> coordinate within the viewport, where <x> is the column of the<y> line
Expand Down Expand Up @@ -248,15 +248,29 @@ public enum DECSCUSR_Style {
/// </summary>
public static string CSI_SetGraphicsRendition (params int [] parameters) => $"{CSI}{string.Join (";", parameters)}m";

/// <summary>
/// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the foreground color.
/// </summary>
/// <param name="code">One of the 16 color codes.</param>
/// <returns></returns>
public static string CSI_SetForegroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code);

/// <summary>
/// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the background color.
/// </summary>
/// <param name="code">One of the 16 color codes.</param>
/// <returns></returns>
public static string CSI_SetBackgroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code+10);

/// <summary>
/// ESC[38;5;{id}m - Set foreground color (256 colors)
/// </summary>
public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}m";
public static string CSI_SetForegroundColor256 (int color) => $"{CSI}38;5;{color}m";

/// <summary>
/// ESC[48;5;{id}m - Set background color (256 colors)
/// </summary>
public static string CSI_SetBackgroundColor (int id) => $"{CSI}48;5;{id}m";
public static string CSI_SetBackgroundColor256 (int color) => $"{CSI}48;5;{color}m";

/// <summary>
/// ESC[38;2;{r};{g};{b}m Set foreground color as RGB.
Expand Down
Loading