Explorar o código

Fixes #666. Refactor `ConsoleDriver`s to simplify and remove duplicated code (#2612)

* Added ClipRegion; cleaned up driver code

* clip region unit tests

* api docs

* Moved color stuff from ConsoleDriver to Color.cs

* Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Code cleanup and Removes unused ConsoleDriver APIs

* Work around https://github.com/gui-cs/Terminal.Gui/issues/2610

* adjusted unit tests

* initial commit

* Made Rows, Cols, Top, Left virtual

* Made Clipboard non-virtual

* Made EnableConsoleScrolling  non-virtual

* Made Contents non-virtual

* Pulled Row/Col up

* Made MoveTo virtual; fixed stupid FakeDriver cursor issue

* Made CurrentAttribute non-virtual

* Made SetAttribute  non-virtual

* Moved clipboard code out

* Code cleanup

* Removes dependecy on NStack from ConsoleDrivers - WIP

* Fixed unit tests

* Fixed unit tests

* Added list of unit tests needed

* Did some perf testing; tweaked code and charmap to address

* Brough in code from PR #2264 (but commented)

* Tons of code cleanup

* Fighting with ScrollView

* Fixing bugs

* Fixed TabView tests

* Fixed View.Visible test that was not really working

* Fixed unit tests

* Cleaned up clipboard APIs in attempt to track down unit test failure

* Add Cut_Preserves_Selection test

* Removed invalid code

* Removed invalid test code; unit tests now pass

* EscSeq* - Adjusted naming, added more sequences, made code more consistent, simplified, etc...

* Added CSI_SetGraphicsRendition

* NetDriver code cleanup

* code cleanup

* Cleaned up color handling in NetDriver

* refixed tabview unit test

* WindowsDriver color code cleanup

* WindowsDriver color code cleanup

* CursesDriver color code cleanup

* CursesDriver - Adding _BOLD has no effect. Further up the stack we cast the return of ColorToCursesColor from int to short and the _BOLD values don't fit in a short.

* CursesDriver color code - make code more accurate

* CursesDriver color code - make code more accurate

* Simplified ConsoleDriver.GetColors API

* Simplified ConsoleDriver.GetColors API further

* Improved encapslation of Attribute; prep for TrueColor & other attributes like blink

* Fixes #2249. CharacterMap isn't refreshing well non-BMP code points on scroll.

* Use GetRange to take some of the runes before convert to string.

* Attempting to fix unit tests not being cleaned up

* Fixes #2658 - ConsoleDriver.IsRuneSupported

* Fixes #2658 - ConsoleDriver.IsRuneSupported (for WindowsDriver)

* Check all the range values and not only the max value.

* Reducing code.

* Fixes #2674 - Unit test process doesn't exit

* Changed Cell to support IsDirty and list of Runes

* add support for rendering TrueColor output on Windows merging veeman & tznind code

* add colorconverter changes

* fixed merged v2_develop

* Fixing merge bugs

* Fixed merge bugs

* Fixed merge bugs - all unit tests pass

* Debugging netdriver

* More netdriver diag

* API docs for escutils

* Update unicode scenario to stress more stuff

* Contents: Now a 2D array of Cells; WIP

* AddRune and ClearContents no longer virtual/abstract

* WindowsDriver renders correctly again

* Progress on Curses

* Progress on Curses

* broke windowsdriver

* Cleaned up FakeMainLoop

* Cleaned up some build warnings

* Removed _init from AutoInitShutdown as it's not needed anymore

* Removed unused var

* Removed unused var

* Fixed nullabiltiy warning in LineCanvas

* Fixed charmap crash

* Fixes #2758 in v2

* Port testonfail fix to v2

* Remove EnableConsoleScrolling

* Backport #2764 from develop (clear last line)

* Remove uneeded usings

* Progress on unicode

* Merged in changes from PR #2786, Fixes #2784

* revamp charmap rendering

* Charmap option to show glyph widths

* Fixed issue with wide glpyhs being overwritten

* Fixed charmap startcodepoint change issue

* Added abiltiy to see ncurses verison/lib

* Fought with CursesDriver; giving up for now. See notes.

* Leverage Wcwidth nuget library instaed of our own tables

* enhanced charmap Details dialog

* Final attempt at fixing curses

---------

Co-authored-by: BDisp <[email protected]>
Co-authored-by: adstep <[email protected]>
Tig %!s(int64=2) %!d(string=hai) anos
pai
achega
0df485a890
Modificáronse 100 ficheiros con 10919 adicións e 9934 borrados
  1. 3 1
      .github/CODEOWNERS
  2. 1 0
      .github/workflows/dotnet-core.yml
  3. 1 0
      .github/workflows/publish.yml
  4. 1 0
      .gitignore
  5. 29 67
      Terminal.Gui/Application.cs
  6. 134 62
      Terminal.Gui/Clipboard/Clipboard.cs
  7. 10 6
      Terminal.Gui/Clipboard/ClipboardBase.cs
  8. 0 4
      Terminal.Gui/Clipboard/IClipboard.cs
  9. 21 8
      Terminal.Gui/Configuration/AttributeJsonConverter.cs
  10. 1 1
      Terminal.Gui/Configuration/ColorJsonConverter.cs
  11. 2 1
      Terminal.Gui/Configuration/ConfigurationManager.cs
  12. 3 3
      Terminal.Gui/Configuration/ThemeScope.cs
  13. 47 0
      Terminal.Gui/Configuration/TrueColorJsonConverter.cs
  14. 477 1025
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  15. 225 0
      Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs
  16. 688 1031
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  17. 6 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  18. 6 4
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  19. 52 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
  20. 6 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs
  21. 114 0
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
  22. 1165 0
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  23. 1933 1939
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  24. 455 607
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  25. 26 87
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  26. 1164 1441
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  27. 561 506
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  28. 14 5
      Terminal.Gui/Drawing/Cell.cs
  29. 845 0
      Terminal.Gui/Drawing/Color.cs
  30. 15 0
      Terminal.Gui/Drawing/Glyphs.cs
  31. 9 5
      Terminal.Gui/Drawing/LineCanvas.cs
  32. 1 1
      Terminal.Gui/Input/Command.cs
  33. 0 109
      Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs
  34. 0 907
      Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs
  35. 24 15
      Terminal.Gui/MainLoop.cs
  36. 0 1
      Terminal.Gui/Resources/config.json
  37. 4 8
      Terminal.Gui/Terminal.Gui.csproj
  38. 2 144
      Terminal.Gui/Text/RuneExtensions.cs
  39. 4 5
      Terminal.Gui/View/Frame.cs
  40. 117 116
      Terminal.Gui/View/ViewDrawing.cs
  41. 4 5
      Terminal.Gui/View/ViewLayout.cs
  42. 11 6
      Terminal.Gui/View/ViewSubViews.cs
  43. 6 8
      Terminal.Gui/Views/CheckBox.cs
  44. 1 8
      Terminal.Gui/Views/ColorPicker.cs
  45. 2 2
      Terminal.Gui/Views/HexView.cs
  46. 0 1
      Terminal.Gui/Views/Menu.cs
  47. 512 513
      Terminal.Gui/Views/ScrollView.cs
  48. 2 1
      Terminal.Gui/Views/StatusBar.cs
  49. 1 1
      Terminal.Gui/Views/TabView.cs
  50. 10 16
      Terminal.Gui/Views/TextField.cs
  51. 12 8
      Terminal.Gui/Views/TextView.cs
  52. 1 1
      Terminal.Gui/Views/TileView.cs
  53. 6 4
      Terminal.Gui/Views/Toplevel.cs
  54. 1 1
      Terminal.Gui/Views/ToplevelOverlapped.cs
  55. 1 3
      Terminal.sln
  56. 3 0
      UICatalog/Properties/launchSettings.json
  57. 2 2
      UICatalog/Scenarios/BasicColors.cs
  58. 290 73
      UICatalog/Scenarios/CharacterMap.cs
  59. 3 4
      UICatalog/Scenarios/Generic.cs
  60. 136 0
      UICatalog/Scenarios/Images.cs
  61. 3 2
      UICatalog/Scenarios/LineDrawing.cs
  62. 2 2
      UICatalog/Scenarios/ProgressBarStyles.cs
  63. 0 1
      UICatalog/Scenarios/TableEditor.cs
  64. 116 0
      UICatalog/Scenarios/TrueColors.cs
  65. 5 13
      UICatalog/Scenarios/Unicode.cs
  66. 4 27
      UICatalog/UICatalog.cs
  67. 21 13
      UnitTests/Application/ApplicationTests.cs
  68. 8 2
      UnitTests/Application/MainLoopTests.cs
  69. 0 10
      UnitTests/AssemblyInfo.cs
  70. 0 4
      UnitTests/Clipboard/ClipboardTests.cs
  71. 24 34
      UnitTests/Configuration/ConfigurationMangerTests.cs
  72. 53 10
      UnitTests/Configuration/JsonConverterTests.cs
  73. 0 6
      UnitTests/Configuration/SettingsScopeTests.cs
  74. 196 0
      UnitTests/ConsoleDrivers/AddRuneTests.cs
  75. 50 39
      UnitTests/ConsoleDrivers/AttributeTests.cs
  76. 116 0
      UnitTests/ConsoleDrivers/ClipRegionTests.cs
  77. 0 13
      UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
  78. 2 120
      UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs
  79. 129 0
      UnitTests/ConsoleDrivers/ContentsTests.cs
  80. 36 34
      UnitTests/ConsoleDrivers/KeyTests.cs
  81. 9 9
      UnitTests/FileServices/FileDialogTests.cs
  82. 32 32
      UnitTests/Input/EscSeqReqTests.cs
  83. 20 20
      UnitTests/Input/EscSeqUtilsTests.cs
  84. 102 124
      UnitTests/TestHelpers.cs
  85. 119 27
      UnitTests/Text/RuneTests.cs
  86. 3 1
      UnitTests/Text/StringTests.cs
  87. 6 5
      UnitTests/Text/UnicodeTests.cs
  88. 63 0
      UnitTests/UnitTests - Backup.csproj
  89. 9 4
      UnitTests/UnitTests.csproj
  90. 6 6
      UnitTests/View/DrawTests.cs
  91. 1 1
      UnitTests/View/KeyboardTests.cs
  92. 3 3
      UnitTests/View/Layout/LayoutTests.cs
  93. 25 20
      UnitTests/View/ViewTests.cs
  94. 1 1
      UnitTests/Views/ListViewTests.cs
  95. 570 570
      UnitTests/Views/ProgressBarTests.cs
  96. 4 1
      UnitTests/Views/TabViewTests.cs
  97. 1 1
      UnitTests/Views/TableViewTests.cs
  98. 1 1
      UnitTests/Views/TextFieldTests.cs
  99. 4 4
      UnitTests/Views/ToplevelTests.cs
  100. 5 4
      UnitTests/Views/TreeViewTests.cs

+ 3 - 1
.github/CODEOWNERS

@@ -2,7 +2,9 @@
 # the repo. Unless a later match takes precedence,
 # @global-owner1 and @global-owner2 will be requested for
 # review when someone opens a pull request.
-*       @migueldeicaza @tig
+* @tig
+
+/docs/ @tig @bdisp @tznind
 
 # Order is important; the last matching pattern takes the most
 # precedence. When someone opens a pull request that only

+ 1 - 0
.github/workflows/dotnet-core.yml

@@ -29,6 +29,7 @@ jobs:
 
     - name: Test
       run: |
+        sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
         dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 

+ 1 - 0
.github/workflows/publish.yml

@@ -50,6 +50,7 @@ jobs:
 
     #- name: Test to generate Code Coverage Report
     #  run: |
+    #    sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json
     #    dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings
     #    mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 

+ 1 - 0
.gitignore

@@ -15,6 +15,7 @@ docfx/api
 docs/
 
 UnitTests/TestResults
+TestResults
 
 #git merge files
 *.orig

+ 29 - 67
Terminal.Gui/Application.cs

@@ -6,7 +6,6 @@ using System.Globalization;
 using System.Reflection;
 using System.IO;
 using System.Text.Json.Serialization;
-using static Terminal.Gui.ConfigurationManager;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -55,42 +54,7 @@ namespace Terminal.Gui {
 
 		// For Unit testing - ignores UseSystemConsole
 		internal static bool _forceFakeConsole;
-
-		private static bool? _enableConsoleScrolling;
-		/// <summary>
-		/// The current <see cref="ConsoleDriver.EnableConsoleScrolling"/> used in the terminal.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// If <see langword="false"/> (the default) the height of the Terminal.Gui application (<see cref="ConsoleDriver.Rows"/>) 
-		/// tracks to the height of the visible console view when the console is resized. In this case 
-		/// scrolling in the console will be disabled and all <see cref="ConsoleDriver.Rows"/> will remain visible.
-		/// </para>
-		/// <para>
-		/// If <see langword="true"/> then height of the Terminal.Gui application <see cref="ConsoleDriver.Rows"/> only tracks 
-		/// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). 
-		/// In this case console scrolling is enabled and the contents (<see cref="ConsoleDriver.Rows"/> high) will scroll
-		/// as the console scrolls. 
-		/// </para>
-		/// This API was previously named 'HeightAsBuffer` but was renamed to make its purpose more clear.
-		/// </remarks>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static bool EnableConsoleScrolling {
-			get {
-				if (Driver == null) {
-					return _enableConsoleScrolling.HasValue && _enableConsoleScrolling.Value;
-				}
-				return Driver.EnableConsoleScrolling;
-			}
-			set {
-				_enableConsoleScrolling = value;
-				if (Driver == null) {
-					return;
-				}
-				Driver.EnableConsoleScrolling = value;
-			}
-		}
-
+		
 		private static List<CultureInfo> _cachedSupportedCultures;
 
 		/// <summary>
@@ -164,7 +128,9 @@ namespace Terminal.Gui {
 		// 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, IMainLoopDriver mainLoopDriver = null, bool calledViaRunT = false)
 		{
-			if (_initialized && driver == null) return;
+			if (_initialized && driver == null) {
+				return;
+			}
 
 			if (_initialized) {
 				throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
@@ -224,7 +190,6 @@ namespace Terminal.Gui {
 			MainLoop = new MainLoop (mainLoopDriver);
 
 			try {
-				Driver.EnableConsoleScrolling = EnableConsoleScrolling;
 				Driver.Init (OnTerminalResized);
 			} catch (InvalidOperationException ex) {
 				// This is a case where the driver is unable to initialize the console.
@@ -277,6 +242,7 @@ namespace Terminal.Gui {
 
 			// BUGBUG: OverlappedTop is not cleared here, but it should be?
 
+			MainLoop?.Stop();
 			MainLoop = null;
 			Driver?.End ();
 			Driver = null;
@@ -289,7 +255,6 @@ namespace Terminal.Gui {
 			NotifyStopRunState = null;
 			_initialized = false;
 			_mouseGrabView = null;
-			_enableConsoleScrolling = false;
 			_lastMouseOwnerView = null;
 
 			// Reset synchronization context to allow the user to run async/await,
@@ -552,7 +517,8 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static void Refresh ()
 		{
-			Driver.UpdateOffScreen ();
+			// TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
+			Driver.ClearContents();
 			View last = null;
 			foreach (var v in _toplevels.Reverse ()) {
 				if (v.Visible) {
@@ -674,13 +640,14 @@ namespace Terminal.Gui {
 				Iteration?.Invoke ();
 
 				EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-				if ((state.Toplevel != Current && Current?.Modal == true)
-					|| (state.Toplevel != Current && Current?.Modal == false)) {
+				if (state.Toplevel != Current) {
 					OverlappedTop?.OnDeactivate (state.Toplevel);
 					state.Toplevel = Current;
 					OverlappedTop?.OnActivate (state.Toplevel);
 					Top.SetSubViewNeedsDisplay ();
 					Refresh ();
+				} else if (Current.SuperView == null && Current?.Modal == true) {
+					Refresh ();
 				}
 				if (Driver.EnsureCursorVisibility ()) {
 					state.Toplevel.SetNeedsDisplay ();
@@ -690,42 +657,41 @@ namespace Terminal.Gui {
 			}
 			firstIteration = false;
 
-			if (state.Toplevel != Top
-				&& (!Top._needsDisplay.IsEmpty || Top._subViewNeedsDisplay || Top.LayoutNeeded)) {
-				state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
+			if (state.Toplevel != Top && 
+				(Top.NeedsDisplay|| Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
+				state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
+				Top.Clear ();
 				Top.Draw ();
 				foreach (var top in _toplevels.Reverse ()) {
 					if (top != Top && top != state.Toplevel) {
 						top.SetNeedsDisplay ();
 						top.SetSubViewNeedsDisplay ();
+						top.Clear ();
 						top.Draw ();
 					}
 				}
 			}
 			if (_toplevels.Count == 1 && state.Toplevel == Top
 				&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
-				&& (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._subViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
-
-				Driver.SetAttribute (Colors.TopLevel.Normal);
-				state.Toplevel.Clear (new Rect (0, 0, Driver.Cols, Driver.Rows));
+				&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
 
+				state.Toplevel.Clear ();
 			}
 
-			if (!state.Toplevel._needsDisplay.IsEmpty || state.Toplevel._subViewNeedsDisplay || state.Toplevel.LayoutNeeded
-				|| OverlappedChildNeedsDisplay ()) {
+			if (state.Toplevel.NeedsDisplay || 
+				state.Toplevel.SubViewNeedsDisplay || 
+				state.Toplevel.LayoutNeeded || 
+				OverlappedChildNeedsDisplay ()) {
+				state.Toplevel.Clear ();
 				state.Toplevel.Draw ();
-				//if (state.Toplevel.SuperView != null) {
-				//	state.Toplevel.SuperView?.OnRenderLineCanvas ();
-				//} else {
-				//	state.Toplevel.OnRenderLineCanvas ();
-				//}
 				state.Toplevel.PositionCursor ();
 				Driver.Refresh ();
 			} else {
 				Driver.UpdateCursor ();
 			}
-			if (state.Toplevel != Top && !state.Toplevel.Modal
-				&& (!Top._needsDisplay.IsEmpty || Top._subViewNeedsDisplay || Top.LayoutNeeded)) {
+			if (state.Toplevel != Top && 
+				!state.Toplevel.Modal &&
+				(Top.NeedsDisplay|| Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
 				Top.Draw ();
 			}
 		}
@@ -735,7 +701,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static void DoEvents ()
 		{
-			MainLoop.Driver.Wakeup ();
+			MainLoop.MainLoopDriver.Wakeup ();
 		}
 
 		/// <summary>
@@ -1011,7 +977,6 @@ namespace Terminal.Gui {
 		{
 			var full = new Rect (0, 0, Driver.Cols, Driver.Rows);
 			TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height });
-			Driver.Clip = full;
 			foreach (var t in _toplevels) {
 				t.SetRelativeLayout (full);
 				t.LayoutSubviews ();
@@ -1073,7 +1038,7 @@ namespace Terminal.Gui {
 			if (!OnGrabbingMouse (view)) {
 				OnGrabbedMouse (view);
 				_mouseGrabView = view;
-				Driver.UncookMouse ();
+				//Driver.UncookMouse ();
 			}
 		}
 
@@ -1087,7 +1052,7 @@ namespace Terminal.Gui {
 			if (!OnUnGrabbingMouse (_mouseGrabView)) {
 				OnUnGrabbedMouse (_mouseGrabView);
 				_mouseGrabView = null;
-				Driver.CookMouse ();
+				//Driver.CookMouse ();
 			}
 		}
 
@@ -1132,10 +1097,7 @@ namespace Terminal.Gui {
 
 		static void ProcessMouseEvent (MouseEvent me)
 		{
-			bool OutsideBounds (Point p, Rect r)
-			{
-				return p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom;
-			}
+			static bool OutsideBounds (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom;
 
 			if (IsMouseDisabled) {
 				return;

+ 134 - 62
Terminal.Gui/Clipboard/Clipboard.cs

@@ -1,31 +1,34 @@
 using System.Text;
 using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides cut, copy, and paste support for the OS clipboard.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.
-	/// </para>
-	/// <para>
-	/// On Linux, when not running under Windows Subsystem for Linux (WSL),
-	/// the <see cref="Clipboard"/> class uses the xclip command line tool. If xclip is not installed,
-	/// the clipboard will not work.
-	/// </para>
-	/// <para>
-	/// On Linux, when running under Windows Subsystem for Linux (WSL),
-	/// the <see cref="Clipboard"/> class launches Windows' powershell.exe via WSL interop and uses the
-	/// "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. 
-	/// </para>
-	/// <para>
-	/// On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools
-	/// and the Mac clipboard APIs vai P/Invoke.
-	/// </para>
-	/// </remarks>
-	public static class Clipboard {
-		static string contents;
+namespace Terminal.Gui;
+
+/// <summary>
+/// Provides cut, copy, and paste support for the OS clipboard.
+/// </summary>
+/// <remarks>
+/// <para>
+/// On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.
+/// </para>
+/// <para>
+/// On Linux, when not running under Windows Subsystem for Linux (WSL),
+/// the <see cref="Clipboard"/> class uses the xclip command line tool. If xclip is not installed,
+/// the clipboard will not work.
+/// </para>
+/// <para>
+/// On Linux, when running under Windows Subsystem for Linux (WSL),
+/// the <see cref="Clipboard"/> class launches Windows' powershell.exe via WSL interop and uses the
+/// "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. 
+/// </para>
+/// <para>
+/// On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools
+/// and the Mac clipboard APIs vai P/Invoke.
+/// </para>
+/// </remarks>
+public static class Clipboard {
+	static string _contents = string.Empty;
 
 		/// <summary>
 		/// Gets (copies from) or sets (pastes to) the contents of the OS clipboard.
@@ -39,12 +42,12 @@ namespace Terminal.Gui {
 							// throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
 							clipData = string.Empty;
 						}
-						contents = clipData;
+						_contents = clipData;
 					}
 				} catch (Exception) {
-					contents = string.Empty;
+					_contents = string.Empty;
 				}
-				return contents;
+				return _contents;
 			}
 			set {
 				try {
@@ -54,51 +57,120 @@ namespace Terminal.Gui {
 						}
 						Application.Driver.Clipboard.SetClipboardData (value);
 					}
-					contents = value;
+					_contents = value;
 				} catch (NotSupportedException e) {
 					throw e;
 				} catch (Exception) {
-					contents = value;
+					_contents = value;
 				}
 			}
 		}
 
-		/// <summary>
-		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; }
+	/// <summary>
+	/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; }
 
-		/// <summary>
-		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
-		/// </summary>
-		/// <param name="result">The contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
-		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
-		public static bool TryGetClipboardData (out string result)
-		{
-			if (IsSupported && Application.Driver.Clipboard.TryGetClipboardData (out result)) {
-				if (contents != result) {
-					contents = result;
-				}
-				return true;
+	/// <summary>
+	/// Copies the _contents of the OS clipboard to <paramref name="result"/> if possible.
+	/// </summary>
+	/// <param name="result">The _contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
+	/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
+	public static bool TryGetClipboardData (out string result)
+	{
+		if (IsSupported && Application.Driver.Clipboard.TryGetClipboardData (out result)) {
+			if (_contents != result) {
+				_contents = result;
 			}
-			result = string.Empty;
-			return false;
+			return true;
 		}
+		result = string.Empty;
+		return false;
+	}
 
-		/// <summary>
-		/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
-		/// </summary>
-		/// <param name="text">The text to paste to the OS clipboard.</param>
-		/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
-		public static bool TrySetClipboardData (string text)
-		{
-			if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) {
-				contents = text;
-				return true;
-			}
-			return false;
+	/// <summary>
+	/// Pastes the <paramref name="text"/> to the OS clipboard if possible.
+	/// </summary>
+	/// <param name="text">The text to paste to the OS clipboard.</param>
+	/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
+	public static bool TrySetClipboardData (string text)
+	{
+		if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text)) {
+			_contents = text;
+			return true;
 		}
+		return false;
 	}
 }
+
+/// <summary>
+/// Helper class for console drivers to invoke shell commands to interact with the clipboard.
+/// Used primarily by CursesDriver, but also used in Unit tests which is why it is in
+/// ConsoleDriver.cs.
+/// </summary>
+internal static class ClipboardProcessRunner {
+	public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false)
+	{
+		var arguments = $"-c \"{commandLine}\"";
+		var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput);
+
+		return (exitCode, result.TrimEnd ());
+	}
+
+	public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true)
+	{
+		var output = string.Empty;
+
+		using (Process process = new Process {
+			StartInfo = new ProcessStartInfo {
+				FileName = cmd,
+				Arguments = arguments,
+				RedirectStandardOutput = true,
+				RedirectStandardError = true,
+				RedirectStandardInput = true,
+				UseShellExecute = false,
+				CreateNoWindow = true,
+			}
+		}) {
+			var eventHandled = new TaskCompletionSource<bool> ();
+			process.Start ();
+			if (!string.IsNullOrEmpty (input)) {
+				process.StandardInput.Write (input);
+				process.StandardInput.Close ();
+			}
+
+			if (!process.WaitForExit (5000)) {
+				var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
+				throw new TimeoutException (timeoutError);
+			}
+
+			if (waitForOutput && process.StandardOutput.Peek () != -1) {
+				output = process.StandardOutput.ReadToEnd ();
+			}
+
+			if (process.ExitCode > 0) {
+				output = $@"Process failed to run. Command line: {cmd} {arguments}.
+										Output: {output}
+										Error: {process.StandardError.ReadToEnd ()}";
+			}
+
+			return (process.ExitCode, output);
+		}
+	}
+
+	public static bool DoubleWaitForExit (this System.Diagnostics.Process process)
+	{
+		var result = process.WaitForExit (500);
+		if (result) {
+			process.WaitForExit ();
+		}
+		return result;
+	}
+
+	public static bool FileExists (this string value)
+	{
+		return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
+	}
+}

+ 10 - 6
Terminal.Gui/Clipboard/ClipboardBase.cs

@@ -22,6 +22,10 @@ namespace Terminal.Gui {
 		public string GetClipboardData ()
 		{
 			try {
+				var result = GetClipboardDataImpl ();
+				if (result == null) {
+					return string.Empty;
+				}
 				return GetClipboardDataImpl ();
 			} catch (NotSupportedException ex) {
 				throw new NotSupportedException ("Failed to copy from the OS clipboard.", ex);
@@ -42,6 +46,10 @@ namespace Terminal.Gui {
 		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		public void SetClipboardData (string text)
 		{
+			if (text == null) {
+				throw new ArgumentNullException (nameof (text));
+			}
+
 			try {
 				SetClipboardDataImpl (text);
 			} catch (NotSupportedException ex) {
@@ -59,25 +67,21 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// </summary>
-		/// <param name="result">The contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
+		/// <param name="result">The contents of the OS clipboard if successful.</param>
 		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
 		public bool TryGetClipboardData (out string result)
 		{
+			result = string.Empty;
 			// Don't even try to read because environment is not set up.
 			if (!IsSupported) {
-				result = null;
 				return false;
 			}
 
 			try {
 				result = GetClipboardDataImpl ();
-				while (result == null) {
-					result = GetClipboardDataImpl ();
-				}
 				return true;
 			} catch (NotSupportedException ex) {
 				System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
-				result = null;
 				return false;
 			}
 		}

+ 0 - 4
Terminal.Gui/Clipboard/IClipboard.cs

@@ -1,8 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Terminal.Gui {
 	/// <summary>

+ 21 - 8
Terminal.Gui/Configuration/AttributeJsonConverter.cs

@@ -32,10 +32,11 @@ namespace Terminal.Gui {
 			Attribute attribute = new Attribute ();
 			Color foreground = (Color)(-1);
 			Color background = (Color)(-1);
-			int valuePair = 0;
+			TrueColor? trueColorForeground = null;
+			TrueColor? trueColorBackground = null;
 			while (reader.Read ()) {
 				if (reader.TokenType == JsonTokenType.EndObject) {
-					if (foreground == (Color)(-1) || background == (Color)(-1)) {
+					if (!attribute.TrueColorForeground.HasValue || !attribute.TrueColorBackground.HasValue) {
 						throw new JsonException ($"Both Foreground and Background colors must be provided.");
 					}
 					return attribute;
@@ -49,8 +50,6 @@ namespace Terminal.Gui {
 				reader.Read ();
 				string color = $"\"{reader.GetString ()}\"";
 
-				valuePair++;
-
 				switch (propertyName.ToLower ()) {
 				case "foreground":
 					foreground = JsonSerializer.Deserialize<Color> (color, options);
@@ -58,6 +57,12 @@ namespace Terminal.Gui {
 				case "background":
 					background = JsonSerializer.Deserialize<Color> (color, options);
 					break;
+				case "truecolorforeground":
+					trueColorForeground = JsonSerializer.Deserialize<TrueColor> (color, options);
+					break;
+				case "truecolorbackground":
+					trueColorBackground = JsonSerializer.Deserialize<TrueColor> (color, options);
+					break;
 				//case "Bright":
 				//	attribute.Bright = reader.GetBoolean ();
 				//	break;
@@ -71,9 +76,11 @@ namespace Terminal.Gui {
 					throw new JsonException ($"Unknown Attribute property {propertyName}.");
 				}
 
-				if (valuePair == 2) {
+				if (foreground != (Color)(-1) && background != (Color)(-1)) {
 					attribute = new Attribute (foreground, background);
-					valuePair = 0;
+				}
+				if (trueColorForeground.HasValue && trueColorBackground.HasValue) {
+					attribute = new Attribute (trueColorForeground, trueColorBackground);
 				}
 			}
 			throw new JsonException ();
@@ -82,10 +89,16 @@ namespace Terminal.Gui {
 		public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
 		{
 			writer.WriteStartObject ();
-			writer.WritePropertyName ("Foreground");
+			writer.WritePropertyName (nameof(Attribute.Foreground));
 			ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
-			writer.WritePropertyName ("Background");
+			writer.WritePropertyName (nameof (Attribute.Background));
 			ColorJsonConverter.Instance.Write (writer, value.Background, options);
+			if (value.TrueColorForeground.HasValue && value.TrueColorBackground.HasValue) {
+				writer.WritePropertyName (nameof (Attribute.TrueColorForeground));
+				TrueColorJsonConverter.Instance.Write (writer, value.TrueColorForeground.Value, options);
+				writer.WritePropertyName (nameof (Attribute.TrueColorBackground));
+				TrueColorJsonConverter.Instance.Write (writer, value.TrueColorBackground.Value, options);
+			}
 			writer.WriteEndObject ();
 		}
 	}

+ 1 - 1
Terminal.Gui/Configuration/ColorJsonConverter.cs

@@ -41,7 +41,7 @@ namespace Terminal.Gui {
 						var r = int.Parse (match.Groups [1].Value);
 						var g = int.Parse (match.Groups [2].Value);
 						var b = int.Parse (match.Groups [3].Value);
-						return new TrueColor (r, g, b).ToConsoleColor ();
+						return TrueColor.ToConsoleColor(new TrueColor (r, g, b));
 					} else {
 						throw new JsonException ($"Invalid Color: '{colorString}'");
 					}

+ 2 - 1
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -1,4 +1,5 @@
-global using CM = Terminal.Gui.ConfigurationManager;
+global using static Terminal.Gui.ConfigurationManager;
+global using CM = Terminal.Gui.ConfigurationManager;
 
 using System;
 using System.Collections;

+ 3 - 3
Terminal.Gui/Configuration/ThemeScope.cs

@@ -2,7 +2,7 @@
 
 #nullable enable
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 /// The root object for a Theme. A Theme is a set of settings that are applied to the running <see cref="Application"/>
@@ -49,7 +49,7 @@ public class ThemeScope : Scope<ThemeScope> {
 	internal override bool Apply ()
 	{
 		var ret = base.Apply ();
-		Application.Driver?.InitalizeColorSchemes ();
+		Application.Driver?.InitializeColorSchemes ();
 		return ret;
 	}
-}
+}

+ 47 - 0
Terminal.Gui/Configuration/TrueColorJsonConverter.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// <see cref="JsonConverter{T}"/> for <see cref="TrueColor"/>.
+	/// </summary>
+	internal class TrueColorJsonConverter : JsonConverter<TrueColor> {
+		private static TrueColorJsonConverter instance;
+
+		/// <summary>
+		/// Singleton
+		/// </summary>
+		public static TrueColorJsonConverter Instance {
+			get {
+				if (instance == null) {
+					instance = new TrueColorJsonConverter ();
+				}
+
+				return instance;
+			}
+		}
+
+		public override TrueColor Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+		{
+			// Check if the value is a string
+			if (reader.TokenType == JsonTokenType.String) {
+				// Get the color string
+				var colorString = reader.GetString ();
+
+				if (!TrueColor.TryParse (colorString, out TrueColor? trueColor)) {
+					throw new JsonException ($"Invalid TrueColor: '{colorString}'");
+				}
+
+				return trueColor.Value;
+			} else {
+				throw new JsonException ($"Unexpected token when parsing TrueColor: {reader.TokenType}");
+			}
+		}
+
+		public override void Write (Utf8JsonWriter writer, TrueColor value, JsonSerializerOptions options)
+		{
+			writer.WriteStringValue (value.ToString ());
+		}
+	}
+}

+ 477 - 1025
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,1135 +1,587 @@
-//
+//
 // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
 using System.Text;
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Diagnostics;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-using Terminal.Gui;
-using static Terminal.Gui.ConfigurationManager;
-
-namespace Terminal.Gui {
+using static Terminal.Gui.ColorScheme;
+
+namespace Terminal.Gui;
+
+
+/// <summary>
+/// Base class for Terminal.Gui ConsoleDriver implementations.
+/// </summary>
+/// <remarks>
+/// There are currently four implementations:
+/// - <see cref="CursesDriver"/> (for Unix and Mac)
+/// - <see cref="WindowsDriver"/>
+/// - <see cref="NetDriver"/> that uses the .NET Console API
+/// - <see cref="FakeConsole"/> for unit testing.
+/// </remarks>
+public abstract class ConsoleDriver {
 	/// <summary>
-	/// Colors that can be used to set the foreground and background colors in console applications.
+	/// Prepare the driver and set the key and mouse events handlers.
 	/// </summary>
-	/// <remarks>
-	/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
-	/// </remarks>
-	[JsonConverter (typeof (ColorJsonConverter))]
-	public enum Color {
-		/// <summary>
-		/// The black color.
-		/// </summary>
-		Black,
-		/// <summary>
-		/// The blue color.
-		/// </summary>
-		Blue,
-		/// <summary>
-		/// The green color.
-		/// </summary>
-		Green,
-		/// <summary>
-		/// The cyan color.
-		/// </summary>
-		Cyan,
-		/// <summary>
-		/// The red color.
-		/// </summary>
-		Red,
-		/// <summary>
-		/// The magenta color.
-		/// </summary>
-		Magenta,
-		/// <summary>
-		/// The brown color.
-		/// </summary>
-		Brown,
-		/// <summary>
-		/// The gray color.
-		/// </summary>
-		Gray,
-		/// <summary>
-		/// The dark gray color.
-		/// </summary>
-		DarkGray,
-		/// <summary>
-		/// The bright bBlue color.
-		/// </summary>
-		BrightBlue,
-		/// <summary>
-		/// The bright green color.
-		/// </summary>
-		BrightGreen,
-		/// <summary>
-		/// The bright cyan color.
-		/// </summary>
-		BrightCyan,
-		/// <summary>
-		/// The bright red color.
-		/// </summary>
-		BrightRed,
-		/// <summary>
-		/// The bright magenta color.
-		/// </summary>
-		BrightMagenta,
-		/// <summary>
-		/// The bright yellow color.
-		/// </summary>
-		BrightYellow,
-		/// <summary>
-		/// The White color.
-		/// </summary>
-		White
-	}
+	/// <param name="mainLoop">The main loop.</param>
+	/// <param name="keyHandler">The handler for ProcessKey</param>
+	/// <param name="keyDownHandler">The handler for key down events</param>
+	/// <param name="keyUpHandler">The handler for key up events</param>
+	/// <param name="mouseHandler">The handler for mouse events</param>
+	public abstract void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler);
 
 	/// <summary>
-	/// Indicates the RGB for true colors.
+	/// The handler fired when the terminal is resized.
 	/// </summary>
-	public class TrueColor {
-		/// <summary>
-		/// Red color component.
-		/// </summary>
-		public int Red { get; }
-		/// <summary>
-		/// Green color component.
-		/// </summary>
-		public int Green { get; }
-		/// <summary>
-		/// Blue color component.
-		/// </summary>
-		public int Blue { get; }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="TrueColor"/> struct.
-		/// </summary>
-		/// <param name="red"></param>
-		/// <param name="green"></param>
-		/// <param name="blue"></param>
-		public TrueColor (int red, int green, int blue)
-		{
-			Red = red;
-			Green = green;
-			Blue = blue;
-		}
-
-		/// <summary>
-		/// Converts true color to console color.
-		/// </summary>
-		/// <returns></returns>
-		public Color ToConsoleColor ()
-		{
-			var trueColorMap = new Dictionary<TrueColor, Color> () {
-				{ new TrueColor (0,0,0),Color.Black},
-				{ new TrueColor (0, 0, 0x80),Color.Blue},
-				{ new TrueColor (0, 0x80, 0),Color.Green},
-				{ new TrueColor (0, 0x80, 0x80),Color.Cyan},
-				{ new TrueColor (0x80, 0, 0),Color.Red},
-				{ new TrueColor (0x80, 0, 0x80),Color.Magenta},
-				{ new TrueColor (0xC1, 0x9C, 0x00),Color.Brown},  // TODO confirm this
-				{ new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray},
-				{ new TrueColor (0x80, 0x80, 0x80),Color.DarkGray},
-				{ new TrueColor (0, 0, 0xFF),Color.BrightBlue},
-				{ new TrueColor (0, 0xFF, 0),Color.BrightGreen},
-				{ new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan},
-				{ new TrueColor (0xFF, 0, 0),Color.BrightRed},
-				{ new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta },
-				{ new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow},
-				{ new TrueColor (0xFF, 0xFF, 0xFF),Color.White},
-				};
-			// Iterate over all colors in the map
-			var distances = trueColorMap.Select (
-							k => Tuple.Create (
-								// the candidate we are considering matching against (RGB)
-								k.Key,
-
-								CalculateDistance (k.Key, this)
-							));
-
-			// get the closest
-			var match = distances.OrderBy (t => t.Item2).First ();
-			return trueColorMap [match.Item1];
-		}
-
-		private float CalculateDistance (TrueColor color1, TrueColor color2)
-		{
-			// use RGB distance
-			return
-				Math.Abs (color1.Red - color2.Red) +
-				Math.Abs (color1.Green - color2.Green) +
-				Math.Abs (color1.Blue - color2.Blue);
-		}
-	}
+	protected Action TerminalResized;
 
 	/// <summary>
-	/// Attributes are used as elements that contain both a foreground and a background or platform specific features.
+	/// The number of columns visible in the terminal.
 	/// </summary>
-	/// <remarks>
-	///   <see cref="Attribute"/>s are needed to map colors to terminal capabilities that might lack colors. 
-	///   They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
-	///   class to define color schemes that can be used in an application.
-	/// </remarks>
-	[JsonConverter (typeof (AttributeJsonConverter))]
-	public struct Attribute {
-		/// <summary>
-		/// The <see cref="ConsoleDriver"/>-specific color attribute value. If <see cref="Initialized"/> is <see langword="false"/> 
-		/// the value of this property is invalid (typically because the Attribute was created before a driver was loaded)
-		/// and the attribute should be re-made (see <see cref="Make(Color, Color)"/>) before it is used.
-		/// </summary>
-		[JsonIgnore (Condition = JsonIgnoreCondition.Always)]
-		public int Value { get; }
+	public virtual int Cols { get; internal set; }
 
-		/// <summary>
-		/// The foreground color.
-		/// </summary>
-		[JsonConverter (typeof (ColorJsonConverter))]
-		public Color Foreground { get; }
-
-		/// <summary>
-		/// The background color.
-		/// </summary>
-		[JsonConverter (typeof (ColorJsonConverter))]
-		public Color Background { get; }
+	/// <summary>
+	/// The number of rows visible in the terminal.
+	/// </summary>
+	public virtual int Rows { get; internal set; }
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Attribute"/> struct with only the value passed to
-		///   and trying to get the colors if defined.
-		/// </summary>
-		/// <param name="value">Value.</param>
-		public Attribute (int value)
-		{
-			Color foreground = default;
-			Color background = default;
-
-			Initialized = false;
-			if (Application.Driver != null) {
-				Application.Driver.GetColors (value, out foreground, out background);
-				Initialized = true;
-			}
-			Value = value;
-			Foreground = foreground;
-			Background = background;
-		}
+	/// <summary>
+	/// The leftmost column in the terminal.
+	/// </summary>
+	public virtual int Left { get; internal set; } = 0;
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Attribute"/> struct.
-		/// </summary>
-		/// <param name="value">Value.</param>
-		/// <param name="foreground">Foreground</param>
-		/// <param name="background">Background</param>
-		public Attribute (int value, Color foreground, Color background)
-		{
-			Value = value;
-			Foreground = foreground;
-			Background = background;
-			Initialized = true;
-		}
+	/// <summary>
+	/// The topmost row in the terminal.
+	/// </summary>
+	public virtual int Top { get; internal set; } = 0;
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Attribute"/> struct.
-		/// </summary>
-		/// <param name="foreground">Foreground</param>
-		/// <param name="background">Background</param>
-		public Attribute (Color foreground = new Color (), Color background = new Color ())
-		{
-			var make = Make (foreground, background);
-			Initialized = make.Initialized;
-			Value = make.Value;
-			Foreground = foreground;
-			Background = background;
-		}
+	/// <summary>
+	/// Get the operating system clipboard.
+	/// </summary>
+	public IClipboard Clipboard { get; internal set; }
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Attribute"/> struct
-		///  with the same colors for the foreground and background.
-		/// </summary>
-		/// <param name="color">The color.</param>
-		public Attribute (Color color) : this (color, color) { }
+	/// <summary>
+	/// The contents of the application output. The driver outputs this buffer to the terminal when <see cref="UpdateScreen"/>
+	/// is called.
+	/// <remarks>
+	/// The format of the array is rows, columns, and 3 values on the last column: Rune, Attribute and Dirty Flag
+	/// </remarks>
+	/// </summary>
+	//public int [,,] Contents { get; internal set; }
 
-		/// <summary>
-		/// Implicit conversion from an <see cref="Attribute"/> to the underlying, driver-specific, Int32 representation
-		/// of the color.
-		/// </summary>
-		/// <returns>The driver-specific color value stored in the attribute.</returns>
-		/// <param name="c">The attribute to convert</param>
-		public static implicit operator int (Attribute c)
-		{
-			if (!c.Initialized) throw new InvalidOperationException ("Attribute: Attributes must be initialized by a driver before use.");
-			return c.Value;
-		}
+	///// <summary>
+	///// The contents of the application output. The driver outputs this buffer to the terminal when <see cref="UpdateScreen"/>
+	///// is called.
+	///// <remarks>
+	///// The format of the array is rows, columns. The first index is the row, the second index is the column.
+	///// </remarks>
+	///// </summary>
+	public Cell [,] Contents { get; internal set; }
 
-		/// <summary>
-		/// Implicitly convert an driver-specific color value into an <see cref="Attribute"/>
-		/// </summary>
-		/// <returns>An attribute with the specified driver-specific color value.</returns>
-		/// <param name="v">value</param>
-		public static implicit operator Attribute (int v) => new Attribute (v);
+	/// <summary>
+	/// Initializes the driver
+	/// </summary>
+	/// <param name="terminalResized">Method to invoke when the terminal is resized.</param>
+	public abstract void Init (Action terminalResized);
 
-		/// <summary>
-		/// Creates an <see cref="Attribute"/> from the specified foreground and background colors.
-		/// </summary>
-		/// <remarks>
-		/// If a <see cref="ConsoleDriver"/> has not been loaded (<c>Application.Driver == null</c>) this
-		/// method will return an attribute with <see cref="Initialized"/> set to  <see langword="false"/>.
-		/// </remarks>
-		/// <returns>The new attribute.</returns>
-		/// <param name="foreground">Foreground color to use.</param>
-		/// <param name="background">Background color to use.</param>
-		public static Attribute Make (Color foreground, Color background)
-		{
-			if (Application.Driver == null) {
-				// Create the attribute, but show it's not been initialized
-				return new Attribute (-1, foreground, background) {
-					Initialized = false
-				};
-			}
-			return Application.Driver.MakeAttribute (foreground, background);
-		}
+	/// <summary>
+	/// Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/>
+	/// are used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+	/// </summary>
+	public int Col { get; internal set; }
 
-		/// <summary>
-		/// Gets the current <see cref="Attribute"/> from the driver.
-		/// </summary>
-		/// <returns>The current attribute.</returns>
-		public static Attribute Get ()
-		{
-			if (Application.Driver == null)
-				throw new InvalidOperationException ("The Application has not been initialized");
-			return Application.Driver.GetAttribute ();
-		}
+	/// <summary>
+	/// Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/>
+	/// are used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+	/// </summary>
+	public int Row { get; internal set; }
 
-		/// <summary>
-		/// If <see langword="true"/> the attribute has been initialized by a <see cref="ConsoleDriver"/> and 
-		/// thus has <see cref="Value"/> that is valid for that driver. If <see langword="false"/> the <see cref="Foreground"/>
-		/// and <see cref="Background"/> colors may have been set '-1' but
-		/// the attribute has not been mapped to a <see cref="ConsoleDriver"/> specific color value.
-		/// </summary>
-		/// <remarks>
-		/// Attributes that have not been initialized must eventually be initialized before being passed to a driver.
-		/// </remarks>
-		[JsonIgnore]
-		public bool Initialized { get; internal set; }
+	/// <summary>
+	/// Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
+	/// Used by <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// This does not move the cursor on the screen, it only updates the internal state of the driver.
+	/// </para>
+	/// <para>
+	/// If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="Cols"/> and <see cref="Rows"/>,
+	/// the method still sets those properties.
+	/// </para>
+	/// </remarks>
+	/// <param name="col">Column to move to.</param>
+	/// <param name="row">Row to move to.</param>
+	public virtual void Move (int col, int row)
+	{
+		Col = col;
+		Row = row;
+	}
 
-		/// <summary>
-		/// Returns <see langword="true"/> if the Attribute is valid (both foreground and background have valid color values).
-		/// </summary>
-		/// <returns></returns>
-		[JsonIgnore]
-		public bool HasValidColors { get => (int)Foreground > -1 && (int)Background > -1; }
+	/// <summary>
+	/// Tests if the specified rune is supported by the driver.
+	/// </summary>
+	/// <param name="rune"></param>
+	/// <returns><see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver
+	/// does not support displaying this rune.</returns>
+	public virtual bool IsRuneSupported (Rune rune)
+	{
+		return Rune.IsValid (rune.Value);
 	}
 
 	/// <summary>
-	/// Defines the color <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>. 
-	/// Containers such as <see cref="Window"/> and <see cref="FrameView"/> use <see cref="ColorScheme"/> to determine
-	/// the colors used by sub-views.
+	/// Adds the specified rune to the display at the current cursor position. 
 	/// </summary>
 	/// <remarks>
-	/// See also: <see cref="Colors.ColorSchemes"/>.
+	/// <para>
+	/// When the method returns, <see cref="Col"/> will be incremented by the number of columns <paramref name="rune"/> required,
+	/// even if the new column value is outside of the <see cref="Clip"/> or screen dimensions defined by <see cref="Cols"/>.
+	/// </para>
+	/// <para>
+	/// If <paramref name="rune"/> requires more than one column, and <see cref="Col"/> plus the number of columns needed
+	/// exceeds the <see cref="Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD) will be added instead.
+	/// </para>
 	/// </remarks>
-	[JsonConverter (typeof (ColorSchemeJsonConverter))]
-	public class ColorScheme : IEquatable<ColorScheme> {
-		Attribute _normal = new Attribute (Color.White, Color.Black);
-		Attribute _focus = new Attribute (Color.White, Color.Black);
-		Attribute _hotNormal = new Attribute (Color.White, Color.Black);
-		Attribute _hotFocus = new Attribute (Color.White, Color.Black);
-		Attribute _disabled = new Attribute (Color.White, Color.Black);
-
-		/// <summary>
-		/// Used by <see cref="Colors.SetColorScheme(ColorScheme, string)"/> and <see cref="Colors.GetColorScheme(string)"/> to track which ColorScheme 
-		/// is being accessed.
-		/// </summary>
-		internal string schemeBeingSet = "";
-
-		/// <summary>
-		/// Creates a new instance.
-		/// </summary>
-		public ColorScheme () { }
-
-		/// <summary>
-		/// Creates a new instance, initialized with the values from <paramref name="scheme"/>.
-		/// </summary>
-		/// <param name="scheme">The scheme to initialize the new instance with.</param>
-		public ColorScheme (ColorScheme scheme) : base ()
-		{
-			if (scheme != null) {
-				_normal = scheme.Normal;
-				_focus = scheme.Focus;
-				_hotNormal = scheme.HotNormal;
-				_disabled = scheme.Disabled;
-				_hotFocus = scheme.HotFocus;
-			}
-		}
-
-		/// <summary>
-		/// Creates a new instance, initialized with the values from <paramref name="attribute"/>.
-		/// </summary>
-		/// <param name="attribute">The attribute to initialize the new instance with.</param>
-		public ColorScheme (Attribute attribute)
-		{
-			_normal = attribute;
-			_focus = attribute;
-			_hotNormal = attribute;
-			_disabled = attribute;
-			_hotFocus = attribute;
-		}
-
-		/// <summary>
-		/// The foreground and background color for text when the view is not focused, hot, or disabled.
-		/// </summary>
-		public Attribute Normal {
-			get { return _normal; }
-			set {
-				if (!value.HasValidColors) {
-					return;
+	/// <param name="rune">Rune to add.</param>
+	public void AddRune (Rune rune)
+	{
+		int runeWidth = -1;
+		var validLocation = IsValidLocation (Col, Row);
+		if (validLocation) {
+			rune = rune.MakePrintable ();
+			runeWidth = rune.GetColumns ();
+			if (runeWidth == 0 && rune.IsCombiningMark () && Col > 0) {
+				// This is a combining character, and we are not at the beginning of the line.
+				// TODO: Remove hard-coded [0] once combining pairs is supported
+
+				// Convert Runes to string and concatenate
+				string combined = Contents [Row, Col - 1].Runes [0].ToString () + rune.ToString ();
+
+				// Normalize to Form C (Canonical Composition)
+				string normalized = combined.Normalize (NormalizationForm.FormC);
+
+				Contents [Row, Col - 1].Runes = new List<Rune> { (Rune)normalized [0] }; ;
+				Contents [Row, Col - 1].Attribute = CurrentAttribute;
+				Contents [Row, Col - 1].IsDirty = true;
+
+				//Col--;
+			} else {
+				Contents [Row, Col].Attribute = CurrentAttribute;
+				Contents [Row, Col].IsDirty = true;
+
+				if (Col > 0) {
+					// Check if cell to left has a wide glyph
+					if (Contents [Row, Col - 1].Runes [0].GetColumns () > 1) {
+						// Invalidate cell to left
+						Contents [Row, Col - 1].Runes = new List<Rune> { Rune.ReplacementChar };
+						Contents [Row, Col - 1].IsDirty = true;
+					}
 				}
-				_normal = value;
-			}
-		}
 
-		/// <summary>
-		/// The foreground and background color for text when the view has the focus.
-		/// </summary>
-		public Attribute Focus {
-			get { return _focus; }
-			set {
-				if (!value.HasValidColors) {
-					return;
-				}
-				_focus = value;
-			}
-		}
 
-		/// <summary>
-		/// The foreground and background color for text when the view is highlighted (hot).
-		/// </summary>
-		public Attribute HotNormal {
-			get { return _hotNormal; }
-			set {
-				if (!value.HasValidColors) {
-					return;
+				if (runeWidth < 1) {
+					Contents [Row, Col].Runes = new List<Rune> { Rune.ReplacementChar };
+
+				} else if (runeWidth == 1) {
+					Contents [Row, Col].Runes = new List<Rune> { rune };
+					if (Col < Clip.Right - 1) {
+						Contents [Row, Col + 1].IsDirty = true;
+					}
+				} else if (runeWidth == 2) {
+					if (Col == Clip.Right - 1) {
+						// We're at the right edge of the clip, so we can't display a wide character.
+						// TODO: Figure out if it is better to show a replacement character or ' '
+						Contents [Row, Col].Runes = new List<Rune> { Rune.ReplacementChar };
+					} else {
+						Contents [Row, Col].Runes = new List<Rune> { rune };
+						if (Col < Clip.Right - 1) {
+							// Invalidate cell to right so that it doesn't get drawn
+							// TODO: Figure out if it is better to show a replacement character or ' '
+							Contents [Row, Col + 1].Runes = new List<Rune> { Rune.ReplacementChar };
+							Contents [Row, Col + 1].IsDirty = true;
+						}
+					}
+				} else {
+					// This is a non-spacing character, so we don't need to do anything
+					Contents [Row, Col].Runes = new List<Rune> { (Rune)' ' };
+					Contents [Row, Col].IsDirty = false;
 				}
-				_hotNormal = value;
+				_dirtyLines [Row] = true;
 			}
 		}
 
-		/// <summary>
-		/// The foreground and background color for text when the view is highlighted (hot) and has focus.
-		/// </summary>
-		public Attribute HotFocus {
-			get { return _hotFocus; }
-			set {
-				if (!value.HasValidColors) {
-					return;
-				}
-				_hotFocus = value;
-			}
+		if (runeWidth is < 0 or > 0) {
+			Col++;
 		}
 
-		/// <summary>
-		/// The default foreground and background color for text, when the view is disabled.
-		/// </summary>
-		public Attribute Disabled {
-			get { return _disabled; }
-			set {
-				if (!value.HasValidColors) {
-					return;
-				}
-				_disabled = value;
-			}
-		}
+		if (runeWidth > 1) {
+			Debug.Assert (runeWidth <= 2);
+			if (validLocation && Col < Clip.Right) {
+				// This is a double-width character, and we are not at the end of the line.
+				// Col now points to the second column of the character. Ensure it doesn't
+				// Get rendered.
+				Contents [Row, Col].IsDirty = false;
+				Contents [Row, Col].Attribute = CurrentAttribute;
 
-		/// <summary>
-		/// Compares two <see cref="ColorScheme"/> objects for equality.
-		/// </summary>
-		/// <param name="obj"></param>
-		/// <returns>true if the two objects are equal</returns>
-		public override bool Equals (object obj)
-		{
-			return Equals (obj as ColorScheme);
-		}
-
-		/// <summary>
-		/// Compares two <see cref="ColorScheme"/> objects for equality.
-		/// </summary>
-		/// <param name="other"></param>
-		/// <returns>true if the two objects are equal</returns>
-		public bool Equals (ColorScheme other)
-		{
-			return other != null &&
-			       EqualityComparer<Attribute>.Default.Equals (_normal, other._normal) &&
-			       EqualityComparer<Attribute>.Default.Equals (_focus, other._focus) &&
-			       EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal) &&
-			       EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus) &&
-			       EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
-		}
-
-		/// <summary>
-		/// Returns a hashcode for this instance.
-		/// </summary>
-		/// <returns>hashcode for this instance</returns>
-		public override int GetHashCode ()
-		{
-			int hashCode = -1242460230;
-			hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
-			hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
-			hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
-			hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
-			hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
-			return hashCode;
-		}
-
-		/// <summary>
-		/// Compares two <see cref="ColorScheme"/> objects for equality.
-		/// </summary>
-		/// <param name="left"></param>
-		/// <param name="right"></param>
-		/// <returns><c>true</c> if the two objects are equivalent</returns>
-		public static bool operator == (ColorScheme left, ColorScheme right)
-		{
-			return EqualityComparer<ColorScheme>.Default.Equals (left, right);
-		}
-
-		/// <summary>
-		/// Compares two <see cref="ColorScheme"/> objects for inequality.
-		/// </summary>
-		/// <param name="left"></param>
-		/// <param name="right"></param>
-		/// <returns><c>true</c> if the two objects are not equivalent</returns>
-		public static bool operator != (ColorScheme left, ColorScheme right)
-		{
-			return !(left == right);
-		}
-
-		internal void Initialize ()
-		{
-			// If the new scheme was created before a driver was loaded, we need to re-make
-			// the attributes
-			if (!_normal.Initialized) {
-				_normal = new Attribute (_normal.Foreground, _normal.Background);
-			}
-			if (!_focus.Initialized) {
-				_focus = new Attribute (_focus.Foreground, _focus.Background);
-			}
-			if (!_hotNormal.Initialized) {
-				_hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background);
-			}
-			if (!_hotFocus.Initialized) {
-				_hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background);
-			}
-			if (!_disabled.Initialized) {
-				_disabled = new Attribute (_disabled.Foreground, _disabled.Background);
+				// TODO: Determine if we should wipe this out (for now now)
+				//Contents [Row, Col].Runes [0] = (Rune)' ';
 			}
+			Col++;
 		}
 	}
 
 	/// <summary>
-	/// The default <see cref="ColorScheme"/>s for the application.
+	/// Adds the specified <see langword="char"/> to the display at the current cursor position. This method
+	/// is a convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
+	/// </summary>
+	/// <param name="c">Character to add.</param>
+	public void AddRune (char c) => AddRune (new Rune (c));
+
+	/// <summary>
+	/// Adds the <paramref name="str"/> to the display at the cursor position.
 	/// </summary>
 	/// <remarks>
-	/// This property can be set in a Theme to change the default <see cref="Colors"/> for the application.
+	/// <para>
+	/// When the method returns, <see cref="Col"/> will be incremented by the number of columns <paramref name="str"/> required,
+	/// unless the new column value is outside of the <see cref="Clip"/> or screen dimensions defined by <see cref="Cols"/>.
+	/// </para>
+	/// <para>
+	/// If <paramref name="str"/> requires more columns than are available, the output will be clipped.
+	/// </para>
 	/// </remarks>
-	public static class Colors {
-		private class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
-			public bool Equals (string x, string y)
-			{
-				if (x != null && y != null) {
-					return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
-				}
-				return false;
-			}
-
-			public int GetHashCode (string obj)
-			{
-				return obj.ToLowerInvariant ().GetHashCode ();
-			}
-		}
-
-		static Colors ()
-		{
-			ColorSchemes = Create ();
-		}
-
-		/// <summary>
-		/// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
-		/// </summary>
-		public static Dictionary<string, ColorScheme> Create ()
-		{
-			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
-			// by the class. 
-			return typeof (Colors).GetProperties ()
-				.Where (p => p.PropertyType == typeof (ColorScheme))
-				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
-				.ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ());
-		}
-
-		/// <summary>
-		/// The application Toplevel color scheme, for the default Toplevel views.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["TopLevel"];</c>
-		/// </para>
-		/// </remarks>
-		public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
-
-		/// <summary>
-		/// The base color scheme, for the default Toplevel views.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Base"];</c>
-		/// </para>
-		/// </remarks>
-		public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
-
-		/// <summary>
-		/// The dialog color scheme, for standard popup dialog boxes
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Dialog"];</c>
-		/// </para>
-		/// </remarks>
-		public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
-
-		/// <summary>
-		/// The menu bar color
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Menu"];</c>
-		/// </para>
-		/// </remarks>
-		public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); }
-
-		/// <summary>
-		/// The color scheme for showing errors.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Error"];</c>
-		/// </para>
-		/// </remarks>
-		public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
-
-		static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null)
-		{
-			return ColorSchemes [schemeBeingSet];
-		}
-
-		static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
-		{
-			ColorSchemes [schemeBeingSet] = colorScheme;
-			colorScheme.schemeBeingSet = schemeBeingSet;
+	/// <param name="str">String.</param>
+	public void AddStr (string str)
+	{
+		foreach (var rune in str.EnumerateRunes ()) {
+			AddRune (rune);
 		}
-
-		/// <summary>
-		/// Provides the defined <see cref="ColorScheme"/>s.
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
-		[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
-		public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
 	}
 
+	Rect _clip;
+
 	/// <summary>
-	/// Cursors Visibility that are displayed
+	/// Tests whether the specified coordinate are valid for drawing. 
 	/// </summary>
-	// 
-	// Hexa value are set as 0xAABBCCDD where :
-	//
-	//     AA stand for the TERMINFO DECSUSR parameter value to be used under Linux & MacOS
-	//     BB stand for the NCurses curs_set parameter value to be used under Linux & MacOS
-	//     CC stand for the CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows
-	//     DD stand for the CONSOLE_CURSOR_INFO.dwSize parameter value to be used under Windows
-	//
-	public enum CursorVisibility {
-		/// <summary>
-		///	Cursor caret has default
-		/// </summary>
-		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking.</remarks>
-		Default = 0x00010119,
-
-		/// <summary>
-		///	Cursor caret is hidden
-		/// </summary>
-		Invisible = 0x03000019,
-
-		/// <summary>
-		///	Cursor caret is normally shown as a blinking underline bar _
-		/// </summary>
-		Underline = 0x03010119,
-
-		/// <summary>
-		///	Cursor caret is normally shown as a underline bar _
-		/// </summary>
-		/// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
-		UnderlineFix = 0x04010119,
-
-		/// <summary>
-		///	Cursor caret is displayed a blinking vertical bar |
-		/// </summary>
-		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-		Vertical = 0x05010119,
-
-		/// <summary>
-		///	Cursor caret is displayed a blinking vertical bar |
-		/// </summary>
-		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
-		VerticalFix = 0x06010119,
-
-		/// <summary>
-		///	Cursor caret is displayed as a blinking block ▉
-		/// </summary>
-		Box = 0x01020164,
+	/// <param name="col">The column.</param>
+	/// <param name="row">The row.</param>
+	/// <returns><see langword="false"/> if the coordinate is outside of the
+	/// screen bounds or outside of <see cref="Clip"/>. <see langword="true"/> otherwise.</returns>
+	public bool IsValidLocation (int col, int row) =>
+		col >= 0 && row >= 0 &&
+		col < Cols && row < Rows &&
+		Clip.Contains (col, row);
 
-		/// <summary>
-		///	Cursor caret is displayed a block ▉
-		/// </summary>
-		/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
-		BoxFix = 0x02020164,
+	/// <summary>
+	/// Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are 
+	/// subject to.
+	/// </summary>
+	/// <value>The rectangle describing the bounds of <see cref="Clip"/>.</value>
+	public Rect Clip {
+		get => _clip;
+		set => _clip = value;
 	}
 
 	/// <summary>
-	/// ConsoleDriver is an abstract class that defines the requirements for a console driver.  
-	/// There are currently three implementations: <see cref="CursesDriver"/> (for Unix and Mac), <see cref="WindowsDriver"/>, and <see cref="NetDriver"/> that uses the .NET Console API.
+	/// Updates the screen to reflect all the changes that have been done to the display buffer
 	/// </summary>
-	public abstract class ConsoleDriver {
-
-		/// <summary>
-		/// The handler fired when the terminal is resized.
-		/// </summary>
-		protected Action TerminalResized;
-
-		/// <summary>
-		/// The current number of columns in the terminal.
-		/// </summary>
-		public abstract int Cols { get; }
+	public abstract void Refresh ();
 
-		/// <summary>
-		/// The current number of rows in the terminal.
-		/// </summary>
-		public abstract int Rows { get; }
-
-		/// <summary>
-		/// The current left in the terminal.
-		/// </summary>
-		public abstract int Left { get; }
-
-		/// <summary>
-		/// The current top in the terminal.
-		/// </summary>
-		public abstract int Top { get; }
+	/// <summary>
+	/// Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.
+	/// </summary>
+	public abstract void UpdateCursor ();
 
-		/// <summary>
-		/// Get the operation system clipboard.
-		/// </summary>
-		public abstract IClipboard Clipboard { get; }
+	/// <summary>
+	/// Gets the terminal cursor visibility.
+	/// </summary>
+	/// <param name="visibility">The current <see cref="CursorVisibility"/></param>
+	/// <returns><see langword="true"/> upon success</returns>
+	public abstract bool GetCursorVisibility (out CursorVisibility visibility);
 
-		/// <summary>
-		/// <para>
-		/// If <see langword="false"/> (the default) the height of the Terminal.Gui application (<see cref="Rows"/>) 
-		/// tracks to the height of the visible console view when the console is resized. In this case 
-		/// scrolling in the console will be disabled and all <see cref="Rows"/> will remain visible.
-		/// </para>
-		/// <para>
-		/// If <see langword="true"/> then height of the Terminal.Gui application <see cref="Rows"/> only tracks 
-		/// the height of the visible console view when the console is made larger (the application will only grow in height, never shrink). 
-		/// In this case console scrolling is enabled and the contents (<see cref="Rows"/> high) will scroll
-		/// as the console scrolls. 
-		/// </para>
-		/// </summary>
-		/// <remarks>
-		/// NOTE: This functionaliy is currently broken on Windows Terminal.
-		/// </remarks>
-		public abstract bool EnableConsoleScrolling { get; set; }
+	/// <summary>
+	/// Sets the terminal cursor visibility.
+	/// </summary>
+	/// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
+	/// <returns><see langword="true"/> upon success</returns>
+	public abstract bool SetCursorVisibility (CursorVisibility visibility);
 
-		/// <summary>
-		/// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
-		/// </summary>
-		public virtual int [,,] Contents { get; }
+	/// <summary>
+	/// Determines if the terminal cursor should be visible or not and sets it accordingly.
+	/// </summary>
+	/// <returns><see langword="true"/> upon success</returns>
+	public abstract bool EnsureCursorVisibility ();
 
-		/// <summary>
-		/// Initializes the driver
-		/// </summary>
-		/// <param name="terminalResized">Method to invoke when the terminal is resized.</param>
-		public abstract void Init (Action terminalResized);
-		/// <summary>
-		/// Moves the cursor to the specified column and row.
-		/// </summary>
-		/// <param name="col">Column to move the cursor to.</param>
-		/// <param name="row">Row to move the cursor to.</param>
-		public abstract void Move (int col, int row);
+	// As performance is a concern, we keep track of the dirty lines and only refresh those.
+	// This is in addition to the dirty flag on each cell.
+	internal bool [] _dirtyLines;
 
-		/// <summary>
-		/// Tests if the specified rune is supported by the driver.
-		/// </summary>
-		/// <param name="rune"></param>
-		/// <returns><see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver
-		/// does not support displaying this rune.</returns>
-		public virtual bool IsRuneSupported (Rune rune)
-		{
-			if (rune.Value > RuneExtensions.MaxUnicodeCodePoint) {
-				return false;
-			}
-			return true;
+	/// <summary>
+	/// Clears the <see cref="Contents"/> of the driver.
+	/// </summary>
+	public void ClearContents ()
+	{
+		// TODO: This method is really "Clear Contents" now and should not be abstract (or virtual)
+		Contents = new Cell [Rows, Cols];
+		Clip = new Rect (0, 0, Cols, Rows);
+		_dirtyLines = new bool [Rows];
+
+		lock (Contents) {
+			// Can raise an exception while is still resizing.
+			try {
+				for (var row = 0; row < Rows; row++) {
+					for (var c = 0; c < Cols; c++) {
+						Contents [row, c] = new Cell () {
+							Runes = new List<Rune> { (Rune)' ' },
+							Attribute = new Attribute (Color.White, Color.Black),
+							IsDirty = true
+						};
+						_dirtyLines [row] = true;
+					}
+				}
+			} catch (IndexOutOfRangeException) { }
 		}
+	}
 
-		/// <summary>
-		/// Adds the specified rune to the display at the current cursor position.
-		/// </summary>
-		/// <param name="rune">Rune to add.</param>
-		public abstract void AddRune (Rune rune);
-
-		/// <summary>
-		/// Ensures that the column and line are in a valid range from the size of the driver.
-		/// </summary>
-		/// <param name="col">The column.</param>
-		/// <param name="row">The row.</param>
-		/// <param name="clip">The clip.</param>
-		/// <returns><c>true</c>if it's a valid range,<c>false</c>otherwise.</returns>
-		public bool IsValidContent (int col, int row, Rect clip) =>
-			col >= 0 && row >= 0 && col < Cols && row < Rows && clip.Contains (col, row);
-
-		/// <summary>
-		/// Adds the <paramref name="str"/> to the display at the cursor position.
-		/// </summary>
-		/// <param name="str">String.</param>
-		public abstract void AddStr (string str);
-
-		/// <summary>
-		/// Prepare the driver and set the key and mouse events handlers.
-		/// </summary>
-		/// <param name="mainLoop">The main loop.</param>
-		/// <param name="keyHandler">The handler for ProcessKey</param>
-		/// <param name="keyDownHandler">The handler for key down events</param>
-		/// <param name="keyUpHandler">The handler for key up events</param>
-		/// <param name="mouseHandler">The handler for mouse events</param>
-		public abstract void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler);
-
-		/// <summary>
-		/// Updates the screen to reflect all the changes that have been done to the display buffer
-		/// </summary>
-		public abstract void Refresh ();
-
-		/// <summary>
-		/// Updates the location of the cursor position
-		/// </summary>
-		public abstract void UpdateCursor ();
-
-		/// <summary>
-		/// Retreive the cursor caret visibility
-		/// </summary>
-		/// <param name="visibility">The current <see cref="CursorVisibility"/></param>
-		/// <returns>true upon success</returns>
-		public abstract bool GetCursorVisibility (out CursorVisibility visibility);
-
-		/// <summary>
-		/// Change the cursor caret visibility
-		/// </summary>
-		/// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
-		/// <returns>true upon success</returns>
-		public abstract bool SetCursorVisibility (CursorVisibility visibility);
+	/// <summary>
+	/// Redraws the physical screen with the contents that have been queued up via any of the printing commands.
+	/// </summary>
+	public abstract void UpdateScreen ();
 
-		/// <summary>
-		/// Ensure the cursor visibility
-		/// </summary>
-		/// <returns>true upon success</returns>
-		public abstract bool EnsureCursorVisibility ();
+	#region Color Handling
 
-		/// <summary>
-		/// Ends the execution of the console driver.
-		/// </summary>
-		public abstract void End ();
 
-		/// <summary>
-		/// Resizes the clip area when the screen is resized.
-		/// </summary>
-		public abstract void ResizeScreen ();
+	/// <summary>
+	/// Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.
+	/// </summary>
+	public virtual bool SupportsTrueColor { get => false; }
 
-		/// <summary>
-		/// Reset and recreate the contents and the driver buffer.
-		/// </summary>
-		public abstract void UpdateOffScreen ();
+	private bool _useTrueColor = true;
 
-		/// <summary>
-		/// Redraws the physical screen with the contents that have been queued up via any of the printing commands.
-		/// </summary>
-		public abstract void UpdateScreen ();
+	// TODO: Make this a ConfiguationManager setting on Application
+	/// <summary>
+	/// Gets or sets whether the <see cref="ConsoleDriver"/> should use TrueColor output.
+	/// </summary>
+	/// <remarks>
+	/// Can only be enabled if <see cref="ConsoleDriver.SupportsTrueColor"/> is true, indicating
+	/// that the <see cref="ConsoleDriver"/> supports it.
+	/// </remarks>
+	public bool UseTrueColor {
+		get => _useTrueColor && SupportsTrueColor;
+		set => this._useTrueColor = (value && SupportsTrueColor);
+	}
 
-		/// <summary>
-		/// The current attribute the driver is using. 
-		/// </summary>
-		public virtual Attribute CurrentAttribute {
-			get => currentAttribute;
-			set {
-				if (!value.Initialized && value.HasValidColors && Application.Driver != null) {
-					CurrentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background);
-					return;
-				}
-				if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use.");
+	Attribute _currentAttribute;
 
-				currentAttribute = value;
+	/// <summary>
+	/// The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/> call.
+	/// </summary>
+	public Attribute CurrentAttribute {
+		get => _currentAttribute;
+		set {
+			if (value is { Initialized: false, HasValidColors: true } && Application.Driver != null) {
+				_currentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background);
+				return;
 			}
-		}
+			if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use.");
 
-		/// <summary>
-		/// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.
-		/// </summary>
-		/// <remarks>
-		/// Implementations should call <c>base.SetAttribute(c)</c>.
-		/// </remarks>
-		/// <param name="c">C.</param>
-		public virtual void SetAttribute (Attribute c)
-		{
-			CurrentAttribute = c;
-		}
-
-		/// <summary>
-		/// Set Colors from limit sets of colors. Not implemented by any driver: See Issue #2300.
-		/// </summary>
-		/// <param name="foreground">Foreground.</param>
-		/// <param name="background">Background.</param>
-		public abstract void SetColors (ConsoleColor foreground, ConsoleColor background);
-
-		// Advanced uses - set colors to any pre-set pairs, you would need to init_color
-		// that independently with the R, G, B values.
-		/// <summary>
-		/// Advanced uses - set colors to any pre-set pairs, you would need to init_color
-		/// that independently with the R, G, B values. Not implemented by any driver: See Issue #2300.
-		/// </summary>
-		/// <param name="foregroundColorId">Foreground color identifier.</param>
-		/// <param name="backgroundColorId">Background color identifier.</param>
-		public abstract void SetColors (short foregroundColorId, short backgroundColorId);
-
-		/// <summary>
-		/// Gets the foreground and background colors based on the value.
-		/// </summary>
-		/// <param name="value">The value.</param>
-		/// <param name="foreground">The foreground.</param>
-		/// <param name="background">The background.</param>
-		/// <returns></returns>
-		public abstract bool GetColors (int value, out Color foreground, out Color background);
-
-		/// <summary>
-		/// Allows sending keys without typing on a keyboard.
-		/// </summary>
-		/// <param name="keyChar">The character key.</param>
-		/// <param name="key">The key.</param>
-		/// <param name="shift">If shift key is sending.</param>
-		/// <param name="alt">If alt key is sending.</param>
-		/// <param name="control">If control key is sending.</param>
-		public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control);
-
-		/// <summary>
-		/// Set the handler when the terminal is resized.
-		/// </summary>
-		/// <param name="terminalResized"></param>
-		public void SetTerminalResized (Action terminalResized)
-		{
-			TerminalResized = terminalResized;
+			_currentAttribute = value;
 		}
+	}
 
-		/// <summary>
-		/// Fills the specified rectangle with the specified rune.
-		/// </summary>
-		/// <param name="rect"></param>
-		/// <param name="rune"></param>
-		public virtual void FillRect (Rect rect, Rune rune = default)
-		{
-			for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
-				for (var c = rect.X; c < rect.X + rect.Width; c++) {
-					Application.Driver.Move (c, r);
-					Application.Driver.AddRune ((Rune)(rune == default ? ' ' : rune.Value));
-				}
-			}
-		}
+	/// <summary>
+	/// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.
+	/// </summary>
+	/// <remarks>
+	/// Implementations should call <c>base.SetAttribute(c)</c>.
+	/// </remarks>
+	/// <param name="c">C.</param>
+	public Attribute SetAttribute (Attribute c)
+	{
+		var prevAttribute = CurrentAttribute;
+		CurrentAttribute = c;
+		return prevAttribute;
+	}
 
-		/// <summary>
-		/// Enables diagnostic functions
-		/// </summary>
-		[Flags]
-		public enum DiagnosticFlags : uint {
-			/// <summary>
-			/// All diagnostics off
-			/// </summary>
-			Off = 0b_0000_0000,
-			/// <summary>
-			/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
-			/// ruler in the frame for any side with a padding value greater than 0.
-			/// </summary>
-			FrameRuler = 0b_0000_0001,
-			/// <summary>
-			/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
-			/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
-			/// </summary>
-			FramePadding = 0b_0000_0010,
-		}
+	/// <summary>
+	/// Make the attribute for the foreground and background colors.
+	/// </summary>
+	/// <param name="fore">Foreground.</param>
+	/// <param name="back">Background.</param>
+	/// <returns></returns>
+	public virtual Attribute MakeAttribute (Color fore, Color back)
+	{
+		return MakeColor (fore, back);
+	}
 
-		/// <summary>
-		/// Set flags to enable/disable <see cref="ConsoleDriver"/> diagnostics.
-		/// </summary>
-		public static DiagnosticFlags Diagnostics { get; set; }
+	/// <summary>
+	/// Gets the foreground and background colors based on a platform-dependent color value.
+	/// </summary>
+	/// <param name="value">The platform-dependent color value.</param>
+	/// <param name="foreground">The foreground.</param>
+	/// <param name="background">The background.</param>
+	internal abstract void GetColors (int value, out Color foreground, out Color background);
 
-		/// <summary>
-		/// Suspend the application, typically needs to save the state, suspend the app and upon return, reset the console driver.
-		/// </summary>
-		public abstract void Suspend ();
+	/// <summary>
+	/// Gets the current <see cref="Attribute"/>.
+	/// </summary>
+	/// <returns>The current attribute.</returns>
+	public Attribute GetAttribute () => CurrentAttribute;
 
-		Rect clip;
+	/// <summary>
+	/// Makes an <see cref="Attribute"/>.
+	/// </summary>
+	/// <param name="foreground">The foreground color.</param>
+	/// <param name="background">The background color.</param>
+	/// <returns>The attribute for the foreground and background colors.</returns>
+	public abstract Attribute MakeColor (Color foreground, Color background);
 
-		/// <summary>
-		/// Controls the current clipping region that AddRune/AddStr is subject to.
-		/// </summary>
-		/// <value>The clip.</value>
-		public Rect Clip {
-			get => clip;
-			set => this.clip = value;
+	/// <summary>
+	/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correctly 
+	/// initialized by the driver.
+	/// </summary>
+	public void InitializeColorSchemes ()
+	{
+		// Ensure all Attributes are initialized by the driver
+		foreach (var s in Colors.ColorSchemes) {
+			s.Value.Initialize ();
 		}
+	}
 
-		/// <summary>
-		/// Start of mouse moves.
-		/// </summary>
-		public abstract void StartReportingMouseMoves ();
-
-		/// <summary>
-		/// Stop reporting mouses moves.
-		/// </summary>
-		public abstract void StopReportingMouseMoves ();
+	#endregion
 
+	/// <summary>
+	/// Enables diagnostic functions
+	/// </summary>
+	[Flags]
+	public enum DiagnosticFlags : uint {
 		/// <summary>
-		/// Disables the cooked event processing from the mouse driver. 
-		/// At startup, it is assumed mouse events are cooked. Not implemented by any driver: See Issue #2300.
+		/// All diagnostics off
 		/// </summary>
-		public abstract void UncookMouse ();
-
+		Off = 0b_0000_0000,
 		/// <summary>
-		/// Enables the cooked event processing from the mouse driver. Not implemented by any driver: See Issue #2300.
+		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
+		/// ruler in the frame for any side with a padding value greater than 0.
 		/// </summary>
-		public abstract void CookMouse ();
-
-		private Attribute currentAttribute;
-
+		FrameRuler = 0b_0000_0001,
 		/// <summary>
-		/// Make the attribute for the foreground and background colors.
+		/// When enabled, <see cref="Frame.OnDrawFrames"/> will draw a 
+		/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
 		/// </summary>
-		/// <param name="fore">Foreground.</param>
-		/// <param name="back">Background.</param>
-		/// <returns></returns>
-		public abstract Attribute MakeAttribute (Color fore, Color back);
-
-		/// <summary>
-		/// Gets the current <see cref="Attribute"/>.
-		/// </summary>
-		/// <returns>The current attribute.</returns>
-		public Attribute GetAttribute () => CurrentAttribute;
+		FramePadding = 0b_0000_0010,
+	}
 
-		/// <summary>
-		/// Make the <see cref="Colors"/> for the <see cref="ColorScheme"/>.
-		/// </summary>
-		/// <param name="foreground">The foreground color.</param>
-		/// <param name="background">The background color.</param>
-		/// <returns>The attribute for the foreground and background colors.</returns>
-		public abstract Attribute MakeColor (Color foreground, Color background);
+	/// <summary>
+	/// Set flags to enable/disable <see cref="ConsoleDriver"/> diagnostics.
+	/// </summary>
+	public static DiagnosticFlags Diagnostics { get; set; }
 
-		/// <summary>
-		/// Ensures all <see cref="Attribute"/>s in <see cref="Colors.ColorSchemes"/> are correctly 
-		/// initialized by the driver.
-		/// </summary>
-		/// <remarks>
-		/// This method was previsouly named CreateColors. It was reanmed to InitalizeColorSchemes when
-		/// <see cref="ConfigurationManager"/> was enabled.
-		/// </remarks>
-		/// <param name="supportsColors">Flag indicating if colors are supported (not used).</param>
-		public void InitalizeColorSchemes (bool supportsColors = true)
-		{
-			// Ensure all Attributes are initialized by the driver
-			foreach (var s in Colors.ColorSchemes) {
-				s.Value.Initialize ();
-			}
+	/// <summary>
+	/// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.
+	/// </summary>
+	/// <remarks>This is only implemented in <see cref="CursesDriver"/>.</remarks>
+	public abstract void Suspend ();
 
-			if (!supportsColors) {
-				return;
+	/// <summary>
+	/// Simulates a key press.
+	/// </summary>
+	/// <param name="keyChar">The key character.</param>
+	/// <param name="key">The key.</param>
+	/// <param name="shift">If <see langword="true"/> simulates the Shift key being pressed.</param>
+	/// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
+	/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
+	public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
+
+	// TODO: Move FillRect to ./Drawing	
+	/// <summary>
+	/// Fills the specified rectangle with the specified rune.
+	/// </summary>
+	/// <param name="rect"></param>
+	/// <param name="rune"></param>
+	public void FillRect (Rect rect, Rune rune = default)
+	{
+		for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
+			for (var c = rect.X; c < rect.X + rect.Width; c++) {
+				Application.Driver.Move (c, r);
+				Application.Driver.AddRune (rune == default ? new Rune (' ') : rune);
 			}
-
-		}
-
-		internal void SetAttribute (object attribute)
-		{
-			throw new NotImplementedException ();
 		}
 	}
 
 	/// <summary>
-	/// Helper class for console drivers to invoke shell commands to interact with the clipboard.
-	/// Used primarily by CursesDriver, but also used in Unit tests which is why it is in
-	/// ConsoleDriver.cs.
+	/// Fills the specified rectangle with the specified <see langword="char"/>. This method
+	/// is a convenience method that calls <see cref="FillRect(Rect, Rune)"/>.
 	/// </summary>
-	internal static class ClipboardProcessRunner {
-		public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false)
-		{
-			var arguments = $"-c \"{commandLine}\"";
-			var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput);
+	/// <param name="rect"></param>
+	/// <param name="c"></param>
+	public void FillRect (Rect rect, char c) => FillRect (rect, new Rune (c));
 
-			return (exitCode, result.TrimEnd ());
-		}
+	/// <summary>
+	/// Ends the execution of the console driver.
+	/// </summary>
+	public abstract void End ();
+	
+	/// <summary>
+	/// Returns the name of the driver and relevant library version information.
+	/// </summary>
+	/// <returns></returns>
+	public virtual string GetVersionInfo () => GetType ().Name;
+}
 
-		public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true)
-		{
-			var output = string.Empty;
-
-			using (Process process = new Process {
-				StartInfo = new ProcessStartInfo {
-					FileName = cmd,
-					Arguments = arguments,
-					RedirectStandardOutput = true,
-					RedirectStandardError = true,
-					RedirectStandardInput = true,
-					UseShellExecute = false,
-					CreateNoWindow = true,
-				}
-			}) {
-				var eventHandled = new TaskCompletionSource<bool> ();
-				process.Start ();
-				if (!string.IsNullOrEmpty (input)) {
-					process.StandardInput.Write (input);
-					process.StandardInput.Close ();
-				}
 
-				if (!process.WaitForExit (5000)) {
-					var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
-					throw new TimeoutException (timeoutError);
-				}
+/// <summary>
+/// Terminal Cursor Visibility settings.
+/// </summary>
+/// <remarks>
+/// Hex value are set as 0xAABBCCDD where :
+///
+///     AA stand for the TERMINFO DECSUSR parameter value to be used under Linux and MacOS
+///     BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS
+///     CC stand for the CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows
+///     DD stand for the CONSOLE_CURSOR_INFO.dwSize parameter value to be used under Windows
+///</remarks>
+public enum CursorVisibility {
+	/// <summary>
+	///	Cursor caret has default
+	/// </summary>
+	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/>. This default directly depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking.</remarks>
+	Default = 0x00010119,
 
-				if (waitForOutput && process.StandardOutput.Peek () != -1) {
-					output = process.StandardOutput.ReadToEnd ();
-				}
+	/// <summary>
+	///	Cursor caret is hidden
+	/// </summary>
+	Invisible = 0x03000019,
 
-				if (process.ExitCode > 0) {
-					output = $@"Process failed to run. Command line: {cmd} {arguments}.
-										Output: {output}
-										Error: {process.StandardError.ReadToEnd ()}";
-				}
+	/// <summary>
+	///	Cursor caret is normally shown as a blinking underline bar _
+	/// </summary>
+	Underline = 0x03010119,
 
-				return (process.ExitCode, output);
-			}
-		}
+	/// <summary>
+	///	Cursor caret is normally shown as a underline bar _
+	/// </summary>
+	/// <remarks>Under Windows, this is equivalent to <see ref="UnderscoreBlinking"/></remarks>
+	UnderlineFix = 0x04010119,
 
-		public static bool DoubleWaitForExit (this System.Diagnostics.Process process)
-		{
-			var result = process.WaitForExit (500);
-			if (result) {
-				process.WaitForExit ();
-			}
-			return result;
-		}
+	/// <summary>
+	///	Cursor caret is displayed a blinking vertical bar |
+	/// </summary>
+	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+	Vertical = 0x05010119,
 
-		public static bool FileExists (this string value)
-		{
-			return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
-		}
-	}
+	/// <summary>
+	///	Cursor caret is displayed a blinking vertical bar |
+	/// </summary>
+	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Underscore"/></remarks>
+	VerticalFix = 0x06010119,
+
+	/// <summary>
+	///	Cursor caret is displayed as a blinking block ▉
+	/// </summary>
+	Box = 0x01020164,
+
+	/// <summary>
+	///	Cursor caret is displayed a block ▉
+	/// </summary>
+	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
+	BoxFix = 0x02020164,
 }

+ 225 - 0
Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs

@@ -0,0 +1,225 @@
+using System;
+using System.Runtime.InteropServices;
+using Unix.Terminal;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///  A clipboard implementation for Linux.
+///  This implementation uses the xclip command to access the clipboard.
+/// </summary>	
+/// <remarks>
+/// If xclip is not installed, this implementation will not work.
+/// </remarks>
+class CursesClipboard : ClipboardBase {
+	public CursesClipboard ()
+	{
+		IsSupported = CheckSupport ();
+	}
+
+	string _xclipPath = string.Empty;
+	public override bool IsSupported { get; }
+
+	bool CheckSupport ()
+	{
+#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
+		try {
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
+			if (exitCode == 0 && result.FileExists ()) {
+				_xclipPath = result;
+				return true;
+			}
+		} catch (Exception) {
+			// Permissions issue.
+		}
+#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
+		return false;
+	}
+
+	protected override string GetClipboardDataImpl ()
+	{
+		var tempFileName = System.IO.Path.GetTempFileName ();
+		var xclipargs = "-selection clipboard -o";
+
+		try {
+			var (exitCode, result) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
+			if (exitCode == 0) {
+				if (Application.Driver is CursesDriver) {
+					Curses.raw ();
+					Curses.noecho ();
+				}
+				return System.IO.File.ReadAllText (tempFileName);
+			}
+		} catch (Exception e) {
+			throw new NotSupportedException ($"\"{_xclipPath} {xclipargs}\" failed.", e);
+		} finally {
+			System.IO.File.Delete (tempFileName);
+		}
+		return string.Empty;
+	}
+
+	protected override void SetClipboardDataImpl (string text)
+	{
+		var xclipargs = "-selection clipboard -i";
+		try {
+			var (exitCode, _) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs}", text, waitForOutput: false);
+			if (exitCode == 0 && Application.Driver is CursesDriver) {
+				Curses.raw ();
+				Curses.noecho ();
+			}
+		} catch (Exception e) {
+			throw new NotSupportedException ($"\"{_xclipPath} {xclipargs} < {text}\" failed", e);
+		}
+	}
+}
+/// <summary>
+///  A clipboard implementation for MacOSX. 
+///  This implementation uses the Mac clipboard API (via P/Invoke) to copy/paste.
+///  The existance of the Mac pbcopy and pbpaste commands 
+///  is used to determine if copy/paste is supported.
+/// </summary>	
+class MacOSXClipboard : ClipboardBase {
+	IntPtr _nsString = objc_getClass ("NSString");
+	IntPtr _nsPasteboard = objc_getClass ("NSPasteboard");
+	IntPtr _utfTextType;
+	IntPtr _generalPasteboard;
+	IntPtr _initWithUtf8Register = sel_registerName ("initWithUTF8String:");
+	IntPtr _allocRegister = sel_registerName ("alloc");
+	IntPtr _setStringRegister = sel_registerName ("setString:forType:");
+	IntPtr _stringForTypeRegister = sel_registerName ("stringForType:");
+	IntPtr _utf8Register = sel_registerName ("UTF8String");
+	IntPtr _nsStringPboardType;
+	IntPtr _generalPasteboardRegister = sel_registerName ("generalPasteboard");
+	IntPtr _clearContentsRegister = sel_registerName ("clearContents");
+
+	public MacOSXClipboard ()
+	{
+		_utfTextType = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, "public.utf8-plain-text");
+		_nsStringPboardType = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, "NSStringPboardType");
+		_generalPasteboard = objc_msgSend (_nsPasteboard, _generalPasteboardRegister);
+		IsSupported = CheckSupport ();
+	}
+
+	public override bool IsSupported { get; }
+
+	bool CheckSupport ()
+	{
+		var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true);
+		if (exitCode != 0 || !result.FileExists ()) {
+			return false;
+		}
+		(exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true);
+		return exitCode == 0 && result.FileExists ();
+	}
+
+	protected override string GetClipboardDataImpl ()
+	{
+		var ptr = objc_msgSend (_generalPasteboard, _stringForTypeRegister, _nsStringPboardType);
+		var charArray = objc_msgSend (ptr, _utf8Register);
+		return Marshal.PtrToStringAnsi (charArray);
+	}
+
+	protected override void SetClipboardDataImpl (string text)
+	{
+		IntPtr str = default;
+		try {
+			str = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, text);
+			objc_msgSend (_generalPasteboard, _clearContentsRegister);
+			objc_msgSend (_generalPasteboard, _setStringRegister, str, _utfTextType);
+		} finally {
+			if (str != default) {
+				objc_msgSend (str, sel_registerName ("release"));
+			}
+		}
+	}
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr objc_getClass (string className);
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector);
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1);
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1);
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
+
+	[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+	static extern IntPtr sel_registerName (string selectorName);
+}
+
+/// <summary>
+///  A clipboard implementation for Linux, when running under WSL. 
+///  This implementation uses the Windows clipboard to store the data, and uses Windows'
+///  powershell.exe (launched via WSL interop services) to set/get the Windows
+///  clipboard. 
+/// </summary>
+class WSLClipboard : ClipboardBase {
+	public WSLClipboard ()
+	{
+	}
+
+	public override bool IsSupported {
+		get {
+			return CheckSupport ();
+		}
+	}
+
+	private static string _powershellPath = string.Empty;
+
+	bool CheckSupport ()
+	{
+		if (string.IsNullOrEmpty (_powershellPath)) {
+			// Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
+			var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true);
+			if (exitCode > 0) {
+				(exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true);
+			}
+
+			if (exitCode == 0) {
+				_powershellPath = result;
+			}
+		}
+		return !string.IsNullOrEmpty (_powershellPath);
+	}
+
+	protected override string GetClipboardDataImpl ()
+	{
+		if (!IsSupported) {
+			return string.Empty;
+		}
+
+		var (exitCode, output) = ClipboardProcessRunner.Process (_powershellPath, "-noprofile -command \"Get-Clipboard\"");
+		if (exitCode == 0) {
+			if (Application.Driver is CursesDriver) {
+				Curses.raw ();
+				Curses.noecho ();
+			}
+
+			if (output.EndsWith ("\r\n")) {
+				output = output.Substring (0, output.Length - 2);
+			}
+			return output;
+		}
+		return string.Empty;
+	}
+
+	protected override void SetClipboardDataImpl (string text)
+	{
+		if (!IsSupported) {
+			return;
+		}
+
+		var (exitCode, output) = ClipboardProcessRunner.Process (_powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\"");
+		if (exitCode == 0) {
+			if (Application.Driver is CursesDriver) {
+				Curses.raw ();
+				Curses.noecho ();
+			}
+		}
+	}
+}

+ 688 - 1031
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -3,1183 +3,840 @@
 //
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
 using System.Runtime.InteropServices;
-using System.Threading.Tasks;
 using System.Text;
 using Unix.Terminal;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
-	/// <summary>
-	/// This is the Curses driver for the gui.cs/Terminal framework.
-	/// </summary>
-	internal class CursesDriver : ConsoleDriver {
-		public override int Cols => Curses.Cols;
-		public override int Rows => Curses.Lines;
-		public override int Left => 0;
-		public override int Top => 0;
-		public override bool EnableConsoleScrolling { get; set; }
-		public override IClipboard Clipboard { get => clipboard; }
-
-		CursorVisibility? initialCursorVisibility = null;
-		CursorVisibility? currentCursorVisibility = null;
-		IClipboard clipboard;
-		int [,,] contents;
-
-		public override int [,,] Contents => contents;
-
-		// Current row, and current col, tracked by Move/AddRune only
-		int ccol, crow;
-		bool needMove;
-		public override void Move (int col, int row)
-		{
-			ccol = col;
-			crow = row;
+/// <summary>
+/// This is the Curses driver for the gui.cs/Terminal framework.
+/// </summary>
+internal class CursesDriver : ConsoleDriver {
+	public override int Cols => Curses.Cols;
+	public override int Rows => Curses.Lines;
 
-			if (Clip.Contains (col, row)) {
-				Curses.move (row, col);
-				needMove = false;
-			} else {
-				Curses.move (Clip.Y, Clip.X);
-				needMove = true;
-			}
-		}
-
-		static bool sync = false;
+	CursorVisibility? _initialCursorVisibility = null;
+	CursorVisibility? _currentCursorVisibility = null;
 
-		public override bool IsRuneSupported (Rune rune)
-		{
-			// See Issue #2615 - CursesDriver is broken with non-BMP characters
-			return base.IsRuneSupported (rune) && rune.IsBmp;
-		}
+	public override string GetVersionInfo () => $"{Curses.curses_version()}";
 
-		public override void AddRune (Rune rune)
-		{
-			if (!IsRuneSupported (rune)) {
-				rune = Rune.ReplacementChar;
-			}
+	public override void Move (int col, int row)
+	{
+		base.Move (col, row);
 
-			rune = rune.MakePrintable ();
-			var runeWidth = rune.GetColumns ();
-			var validClip = IsValidContent (ccol, crow, Clip);
+		if (IsValidLocation (col, row)) {
+			Curses.move (row, col);
+		} else {
+			// Not a valid location (outside screen or clip region)
+			// Move within the clip region, then AddRune will actually move to Col, Row
+			Curses.move (Clip.Y, Clip.X);
+		}
+	}
 
-			if (validClip) {
-				if (needMove) {
-					Curses.move (crow, ccol);
-					needMove = false;
-				}
-				if (runeWidth == 0 && ccol > 0) {
-					var r = contents [crow, ccol - 1, 0];
-					var s = new string (new char [] { (char)r, (char)rune.Value });
-					string sn;
-					if (!s.IsNormalized ()) {
-						sn = s.Normalize ();
-					} else {
-						sn = s;
-					}
-					var c = sn [0];
-					Curses.mvaddch (crow, ccol - 1, (int)(uint)c);
-					contents [crow, ccol - 1, 0] = c;
-					contents [crow, ccol - 1, 1] = CurrentAttribute;
-					contents [crow, ccol - 1, 2] = 1;
+	public override bool IsRuneSupported (Rune rune)
+	{
+		// See Issue #2615 - CursesDriver is broken with non-BMP characters
+		return base.IsRuneSupported (rune) && rune.IsBmp;
+	}
 
-				} else {
-					if (runeWidth < 2 && ccol > 0
-						&& ((Rune)(char)contents [crow, ccol - 1, 0]).GetColumns () > 1) {
-
-						var curAtttib = CurrentAttribute;
-						Curses.attrset (contents [crow, ccol - 1, 1]);
-						Curses.mvaddch (crow, ccol - 1, (int)(uint)' ');
-						contents [crow, ccol - 1, 0] = (int)(uint)' ';
-						Curses.move (crow, ccol);
-						Curses.attrset (curAtttib);
-
-					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-						&& ((Rune)(char)contents [crow, ccol, 0]).GetColumns () > 1) {
-
-						var curAtttib = CurrentAttribute;
-						Curses.attrset (contents [crow, ccol + 1, 1]);
-						Curses.mvaddch (crow, ccol + 1, (int)(uint)' ');
-						contents [crow, ccol + 1, 0] = (int)(uint)' ';
-						Curses.move (crow, ccol);
-						Curses.attrset (curAtttib);
+	public override void Refresh ()
+	{
+		UpdateScreen ();
+		UpdateCursor ();
+	}
 
-					}
-					if (runeWidth > 1 && ccol == Clip.Right - 1) {
-						Curses.addch ((int)(uint)' ');
-						contents [crow, ccol, 0] = (int)(uint)' ';
-					} else {
-						Curses.addch ((int)(uint)rune.Value);
-						contents [crow, ccol, 0] = (int)(uint)rune.Value;
-					}
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 1;
-				}
-			} else {
-				needMove = true;
-			}
+	private void ProcessWinChange ()
+	{
+		if (Curses.CheckWinChange ()) {
+			ClearContents ();
+			TerminalResized?.Invoke ();
+		}
+	}
 
-			if (runeWidth < 0 || runeWidth > 0) {
-				ccol++;
-			}
+	#region Color Handling
 
-			if (runeWidth > 1) {
-				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 0;
-				}
-				ccol++;
-			}
+	/// <summary>
+	/// Creates an Attribute from the provided curses-based foreground and background color numbers
+	/// </summary>
+	/// <param name="foreground">Contains the curses color number for the foreground (color, plus any attributes)</param>
+	/// <param name="background">Contains the curses color number for the background (color, plus any attributes)</param>
+	/// <returns></returns>
+	static Attribute MakeColor (short foreground, short background)
+	{
+		var v = (short)((int)foreground | background << 4);
+		// TODO: for TrueColor - Use InitExtendedPair
+		Curses.InitColorPair (v, foreground, background);
+		return new Attribute (
+			value: Curses.ColorPair (v),
+			foreground: CursesColorNumberToColor (foreground),
+			background: CursesColorNumberToColor (background));
+	}
 
-			if (sync) {
-				UpdateScreen ();
-			}
-		}
+	/// <remarks>
+	/// In the CursesDriver, colors are encoded as an int. 
+	/// The foreground color is stored in the most significant 4 bits, 
+	/// and the background color is stored in the least significant 4 bits.
+	/// The Terminal.GUi Color values are converted to curses color encoding before being encoded.
+	/// </remarks>
+	public override Attribute MakeColor (Color fore, Color back)
+	{
+		return MakeColor (ColorToCursesColorNumber (fore), ColorToCursesColorNumber (back));
+	}
 
-		public override void AddStr (string str)
-		{
-			// TODO; optimize this to determine if the str fits in the clip region, and if so, use Curses.addstr directly
-			foreach (var rune in str.EnumerateRunes ())
-				AddRune (rune);
-		}
+	static short ColorToCursesColorNumber (Color color)
+	{
+		switch (color) {
+		case Color.Black:
+			return Curses.COLOR_BLACK;
+		case Color.Blue:
+			return Curses.COLOR_BLUE;
+		case Color.Green:
+			return Curses.COLOR_GREEN;
+		case Color.Cyan:
+			return Curses.COLOR_CYAN;
+		case Color.Red:
+			return Curses.COLOR_RED;
+		case Color.Magenta:
+			return Curses.COLOR_MAGENTA;
+		case Color.Brown:
+			return Curses.COLOR_YELLOW;
+		case Color.Gray:
+			return Curses.COLOR_WHITE;
+		case Color.DarkGray:
+			return Curses.COLOR_GRAY;
+		case Color.BrightBlue:
+			return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
+		case Color.BrightGreen:
+			return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
+		case Color.BrightCyan:
+			return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
+		case Color.BrightRed:
+			return Curses.COLOR_RED | Curses.COLOR_GRAY;
+		case Color.BrightMagenta:
+			return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
+		case Color.BrightYellow:
+			return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
+		case Color.White:
+			return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
+		}
+		throw new ArgumentException ("Invalid color code");
+	}
 
-		public override void Refresh ()
-		{
-			Curses.raw ();
-			Curses.noecho ();
-			Curses.refresh ();
-			ProcessWinChange ();
-		}
+	static Color CursesColorNumberToColor (short color)
+	{
+		switch (color) {
+		case Curses.COLOR_BLACK:
+			return Color.Black;
+		case Curses.COLOR_BLUE:
+			return Color.Blue;
+		case Curses.COLOR_GREEN:
+			return Color.Green;
+		case Curses.COLOR_CYAN:
+			return Color.Cyan;
+		case Curses.COLOR_RED:
+			return Color.Red;
+		case Curses.COLOR_MAGENTA:
+			return Color.Magenta;
+		case Curses.COLOR_YELLOW:
+			return Color.Brown;
+		case Curses.COLOR_WHITE:
+			return Color.Gray;
+		case Curses.COLOR_GRAY:
+			return Color.DarkGray;
+		case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
+			return Color.BrightBlue;
+		case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
+			return Color.BrightGreen;
+		case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
+			return Color.BrightCyan;
+		case Curses.COLOR_RED | Curses.COLOR_GRAY:
+			return Color.BrightRed;
+		case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
+			return Color.BrightMagenta;
+		case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
+			return Color.BrightYellow;
+		case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
+			return Color.White;
+		}
+		throw new ArgumentException ("Invalid curses color code");
+	}
 
-		private void ProcessWinChange ()
-		{
-			if (Curses.CheckWinChange ()) {
-				ResizeScreen ();
-				UpdateOffScreen ();
-				TerminalResized?.Invoke ();
-			}
-		}
+	/// <remarks>
+	/// In the CursesDriver, colors are encoded as an int. 
+	/// The foreground color is stored in the most significant 4 bits, 
+	/// and the background color is stored in the least significant 4 bits.
+	/// The Terminal.GUI Color values are converted to curses color encoding before being encoded.
+	/// </remarks>
+	internal override void GetColors (int value, out Color foreground, out Color background)
+	{
+		// Assume a 4-bit encoded value for both foreground and background colors.
+		foreground = CursesColorNumberToColor ((short)((value >> 4) & 0xF));
+		background = CursesColorNumberToColor ((short)(value & 0xF));
+	}
 
-		public override void UpdateCursor () => Refresh ();
+	#endregion
 
-		public override void End ()
-		{
-			StopReportingMouseMoves ();
-			SetCursorVisibility (CursorVisibility.Default);
+	public override void UpdateCursor ()
+	{
+		EnsureCursorVisibility ();
 
-			Curses.endwin ();
+		if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) {
+			Curses.move (Row, Col);
 		}
+	}
 
-		public override void UpdateScreen () => window.redrawwin ();
+	public override void End ()
+	{
+		StopReportingMouseMoves ();
+		SetCursorVisibility (CursorVisibility.Default);
 
-		public override void SetAttribute (Attribute c)
-		{
-			base.SetAttribute (c);
-			Curses.attrset (CurrentAttribute);
-		}
+		// throws away any typeahead that has been typed by
+		// the user and has not yet been read by the program.
+		Curses.flushinp ();
 
-		public Curses.Window window;
+		Curses.endwin ();
+	}
 
-		//static short last_color_pair = 16;
+	public override void UpdateScreen ()
+	{
+		for (int row = 0; row < Rows; row++) {
+			if (!_dirtyLines [row]) {
+				continue;
+			}
+			_dirtyLines [row] = false;
 
-		/// <summary>
-		/// Creates a curses color from the provided foreground and background colors
-		/// </summary>
-		/// <param name="foreground">Contains the curses attributes for the foreground (color, plus any attributes)</param>
-		/// <param name="background">Contains the curses attributes for the background (color, plus any attributes)</param>
-		/// <returns></returns>
-		public static Attribute MakeColor (short foreground, short background)
-		{
-			var v = (short)((int)foreground | background << 4);
-			//Curses.InitColorPair (++last_color_pair, foreground, background);
-			Curses.InitColorPair (v, foreground, background);
-			return new Attribute (
-				//value: Curses.ColorPair (last_color_pair),
-				value: Curses.ColorPair (v),
-				//foreground: (Color)foreground,
-				foreground: MapCursesColor (foreground),
-				//background: (Color)background);
-				background: MapCursesColor (background));
-		}
+			for (int col = 0; col < Cols; col++) {
+				if (Contents [row, col].IsDirty == false) {
+					continue;
+				}
+				Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().Value);
+
+				var rune = Contents [row, col].Runes [0];
+				if (rune.IsBmp) {
+					// BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
+					if (rune.GetColumns () < 2) {
+						Curses.mvaddch (row, col, rune.Value);
+					} else /*if (col + 1 < Cols)*/ {
+						Curses.mvaddwstr (row, col, rune.ToString ());
+					}
 
-		public override Attribute MakeColor (Color fore, Color back)
-		{
-			return MakeColor ((short)MapColor (fore), (short)MapColor (back));
+				} else {
+					Curses.mvaddwstr (row, col, rune.ToString());
+					if (rune.GetColumns () > 1 && col + 1 < Cols) {
+						// TODO: This is a hack to deal with non-BMP and wide characters.
+						//col++;
+						Curses.mvaddch (row, ++col, '*');
+					}
+				}
+			}
 		}
 
-		int [,] colorPairs = new int [16, 16];
+		Curses.move (Row, Col);
+		_window.wrefresh ();
+	}
 
-		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-		{
-			// BUGBUG: This code is never called ?? See Issue #2300
-			int f = (short)foreground;
-			int b = (short)background;
-			var v = colorPairs [f, b];
-			if ((v & 0x10000) == 0) {
-				b &= 0x7;
-				bool bold = (f & 0x8) != 0;
-				f &= 0x7;
-
-				v = MakeColor ((short)f, (short)b) | (bold ? Curses.A_BOLD : 0);
-				colorPairs [(int)foreground, (int)background] = v | 0x1000;
-			}
-			SetAttribute (v & 0xffff);
+	public Curses.Window _window;
+
+	static Key MapCursesKey (int cursesKey)
+	{
+		switch (cursesKey) {
+		case Curses.KeyF1: return Key.F1;
+		case Curses.KeyF2: return Key.F2;
+		case Curses.KeyF3: return Key.F3;
+		case Curses.KeyF4: return Key.F4;
+		case Curses.KeyF5: return Key.F5;
+		case Curses.KeyF6: return Key.F6;
+		case Curses.KeyF7: return Key.F7;
+		case Curses.KeyF8: return Key.F8;
+		case Curses.KeyF9: return Key.F9;
+		case Curses.KeyF10: return Key.F10;
+		case Curses.KeyF11: return Key.F11;
+		case Curses.KeyF12: return Key.F12;
+		case Curses.KeyUp: return Key.CursorUp;
+		case Curses.KeyDown: return Key.CursorDown;
+		case Curses.KeyLeft: return Key.CursorLeft;
+		case Curses.KeyRight: return Key.CursorRight;
+		case Curses.KeyHome: return Key.Home;
+		case Curses.KeyEnd: return Key.End;
+		case Curses.KeyNPage: return Key.PageDown;
+		case Curses.KeyPPage: return Key.PageUp;
+		case Curses.KeyDeleteChar: return Key.DeleteChar;
+		case Curses.KeyInsertChar: return Key.InsertChar;
+		case Curses.KeyTab: return Key.Tab;
+		case Curses.KeyBackTab: return Key.BackTab;
+		case Curses.KeyBackspace: return Key.Backspace;
+		case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask;
+		case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask;
+		case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask;
+		case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask;
+		case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask;
+		case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask;
+		case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask;
+		case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask;
+		case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask;
+		case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask;
+		case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask;
+		case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask;
+		case Curses.AltKeyHome: return Key.Home | Key.AltMask;
+		case Curses.AltKeyEnd: return Key.End | Key.AltMask;
+		case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask;
+		case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask;
+		case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask;
+		case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask;
+		case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask;
+		case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask;
+		case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask;
+		case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask;
+		case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask;
+		case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask;
+		case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask;
+		case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask;
+		case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask;
+		case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask;
+		case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask;
+		case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask;
+		default: return Key.Unknown;
 		}
+	}
 
-		Dictionary<int, int> rawPairs = new Dictionary<int, int> ();
-		public override void SetColors (short foreColorId, short backgroundColorId)
-		{
-			// BUGBUG: This code is never called ?? See Issue #2300
-			int key = ((ushort)foreColorId << 16) | (ushort)backgroundColorId;
-			if (!rawPairs.TryGetValue (key, out var v)) {
-				v = MakeColor (foreColorId, backgroundColorId);
-				rawPairs [key] = v;
-			}
-			SetAttribute (v);
-		}
+	KeyModifiers _keyModifiers;
 
-		static Key MapCursesKey (int cursesKey)
-		{
-			switch (cursesKey) {
-			case Curses.KeyF1: return Key.F1;
-			case Curses.KeyF2: return Key.F2;
-			case Curses.KeyF3: return Key.F3;
-			case Curses.KeyF4: return Key.F4;
-			case Curses.KeyF5: return Key.F5;
-			case Curses.KeyF6: return Key.F6;
-			case Curses.KeyF7: return Key.F7;
-			case Curses.KeyF8: return Key.F8;
-			case Curses.KeyF9: return Key.F9;
-			case Curses.KeyF10: return Key.F10;
-			case Curses.KeyF11: return Key.F11;
-			case Curses.KeyF12: return Key.F12;
-			case Curses.KeyUp: return Key.CursorUp;
-			case Curses.KeyDown: return Key.CursorDown;
-			case Curses.KeyLeft: return Key.CursorLeft;
-			case Curses.KeyRight: return Key.CursorRight;
-			case Curses.KeyHome: return Key.Home;
-			case Curses.KeyEnd: return Key.End;
-			case Curses.KeyNPage: return Key.PageDown;
-			case Curses.KeyPPage: return Key.PageUp;
-			case Curses.KeyDeleteChar: return Key.DeleteChar;
-			case Curses.KeyInsertChar: return Key.InsertChar;
-			case Curses.KeyTab: return Key.Tab;
-			case Curses.KeyBackTab: return Key.BackTab;
-			case Curses.KeyBackspace: return Key.Backspace;
-			case Curses.ShiftKeyUp: return Key.CursorUp | Key.ShiftMask;
-			case Curses.ShiftKeyDown: return Key.CursorDown | Key.ShiftMask;
-			case Curses.ShiftKeyLeft: return Key.CursorLeft | Key.ShiftMask;
-			case Curses.ShiftKeyRight: return Key.CursorRight | Key.ShiftMask;
-			case Curses.ShiftKeyHome: return Key.Home | Key.ShiftMask;
-			case Curses.ShiftKeyEnd: return Key.End | Key.ShiftMask;
-			case Curses.ShiftKeyNPage: return Key.PageDown | Key.ShiftMask;
-			case Curses.ShiftKeyPPage: return Key.PageUp | Key.ShiftMask;
-			case Curses.AltKeyUp: return Key.CursorUp | Key.AltMask;
-			case Curses.AltKeyDown: return Key.CursorDown | Key.AltMask;
-			case Curses.AltKeyLeft: return Key.CursorLeft | Key.AltMask;
-			case Curses.AltKeyRight: return Key.CursorRight | Key.AltMask;
-			case Curses.AltKeyHome: return Key.Home | Key.AltMask;
-			case Curses.AltKeyEnd: return Key.End | Key.AltMask;
-			case Curses.AltKeyNPage: return Key.PageDown | Key.AltMask;
-			case Curses.AltKeyPPage: return Key.PageUp | Key.AltMask;
-			case Curses.CtrlKeyUp: return Key.CursorUp | Key.CtrlMask;
-			case Curses.CtrlKeyDown: return Key.CursorDown | Key.CtrlMask;
-			case Curses.CtrlKeyLeft: return Key.CursorLeft | Key.CtrlMask;
-			case Curses.CtrlKeyRight: return Key.CursorRight | Key.CtrlMask;
-			case Curses.CtrlKeyHome: return Key.Home | Key.CtrlMask;
-			case Curses.CtrlKeyEnd: return Key.End | Key.CtrlMask;
-			case Curses.CtrlKeyNPage: return Key.PageDown | Key.CtrlMask;
-			case Curses.CtrlKeyPPage: return Key.PageUp | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyUp: return Key.CursorUp | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyDown: return Key.CursorDown | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyRight: return Key.CursorRight | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyHome: return Key.Home | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyEnd: return Key.End | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyNPage: return Key.PageDown | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftCtrlKeyPPage: return Key.PageUp | Key.ShiftMask | Key.CtrlMask;
-			case Curses.ShiftAltKeyUp: return Key.CursorUp | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyDown: return Key.CursorDown | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyLeft: return Key.CursorLeft | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyRight: return Key.CursorRight | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyNPage: return Key.PageDown | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyPPage: return Key.PageUp | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyHome: return Key.Home | Key.ShiftMask | Key.AltMask;
-			case Curses.ShiftAltKeyEnd: return Key.End | Key.ShiftMask | Key.AltMask;
-			case Curses.AltCtrlKeyNPage: return Key.PageDown | Key.AltMask | Key.CtrlMask;
-			case Curses.AltCtrlKeyPPage: return Key.PageUp | Key.AltMask | Key.CtrlMask;
-			case Curses.AltCtrlKeyHome: return Key.Home | Key.AltMask | Key.CtrlMask;
-			case Curses.AltCtrlKeyEnd: return Key.End | Key.AltMask | Key.CtrlMask;
-			default: return Key.Unknown;
-			}
+	KeyModifiers MapKeyModifiers (Key key)
+	{
+		if (_keyModifiers == null) {
+			_keyModifiers = new KeyModifiers ();
 		}
 
-		KeyModifiers keyModifiers;
-
-		KeyModifiers MapKeyModifiers (Key key)
-		{
-			if (keyModifiers == null)
-				keyModifiers = new KeyModifiers ();
+		if (!_keyModifiers.Shift && (key & Key.ShiftMask) != 0) {
+			_keyModifiers.Shift = true;
+		}
+		if (!_keyModifiers.Alt && (key & Key.AltMask) != 0) {
+			_keyModifiers.Alt = true;
+		}
+		if (!_keyModifiers.Ctrl && (key & Key.CtrlMask) != 0) {
+			_keyModifiers.Ctrl = true;
+		}
 
-			if (!keyModifiers.Shift && (key & Key.ShiftMask) != 0)
-				keyModifiers.Shift = true;
-			if (!keyModifiers.Alt && (key & Key.AltMask) != 0)
-				keyModifiers.Alt = true;
-			if (!keyModifiers.Ctrl && (key & Key.CtrlMask) != 0)
-				keyModifiers.Ctrl = true;
+		return _keyModifiers;
+	}
 
-			return keyModifiers;
+	void ProcessInput ()
+	{
+		int wch;
+		var code = Curses.get_wch (out wch);
+		//System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
+		if (code == Curses.ERR) {
+			return;
 		}
 
-		void ProcessInput ()
-		{
-			int wch;
-			var code = Curses.get_wch (out wch);
-			//System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
-			if (code == Curses.ERR)
-				return;
-
-			keyModifiers = new KeyModifiers ();
-			Key k = Key.Null;
+		_keyModifiers = new KeyModifiers ();
+		Key k = Key.Null;
 
-			if (code == Curses.KEY_CODE_YES) {
-				if (wch == Curses.KeyResize) {
-					ProcessWinChange ();
-				}
-				if (wch == Curses.KeyMouse) {
-					int wch2 = wch;
+		if (code == Curses.KEY_CODE_YES) {
+			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
+				ProcessWinChange ();
+				code = Curses.get_wch (out wch);
+			}
+			if (wch == 0) {
+				return;
+			}
+			if (wch == Curses.KeyMouse) {
+				int wch2 = wch;
 
-					while (wch2 == Curses.KeyMouse) {
-						KeyEvent key = null;
-						ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
+				while (wch2 == Curses.KeyMouse) {
+					KeyEvent key = null;
+					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
 							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 0, false, false, false),
 							new ConsoleKeyInfo ('<', 0, false, false, false)
 						};
-						code = 0;
-						GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
-					}
-					return;
-				}
-				k = MapCursesKey (wch);
-				if (wch >= 277 && wch <= 288) { // Shift+(F1 - F12)
-					wch -= 12;
-					k = Key.ShiftMask | MapCursesKey (wch);
-				} else if (wch >= 289 && wch <= 300) { // Ctrl+(F1 - F12)
-					wch -= 24;
-					k = Key.CtrlMask | MapCursesKey (wch);
-				} else if (wch >= 301 && wch <= 312) { // Ctrl+Shift+(F1 - F12)
-					wch -= 36;
-					k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch);
-				} else if (wch >= 313 && wch <= 324) { // Alt+(F1 - F12)
-					wch -= 48;
-					k = Key.AltMask | MapCursesKey (wch);
-				} else if (wch >= 325 && wch <= 327) { // Shift+Alt+(F1 - F3)
-					wch -= 60;
-					k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch);
+					code = 0;
+					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 				}
-				keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
-				keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
-				keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
 				return;
 			}
+			k = MapCursesKey (wch);
+			if (wch >= 277 && wch <= 288) {
+				// Shift+(F1 - F12)
+				wch -= 12;
+				k = Key.ShiftMask | MapCursesKey (wch);
+			} else if (wch >= 289 && wch <= 300) {
+				// Ctrl+(F1 - F12)
+				wch -= 24;
+				k = Key.CtrlMask | MapCursesKey (wch);
+			} else if (wch >= 301 && wch <= 312) {
+				// Ctrl+Shift+(F1 - F12)
+				wch -= 36;
+				k = Key.CtrlMask | Key.ShiftMask | MapCursesKey (wch);
+			} else if (wch >= 313 && wch <= 324) {
+				// Alt+(F1 - F12)
+				wch -= 48;
+				k = Key.AltMask | MapCursesKey (wch);
+			} else if (wch >= 325 && wch <= 327) {
+				// Shift+Alt+(F1 - F3)
+				wch -= 60;
+				k = Key.ShiftMask | Key.AltMask | MapCursesKey (wch);
+			}
+			_keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			_keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			_keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			return;
+		}
+
+		// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
+		if (wch == 27) {
+			Curses.timeout (10);
+
+			code = Curses.get_wch (out int wch2);
 
-			// Special handling for ESC, we want to try to catch ESC+letter to simulate alt-letter as well as Alt-Fkey
-			if (wch == 27) {
-				Curses.timeout (10);
-
-				code = Curses.get_wch (out int wch2);
-
-				if (code == Curses.KEY_CODE_YES) {
-					k = Key.AltMask | MapCursesKey (wch);
-				}
-				if (code == 0) {
-					KeyEvent key = null;
-
-					// The ESC-number handling, debatable.
-					// Simulates the AltMask itself by pressing Alt + Space.
-					if (wch2 == (int)Key.Space) {
-						k = Key.AltMask;
-					} else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) {
-						k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space));
-					} else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) {
-						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
-					} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
-						k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
-					} else if (wch2 == Curses.KeyCSI) {
-						ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
+			if (code == Curses.KEY_CODE_YES) {
+				k = Key.AltMask | MapCursesKey (wch);
+			}
+			if (code == 0) {
+				KeyEvent key = null;
+
+				// The ESC-number handling, debatable.
+				// Simulates the AltMask itself by pressing Alt + Space.
+				if (wch2 == (int)Key.Space) {
+					k = Key.AltMask;
+				} else if (wch2 - (int)Key.Space >= (uint)Key.A && wch2 - (int)Key.Space <= (uint)Key.Z) {
+					k = (Key)((uint)Key.AltMask + (wch2 - (int)Key.Space));
+				} else if (wch2 >= (uint)Key.A - 64 && wch2 <= (uint)Key.Z - 64) {
+					k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + (wch2 + 64));
+				} else if (wch2 >= (uint)Key.D0 && wch2 <= (uint)Key.D9) {
+					k = (Key)((uint)Key.AltMask + (uint)Key.D0 + (wch2 - (uint)Key.D0));
+				} else if (wch2 == Curses.KeyCSI) {
+					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
 							new ConsoleKeyInfo ((char)Key.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 0, false, false, false)
 						};
-						GetEscSeq (ref code, ref k, ref wch2, ref key, ref cki);
-						return;
+					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
+					return;
+				} else {
+					// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
+					if (((Key)wch2 & Key.CtrlMask) != 0) {
+						_keyModifiers.Ctrl = true;
+					}
+					if (wch2 == 0) {
+						k = Key.CtrlMask | Key.AltMask | Key.Space;
+					} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
+						_keyModifiers.Shift = true;
+						_keyModifiers.Alt = true;
+					} else if (wch2 < 256) {
+						k = (Key)wch2;
+						_keyModifiers.Alt = true;
 					} else {
-						// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
-						if (((Key)wch2 & Key.CtrlMask) != 0) {
-							keyModifiers.Ctrl = true;
-						}
-						if (wch2 == 0) {
-							k = Key.CtrlMask | Key.AltMask | Key.Space;
-						} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
-							keyModifiers.Shift = true;
-							keyModifiers.Alt = true;
-						} else if (wch2 < 256) {
-							k = (Key)wch2;
-							keyModifiers.Alt = true;
-						} else {
-							k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2);
-						}
+						k = (Key)((uint)(Key.AltMask | Key.CtrlMask) + wch2);
 					}
-					key = new KeyEvent (k, MapKeyModifiers (k));
-					keyDownHandler (key);
-					keyHandler (key);
-				} else {
-					k = Key.Esc;
-					keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
 				}
-			} else if (wch == Curses.KeyTab) {
-				k = MapCursesKey (wch);
-				keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
-				keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+				key = new KeyEvent (k, MapKeyModifiers (k));
+				_keyDownHandler (key);
+				_keyHandler (key);
 			} else {
-				// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
-				k = (Key)wch;
-				if (wch == 0) {
-					k = Key.CtrlMask | Key.Space;
-				} else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) {
-					if ((Key)(wch + 64) != Key.J) {
-						k = Key.CtrlMask | (Key)(wch + 64);
-					}
-				} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
-					keyModifiers.Shift = true;
+				k = Key.Esc;
+				_keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			}
+		} else if (wch == Curses.KeyTab) {
+			k = MapCursesKey (wch);
+			_keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			_keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+		} else {
+			// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
+			k = (Key)wch;
+			if (wch == 0) {
+				k = Key.CtrlMask | Key.Space;
+			} else if (wch >= (uint)Key.A - 64 && wch <= (uint)Key.Z - 64) {
+				if ((Key)(wch + 64) != Key.J) {
+					k = Key.CtrlMask | (Key)(wch + 64);
 				}
-				keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
-				keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
-				keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
-			}
-			// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
-			// will not impact KeyUp.
-			// This is causing ESC firing even if another keystroke was handled.
-			//if (wch == Curses.KeyTab) {
-			//	keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers));
-			//} else {
-			//	keyUpHandler (new KeyEvent ((Key)wch, keyModifiers));
-			//}
-		}
+			} else if (wch >= (uint)Key.A && wch <= (uint)Key.Z) {
+				_keyModifiers.Shift = true;
+			}
+			_keyDownHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			_keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			_keyUpHandler (new KeyEvent (k, MapKeyModifiers (k)));
+		}
+		// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
+		// will not impact KeyUp.
+		// This is causing ESC firing even if another keystroke was handled.
+		//if (wch == Curses.KeyTab) {
+		//	keyUpHandler (new KeyEvent (MapCursesKey (wch), keyModifiers));
+		//} else {
+		//	keyUpHandler (new KeyEvent ((Key)wch, keyModifiers));
+		//}
+	}
 
-		void GetEscSeq (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki)
-		{
-			ConsoleKey ck = 0;
-			ConsoleModifiers mod = 0;
-			while (code == 0) {
-				code = Curses.get_wch (out wch2);
-				var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
-				if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
-					EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List<MouseFlags> mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed);
-					if (isKeyMouse) {
-						foreach (var mf in mouseFlags) {
-							ProcessMouseEvent (mf, pos);
-						}
-						cki = null;
-						if (wch2 == 27) {
-							cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
-								false, false, false), cki);
-						}
-					} else {
-						k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
-						k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
-						key = new KeyEvent (k, MapKeyModifiers (k));
-						keyDownHandler (key);
-						keyHandler (key);
+	void HandleEscSeqResponse (ref int code, ref Key k, ref int wch2, ref KeyEvent key, ref ConsoleKeyInfo [] cki)
+	{
+		ConsoleKey ck = 0;
+		ConsoleModifiers mod = 0;
+		while (code == 0) {
+			code = Curses.get_wch (out wch2);
+			var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
+			if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
+				EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List<MouseFlags> mouseFlags, out Point pos, out _, ProcessContinuousButtonPressed);
+				if (isKeyMouse) {
+					foreach (var mf in mouseFlags) {
+						ProcessMouseEvent (mf, pos);
+					}
+					cki = null;
+					if (wch2 == 27) {
+						cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
+							false, false, false), cki);
 					}
 				} else {
-					cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
+					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
+					k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
+					key = new KeyEvent (k, MapKeyModifiers (k));
+					_keyDownHandler (key);
+					_keyHandler (key);
 				}
+			} else {
+				cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
 			}
 		}
+	}
+
+	MouseFlags _lastMouseFlags;
 
-		void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
+	void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
+	{
+		bool WasButtonReleased (MouseFlags flag)
 		{
-			var me = new MouseEvent () {
-				Flags = mouseFlag,
-				X = pos.X,
-				Y = pos.Y
-			};
-			mouseHandler (me);
+			return flag.HasFlag (MouseFlags.Button1Released) ||
+				flag.HasFlag (MouseFlags.Button2Released) ||
+				flag.HasFlag (MouseFlags.Button3Released) ||
+				flag.HasFlag (MouseFlags.Button4Released);
 		}
 
-		void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos)
+		bool IsButtonNotPressed (MouseFlags flag)
 		{
-			ProcessMouseEvent (mouseFlag, pos);
+			return !flag.HasFlag (MouseFlags.Button1Pressed) &&
+				!flag.HasFlag (MouseFlags.Button2Pressed) &&
+				!flag.HasFlag (MouseFlags.Button3Pressed) &&
+				!flag.HasFlag (MouseFlags.Button4Pressed);
 		}
 
-		Action<KeyEvent> keyHandler;
-		Action<KeyEvent> keyDownHandler;
-		Action<KeyEvent> keyUpHandler;
-		Action<MouseEvent> mouseHandler;
-
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+		bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
 		{
-			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			Curses.timeout (0);
-			this.keyHandler = keyHandler;
-			this.keyDownHandler = keyDownHandler;
-			this.keyUpHandler = keyUpHandler;
-			this.mouseHandler = mouseHandler;
-
-			var mLoop = mainLoop.Driver as UnixMainLoop;
-
-			mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
-				ProcessInput ();
-				return true;
-			});
-
-			mLoop.WinChanged += () => {
-				ProcessWinChange ();
-			};
+			return flag.HasFlag (MouseFlags.Button1Clicked) ||
+				flag.HasFlag (MouseFlags.Button2Clicked) ||
+				flag.HasFlag (MouseFlags.Button3Clicked) ||
+				flag.HasFlag (MouseFlags.Button4Clicked) ||
+				flag.HasFlag (MouseFlags.Button1DoubleClicked) ||
+				flag.HasFlag (MouseFlags.Button2DoubleClicked) ||
+				flag.HasFlag (MouseFlags.Button3DoubleClicked) ||
+				flag.HasFlag (MouseFlags.Button4DoubleClicked);
 		}
 
-		public override void Init (Action terminalResized)
-		{
-			if (window != null)
-				return;
+		if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) ||
+			(IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) {
+			return;
+		}
 
-			try {
-				window = Curses.initscr ();
-				Curses.set_escdelay (10);
-			} catch (Exception e) {
-				throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}");
-			}
+		_lastMouseFlags = mouseFlag;
 
-			// Ensures that all procedures are performed at some previous closing.
-			Curses.doupdate ();
+		var me = new MouseEvent () {
+			Flags = mouseFlag,
+			X = pos.X,
+			Y = pos.Y
+		};
+		_mouseHandler (me);
+	}
 
-			// 
-			// We are setting Invisible as default so we could ignore XTerm DECSUSR setting
-			//
-			switch (Curses.curs_set (0)) {
-			case 0:
-				currentCursorVisibility = initialCursorVisibility = CursorVisibility.Invisible;
-				break;
 
-			case 1:
-				currentCursorVisibility = initialCursorVisibility = CursorVisibility.Underline;
-				Curses.curs_set (1);
-				break;
+	void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos)
+	{
+		ProcessMouseEvent (mouseFlag, pos);
+	}
 
-			case 2:
-				currentCursorVisibility = initialCursorVisibility = CursorVisibility.Box;
-				Curses.curs_set (2);
-				break;
+	Action<KeyEvent> _keyHandler;
+	Action<KeyEvent> _keyDownHandler;
+	Action<KeyEvent> _keyUpHandler;
+	Action<MouseEvent> _mouseHandler;
 
-			default:
-				currentCursorVisibility = initialCursorVisibility = null;
-				break;
-			}
+	public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+	{
+		// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
+		Curses.timeout (0);
+		this._keyHandler = keyHandler;
+		this._keyDownHandler = keyDownHandler;
+		this._keyUpHandler = keyUpHandler;
+		this._mouseHandler = mouseHandler;
 
-			if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				clipboard = new MacOSXClipboard ();
-			} else {
-				if (Is_WSL_Platform ()) {
-					clipboard = new WSLClipboard ();
-				} else {
-					clipboard = new CursesClipboard ();
-				}
-			}
+		var mLoop = mainLoop.MainLoopDriver as UnixMainLoop;
 
-			Curses.raw ();
-			Curses.noecho ();
+		mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
+			ProcessInput ();
+			return true;
+		});
 
-			Curses.Window.Standard.keypad (true);
-			TerminalResized = terminalResized;
-			StartReportingMouseMoves ();
+		mLoop.WinChanged += ProcessInput;
+	}
 
-			CurrentAttribute = MakeColor (Color.White, Color.Black);
+	public override void Init (Action terminalResized)
+	{
+		if (_window != null) {
+			return;
+		}
 
-			if (Curses.HasColors) {
-				Curses.StartColor ();
-				Curses.UseDefaultColors ();
+		try {
+			_window = Curses.initscr ();
+			Curses.set_escdelay (10);
+		} catch (Exception e) {
+			throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}");
+		}
 
-				InitalizeColorSchemes ();
-			} else {
-				InitalizeColorSchemes (false);
-
-				// BUGBUG: This is a hack to make the colors work on the Mac?
-				// The new Theme support overwrites these colors, so this is not needed?
-				Colors.TopLevel.Normal = Curses.COLOR_GREEN;
-				Colors.TopLevel.Focus = Curses.COLOR_WHITE;
-				Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW;
-				Colors.TopLevel.HotFocus = Curses.COLOR_YELLOW;
-				Colors.TopLevel.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
-				Colors.Base.Normal = Curses.A_NORMAL;
-				Colors.Base.Focus = Curses.A_REVERSE;
-				Colors.Base.HotNormal = Curses.A_BOLD;
-				Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE;
-				Colors.Base.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
-				Colors.Menu.Normal = Curses.A_REVERSE;
-				Colors.Menu.Focus = Curses.A_NORMAL;
-				Colors.Menu.HotNormal = Curses.A_BOLD;
-				Colors.Menu.HotFocus = Curses.A_NORMAL;
-				Colors.Menu.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
-				Colors.Dialog.Normal = Curses.A_REVERSE;
-				Colors.Dialog.Focus = Curses.A_NORMAL;
-				Colors.Dialog.HotNormal = Curses.A_BOLD;
-				Colors.Dialog.HotFocus = Curses.A_NORMAL;
-				Colors.Dialog.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
-				Colors.Error.Normal = Curses.A_BOLD;
-				Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE;
-				Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE;
-				Colors.Error.HotFocus = Curses.A_REVERSE;
-				Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY;
-			}
+		// Ensures that all procedures are performed at some previous closing.
+		Curses.doupdate ();
 
-			ResizeScreen ();
-			UpdateOffScreen ();
+		// 
+		// We are setting Invisible as default so we could ignore XTerm DECSUSR setting
+		//
+		switch (Curses.curs_set (0)) {
+		case 0:
+			_currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Invisible;
+			break;
 
-		}
+		case 1:
+			_currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Underline;
+			Curses.curs_set (1);
+			break;
 
-		public override void ResizeScreen ()
-		{
-			Clip = new Rect (0, 0, Cols, Rows);
-			Curses.refresh ();
-		}
+		case 2:
+			_currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box;
+			Curses.curs_set (2);
+			break;
 
-		public override void UpdateOffScreen ()
-		{
-			contents = new int [Rows, Cols, 3];
-			for (int row = 0; row < Rows; row++) {
-				for (int col = 0; col < Cols; col++) {
-					//Curses.move (row, col);
-					//Curses.attrset (Colors.TopLevel.Normal);
-					//Curses.addch ((int)(uint)' ');
-					contents [row, col, 0] = ' ';
-					contents [row, col, 1] = Colors.TopLevel.Normal;
-					contents [row, col, 2] = 0;
-				}
-			}
+		default:
+			_currentCursorVisibility = _initialCursorVisibility = null;
+			break;
 		}
 
-		public static bool Is_WSL_Platform ()
-		{
-			// xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
-			//if (new CursesClipboard ().IsSupported) {
-			//	// If xclip is installed on Linux under WSL, this will return true.
-			//	return false;
-			//}
-			var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
-			if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
-				return true;
+		if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+			Clipboard = new MacOSXClipboard ();
+		} else {
+			if (Is_WSL_Platform ()) {
+				Clipboard = new WSLClipboard ();
+			} else {
+				Clipboard = new CursesClipboard ();
 			}
-			return false;
 		}
 
-		static int MapColor (Color color)
-		{
-			switch (color) {
-			case Color.Black:
-				return Curses.COLOR_BLACK;
-			case Color.Blue:
-				return Curses.COLOR_BLUE;
-			case Color.Green:
-				return Curses.COLOR_GREEN;
-			case Color.Cyan:
-				return Curses.COLOR_CYAN;
-			case Color.Red:
-				return Curses.COLOR_RED;
-			case Color.Magenta:
-				return Curses.COLOR_MAGENTA;
-			case Color.Brown:
-				return Curses.COLOR_YELLOW;
-			case Color.Gray:
-				return Curses.COLOR_WHITE;
-			case Color.DarkGray:
-				//return Curses.COLOR_BLACK | Curses.A_BOLD;
-				return Curses.COLOR_GRAY;
-			case Color.BrightBlue:
-				return Curses.COLOR_BLUE | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.BrightGreen:
-				return Curses.COLOR_GREEN | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.BrightCyan:
-				return Curses.COLOR_CYAN | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.BrightRed:
-				return Curses.COLOR_RED | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.BrightMagenta:
-				return Curses.COLOR_MAGENTA | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.BrightYellow:
-				return Curses.COLOR_YELLOW | Curses.A_BOLD | Curses.COLOR_GRAY;
-			case Color.White:
-				return Curses.COLOR_WHITE | Curses.A_BOLD | Curses.COLOR_GRAY;
-			}
-			throw new ArgumentException ("Invalid color code");
+		if (!Curses.HasColors) {
+			throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does.");
 		}
 
-		static Color MapCursesColor (int color)
-		{
-			switch (color) {
-			case Curses.COLOR_BLACK:
-				return Color.Black;
-			case Curses.COLOR_BLUE:
-				return Color.Blue;
-			case Curses.COLOR_GREEN:
-				return Color.Green;
-			case Curses.COLOR_CYAN:
-				return Color.Cyan;
-			case Curses.COLOR_RED:
-				return Color.Red;
-			case Curses.COLOR_MAGENTA:
-				return Color.Magenta;
-			case Curses.COLOR_YELLOW:
-				return Color.Brown;
-			case Curses.COLOR_WHITE:
-				return Color.Gray;
-			case Curses.COLOR_GRAY:
-				return Color.DarkGray;
-			case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
-				return Color.BrightBlue;
-			case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
-				return Color.BrightGreen;
-			case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
-				return Color.BrightCyan;
-			case Curses.COLOR_RED | Curses.COLOR_GRAY:
-				return Color.BrightRed;
-			case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
-				return Color.BrightMagenta;
-			case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
-				return Color.BrightYellow;
-			case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
-				return Color.White;
-			}
-			throw new ArgumentException ("Invalid curses color code");
-		}
+		Curses.raw ();
+		Curses.noecho ();
 
-		public override Attribute MakeAttribute (Color fore, Color back)
-		{
-			var f = MapColor (fore);
-			//return MakeColor ((short)(f & 0xffff), (short)MapColor (back)) | ((f & Curses.A_BOLD) != 0 ? Curses.A_BOLD : 0);
-			return MakeColor ((short)(f & 0xffff), (short)MapColor (back));
-		}
+		Curses.Window.Standard.keypad (true);
 
-		public override void Suspend ()
-		{
-			StopReportingMouseMoves ();
-			Platform.Suspend ();
-			Curses.Window.Standard.redrawwin ();
-			Curses.refresh ();
-			StartReportingMouseMoves ();
-		}
+		TerminalResized = terminalResized;
 
-		public override void StartReportingMouseMoves ()
-		{
-			Console.Out.Write (EscSeqUtils.EnableMouseEvents);
-		}
+		Curses.StartColor ();
+		Curses.UseDefaultColors ();
+		CurrentAttribute = MakeColor (Color.White, Color.Black);
+		InitializeColorSchemes ();
 
-		public override void StopReportingMouseMoves ()
-		{
-			Console.Out.Write (EscSeqUtils.DisableMouseEvents);
-		}
+		Curses.CheckWinChange ();
+		ClearContents ();
+		Curses.refresh ();
 
-		//int lastMouseInterval;
-		//bool mouseGrabbed;
+		StartReportingMouseMoves ();
+	}
 
-		public override void UncookMouse ()
-		{
-			//if (mouseGrabbed)
-			//	return;
-			//lastMouseInterval = Curses.mouseinterval (0);
-			//mouseGrabbed = true;
+	public static bool Is_WSL_Platform ()
+	{
+		// xclip does not work on WSL, so we need to use the Windows clipboard vis Powershell
+		//if (new CursesClipboard ().IsSupported) {
+		//	// If xclip is installed on Linux under WSL, this will return true.
+		//	return false;
+		//}
+		var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
+		if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
+			return true;
 		}
+		return false;
+	}
 
-		public override void CookMouse ()
-		{
-			//mouseGrabbed = false;
-			//Curses.mouseinterval (lastMouseInterval);
-		}
+	public override void Suspend ()
+	{
+		StopReportingMouseMoves ();
+		Platform.Suspend ();
+		Curses.Window.Standard.redrawwin ();
+		Curses.refresh ();
+		StartReportingMouseMoves ();
+	}
 
-		/// <inheritdoc/>
-		public override bool GetCursorVisibility (out CursorVisibility visibility)
-		{
-			visibility = CursorVisibility.Invisible;
+	public void StartReportingMouseMoves ()
+	{
+		Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+	}
 
-			if (!currentCursorVisibility.HasValue)
-				return false;
+	public void StopReportingMouseMoves ()
+	{
+		Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+	}
 
-			visibility = currentCursorVisibility.Value;
+	/// <inheritdoc/>
+	public override bool GetCursorVisibility (out CursorVisibility visibility)
+	{
+		visibility = CursorVisibility.Invisible;
 
-			return true;
-		}
+		if (!_currentCursorVisibility.HasValue)
+			return false;
 
-		/// <inheritdoc/>
-		public override bool SetCursorVisibility (CursorVisibility visibility)
-		{
-			if (initialCursorVisibility.HasValue == false)
-				return false;
+		visibility = _currentCursorVisibility.Value;
 
-			Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
+		return true;
+	}
 
-			if (visibility != CursorVisibility.Invisible) {
-				Console.Out.Write ("\x1b[{0} q", ((int)visibility >> 24) & 0xFF);
-			}
+	/// <inheritdoc/>
+	public override bool SetCursorVisibility (CursorVisibility visibility)
+	{
+		if (_initialCursorVisibility.HasValue == false) {
+			return false;
+		}
 
-			currentCursorVisibility = visibility;
+		Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
 
-			return true;
+		if (visibility != CursorVisibility.Invisible) {
+			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF)));
 		}
 
-		/// <inheritdoc/>
-		public override bool EnsureCursorVisibility ()
-		{
-			return false;
-		}
+		_currentCursorVisibility = visibility;
 
-		public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
-		{
-			Key key;
+		return true;
+	}
 
-			if (consoleKey == ConsoleKey.Packet) {
-				ConsoleModifiers mod = new ConsoleModifiers ();
-				if (shift) {
-					mod |= ConsoleModifiers.Shift;
-				}
-				if (alt) {
-					mod |= ConsoleModifiers.Alt;
-				}
-				if (control) {
-					mod |= ConsoleModifiers.Control;
-				}
-				var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _);
-				key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable);
-				if (mappable) {
-					key = (Key)kchar;
-				}
-			} else {
-				key = (Key)keyChar;
-			}
+	/// <inheritdoc/>
+	public override bool EnsureCursorVisibility ()
+	{
+		return false;
+	}
 
-			KeyModifiers km = new KeyModifiers ();
+	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
+	{
+		Key key;
+
+		if (consoleKey == ConsoleKey.Packet) {
+			ConsoleModifiers mod = new ConsoleModifiers ();
 			if (shift) {
-				if (keyChar == 0) {
-					key |= Key.ShiftMask;
-				}
-				km.Shift = shift;
+				mod |= ConsoleModifiers.Shift;
 			}
 			if (alt) {
-				key |= Key.AltMask;
-				km.Alt = alt;
+				mod |= ConsoleModifiers.Alt;
 			}
 			if (control) {
-				key |= Key.CtrlMask;
-				km.Ctrl = control;
+				mod |= ConsoleModifiers.Control;
 			}
-			keyDownHandler (new KeyEvent (key, km));
-			keyHandler (new KeyEvent (key, km));
-			keyUpHandler (new KeyEvent (key, km));
-		}
-
-		public override bool GetColors (int value, out Color foreground, out Color background)
-		{
-			bool hasColor = false;
-			foreground = default;
-			background = default;
-			int back = -1;
-			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-				.OfType<ConsoleColor> ()
-				.Select (s => (int)s);
-			if (values.Contains ((value >> 12) & 0xffff)) {
-				hasColor = true;
-				back = (value >> 12) & 0xffff;
-				background = MapCursesColor (back);
+			var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _);
+			key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable);
+			if (mappable) {
+				key = (Key)kchar;
 			}
-			if (values.Contains ((value - (back << 12)) >> 8)) {
-				hasColor = true;
-				foreground = MapCursesColor ((value - (back << 12)) >> 8);
-			}
-			return hasColor;
+		} else {
+			key = (Key)keyChar;
 		}
-	}
 
-	internal static class Platform {
-		[DllImport ("libc")]
-		static extern int uname (IntPtr buf);
-
-		[DllImport ("libc")]
-		static extern int killpg (int pgrp, int pid);
-
-		static int suspendSignal;
-
-		static int GetSuspendSignal ()
-		{
-			if (suspendSignal != 0)
-				return suspendSignal;
-
-			IntPtr buf = Marshal.AllocHGlobal (8192);
-			if (uname (buf) != 0) {
-				Marshal.FreeHGlobal (buf);
-				suspendSignal = -1;
-				return suspendSignal;
-			}
-			try {
-				switch (Marshal.PtrToStringAnsi (buf)) {
-				case "Darwin":
-				case "DragonFly":
-				case "FreeBSD":
-				case "NetBSD":
-				case "OpenBSD":
-					suspendSignal = 18;
-					break;
-				case "Linux":
-					// TODO: should fetch the machine name and
-					// if it is MIPS return 24
-					suspendSignal = 20;
-					break;
-				case "Solaris":
-					suspendSignal = 24;
-					break;
-				default:
-					suspendSignal = -1;
-					break;
-				}
-				return suspendSignal;
-			} finally {
-				Marshal.FreeHGlobal (buf);
+		KeyModifiers km = new KeyModifiers ();
+		if (shift) {
+			if (keyChar == 0) {
+				key |= Key.ShiftMask;
 			}
+			km.Shift = shift;
 		}
-
-		/// <summary>
-		/// Suspends the process by sending SIGTSTP to itself
-		/// </summary>
-		/// <returns>The suspend.</returns>
-		static public bool Suspend ()
-		{
-			int signal = GetSuspendSignal ();
-			if (signal == -1)
-				return false;
-			killpg (0, signal);
-			return true;
+		if (alt) {
+			key |= Key.AltMask;
+			km.Alt = alt;
 		}
-	}
-
-	/// <summary>
-	///  A clipboard implementation for Linux.
-	///  This implementation uses the xclip command to access the clipboard.
-	/// </summary>	
-	/// <remarks>
-	/// If xclip is not installed, this implementation will not work.
-	/// </remarks>
-	class CursesClipboard : ClipboardBase {
-		public CursesClipboard ()
-		{
-			IsSupported = CheckSupport ();
+		if (control) {
+			key |= Key.CtrlMask;
+			km.Ctrl = control;
 		}
+		_keyDownHandler (new KeyEvent (key, km));
+		_keyHandler (new KeyEvent (key, km));
+		_keyUpHandler (new KeyEvent (key, km));
+	}
 
-		string xclipPath = string.Empty;
-		public override bool IsSupported { get; }
-
-		bool CheckSupport ()
-		{
-#pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
-			try {
-				var (exitCode, result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
-				if (exitCode == 0 && result.FileExists ()) {
-					xclipPath = result;
-					return true;
-				}
-			} catch (Exception) {
-				// Permissions issue.
-			}
-#pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
-			return false;
-		}
 
-		protected override string GetClipboardDataImpl ()
-		{
-			var tempFileName = System.IO.Path.GetTempFileName ();
-			var xclipargs = "-selection clipboard -o";
-
-			try {
-				var (exitCode, result) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
-				if (exitCode == 0) {
-					if (Application.Driver is CursesDriver) {
-						Curses.raw ();
-						Curses.noecho ();
-					}
-					return System.IO.File.ReadAllText (tempFileName);
-				}
-			} catch (Exception e) {
-				throw new NotSupportedException ($"\"{xclipPath} {xclipargs}\" failed.", e);
-			} finally {
-				System.IO.File.Delete (tempFileName);
-			}
-			return string.Empty;
-		}
+}
 
-		protected override void SetClipboardDataImpl (string text)
-		{
-			var xclipargs = "-selection clipboard -i";
-			try {
-				var (exitCode, _) = ClipboardProcessRunner.Bash ($"{xclipPath} {xclipargs}", text, waitForOutput: false);
-				if (exitCode == 0 && Application.Driver is CursesDriver) {
-					Curses.raw ();
-					Curses.noecho ();
-				}
-			} catch (Exception e) {
-				throw new NotSupportedException ($"\"{xclipPath} {xclipargs} < {text}\" failed", e);
-			}
-		}
-	}
+internal static class Platform {
+	[DllImport ("libc")]
+	static extern int uname (IntPtr buf);
 
-	/// <summary>
-	///  A clipboard implementation for MacOSX. 
-	///  This implementation uses the Mac clipboard API (via P/Invoke) to copy/paste.
-	///  The existance of the Mac pbcopy and pbpaste commands 
-	///  is used to determine if copy/paste is supported.
-	/// </summary>	
-	class MacOSXClipboard : ClipboardBase {
-		IntPtr nsString = objc_getClass ("NSString");
-		IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
-		IntPtr utfTextType;
-		IntPtr generalPasteboard;
-		IntPtr initWithUtf8Register = sel_registerName ("initWithUTF8String:");
-		IntPtr allocRegister = sel_registerName ("alloc");
-		IntPtr setStringRegister = sel_registerName ("setString:forType:");
-		IntPtr stringForTypeRegister = sel_registerName ("stringForType:");
-		IntPtr utf8Register = sel_registerName ("UTF8String");
-		IntPtr nsStringPboardType;
-		IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
-		IntPtr clearContentsRegister = sel_registerName ("clearContents");
-
-		public MacOSXClipboard ()
-		{
-			utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
-			nsStringPboardType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "NSStringPboardType");
-			generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
-			IsSupported = CheckSupport ();
-		}
+	[DllImport ("libc")]
+	static extern int killpg (int pgrp, int pid);
 
-		public override bool IsSupported { get; }
+	static int _suspendSignal;
 
-		bool CheckSupport ()
-		{
-			var (exitCode, result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true);
-			if (exitCode != 0 || !result.FileExists ()) {
-				return false;
-			}
-			(exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true);
-			return exitCode == 0 && result.FileExists ();
+	static int GetSuspendSignal ()
+	{
+		if (_suspendSignal != 0) {
+			return _suspendSignal;
 		}
 
-		protected override string GetClipboardDataImpl ()
-		{
-			var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
-			var charArray = objc_msgSend (ptr, utf8Register);
-			return Marshal.PtrToStringAnsi (charArray);
+		IntPtr buf = Marshal.AllocHGlobal (8192);
+		if (uname (buf) != 0) {
+			Marshal.FreeHGlobal (buf);
+			_suspendSignal = -1;
+			return _suspendSignal;
 		}
-
-		protected override void SetClipboardDataImpl (string text)
-		{
-			IntPtr str = default;
-			try {
-				str = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, text);
-				objc_msgSend (generalPasteboard, clearContentsRegister);
-				objc_msgSend (generalPasteboard, setStringRegister, str, utfTextType);
-			} finally {
-				if (str != default) {
-					objc_msgSend (str, sel_registerName ("release"));
-				}
+		try {
+			switch (Marshal.PtrToStringAnsi (buf)) {
+			case "Darwin":
+			case "DragonFly":
+			case "FreeBSD":
+			case "NetBSD":
+			case "OpenBSD":
+				_suspendSignal = 18;
+				break;
+			case "Linux":
+				// TODO: should fetch the machine name and
+				// if it is MIPS return 24
+				_suspendSignal = 20;
+				break;
+			case "Solaris":
+				_suspendSignal = 24;
+				break;
+			default:
+				_suspendSignal = -1;
+				break;
 			}
+			return _suspendSignal;
+		} finally {
+			Marshal.FreeHGlobal (buf);
 		}
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr objc_getClass (string className);
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector);
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, string arg1);
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1);
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr objc_msgSend (IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
-
-		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
-		static extern IntPtr sel_registerName (string selectorName);
 	}
 
 	/// <summary>
-	///  A clipboard implementation for Linux, when running under WSL. 
-	///  This implementation uses the Windows clipboard to store the data, and uses Windows'
-	///  powershell.exe (launched via WSL interop services) to set/get the Windows
-	///  clipboard. 
+	/// Suspends the process by sending SIGTSTP to itself
 	/// </summary>
-	class WSLClipboard : ClipboardBase {
-		bool isSupported = false;
-		public WSLClipboard ()
-		{
-			isSupported = CheckSupport ();
-		}
-
-		public override bool IsSupported {
-			get {
-				return isSupported = CheckSupport ();
-			}
-		}
-
-		private static string powershellPath = string.Empty;
-
-		bool CheckSupport ()
-		{
-			if (string.IsNullOrEmpty (powershellPath)) {
-				// Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
-				var (exitCode, result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true);
-				if (exitCode > 0) {
-					(exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true);
-				}
-
-				if (exitCode == 0) {
-					powershellPath = result;
-				}
-			}
-			return !string.IsNullOrEmpty (powershellPath);
-		}
-
-		protected override string GetClipboardDataImpl ()
-		{
-			if (!IsSupported) {
-				return string.Empty;
-			}
-
-			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, "-noprofile -command \"Get-Clipboard\"");
-			if (exitCode == 0) {
-				if (Application.Driver is CursesDriver) {
-					Curses.raw ();
-					Curses.noecho ();
-				}
-
-				if (output.EndsWith ("\r\n")) {
-					output = output.Substring (0, output.Length - 2);
-				}
-				return output;
-			}
-			return string.Empty;
-		}
-
-		protected override void SetClipboardDataImpl (string text)
-		{
-			if (!IsSupported) {
-				return;
-			}
-
-			var (exitCode, output) = ClipboardProcessRunner.Process (powershellPath, $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\"");
-			if (exitCode == 0) {
-				if (Application.Driver is CursesDriver) {
-					Curses.raw ();
-					Curses.noecho ();
-				}
-			}
+	/// <returns>The suspend.</returns>
+	static public bool Suspend ()
+	{
+		int signal = GetSuspendSignal ();
+		if (signal == -1) {
+			return false;
 		}
+		killpg (0, signal);
+		return true;
 	}
-}
+}

+ 6 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -97,8 +97,8 @@ namespace Terminal.Gui {
 		{
 			this.mainLoop = mainLoop;
 			pipe (wakeupPipes);
-			AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
-				read (wakeupPipes [0], ignore, readHandle);
+			AddWatch (wakeupPipes [1], Condition.PollIn, ml => {
+				read (wakeupPipes [1], ignore, readHandle);
 				return true;
 			});
 		}
@@ -212,5 +212,9 @@ namespace Terminal.Gui {
 				}
 			}
 		}
+		public void TearDown ()
+		{
+			//throw new NotImplementedException ();
+		}
 	}
 }

+ 6 - 4
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -91,7 +91,7 @@ namespace Unix.Terminal {
 		const int RTLD_LAZY = 1;
 		const int RTLD_GLOBAL = 8;
 
-		readonly string libraryPath;
+		public readonly string LibraryPath;
 		readonly IntPtr handle;
 
 		public IntPtr NativeLibraryHandle => handle;
@@ -104,13 +104,15 @@ namespace Unix.Terminal {
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		{
 			if (isFullPath) {
-				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
-				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
+				this.LibraryPath = FirstValidLibraryPath (libraryPathAlternatives);
+				this.handle = PlatformSpecificLoadLibrary (this.LibraryPath);
 			} else {
 				foreach (var lib in libraryPathAlternatives) {
 					this.handle = PlatformSpecificLoadLibrary (lib);
-					if (this.handle != IntPtr.Zero)
+					if (this.handle != IntPtr.Zero) {
+						this.LibraryPath = lib;
 						break;
+					}
 				}
 			}
 

+ 52 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -71,6 +71,7 @@ namespace Unix.Terminal {
 		//static bool use_naked_driver;
 
 		static UnmanagedLibrary curses_library;
+
 		static NativeMethods methods;
 
 		[DllImport ("libc")]
@@ -97,6 +98,13 @@ namespace Unix.Terminal {
 			cols_ptr = get_ptr ("COLS");
 		}
 
+		static public string curses_version ()
+		{
+			var v = methods.curses_version ();
+			return $"{Marshal.PtrToStringAnsi (v)}, {curses_library.LibraryPath}";
+
+		}
+
 		static public Window initscr ()
 		{
 			setlocale (LC_ALL, "");
@@ -116,6 +124,9 @@ namespace Unix.Terminal {
 							 "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig");
 				Environment.Exit (1);
 			}
+
+			//Console.Error.WriteLine ($"using curses {Curses.curses_version ()}");
+
 			return main_window;
 		}
 
@@ -141,7 +152,10 @@ namespace Unix.Terminal {
 
 			console_sharp_get_dims (out l, out c);
 
-			if (l == 1 || l != lines || c != cols) {
+			if (l < 1) {
+				l = 1;
+			}
+			if (l != lines || c != cols) {
 				lines = l;
 				cols = c;
 				return true;
@@ -250,7 +264,35 @@ namespace Unix.Terminal {
 
 		public static int StartColor () => methods.start_color ();
 		public static bool HasColors => methods.has_colors ();
+		/// <summary>
+		/// The init_pair routine changes the definition of a color-pair.It takes
+		/// three arguments: the number of the color-pair to be changed, the  fore-
+		/// ground color number, and the background color number.For portable ap-
+		/// plications:
+		/// 
+		/// o The first argument must be a legal color pair  value.If  default
+		/// colors are used (see use_default_colors(3x)) the upper limit is ad-
+		/// justed to allow for extra pairs which use a default color in  fore-
+		/// ground and/or background.
+		/// 
+		/// o The second and third arguments must be legal color values.
+		/// 
+		/// If the  color-pair was previously initialized, the screen is refreshed
+		/// and all occurrences of that color-pair are changed to the new defini-
+		/// tion.
+		/// 
+		/// As an  extension,  ncurses allows you to set color pair 0 via the as-
+		/// sume_default_colors (3x) routine, or to specify the use of default  col-
+		/// ors (color number  -1) if you first invoke the use_default_colors (3x)
+		/// routine.
+		/// </summary>
+		/// <param name="pair"></param>
+		/// <param name="foreground"></param>
+		/// <param name="background"></param>
+		/// <returns></returns>
 		public static int InitColorPair (short pair, short foreground, short background) => methods.init_pair (pair, foreground, background);
+		// TODO: Upgrade to ncurses 6.1 and use the extended version
+		//public static int InitExtendedPair (int pair, int foreground, int background) => methods.init_extended_pair (pair, foreground, background);
 		public static int UseDefaultColors () => methods.use_default_colors ();
 		public static int ColorPairs => methods.COLOR_PAIRS ();
 
@@ -294,10 +336,12 @@ namespace Unix.Terminal {
 		static public int move (int line, int col) => methods.move (line, col);
 		static public int curs_set (int visibility) => methods.curs_set (visibility);
 		//static public int addch (int ch) => methods.addch (ch);
+		static public int echochar (int ch) => methods.echochar (ch);
 		static public int addwstr (string s) => methods.addwstr (s);
 		static public int mvaddwstr (int y, int x, string s) => methods.mvaddwstr (y, x, s);
 		static public int wmove (IntPtr win, int line, int col) => methods.wmove (win, line, col);
 		static public int waddch (IntPtr win, int ch) => methods.waddch (win, ch);
+		//static public int wechochar (IntPtr win, int ch) => methods.wechochar (win, ch);
 		static public int attron (int attrs) => methods.attron (attrs);
 		static public int attroff (int attrs) => methods.attroff (attrs);
 		static public int attrset (int attrs) => methods.attrset (attrs);
@@ -369,6 +413,7 @@ namespace Unix.Terminal {
 		public delegate int move (int line, int col);
 		public delegate int curs_set (int visibility);
 		public delegate int addch (int ch);
+		public delegate int echochar (int ch);
 		public delegate int mvaddch (int y, int x, int ch);
 		public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
 		public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s);
@@ -402,6 +447,7 @@ namespace Unix.Terminal {
 		public delegate int savetty ();
 		public delegate int resetty ();
 		public delegate int set_escdelay (int size);
+		public delegate IntPtr curses_version ();
 	}
 
 	internal class NativeMethods {
@@ -443,11 +489,13 @@ namespace Unix.Terminal {
 		public readonly Delegates.move move;
 		public readonly Delegates.curs_set curs_set;
 		public readonly Delegates.addch addch;
+		public readonly Delegates.echochar echochar;
 		public readonly Delegates.mvaddch mvaddch;
 		public readonly Delegates.addwstr addwstr;
 		public readonly Delegates.mvaddwstr mvaddwstr;
 		public readonly Delegates.wmove wmove;
 		public readonly Delegates.waddch waddch;
+		//public readonly Delegates.wechochar wechochar;
 		public readonly Delegates.attron attron;
 		public readonly Delegates.attroff attroff;
 		public readonly Delegates.attrset attrset;
@@ -476,6 +524,7 @@ namespace Unix.Terminal {
 		public readonly Delegates.savetty savetty;
 		public readonly Delegates.resetty resetty;
 		public readonly Delegates.set_escdelay set_escdelay;
+		public readonly Delegates.curses_version curses_version;
 		public UnmanagedLibrary UnmanagedLibrary;
 
 		public NativeMethods (UnmanagedLibrary lib)
@@ -519,6 +568,7 @@ namespace Unix.Terminal {
 			move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
 			curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
 			addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
+			echochar = lib.GetNativeMethodDelegate<Delegates.echochar> ("echochar");
 			mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
 			addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
 			mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
@@ -552,6 +602,7 @@ namespace Unix.Terminal {
 			savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
 			resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
 			set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
+			curses_version = lib.GetNativeMethodDelegate<Delegates.curses_version> ("curses_version");
 		}
 	}
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

+ 6 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs

@@ -149,7 +149,12 @@ namespace Unix.Terminal {
 			{
 				return Curses.waddch (Handle, ch);
 			}
-	
+
+			//public int echochar (char ch)
+			//{
+			//	return Curses.wechochar (Handle, ch);
+			//}
+
 			public int refresh ()
 			{
 				return Curses.wrefresh (Handle);

+ 114 - 0
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs

@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Represents the status of an ANSI escape sequence request made to the terminal using <see cref="EscSeqRequests"/>.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public class EscSeqReqStatus {
+		/// <summary>
+		/// Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator).
+		/// </summary>
+		public string Terminator { get; }
+		/// <summary>
+		/// Gets the number of requests.
+		/// </summary>
+		public int NumRequests { get; }
+		/// <summary>
+		/// Gets the number of unfinished requests.
+		/// </summary>
+		public int NumOutstanding { get; set; }
+
+		/// <summary>
+		/// Creates a new state of escape sequence request.
+		/// </summary>
+		/// <param name="terminator">The terminator.</param>
+		/// <param name="numReq">The number of requests.</param>
+		public EscSeqReqStatus (string terminator, int numReq)
+		{
+			Terminator = terminator;
+			NumRequests = NumOutstanding = numReq;
+		}
+	}
+
+	// TODO: This class is a singleton. It should use the singleton pattern.
+	/// <summary>
+	/// Manages ANSI Escape Sequence requests and responses. The list of <see cref="EscSeqReqStatus"/> contains the status of the request.
+	/// Each request is identified by the terminator (e.g. ESC[8t ... t is the terminator).
+	/// </summary>
+	public class EscSeqRequests {
+		/// <summary>
+		/// Gets the <see cref="EscSeqReqStatus"/> list.
+		/// </summary>
+		public List<EscSeqReqStatus> Statuses { get; } = new List<EscSeqReqStatus> ();
+
+		/// <summary>
+		/// Adds a new request for the ANSI Escape Sequence defined by <paramref name="terminator"/>. Adds a <see cref="EscSeqReqStatus"/>
+		/// instance to <see cref="Statuses"/> list.
+		/// </summary>
+		/// <param name="terminator">The terminator.</param>
+		/// <param name="numReq">The number of requests.</param>
+		public void Add (string terminator, int numReq = 1)
+		{
+			lock (Statuses) {
+				var found = Statuses.Find (x => x.Terminator == terminator);
+				if (found == null) {
+					Statuses.Add (new EscSeqReqStatus (terminator, numReq));
+				} else if (found != null && found.NumOutstanding < found.NumRequests) {
+					found.NumOutstanding = Math.Min (found.NumOutstanding + numReq, found.NumRequests);
+				}
+			}
+		}
+
+		/// <summary>
+		/// Removes a request defined by <paramref name="terminator"/>. If a matching <see cref="EscSeqReqStatus"/> is found
+		/// and the number of outstanding
+		/// requests is greater than 0, the number of outstanding requests is decremented. If the number of outstanding requests
+		/// is 0, the <see cref="EscSeqReqStatus"/> is removed from <see cref="Statuses"/>.
+		/// </summary>
+		/// <param name="terminator">The terminating string.</param>
+		public void Remove (string terminator)
+		{
+			lock (Statuses) {
+				var found = Statuses.Find (x => x.Terminator == terminator);
+				if (found == null) {
+					return;
+				}
+				if (found != null && found.NumOutstanding == 0) {
+					Statuses.Remove (found);
+				} else if (found != null && found.NumOutstanding > 0) {
+					found.NumOutstanding--;
+					if (found.NumOutstanding == 0) {
+						Statuses.Remove (found);
+					}
+				}
+			}
+		}
+
+		/// <summary>
+		/// Indicates if a <see cref="EscSeqReqStatus"/> with the <paramref name="terminator"/> exists
+		/// in the <see cref="Statuses"/> list.
+		/// </summary>
+		/// <param name="terminator"></param>
+		/// <returns><see langword="true"/> if exist, <see langword="false"/> otherwise.</returns>
+		public bool HasResponse (string terminator)
+		{
+			lock (Statuses) {
+				var found = Statuses.Find (x => x.Terminator == terminator);
+				if (found == null) {
+					return false;
+				}
+				if (found != null && found.NumOutstanding > 0) {
+					return true;
+				} else {
+					// BUGBUG: Why does an API that returns a bool remove the entry from the list?
+					// NetDriver and Unit tests never exercise this line of code. Maybe Curses does?
+					Statuses.Remove (found);
+				}
+				return false;
+			}
+		}
+	}
+}

+ 1165 - 0
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -0,0 +1,1165 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Management;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui;
+/// <summary>
+/// Provides a platform-independent API for managing ANSI escape sequences.
+/// </summary>
+/// <remarks>
+/// Useful resources:
+/// * https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
+/// * https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+/// * https://vt100.net/
+/// </remarks>
+public static class EscSeqUtils {
+	/// <summary>
+	/// Escape key code (ASCII 27/0x1B).
+	/// </summary>
+	public static readonly char KeyEsc = (char)Key.Esc;
+
+	/// <summary>
+	/// ESC [ - The CSI (Control Sequence Introducer).
+	/// </summary>
+	public static readonly string CSI = $"{KeyEsc}[";
+
+	/// <summary>
+	/// ESC [ ? 1003 h - Enable  mouse event tracking.
+	/// </summary>
+	public static readonly string CSI_EnableAnyEventMouse = CSI + "?1003h";
+
+	/// <summary>
+	/// ESC [ ? 1006 h - Enable SGR (Select Graphic Rendition).
+	/// </summary>
+	public static readonly string CSI_EnableSgrExtModeMouse = CSI + "?1006h";
+
+	/// <summary>
+	/// ESC [ ? 1015 h - Enable URXVT (Unicode Extended Virtual Terminal).
+	/// </summary>
+	public static readonly string CSI_EnableUrxvtExtModeMouse = CSI + "?1015h";
+
+	/// <summary>
+	/// ESC [ ? 1003 l - Disable any mouse event tracking.
+	/// </summary>
+	public static readonly string CSI_DisableAnyEventMouse = CSI + "?1003l";
+
+	/// <summary>
+	/// ESC [ ? 1006 l - Disable SGR (Select Graphic Rendition).
+	/// </summary>
+	public static readonly string CSI_DisableSgrExtModeMouse = CSI + "?1006l";
+
+	/// <summary>
+	/// ESC [ ? 1015 l - Disable URXVT (Unicode Extended Virtual Terminal).
+	/// </summary>
+	public static readonly string CSI_DisableUrxvtExtModeMouse = CSI + "?1015l";
+
+	/// <summary>
+	/// Control sequence for enabling mouse events.
+	/// </summary>
+	public static string CSI_EnableMouseEvents { get; set; } =
+		CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
+
+	/// <summary>
+	/// Control sequence for disabling mouse events.
+	/// </summary>
+	public static string CSI_DisableMouseEvents { get; set; } =
+		CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
+
+	/// <summary>
+	/// ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll)
+	/// </summary>
+	public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
+
+	/// <summary>
+	/// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll)
+	/// </summary>
+	public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
+
+	/// <summary>
+	/// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
+	/// </summary>
+	public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
+
+	/// <summary>
+	/// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
+	/// </summary>
+	public static readonly string CSI_RestoreCursorAndActivateAltBufferWithBackscroll = CSI + "?1049l";
+
+	/// <summary>
+	/// Options for ANSI ESC "[xJ" - Clears part of the screen.
+	/// </summary>
+	public enum ClearScreenOptions {
+		/// <summary>
+		/// If n is 0 (or missing), clear from cursor to end of screen.
+		/// </summary>
+		CursorToEndOfScreen = 0,
+		/// <summary>
+		/// If n is 1, clear from cursor to beginning of the screen.
+		/// </summary>
+		CursorToBeginningOfScreen = 1,
+		/// <summary>
+		/// If n is 2, clear entire screen (and moves cursor to upper left on DOS ANSI.SYS).
+		/// </summary>
+		EntireScreen = 2,
+		/// <summary>
+		/// If n is 3, clear entire screen and delete all lines saved in the scrollback buffer
+		/// </summary>
+		EntireScreenAndScrollbackBuffer = 3
+	}
+
+	/// <summary>
+	/// ESC [ x J - Clears part of the screen. See <see cref="ClearScreenOptions"/>.
+	/// </summary>
+	/// <param name="option"></param>
+	/// <returns></returns>
+	public static string CSI_ClearScreen (ClearScreenOptions option) => $"{CSI}{(int)option}J";
+
+	#region Cursor
+	//ESC [ M - RI Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary*
+
+	/// <summary>
+	/// ESC [ 7 - Save Cursor Position in Memory**
+	/// </summary>
+	public static readonly string CSI_SaveCursorPosition = CSI + "7";
+
+	/// <summary>
+	/// ESC [ 8 - DECSR Restore Cursor Position from Memory**
+	/// </summary>
+	public static readonly string CSI_RestoreCursorPosition = CSI + "8";
+
+	/// <summary>
+	/// ESC [ 8 ; height ; width t - Set Terminal Window Size
+	/// https://terminalguide.namepad.de/seq/csi_st-8/
+	/// </summary>
+	public static string CSI_SetTerminalWindowSize (int height, int width) => $"{CSI}8;{height};{width}t";
+
+	//ESC [ < n > A - CUU - Cursor Up       Cursor up by < n >
+	//ESC [ < n > B - CUD - Cursor Down     Cursor down by < n >
+	//ESC [ < n > C - CUF - Cursor Forward  Cursor forward (Right) by < n >
+	//ESC [ < n > D - CUB - Cursor Backward Cursor backward (Left) by < n >
+	//ESC [ < n > E - CNL - Cursor Next Line - Cursor down < n > lines from current position
+	//ESC [ < n > F - CPL - Cursor Previous Line    Cursor up < n > lines from current position
+	//ESC [ < n > G - CHA - Cursor Horizontal Absolute      Cursor moves to < n > th position horizontally in the current line
+	//ESC [ < n > d - VPA - Vertical Line Position Absolute Cursor moves to the < n > th position vertically in the current column
+
+	/// <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>
+	/// <returns></returns>
+	public static string CSI_SetCursorPosition (int x, int y) => $"{CSI}{y};{x}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
+	//ESC [ s - ANSISYSSC       Save Cursor – Ansi.sys emulation	**With no parameters, performs a save cursor operation like DECSC
+	//ESC [ u - ANSISYSRC       Restore Cursor – Ansi.sys emulation	**With no parameters, performs a restore cursor operation like DECRC
+	//ESC [ ? 12 h - ATT160  Text Cursor Enable Blinking     Start the cursor blinking
+	//ESC [ ? 12 l - ATT160  Text Cursor Disable Blinking    Stop blinking the cursor
+	/// <summary>
+	/// ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show    Show the cursor
+	/// </summary>
+	public static readonly string CSI_ShowCursor = CSI + "?25h";
+
+	/// <summary>
+	/// ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide    Hide the cursor
+	/// </summary>
+	public static readonly string CSI_HideCursor = CSI + "?25l";
+	//ESC [ ? 12 h - ATT160  Text Cursor Enable Blinking     Start the cursor blinking
+	//ESC [ ? 12 l - ATT160  Text Cursor Disable Blinking    Stop blinking the cursor
+	//ESC [ ? 25 h - DECTCEM Text Cursor Enable Mode Show    Show the cursor
+	//ESC [ ? 25 l - DECTCEM Text Cursor Enable Mode Hide    Hide the cursor
+
+	/// <summary>
+	/// Styles for ANSI ESC "[x q" - Set Cursor Style
+	/// </summary>
+	public enum DECSCUSR_Style {
+		/// <summary>
+		/// DECSCUSR - User Shape - Default cursor shape configured by the user
+		/// </summary>
+		UserShape = 0,
+		/// <summary>
+		/// DECSCUSR - Blinking Block - Blinking block cursor shape
+		/// </summary>
+		BlinkingBlock = 1,
+		/// <summary>
+		/// DECSCUSR - Steady Block - Steady block cursor shape
+		/// </summary>
+		SteadyBlock = 2,
+		/// <summary>
+		/// DECSCUSR - Blinking Underline - Blinking underline cursor shape
+		/// </summary>
+		BlinkingUnderline = 3,
+		/// <summary>
+		/// DECSCUSR - Steady Underline - Steady underline cursor shape
+		/// </summary>
+		SteadyUnderline = 4,
+		/// <summary>
+		/// DECSCUSR - Blinking Bar - Blinking bar cursor shape
+		/// </summary>
+		BlinkingBar = 5,
+		/// <summary>
+		/// DECSCUSR - Steady Bar - Steady bar cursor shape
+		/// </summary>
+		SteadyBar = 6
+	}
+
+	/// <summary>
+	/// ESC [ n SP q - Select Cursor Style (DECSCUSR)
+	/// https://terminalguide.namepad.de/seq/csi_sq_t_space/ 
+	/// </summary>
+	/// <param name="style"></param>
+	/// <returns></returns>
+	public static string CSI_SetCursorStyle (DECSCUSR_Style style) => $"{CSI}{(int)style} q";
+
+	#endregion
+
+	#region Colors
+	/// <summary>
+	/// ESC [ (n) m - SGR - Set Graphics Rendition - Set the format of the screen and text as specified by (n)
+	/// This command is special in that the (n) position can accept between 0 and 16 parameters separated by semicolons.
+	/// When no parameters are specified, it is treated the same as a single 0 parameter.
+	/// https://terminalguide.namepad.de/seq/csi_sm/
+	/// </summary>
+	public static string CSI_SetGraphicsRendition (params int [] parameters) => $"{CSI}{string.Join (";", parameters)}m";
+
+	/// <summary>
+	/// ESC[38;5;{id}m - Set foreground color (256 colors)
+	/// </summary>
+	public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}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";
+
+	/// <summary>
+	/// ESC[38;2;{r};{g};{b}m	Set foreground color as RGB.
+	/// </summary>
+	public static string CSI_SetForegroundColorRGB (int r, int g, int b) => $"{CSI}38;2;{r};{g};{b}m";
+
+	/// <summary>
+	/// ESC[48;2;{r};{g};{b}m	Set background color as RGB.
+	/// </summary>
+	public static string CSI_SetBackgroundColorRGB (int r, int g, int b) => $"{CSI}48;2;{r};{g};{b}m";
+
+	#endregion
+
+	#region Requests
+	/// <summary>
+	/// 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";
+
+	/// <summary>
+	/// The terminal reply to <see cref="CSI_RequestCursorPositionReport"/>. ESC [ ? (y) ; (x) R
+	/// </summary>
+	public const string CSI_RequestCursorPositionReport_Terminator = "R";
+
+	/// <summary>
+	/// ESC [ 0 c - Send Device Attributes (Primary DA)
+	///
+	/// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Application-Program-Command-functions
+	/// https://www.xfree86.org/current/ctlseqs.html
+	/// Windows Terminal v1.17 and below emits “\x1b[?1;0c”, indicating "VT101 with No Options".
+	/// Windows Terminal v1.18+ emits: \x1b[?61;6;7;22;23;24;28;32;42c"
+	/// See https://github.com/microsoft/terminal/pull/14906
+	///
+	/// 61 - The device conforms to level 1 of the character cell display architecture
+	/// (See https://github.com/microsoft/terminal/issues/15693#issuecomment-1633304497)
+	/// 6 = Selective erase
+	/// 7 = Soft fonts
+	/// 22 = Color text
+	/// 23 = Greek character sets
+	/// 24 = Turkish character sets
+	/// 28 = Rectangular area operations
+	/// 32 = Text macros
+	/// 42 = ISO Latin-2 character set
+	/// 
+	/// </summary>
+	public static readonly string CSI_SendDeviceAttributes = CSI + "0c";
+
+	/// <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";
+
+	/// <summary>
+	/// The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or <see cref="CSI_SendDeviceAttributes2"/>
+	/// 
+	/// </summary>
+	public const string CSI_ReportDeviceAttributes_Terminator = "c";
+
+	/// <summary>
+	/// 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";
+
+	/// <summary>
+	/// The terminator indicating a reply to <see cref="CSI_ReportTerminalSizeInChars"/> : ESC [ 8 ; height ; width t
+	/// </summary>
+	public const string CSI_ReportTerminalSizeInChars_Terminator = "t";
+
+	/// <summary>
+	/// The value of the response to <see cref="CSI_ReportTerminalSizeInChars"/> indicating value 1 and 2 are the terminal size in chars.
+	/// </summary>
+	public const string CSI_ReportTerminalSizeInChars_ResponseValue = "8";
+	#endregion
+
+	/// <summary>
+	/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
+	/// <returns>The <see cref="ConsoleKeyInfo"/> modified.</returns>
+	public static ConsoleKeyInfo MapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
+		ConsoleKey key;
+		var keyChar = consoleKeyInfo.KeyChar;
+		switch ((uint)keyChar) {
+		case 0:
+			if (consoleKeyInfo.Key == (ConsoleKey)64) {    // Ctrl+Space in Windows.
+				newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+			}
+			break;
+		case uint n when n > 0 && n <= KeyEsc:
+			if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') {
+				key = ConsoleKey.Enter;
+				newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar,
+					key,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+			} else if (consoleKeyInfo.Key == 0) {
+				key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
+				newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
+					key,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+					(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+					true);
+			}
+			break;
+		case 127: // DEL
+			newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace,
+				(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+				(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+				(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
+			break;
+		default:
+			newConsoleKeyInfo = consoleKeyInfo;
+			break;
+		}
+
+		return newConsoleKeyInfo;
+	}
+
+	/// <summary>
+	/// A helper to resize the <see cref="ConsoleKeyInfo"/> as needed.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
+	/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array to resize.</param>
+	/// <returns>The <see cref="ConsoleKeyInfo"/> resized.</returns>
+	public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki)
+	{
+		Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1);
+		cki [cki.Length - 1] = consoleKeyInfo;
+		return cki;
+	}
+
+	/// <summary>
+	/// Decodes an ANSI escape sequence.
+	/// </summary>
+	/// <param name="escSeqRequests">The <see cref="EscSeqRequests"/> which may contain a request.</param>
+	/// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may changes.</param>
+	/// <param name="key">The <see cref="ConsoleKey"/> which may changes.</param>
+	/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
+	/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
+	/// <param name="c1Control">The control returned by the <see cref="GetC1ControlChar(char)"/> method.</param>
+	/// <param name="code">The code returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+	/// <param name="values">The values returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+	/// <param name="terminator">The terminator returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
+	/// <param name="isMouse">Indicates if the escape sequence is a mouse event.</param>
+	/// <param name="buttonState">The <see cref="MouseFlags"/> button state.</param>
+	/// <param name="pos">The <see cref="MouseFlags"/> position.</param>
+	/// <param name="isResponse">Indicates if the escape sequence is a response to a request.</param>
+	/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
+	public static void DecodeEscSeq (EscSeqRequests escSeqRequests, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminator, out bool isMouse, out List<MouseFlags> buttonState, out Point pos, out bool isResponse, Action<MouseFlags, Point> continuousButtonPressedHandler)
+	{
+		char [] kChars = GetKeyCharArray (cki);
+		(c1Control, code, values, terminator) = GetEscapeResult (kChars);
+		isMouse = false;
+		buttonState = new List<MouseFlags> () { 0 };
+		pos = default;
+		isResponse = false;
+		switch (c1Control) {
+		case "ESC":
+			if (values == null && string.IsNullOrEmpty (terminator)) {
+				key = ConsoleKey.Escape;
+				newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key,
+					(mod & ConsoleModifiers.Shift) != 0,
+					(mod & ConsoleModifiers.Alt) != 0,
+					(mod & ConsoleModifiers.Control) != 0);
+			} else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) {
+				key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1);
+				newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar,
+					key,
+					false,
+					true,
+					true);
+			} else {
+				if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) {
+					key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0];
+				} else {
+					key = (ConsoleKey)cki [1].KeyChar;
+				}
+				newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
+					(ConsoleKey)Math.Min ((uint)key, 255),
+					false,
+					true,
+					false);
+			}
+			break;
+		case "SS3":
+			key = GetConsoleKey (terminator [0], values [0], ref mod);
+			newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
+				key,
+				(mod & ConsoleModifiers.Shift) != 0,
+				(mod & ConsoleModifiers.Alt) != 0,
+				(mod & ConsoleModifiers.Control) != 0);
+			break;
+		case "CSI":
+			if (!string.IsNullOrEmpty (code) && code == "<") {
+				GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler);
+				isMouse = true;
+				return;
+			} else if (escSeqRequests != null && escSeqRequests.HasResponse (terminator)) {
+				isResponse = true;
+				escSeqRequests.Remove (terminator);
+				return;
+			}
+			key = GetConsoleKey (terminator [0], values [0], ref mod);
+			if (key != 0 && values.Length > 1) {
+				mod |= GetConsoleModifiers (values [1]);
+			}
+			newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
+				key,
+				(mod & ConsoleModifiers.Shift) != 0,
+				(mod & ConsoleModifiers.Alt) != 0,
+				(mod & ConsoleModifiers.Control) != 0);
+			break;
+		}
+	}
+
+	/// <summary>
+	/// Gets all the needed information about a escape sequence.
+	/// </summary>
+	/// <param name="kChar">The array with all chars.</param>
+	/// <returns>
+	/// The c1Control returned by <see cref="GetC1ControlChar(char)"/>, code, values and terminating.
+	/// </returns>
+	public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar)
+	{
+		if (kChar == null || kChar.Length == 0) {
+			return (null, null, null, null);
+		}
+		if (kChar [0] != KeyEsc) {
+			throw new InvalidOperationException ("Invalid escape character!");
+		}
+		if (kChar.Length == 1) {
+			return ("ESC", null, null, null);
+		}
+		if (kChar.Length == 2) {
+			return ("ESC", null, null, kChar [1].ToString ());
+		}
+		string c1Control = GetC1ControlChar (kChar [1]);
+		string code = null;
+		int nSep = kChar.Count (x => x == ';') + 1;
+		string [] values = new string [nSep];
+		int valueIdx = 0;
+		string terminating = "";
+		for (int i = 2; i < kChar.Length; i++) {
+			var c = kChar [i];
+			if (char.IsDigit (c)) {
+				values [valueIdx] += c.ToString ();
+			} else if (c == ';') {
+				valueIdx++;
+			} else if (valueIdx == nSep - 1 || i == kChar.Length - 1) {
+				terminating += c.ToString ();
+			} else {
+				code += c.ToString ();
+			}
+		}
+
+		return (c1Control, code, values, terminating);
+	}
+
+	/// <summary>
+	/// Gets the c1Control used in the called escape sequence.
+	/// </summary>
+	/// <param name="c">The char used.</param>
+	/// <returns>The c1Control.</returns>
+	public static string GetC1ControlChar (char c)
+	{
+		// These control characters are used in the vtXXX emulation.
+		switch (c) {
+		case 'D':
+			return "IND"; // Index
+		case 'E':
+			return "NEL"; // Next Line
+		case 'H':
+			return "HTS"; // Tab Set
+		case 'M':
+			return "RI"; // Reverse Index
+		case 'N':
+			return "SS2"; // Single Shift Select of G2 Character Set: affects next character only
+		case 'O':
+			return "SS3"; // Single Shift Select of G3 Character Set: affects next character only
+		case 'P':
+			return "DCS"; // Device Control String
+		case 'V':
+			return "SPA"; // Start of Guarded Area
+		case 'W':
+			return "EPA"; // End of Guarded Area
+		case 'X':
+			return "SOS"; // Start of String
+		case 'Z':
+			return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA)
+		case '[':
+			return "CSI"; // Control Sequence Introducer
+		case '\\':
+			return "ST"; // String Terminator
+		case ']':
+			return "OSC"; // Operating System Command
+		case '^':
+			return "PM"; // Privacy Message
+		case '_':
+			return "APC"; // Application Program Command
+		default:
+			return ""; // Not supported
+		}
+	}
+
+	/// <summary>
+	/// Gets the <see cref="ConsoleModifiers"/> from the value.
+	/// </summary>
+	/// <param name="value">The value.</param>
+	/// <returns>The <see cref="ConsoleModifiers"/> or zero.</returns>
+	public static ConsoleModifiers GetConsoleModifiers (string value)
+	{
+		switch (value) {
+		case "2":
+			return ConsoleModifiers.Shift;
+		case "3":
+			return ConsoleModifiers.Alt;
+		case "4":
+			return ConsoleModifiers.Shift | ConsoleModifiers.Alt;
+		case "5":
+			return ConsoleModifiers.Control;
+		case "6":
+			return ConsoleModifiers.Shift | ConsoleModifiers.Control;
+		case "7":
+			return ConsoleModifiers.Alt | ConsoleModifiers.Control;
+		case "8":
+			return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control;
+		default:
+			return 0;
+		}
+	}
+
+	/// <summary>
+	/// Gets the <see cref="ConsoleKey"/> depending on terminating and value.
+	/// </summary>
+	/// <param name="terminating">The terminating.</param>
+	/// <param name="value">The value.</param>
+	/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
+	/// <returns>The <see cref="ConsoleKey"/> and probably the <see cref="ConsoleModifiers"/>.</returns>
+	public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod)
+	{
+		ConsoleKey key;
+		switch (terminating) {
+		case 'A':
+			key = ConsoleKey.UpArrow;
+			break;
+		case 'B':
+			key = ConsoleKey.DownArrow;
+			break;
+		case 'C':
+			key = ConsoleKey.RightArrow;
+			break;
+		case 'D':
+			key = ConsoleKey.LeftArrow;
+			break;
+		case 'F':
+			key = ConsoleKey.End;
+			break;
+		case 'H':
+			key = ConsoleKey.Home;
+			break;
+		case 'P':
+			key = ConsoleKey.F1;
+			break;
+		case 'Q':
+			key = ConsoleKey.F2;
+			break;
+		case 'R':
+			key = ConsoleKey.F3;
+			break;
+		case 'S':
+			key = ConsoleKey.F4;
+			break;
+		case 'Z':
+			key = ConsoleKey.Tab;
+			mod |= ConsoleModifiers.Shift;
+			break;
+		case '~':
+			switch (value) {
+			case "2":
+				key = ConsoleKey.Insert;
+				break;
+			case "3":
+				key = ConsoleKey.Delete;
+				break;
+			case "5":
+				key = ConsoleKey.PageUp;
+				break;
+			case "6":
+				key = ConsoleKey.PageDown;
+				break;
+			case "15":
+				key = ConsoleKey.F5;
+				break;
+			case "17":
+				key = ConsoleKey.F6;
+				break;
+			case "18":
+				key = ConsoleKey.F7;
+				break;
+			case "19":
+				key = ConsoleKey.F8;
+				break;
+			case "20":
+				key = ConsoleKey.F9;
+				break;
+			case "21":
+				key = ConsoleKey.F10;
+				break;
+			case "23":
+				key = ConsoleKey.F11;
+				break;
+			case "24":
+				key = ConsoleKey.F12;
+				break;
+			default:
+				key = 0;
+				break;
+			}
+			break;
+		default:
+			key = 0;
+			break;
+		}
+
+		return key;
+	}
+
+	/// <summary>
+	/// A helper to get only the <see cref="ConsoleKeyInfo.KeyChar"/> from the <see cref="ConsoleKeyInfo"/> array.
+	/// </summary>
+	/// <param name="cki"></param>
+	/// <returns>The char array of the escape sequence.</returns>
+	public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
+	{
+		char [] kChar = new char [] { };
+		var length = 0;
+		foreach (var kc in cki) {
+			length++;
+			Array.Resize (ref kChar, length);
+			kChar [length - 1] = kc.KeyChar;
+		}
+
+		return kChar;
+	}
+
+	private static MouseFlags? lastMouseButtonPressed;
+	//private static MouseFlags? lastMouseButtonReleased;
+	private static bool isButtonPressed;
+	//private static bool isButtonReleased;
+	private static bool isButtonClicked;
+	private static bool isButtonDoubleClicked;
+	private static bool isButtonTripleClicked;
+	private static Point point;
+
+	/// <summary>
+	/// Gets the <see cref="MouseFlags"/> mouse button flags and the position.
+	/// </summary>
+	/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
+	/// <param name="mouseFlags">The mouse button flags.</param>
+	/// <param name="pos">The mouse position.</param>
+	/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
+	public static void GetMouse (ConsoleKeyInfo [] cki, out List<MouseFlags> mouseFlags, out Point pos, Action<MouseFlags, Point> continuousButtonPressedHandler)
+	{
+		MouseFlags buttonState = 0;
+		pos = new Point ();
+		int buttonCode = 0;
+		bool foundButtonCode = false;
+		int foundPoint = 0;
+		string value = "";
+		var kChar = GetKeyCharArray (cki);
+		//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
+		for (int i = 0; i < kChar.Length; i++) {
+			var c = kChar [i];
+			if (c == '<') {
+				foundButtonCode = true;
+			} else if (foundButtonCode && c != ';') {
+				value += c.ToString ();
+			} else if (c == ';') {
+				if (foundButtonCode) {
+					foundButtonCode = false;
+					buttonCode = int.Parse (value);
+				}
+				if (foundPoint == 1) {
+					pos.X = int.Parse (value) - 1;
+				}
+				value = "";
+				foundPoint++;
+			} else if (foundPoint > 0 && c != 'm' && c != 'M') {
+				value += c.ToString ();
+			} else if (c == 'm' || c == 'M') {
+				//pos.Y = int.Parse (value) + Console.WindowTop - 1;
+				pos.Y = int.Parse (value) - 1;
+
+				switch (buttonCode) {
+				case 0:
+				case 8:
+				case 16:
+				case 24:
+				case 32:
+				case 36:
+				case 40:
+				case 48:
+				case 56:
+					buttonState = c == 'M' ? MouseFlags.Button1Pressed
+						: MouseFlags.Button1Released;
+					break;
+				case 1:
+				case 9:
+				case 17:
+				case 25:
+				case 33:
+				case 37:
+				case 41:
+				case 45:
+				case 49:
+				case 53:
+				case 57:
+				case 61:
+					buttonState = c == 'M' ? MouseFlags.Button2Pressed
+						: MouseFlags.Button2Released;
+					break;
+				case 2:
+				case 10:
+				case 14:
+				case 18:
+				case 22:
+				case 26:
+				case 30:
+				case 34:
+				case 42:
+				case 46:
+				case 50:
+				case 54:
+				case 58:
+				case 62:
+					buttonState = c == 'M' ? MouseFlags.Button3Pressed
+						: MouseFlags.Button3Released;
+					break;
+				case 35:
+				//// Needed for Windows OS
+				//if (isButtonPressed && c == 'm'
+				//	&& (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
+				//	|| lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
+				//	|| lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
+
+				//	switch (lastMouseEvent.ButtonState) {
+				//	case MouseFlags.Button1Pressed:
+				//		buttonState = MouseFlags.Button1Released;
+				//		break;
+				//	case MouseFlags.Button2Pressed:
+				//		buttonState = MouseFlags.Button2Released;
+				//		break;
+				//	case MouseFlags.Button3Pressed:
+				//		buttonState = MouseFlags.Button3Released;
+				//		break;
+				//	}
+				//} else {
+				//	buttonState = MouseFlags.ReportMousePosition;
+				//}
+				//break;
+				case 39:
+				case 43:
+				case 47:
+				case 51:
+				case 55:
+				case 59:
+				case 63:
+					buttonState = MouseFlags.ReportMousePosition;
+					break;
+				case 64:
+					buttonState = MouseFlags.WheeledUp;
+					break;
+				case 65:
+					buttonState = MouseFlags.WheeledDown;
+					break;
+				case 68:
+				case 72:
+				case 80:
+					buttonState = MouseFlags.WheeledLeft;       // Shift/Ctrl+WheeledUp
+					break;
+				case 69:
+				case 73:
+				case 81:
+					buttonState = MouseFlags.WheeledRight;      // Shift/Ctrl+WheeledDown
+					break;
+				}
+				// Modifiers.
+				switch (buttonCode) {
+				case 8:
+				case 9:
+				case 10:
+				case 43:
+					buttonState |= MouseFlags.ButtonAlt;
+					break;
+				case 14:
+				case 47:
+					buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+					break;
+				case 16:
+				case 17:
+				case 18:
+				case 51:
+					buttonState |= MouseFlags.ButtonCtrl;
+					break;
+				case 22:
+				case 55:
+					buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+					break;
+				case 24:
+				case 25:
+				case 26:
+				case 59:
+					buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+					break;
+				case 30:
+				case 63:
+					buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+					break;
+				case 32:
+				case 33:
+				case 34:
+					buttonState |= MouseFlags.ReportMousePosition;
+					break;
+				case 36:
+				case 37:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
+					break;
+				case 39:
+				case 68:
+				case 69:
+					buttonState |= MouseFlags.ButtonShift;
+					break;
+				case 40:
+				case 41:
+				case 42:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
+					break;
+				case 45:
+				case 46:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+					break;
+				case 48:
+				case 49:
+				case 50:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
+					break;
+				case 53:
+				case 54:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+					break;
+				case 56:
+				case 57:
+				case 58:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+					break;
+				case 61:
+				case 62:
+					buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+					break;
+				}
+			}
+		}
+
+		mouseFlags = new List<MouseFlags> () { MouseFlags.AllEvents };
+
+		if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition)
+			&& !buttonState.HasFlag (MouseFlags.Button1Released)
+			&& !buttonState.HasFlag (MouseFlags.Button2Released)
+			&& !buttonState.HasFlag (MouseFlags.Button3Released)
+			&& !buttonState.HasFlag (MouseFlags.Button4Released)) {
+
+			lastMouseButtonPressed = null;
+			isButtonPressed = false;
+		}
+
+		if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+			  buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) ||
+			  isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) {
+
+			mouseFlags [0] = buttonState;
+			lastMouseButtonPressed = buttonState;
+			isButtonPressed = true;
+
+			if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
+				point = new Point () {
+					X = pos.X,
+					Y = pos.Y
+				};
+
+				Application.MainLoop.AddIdle (() => {
+					Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
+					return false;
+				});
+			} else if (mouseFlags [0] == MouseFlags.ReportMousePosition) {
+				isButtonPressed = false;
+			}
+
+		} else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+			buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
+
+			mouseFlags [0] = GetButtonTripleClicked (buttonState);
+			isButtonDoubleClicked = false;
+			isButtonTripleClicked = true;
+
+		} else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
+			buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
+
+			mouseFlags [0] = GetButtonDoubleClicked (buttonState);
+			isButtonClicked = false;
+			isButtonDoubleClicked = true;
+			Application.MainLoop.AddIdle (() => {
+				Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
+				return false;
+			});
+
+		}
+		//else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) {
+		//	mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased);
+		//	lastMouseButtonReleased = null;
+		//	isButtonReleased = false;
+		//	isButtonClicked = true;
+		//	Application.MainLoop.AddIdle (() => {
+		//		Task.Run (async () => await ProcessButtonClickedAsync ());
+		//		return false;
+		//	});
+
+		//} 
+		else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released ||
+			  buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) {
+
+			mouseFlags [0] = buttonState;
+			isButtonPressed = false;
+
+			if (isButtonTripleClicked) {
+				isButtonTripleClicked = false;
+			} else if (pos.X == point.X && pos.Y == point.Y) {
+				mouseFlags.Add (GetButtonClicked (buttonState));
+				isButtonClicked = true;
+				Application.MainLoop.AddIdle (() => {
+					Task.Run (async () => await ProcessButtonClickedAsync ());
+					return false;
+				});
+			}
+
+			point = pos;
+
+			//if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) {
+			//	lastMouseButtonReleased = buttonState;
+			//	isButtonPressed = false;
+			//	isButtonReleased = true;
+			//} else {
+			//	lastMouseButtonPressed = null;
+			//	isButtonPressed = false;
+			//}
+
+		} else if (buttonState == MouseFlags.WheeledUp) {
+
+			mouseFlags [0] = MouseFlags.WheeledUp;
+
+		} else if (buttonState == MouseFlags.WheeledDown) {
+
+			mouseFlags [0] = MouseFlags.WheeledDown;
+
+		} else if (buttonState == MouseFlags.WheeledLeft) {
+
+			mouseFlags [0] = MouseFlags.WheeledLeft;
+
+		} else if (buttonState == MouseFlags.WheeledRight) {
+
+			mouseFlags [0] = MouseFlags.WheeledRight;
+
+		} else if (buttonState == MouseFlags.ReportMousePosition) {
+			mouseFlags [0] = MouseFlags.ReportMousePosition;
+
+		} else {
+			mouseFlags [0] = buttonState;
+			//foreach (var flag in buttonState.GetUniqueFlags()) {
+			//	mouseFlag [0] |= flag;
+			//}
+		}
+
+		mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]);
+		//buttonState = mouseFlags;
+
+		//System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}");
+		//foreach (var mf in mouseFlags) {
+		//	System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}");
+		//}
+	}
+
+	private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
+	{
+		while (isButtonPressed) {
+			await Task.Delay (100);
+			//var me = new MouseEvent () {
+			//	X = point.X,
+			//	Y = point.Y,
+			//	Flags = mouseFlag
+			//};
+
+			var view = Application.WantContinuousButtonPressedView;
+			if (view == null)
+				break;
+			if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
+				Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point));
+			}
+		}
+	}
+
+	private static async Task ProcessButtonClickedAsync ()
+	{
+		await Task.Delay (300);
+		isButtonClicked = false;
+	}
+
+	private static async Task ProcessButtonDoubleClickedAsync ()
+	{
+		await Task.Delay (300);
+		isButtonDoubleClicked = false;
+	}
+
+	private static MouseFlags GetButtonClicked (MouseFlags mouseFlag)
+	{
+		MouseFlags mf = default;
+		switch (mouseFlag) {
+		case MouseFlags.Button1Released:
+			mf = MouseFlags.Button1Clicked;
+			break;
+
+		case MouseFlags.Button2Released:
+			mf = MouseFlags.Button2Clicked;
+			break;
+
+		case MouseFlags.Button3Released:
+			mf = MouseFlags.Button3Clicked;
+			break;
+		}
+		return mf;
+	}
+
+	private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag)
+	{
+		MouseFlags mf = default;
+		switch (mouseFlag) {
+		case MouseFlags.Button1Pressed:
+			mf = MouseFlags.Button1DoubleClicked;
+			break;
+
+		case MouseFlags.Button2Pressed:
+			mf = MouseFlags.Button2DoubleClicked;
+			break;
+
+		case MouseFlags.Button3Pressed:
+			mf = MouseFlags.Button3DoubleClicked;
+			break;
+		}
+		return mf;
+	}
+
+	private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag)
+	{
+		MouseFlags mf = default;
+		switch (mouseFlag) {
+		case MouseFlags.Button1Pressed:
+			mf = MouseFlags.Button1TripleClicked;
+			break;
+
+		case MouseFlags.Button2Pressed:
+			mf = MouseFlags.Button2TripleClicked;
+			break;
+
+		case MouseFlags.Button3Pressed:
+			mf = MouseFlags.Button3TripleClicked;
+			break;
+		}
+		return mf;
+	}
+
+	private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
+	{
+		if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
+			mouseFlag |= MouseFlags.ButtonCtrl;
+
+		if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
+			mouseFlag |= MouseFlags.ButtonShift;
+
+		if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
+			mouseFlag |= MouseFlags.ButtonAlt;
+		return mouseFlag;
+	}
+
+	// TODO: Move this out of here and into ConsoleDriver or somewhere else.
+	/// <summary>
+	/// Get the terminal that holds the console driver.
+	/// </summary>
+	/// <param name="process">The process.</param>
+	/// <returns>If supported the executable console process, null otherwise.</returns>
+	public static Process GetParentProcess (Process process)
+	{
+		if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+			return null;
+		}
+
+		string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id;
+		using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) {
+			foreach (ManagementObject mo in mos.Get ()) {
+				if (mo ["ParentProcessId"] != null) {
+					try {
+						var id = Convert.ToInt32 (mo ["ParentProcessId"]);
+						return Process.GetProcessById (id);
+					} catch {
+					}
+				}
+			}
+		}
+		return null;
+	}
+}

+ 1933 - 1939
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -5,2013 +5,2007 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
+using Rune = System.Text.Rune;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
 #pragma warning disable RCS1138 // Add summary to documentation comment.
+/// <summary>
+/// 
+/// </summary>
+public static class FakeConsole {
+#pragma warning restore RCS1138 // Add summary to documentation comment.
+
+	//
+	// Summary:
+	//	Gets or sets the width of the console window.
+	//
+	// Returns:
+	//	The width of the console window measured in columns.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
+	//	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
+	//	property plus the value of the System.Console.WindowTop property is greater than
+	//	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
+	//	property or the value of the System.Console.WindowHeight property is greater
+	//	than the largest possible window width or height for the current screen resolution
+	//	and console font.
+	//
+	//	T:System.IO.IOException:
+	//	Error reading or writing information.
+#pragma warning disable RCS1138 // Add summary to documentation comment.
+
+	/// <summary>
+	/// Specifies the initial console width.
+	/// </summary>
+	public const int WIDTH = 80;
+
+	/// <summary>
+	/// Specifies the initial console height.
+	/// </summary>
+	public const int HEIGHT = 25;
+
 	/// <summary>
 	/// 
 	/// </summary>
-	public static class FakeConsole {
-#pragma warning restore RCS1138 // Add summary to documentation comment.
+	public static int WindowWidth { get; set; } = WIDTH;
+	//
+	// Summary:
+	//	Gets a value that indicates whether output has been redirected from the standard
+	//	output stream.
+	//
+	// Returns:
+	//	true if output is redirected; otherwise, false.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool IsOutputRedirected { get; }
+	//
+	// Summary:
+	//	Gets a value that indicates whether the error output stream has been redirected
+	//	from the standard error stream.
+	//
+	// Returns:
+	//	true if error output is redirected; otherwise, false.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool IsErrorRedirected { get; }
+	//
+	// Summary:
+	//	Gets the standard input stream.
+	//
+	// Returns:
+	//	A System.IO.TextReader that represents the standard input stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static TextReader In { get; }
+	//
+	// Summary:
+	//	Gets the standard output stream.
+	//
+	// Returns:
+	//	A System.IO.TextWriter that represents the standard output stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static TextWriter Out { get; }
+	//
+	// Summary:
+	//	Gets the standard error output stream.
+	//
+	// Returns:
+	//	A System.IO.TextWriter that represents the standard error output stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static TextWriter Error { get; }
+	//
+	// Summary:
+	//	Gets or sets the encoding the console uses to read input.
+	//
+	// Returns:
+	//	The encoding used to read console input.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	The property value in a set operation is null.
+	//
+	//	T:System.IO.IOException:
+	//	An error occurred during the execution of this operation.
+	//
+	//	T:System.Security.SecurityException:
+	//	Your application does not have permission to perform this operation.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Encoding InputEncoding { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the encoding the console uses to write output.
+	//
+	// Returns:
+	//	The encoding used to write console output.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	The property value in a set operation is null.
+	//
+	//	T:System.IO.IOException:
+	//	An error occurred during the execution of this operation.
+	//
+	//	T:System.Security.SecurityException:
+	//	Your application does not have permission to perform this operation.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Encoding OutputEncoding { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the background color of the console.
+	//
+	// Returns:
+	//	A value that specifies the background color of the console; that is, the color
+	//	that appears behind each character. The default is black.
+	//
+	// Exceptions:
+	//	T:System.ArgumentException:
+	//	The color specified in a set operation is not a valid member of System.ConsoleColor.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+
+	static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
 
-		//
-		// Summary:
-		//	Gets or sets the width of the console window.
-		//
-		// Returns:
-		//	The width of the console window measured in columns.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
-		//	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
-		//	property plus the value of the System.Console.WindowTop property is greater than
-		//	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
-		//	property or the value of the System.Console.WindowHeight property is greater
-		//	than the largest possible window width or height for the current screen resolution
-		//	and console font.
-		//
-		//	T:System.IO.IOException:
-		//	Error reading or writing information.
-#pragma warning disable RCS1138 // Add summary to documentation comment.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
+
+	//
+	// Summary:
+	//	Gets or sets the foreground color of the console.
+	//
+	// Returns:
+	//	A System.ConsoleColor that specifies the foreground color of the console; that
+	//	is, the color of each character that is displayed. The default is gray.
+	//
+	// Exceptions:
+	//	T:System.ArgumentException:
+	//	The color specified in a set operation is not a valid member of System.ConsoleColor.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+
+	static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
 
-		/// <summary>
-		/// Specifies the initial console width.
-		/// </summary>
-		public const int WIDTH = 80;
-
-		/// <summary>
-		/// Specifies the initial console height.
-		/// </summary>
-		public const int HEIGHT = 25;
-
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int WindowWidth { get; set; } = WIDTH;
-		//
-		// Summary:
-		//	Gets a value that indicates whether output has been redirected from the standard
-		//	output stream.
-		//
-		// Returns:
-		//	true if output is redirected; otherwise, false.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool IsOutputRedirected { get; }
-		//
-		// Summary:
-		//	Gets a value that indicates whether the error output stream has been redirected
-		//	from the standard error stream.
-		//
-		// Returns:
-		//	true if error output is redirected; otherwise, false.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool IsErrorRedirected { get; }
-		//
-		// Summary:
-		//	Gets the standard input stream.
-		//
-		// Returns:
-		//	A System.IO.TextReader that represents the standard input stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static TextReader In { get; }
-		//
-		// Summary:
-		//	Gets the standard output stream.
-		//
-		// Returns:
-		//	A System.IO.TextWriter that represents the standard output stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static TextWriter Out { get; }
-		//
-		// Summary:
-		//	Gets the standard error output stream.
-		//
-		// Returns:
-		//	A System.IO.TextWriter that represents the standard error output stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static TextWriter Error { get; }
-		//
-		// Summary:
-		//	Gets or sets the encoding the console uses to read input.
-		//
-		// Returns:
-		//	The encoding used to read console input.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	The property value in a set operation is null.
-		//
-		//	T:System.IO.IOException:
-		//	An error occurred during the execution of this operation.
-		//
-		//	T:System.Security.SecurityException:
-		//	Your application does not have permission to perform this operation.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Encoding InputEncoding { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the encoding the console uses to write output.
-		//
-		// Returns:
-		//	The encoding used to write console output.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	The property value in a set operation is null.
-		//
-		//	T:System.IO.IOException:
-		//	An error occurred during the execution of this operation.
-		//
-		//	T:System.Security.SecurityException:
-		//	Your application does not have permission to perform this operation.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Encoding OutputEncoding { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the background color of the console.
-		//
-		// Returns:
-		//	A value that specifies the background color of the console; that is, the color
-		//	that appears behind each character. The default is black.
-		//
-		// Exceptions:
-		//	T:System.ArgumentException:
-		//	The color specified in a set operation is not a valid member of System.ConsoleColor.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-
-		static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
-
-		/// <summary>
-		/// 
-		/// </summary>
-		public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
-
-		//
-		// Summary:
-		//	Gets or sets the foreground color of the console.
-		//
-		// Returns:
-		//	A System.ConsoleColor that specifies the foreground color of the console; that
-		//	is, the color of each character that is displayed. The default is gray.
-		//
-		// Exceptions:
-		//	T:System.ArgumentException:
-		//	The color specified in a set operation is not a valid member of System.ConsoleColor.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-
-		static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
-
-		/// <summary>
-		/// 
-		/// </summary>
-		public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
-		//
-		// Summary:
-		//	Gets or sets the height of the buffer area.
-		//
-		// Returns:
-		//	The current height, in rows, of the buffer area.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value in a set operation is less than or equal to zero.-or- The value in
-		//	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
-		//	in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int BufferHeight { get; set; } = HEIGHT;
-		//
-		// Summary:
-		//	Gets or sets the width of the buffer area.
-		//
-		// Returns:
-		//	The current width, in columns, of the buffer area.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value in a set operation is less than or equal to zero.-or- The value in
-		//	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
-		//	in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int BufferWidth { get; set; } = WIDTH;
-		//
-		// Summary:
-		//	Gets or sets the height of the console window area.
-		//
-		// Returns:
-		//	The height of the console window measured in rows.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
-		//	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
-		//	property plus the value of the System.Console.WindowTop property is greater than
-		//	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
-		//	property or the value of the System.Console.WindowHeight property is greater
-		//	than the largest possible window width or height for the current screen resolution
-		//	and console font.
-		//
-		//	T:System.IO.IOException:
-		//	Error reading or writing information.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int WindowHeight { get; set; } = HEIGHT;
-		//
-		// Summary:
-		//	Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control
-		//	modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary
-		//	input or as an interruption that is handled by the operating system.
-		//
-		// Returns:
-		//	true if Ctrl+C is treated as ordinary input; otherwise, false.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	Unable to get or set the input mode of the console input buffer.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool TreatControlCAsInput { get; set; }
-		//
-		// Summary:
-		//	Gets the largest possible number of console window columns, based on the current
-		//	font and screen resolution.
-		//
-		// Returns:
-		//	The width of the largest possible console window measured in columns.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int LargestWindowWidth { get; }
-		//
-		// Summary:
-		//	Gets the largest possible number of console window rows, based on the current
-		//	font and screen resolution.
-		//
-		// Returns:
-		//	The height of the largest possible console window measured in rows.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int LargestWindowHeight { get; }
-		//
-		// Summary:
-		//	Gets or sets the leftmost position of the console window area relative to the
-		//	screen buffer.
-		//
-		// Returns:
-		//	The leftmost console window position measured in columns.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	In a set operation, the value to be assigned is less than zero.-or-As a result
-		//	of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth
-		//	would exceed System.Console.BufferWidth.
-		//
-		//	T:System.IO.IOException:
-		//	Error reading or writing information.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int WindowLeft { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the top position of the console window area relative to the screen
-		//	buffer.
-		//
-		// Returns:
-		//	The uppermost console window position measured in rows.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	In a set operation, the value to be assigned is less than zero.-or-As a result
-		//	of the assignment, System.Console.WindowTop plus System.Console.WindowHeight
-		//	would exceed System.Console.BufferHeight.
-		//
-		//	T:System.IO.IOException:
-		//	Error reading or writing information.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int WindowTop { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the column position of the cursor within the buffer area.
-		//
-		// Returns:
-		//	The current position, in columns, of the cursor.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value in a set operation is less than zero.-or- The value in a set operation
-		//	is greater than or equal to System.Console.BufferWidth.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int CursorLeft { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the row position of the cursor within the buffer area.
-		//
-		// Returns:
-		//	The current position, in rows, of the cursor.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value in a set operation is less than zero.-or- The value in a set operation
-		//	is greater than or equal to System.Console.BufferHeight.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int CursorTop { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the height of the cursor within a character cell.
-		//
-		// Returns:
-		//	The size of the cursor expressed as a percentage of the height of a character
-		//	cell. The property value ranges from 1 to 100.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	The value specified in a set operation is less than 1 or greater than 100.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int CursorSize { get; set; }
-		//
-		// Summary:
-		//	Gets or sets a value indicating whether the cursor is visible.
-		//
-		// Returns:
-		//	true if the cursor is visible; otherwise, false.
-		//
-		// Exceptions:
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool CursorVisible { get; set; }
-		//
-		// Summary:
-		//	Gets or sets the title to display in the console title bar.
-		//
-		// Returns:
-		//	The string to be displayed in the title bar of the console. The maximum length
-		//	of the title string is 24500 characters.
-		//
-		// Exceptions:
-		//	T:System.InvalidOperationException:
-		//	In a get operation, the retrieved title is longer than 24500 characters.
-		//
-		//	T:System.ArgumentOutOfRangeException:
-		//	In a set operation, the specified title is longer than 24500 characters.
-		//
-		//	T:System.ArgumentNullException:
-		//	In a set operation, the specified title is null.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static string Title { get; set; }
-		//
-		// Summary:
-		//	Gets a value indicating whether a key press is available in the input stream.
-		//
-		// Returns:
-		//	true if a key press is available; otherwise, false.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.InvalidOperationException:
-		//	Standard input is redirected to a file instead of the keyboard.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool KeyAvailable { get; }
-		//
-		// Summary:
-		//	Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or
-		//	turned off.
-		//
-		// Returns:
-		//	true if NUM LOCK is turned on; false if NUM LOCK is turned off.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool NumberLock { get; }
-		//
-		// Summary:
-		//	Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or
-		//	turned off.
-		//
-		// Returns:
-		//	true if CAPS LOCK is turned on; false if CAPS LOCK is turned off.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool CapsLock { get; }
-		//
-		// Summary:
-		//	Gets a value that indicates whether input has been redirected from the standard
-		//	input stream.
-		//
-		// Returns:
-		//	true if input is redirected; otherwise, false.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static bool IsInputRedirected { get; }
-
-		//
-		// Summary:
-		//	Plays the sound of a beep through the console speaker.
-		//
-		// Exceptions:
-		//	T:System.Security.HostProtectionException:
-		//	This method was executed on a server, such as SQL Server, that does not permit
-		//	access to a user interface.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void Beep ()
-		{
-			throw new NotImplementedException ();
-		}
-		//
-		// Summary:
-		//	Plays the sound of a beep of a specified frequency and duration through the console
-		//	speaker.
-		//
-		// Parameters:
-		//	frequency:
-		//	The frequency of the beep, ranging from 37 to 32767 hertz.
-		//
-		//	duration:
-		//	The duration of the beep measured in milliseconds.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	frequency is less than 37 or more than 32767 hertz.-or- duration is less than
-		//	or equal to zero.
-		//
-		//	T:System.Security.HostProtectionException:
-		//	This method was executed on a server, such as SQL Server, that does not permit
-		//	access to the console.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void Beep (int frequency, int duration)
-		{
-			throw new NotImplementedException ();
-		}
-		//
-		// Summary:
-		//	Clears the console buffer and corresponding console window of display information.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-
-		static char [,] _buffer = new char [WindowWidth, WindowHeight];
-
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void Clear ()
-		{
-			_buffer = new char [BufferWidth, BufferHeight];
-			SetCursorPosition (0, 0);
-		}
+	/// <summary>
+	/// 
+	/// </summary>
+	public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
+	//
+	// Summary:
+	//	Gets or sets the height of the buffer area.
+	//
+	// Returns:
+	//	The current height, in rows, of the buffer area.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value in a set operation is less than or equal to zero.-or- The value in
+	//	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
+	//	in a set operation is less than System.Console.WindowTop + System.Console.WindowHeight.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int BufferHeight { get; set; } = HEIGHT;
+	//
+	// Summary:
+	//	Gets or sets the width of the buffer area.
+	//
+	// Returns:
+	//	The current width, in columns, of the buffer area.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value in a set operation is less than or equal to zero.-or- The value in
+	//	a set operation is greater than or equal to System.Int16.MaxValue.-or- The value
+	//	in a set operation is less than System.Console.WindowLeft + System.Console.WindowWidth.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int BufferWidth { get; set; } = WIDTH;
+
+	//
+	// Summary:
+	//	Gets or sets the height of the console window area.
+	//
+	// Returns:
+	//	The height of the console window measured in rows.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
+	//	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
+	//	property plus the value of the System.Console.WindowTop property is greater than
+	//	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
+	//	property or the value of the System.Console.WindowHeight property is greater
+	//	than the largest possible window width or height for the current screen resolution
+	//	and console font.
+	//
+	//	T:System.IO.IOException:
+	//	Error reading or writing information.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int WindowHeight { get; set; } = HEIGHT;
+	//
+	// Summary:
+	//	Gets or sets a value indicating whether the combination of the System.ConsoleModifiers.Control
+	//	modifier key and System.ConsoleKey.C console key (Ctrl+C) is treated as ordinary
+	//	input or as an interruption that is handled by the operating system.
+	//
+	// Returns:
+	//	true if Ctrl+C is treated as ordinary input; otherwise, false.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	Unable to get or set the input mode of the console input buffer.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool TreatControlCAsInput { get; set; }
+	//
+	// Summary:
+	//	Gets the largest possible number of console window columns, based on the current
+	//	font and screen resolution.
+	//
+	// Returns:
+	//	The width of the largest possible console window measured in columns.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int LargestWindowWidth { get; }
+	//
+	// Summary:
+	//	Gets the largest possible number of console window rows, based on the current
+	//	font and screen resolution.
+	//
+	// Returns:
+	//	The height of the largest possible console window measured in rows.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int LargestWindowHeight { get; }
+	//
+	// Summary:
+	//	Gets or sets the leftmost position of the console window area relative to the
+	//	screen buffer.
+	//
+	// Returns:
+	//	The leftmost console window position measured in columns.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	In a set operation, the value to be assigned is less than zero.-or-As a result
+	//	of the assignment, System.Console.WindowLeft plus System.Console.WindowWidth
+	//	would exceed System.Console.BufferWidth.
+	//
+	//	T:System.IO.IOException:
+	//	Error reading or writing information.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int WindowLeft { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the top position of the console window area relative to the screen
+	//	buffer.
+	//
+	// Returns:
+	//	The uppermost console window position measured in rows.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	In a set operation, the value to be assigned is less than zero.-or-As a result
+	//	of the assignment, System.Console.WindowTop plus System.Console.WindowHeight
+	//	would exceed System.Console.BufferHeight.
+	//
+	//	T:System.IO.IOException:
+	//	Error reading or writing information.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int WindowTop { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the column position of the cursor within the buffer area.
+	//
+	// Returns:
+	//	The current position, in columns, of the cursor.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value in a set operation is less than zero.-or- The value in a set operation
+	//	is greater than or equal to System.Console.BufferWidth.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int CursorLeft { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the row position of the cursor within the buffer area.
+	//
+	// Returns:
+	//	The current position, in rows, of the cursor.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value in a set operation is less than zero.-or- The value in a set operation
+	//	is greater than or equal to System.Console.BufferHeight.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int CursorTop { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the height of the cursor within a character cell.
+	//
+	// Returns:
+	//	The size of the cursor expressed as a percentage of the height of a character
+	//	cell. The property value ranges from 1 to 100.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	The value specified in a set operation is less than 1 or greater than 100.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int CursorSize { get; set; }
+	//
+	// Summary:
+	//	Gets or sets a value indicating whether the cursor is visible.
+	//
+	// Returns:
+	//	true if the cursor is visible; otherwise, false.
+	//
+	// Exceptions:
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool CursorVisible { get; set; }
+	//
+	// Summary:
+	//	Gets or sets the title to display in the console title bar.
+	//
+	// Returns:
+	//	The string to be displayed in the title bar of the console. The maximum length
+	//	of the title string is 24500 characters.
+	//
+	// Exceptions:
+	//	T:System.InvalidOperationException:
+	//	In a get operation, the retrieved title is longer than 24500 characters.
+	//
+	//	T:System.ArgumentOutOfRangeException:
+	//	In a set operation, the specified title is longer than 24500 characters.
+	//
+	//	T:System.ArgumentNullException:
+	//	In a set operation, the specified title is null.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static string Title { get; set; }
+	//
+	// Summary:
+	//	Gets a value indicating whether a key press is available in the input stream.
+	//
+	// Returns:
+	//	true if a key press is available; otherwise, false.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.InvalidOperationException:
+	//	Standard input is redirected to a file instead of the keyboard.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool KeyAvailable { get; }
+	//
+	// Summary:
+	//	Gets a value indicating whether the NUM LOCK keyboard toggle is turned on or
+	//	turned off.
+	//
+	// Returns:
+	//	true if NUM LOCK is turned on; false if NUM LOCK is turned off.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool NumberLock { get; }
+	//
+	// Summary:
+	//	Gets a value indicating whether the CAPS LOCK keyboard toggle is turned on or
+	//	turned off.
+	//
+	// Returns:
+	//	true if CAPS LOCK is turned on; false if CAPS LOCK is turned off.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool CapsLock { get; }
+	//
+	// Summary:
+	//	Gets a value that indicates whether input has been redirected from the standard
+	//	input stream.
+	//
+	// Returns:
+	//	true if input is redirected; otherwise, false.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static bool IsInputRedirected { get; }
+
+	//
+	// Summary:
+	//	Plays the sound of a beep through the console speaker.
+	//
+	// Exceptions:
+	//	T:System.Security.HostProtectionException:
+	//	This method was executed on a server, such as SQL Server, that does not permit
+	//	access to a user interface.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void Beep ()
+	{
+		throw new NotImplementedException ();
+	}
+	//
+	// Summary:
+	//	Plays the sound of a beep of a specified frequency and duration through the console
+	//	speaker.
+	//
+	// Parameters:
+	//	frequency:
+	//	The frequency of the beep, ranging from 37 to 32767 hertz.
+	//
+	//	duration:
+	//	The duration of the beep measured in milliseconds.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	frequency is less than 37 or more than 32767 hertz.-or- duration is less than
+	//	or equal to zero.
+	//
+	//	T:System.Security.HostProtectionException:
+	//	This method was executed on a server, such as SQL Server, that does not permit
+	//	access to the console.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void Beep (int frequency, int duration)
+	{
+		throw new NotImplementedException ();
+	}
+	//
+	// Summary:
+	//	Clears the console buffer and corresponding console window of display information.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
 
-		//
-		// Summary:
-		//	Copies a specified source area of the screen buffer to a specified destination
-		//	area.
-		//
-		// Parameters:
-		//	sourceLeft:
-		//	The leftmost column of the source area.
-		//
-		//	sourceTop:
-		//	The topmost row of the source area.
-		//
-		//	sourceWidth:
-		//	The number of columns in the source area.
-		//
-		//	sourceHeight:
-		//	The number of rows in the source area.
-		//
-		//	targetLeft:
-		//	The leftmost column of the destination area.
-		//
-		//	targetTop:
-		//	The topmost row of the destination area.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
-		//	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
-		//	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
-		//	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
-		//	is greater than or equal to System.Console.BufferWidth.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop)
-		{
-			throw new NotImplementedException ();
-		}
+	static char [,] _buffer = new char [WindowWidth, WindowHeight];
 
-		//
-		// Summary:
-		//	Copies a specified source area of the screen buffer to a specified destination
-		//	area.
-		//
-		// Parameters:
-		//	sourceLeft:
-		//	The leftmost column of the source area.
-		//
-		//	sourceTop:
-		//	The topmost row of the source area.
-		//
-		//	sourceWidth:
-		//	The number of columns in the source area.
-		//
-		//	sourceHeight:
-		//	The number of rows in the source area.
-		//
-		//	targetLeft:
-		//	The leftmost column of the destination area.
-		//
-		//	targetTop:
-		//	The topmost row of the destination area.
-		//
-		//	sourceChar:
-		//	The character used to fill the source area.
-		//
-		//	sourceForeColor:
-		//	The foreground color used to fill the source area.
-		//
-		//	sourceBackColor:
-		//	The background color used to fill the source area.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
-		//	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
-		//	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
-		//	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
-		//	is greater than or equal to System.Console.BufferWidth.
-		//
-		//	T:System.ArgumentException:
-		//	One or both of the color parameters is not a member of the System.ConsoleColor
-		//	enumeration.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
-		{
-			throw new NotImplementedException ();
-		}
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void Clear ()
+	{
+		_buffer = new char [BufferWidth, BufferHeight];
+		SetCursorPosition (0, 0);
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard error stream.
-		//
-		// Returns:
-		//	The standard error stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardError ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Copies a specified source area of the screen buffer to a specified destination
+	//	area.
+	//
+	// Parameters:
+	//	sourceLeft:
+	//	The leftmost column of the source area.
+	//
+	//	sourceTop:
+	//	The topmost row of the source area.
+	//
+	//	sourceWidth:
+	//	The number of columns in the source area.
+	//
+	//	sourceHeight:
+	//	The number of rows in the source area.
+	//
+	//	targetLeft:
+	//	The leftmost column of the destination area.
+	//
+	//	targetTop:
+	//	The topmost row of the destination area.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
+	//	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
+	//	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
+	//	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
+	//	is greater than or equal to System.Console.BufferWidth.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard error stream, which is set to a specified buffer size.
-		//
-		// Parameters:
-		//	bufferSize:
-		//	The internal stream buffer size.
-		//
-		// Returns:
-		//	The standard error stream.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	bufferSize is less than or equal to zero.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardError (int bufferSize)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Copies a specified source area of the screen buffer to a specified destination
+	//	area.
+	//
+	// Parameters:
+	//	sourceLeft:
+	//	The leftmost column of the source area.
+	//
+	//	sourceTop:
+	//	The topmost row of the source area.
+	//
+	//	sourceWidth:
+	//	The number of columns in the source area.
+	//
+	//	sourceHeight:
+	//	The number of rows in the source area.
+	//
+	//	targetLeft:
+	//	The leftmost column of the destination area.
+	//
+	//	targetTop:
+	//	The topmost row of the destination area.
+	//
+	//	sourceChar:
+	//	The character used to fill the source area.
+	//
+	//	sourceForeColor:
+	//	The foreground color used to fill the source area.
+	//
+	//	sourceBackColor:
+	//	The background color used to fill the source area.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	One or more of the parameters is less than zero.-or- sourceLeft or targetLeft
+	//	is greater than or equal to System.Console.BufferWidth.-or- sourceTop or targetTop
+	//	is greater than or equal to System.Console.BufferHeight.-or- sourceTop + sourceHeight
+	//	is greater than or equal to System.Console.BufferHeight.-or- sourceLeft + sourceWidth
+	//	is greater than or equal to System.Console.BufferWidth.
+	//
+	//	T:System.ArgumentException:
+	//	One or both of the color parameters is not a member of the System.ConsoleColor
+	//	enumeration.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void MoveBufferArea (int sourceLeft, int sourceTop, int sourceWidth, int sourceHeight, int targetLeft, int targetTop, char sourceChar, ConsoleColor sourceForeColor, ConsoleColor sourceBackColor)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard input stream, which is set to a specified buffer size.
-		//
-		// Parameters:
-		//	bufferSize:
-		//	The internal stream buffer size.
-		//
-		// Returns:
-		//	The standard input stream.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	bufferSize is less than or equal to zero.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardInput (int bufferSize)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Acquires the standard error stream.
+	//
+	// Returns:
+	//	The standard error stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardError ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard input stream.
-		//
-		// Returns:
-		//	The standard input stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardInput ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Acquires the standard error stream, which is set to a specified buffer size.
+	//
+	// Parameters:
+	//	bufferSize:
+	//	The internal stream buffer size.
+	//
+	// Returns:
+	//	The standard error stream.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	bufferSize is less than or equal to zero.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardError (int bufferSize)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard output stream, which is set to a specified buffer size.
-		//
-		// Parameters:
-		//	bufferSize:
-		//	The internal stream buffer size.
-		//
-		// Returns:
-		//	The standard output stream.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	bufferSize is less than or equal to zero.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardOutput (int bufferSize)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Acquires the standard input stream, which is set to a specified buffer size.
+	//
+	// Parameters:
+	//	bufferSize:
+	//	The internal stream buffer size.
+	//
+	// Returns:
+	//	The standard input stream.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	bufferSize is less than or equal to zero.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardInput (int bufferSize)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Acquires the standard output stream.
-		//
-		// Returns:
-		//	The standard output stream.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static Stream OpenStandardOutput ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Acquires the standard input stream.
+	//
+	// Returns:
+	//	The standard input stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardInput ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Reads the next character from the standard input stream.
-		//
-		// Returns:
-		//	The next character from the input stream, or negative one (-1) if there are currently
-		//	no more characters to be read.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static int Read ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Acquires the standard output stream, which is set to a specified buffer size.
+	//
+	// Parameters:
+	//	bufferSize:
+	//	The internal stream buffer size.
+	//
+	// Returns:
+	//	The standard output stream.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	bufferSize is less than or equal to zero.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardOutput (int bufferSize)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Obtains the next character or function key pressed by the user. The pressed key
-		//	is optionally displayed in the console window.
-		//
-		// Parameters:
-		//	intercept:
-		//	Determines whether to display the pressed key in the console window. true to
-		//	not display the pressed key; otherwise, false.
-		//
-		// Returns:
-		//	An object that describes the System.ConsoleKey constant and Unicode character,
-		//	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
-		//	object also describes, in a bitwise combination of System.ConsoleModifiers values,
-		//	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
-		//	with the console key.
-		//
-		// Exceptions:
-		//	T:System.InvalidOperationException:
-		//	The System.Console.In property is redirected from some stream other than the
-		//	console.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static ConsoleKeyInfo ReadKey (bool intercept)
-		{
-			if (MockKeyPresses.Count > 0) {
-				return MockKeyPresses.Pop ();
-			} else {
-				return new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', false, false, false);
-			}
-		}
+	//
+	// Summary:
+	//	Acquires the standard output stream.
+	//
+	// Returns:
+	//	The standard output stream.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static Stream OpenStandardOutput ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		/// <summary>
-		/// A stack of keypresses to return when ReadKey is called.
-		/// </summary>
-		public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
-
-		/// <summary>
-		///  Helper to push a <see cref="Key"/> onto <see cref="MockKeyPresses"/>.
-		/// </summary>
-		/// <param name="key"></param>
-		public static void PushMockKeyPress (Key key)
-		{
-			MockKeyPresses.Push (new ConsoleKeyInfo (
-				(char)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask),
-				ConsoleKeyMapping.GetConsoleKeyFromKey (key),
-				key.HasFlag (Key.ShiftMask),
-				key.HasFlag (Key.AltMask),
-				key.HasFlag (Key.CtrlMask)));
-		}
+	//
+	// Summary:
+	//	Reads the next character from the standard input stream.
+	//
+	// Returns:
+	//	The next character from the input stream, or negative one (-1) if there are currently
+	//	no more characters to be read.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static int Read ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Obtains the next character or function key pressed by the user. The pressed key
-		//	is displayed in the console window.
-		//
-		// Returns:
-		//	An object that describes the System.ConsoleKey constant and Unicode character,
-		//	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
-		//	object also describes, in a bitwise combination of System.ConsoleModifiers values,
-		//	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
-		//	with the console key.
-		//
-		// Exceptions:
-		//	T:System.InvalidOperationException:
-		//	The System.Console.In property is redirected from some stream other than the
-		//	console.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static ConsoleKeyInfo ReadKey ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Obtains the next character or function key pressed by the user. The pressed key
+	//	is optionally displayed in the console window.
+	//
+	// Parameters:
+	//	intercept:
+	//	Determines whether to display the pressed key in the console window. true to
+	//	not display the pressed key; otherwise, false.
+	//
+	// Returns:
+	//	An object that describes the System.ConsoleKey constant and Unicode character,
+	//	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
+	//	object also describes, in a bitwise combination of System.ConsoleModifiers values,
+	//	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
+	//	with the console key.
+	//
+	// Exceptions:
+	//	T:System.InvalidOperationException:
+	//	The System.Console.In property is redirected from some stream other than the
+	//	console.
+	//[SecuritySafeCritical]
 
-		//
-		// Summary:
-		//	Reads the next line of characters from the standard input stream.
-		//
-		// Returns:
-		//	The next line of characters from the input stream, or null if no more lines are
-		//	available.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.OutOfMemoryException:
-		//	There is insufficient memory to allocate a buffer for the returned string.
-		//
-		//	T:System.ArgumentOutOfRangeException:
-		//	The number of characters in the next line of characters is greater than System.Int32.MaxValue.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static string ReadLine ()
-		{
-			throw new NotImplementedException ();
-		}
+	/// <summary>
+	/// A stack of keypresses to return when ReadKey is called.
+	/// </summary>
+	public static Stack<ConsoleKeyInfo> MockKeyPresses = new Stack<ConsoleKeyInfo> ();
 
-		//
-		// Summary:
-		//	Sets the foreground and background console colors to their defaults.
-		//
-		// Exceptions:
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void ResetColor ()
-		{
-			BackgroundColor = _defaultBackgroundColor;
-			ForegroundColor = _defaultForegroundColor;
-		}
+	/// <summary>
+	///  Helper to push a <see cref="Key"/> onto <see cref="MockKeyPresses"/>.
+	/// </summary>
+	/// <param name="key"></param>
+	public static void PushMockKeyPress (Key key)
+	{
+		MockKeyPresses.Push (new ConsoleKeyInfo (
+			(char)(key & ~Key.CtrlMask & ~Key.ShiftMask & ~Key.AltMask),
+			ConsoleKeyMapping.GetConsoleKeyFromKey (key),
+			key.HasFlag (Key.ShiftMask),
+			key.HasFlag (Key.AltMask),
+			key.HasFlag (Key.CtrlMask)));
+	}
 
-		//
-		// Summary:
-		//	Sets the height and width of the screen buffer area to the specified values.
-		//
-		// Parameters:
-		//	width:
-		//	The width of the buffer area measured in columns.
-		//
-		//	height:
-		//	The height of the buffer area measured in rows.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	height or width is less than or equal to zero.-or- height or width is greater
-		//	than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft
-		//	+ System.Console.WindowWidth.-or- height is less than System.Console.WindowTop
-		//	+ System.Console.WindowHeight.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void SetBufferSize (int width, int height)
-		{
-			BufferWidth = width;
-			BufferHeight = height;
-			_buffer = new char [BufferWidth, BufferHeight];
-		}
+	//
+	// Summary:
+	//	Obtains the next character or function key pressed by the user. The pressed key
+	//	is displayed in the console window.
+	//
+	// Returns:
+	//	An object that describes the System.ConsoleKey constant and Unicode character,
+	//	if any, that correspond to the pressed console key. The System.ConsoleKeyInfo
+	//	object also describes, in a bitwise combination of System.ConsoleModifiers values,
+	//	whether one or more Shift, Alt, or Ctrl modifier keys was pressed simultaneously
+	//	with the console key.
+	//
+	// Exceptions:
+	//	T:System.InvalidOperationException:
+	//	The System.Console.In property is redirected from some stream other than the
+	//	console.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static ConsoleKeyInfo ReadKey ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Sets the position of the cursor.
-		//
-		// Parameters:
-		//	left:
-		//	The column position of the cursor. Columns are numbered from left to right starting
-		//	at 0.
-		//
-		//	top:
-		//	The row position of the cursor. Rows are numbered from top to bottom starting
-		//	at 0.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or-
-		//	top is greater than or equal to System.Console.BufferHeight.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void SetCursorPosition (int left, int top)
-		{
-			CursorLeft = left;
-			CursorTop = top;
-			WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0);
-			WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0);
-		}
+	//
+	// Summary:
+	//	Reads the next line of characters from the standard input stream.
+	//
+	// Returns:
+	//	The next line of characters from the input stream, or null if no more lines are
+	//	available.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.OutOfMemoryException:
+	//	There is insufficient memory to allocate a buffer for the returned string.
+	//
+	//	T:System.ArgumentOutOfRangeException:
+	//	The number of characters in the next line of characters is greater than System.Int32.MaxValue.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static string ReadLine ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Sets the System.Console.Error property to the specified System.IO.TextWriter
-		//	object.
-		//
-		// Parameters:
-		//	newError:
-		//	A stream that is the new standard error output.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	newError is null.
-		//
-		//	T:System.Security.SecurityException:
-		//	The caller does not have the required permission.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void SetError (TextWriter newError)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the foreground and background console colors to their defaults.
+	//
+	// Exceptions:
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void ResetColor ()
+	{
+		BackgroundColor = _defaultBackgroundColor;
+		ForegroundColor = _defaultForegroundColor;
+	}
 
-		//
-		// Summary:
-		//	Sets the System.Console.In property to the specified System.IO.TextReader object.
-		//
-		// Parameters:
-		//	newIn:
-		//	A stream that is the new standard input.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	newIn is null.
-		//
-		//	T:System.Security.SecurityException:
-		//	The caller does not have the required permission.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void SetIn (TextReader newIn)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the height and width of the screen buffer area to the specified values.
+	//
+	// Parameters:
+	//	width:
+	//	The width of the buffer area measured in columns.
+	//
+	//	height:
+	//	The height of the buffer area measured in rows.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	height or width is less than or equal to zero.-or- height or width is greater
+	//	than or equal to System.Int16.MaxValue.-or- width is less than System.Console.WindowLeft
+	//	+ System.Console.WindowWidth.-or- height is less than System.Console.WindowTop
+	//	+ System.Console.WindowHeight.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void SetBufferSize (int width, int height)
+	{
+		BufferWidth = width;
+		BufferHeight = height;
+		_buffer = new char [BufferWidth, BufferHeight];
+	}
 
-		//
-		// Summary:
-		//	Sets the System.Console.Out property to the specified System.IO.TextWriter object.
-		//
-		// Parameters:
-		//	newOut:
-		//	A stream that is the new standard output.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	newOut is null.
-		//
-		//	T:System.Security.SecurityException:
-		//	The caller does not have the required permission.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="newOut"></param>
-		public static void SetOut (TextWriter newOut)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the position of the cursor.
+	//
+	// Parameters:
+	//	left:
+	//	The column position of the cursor. Columns are numbered from left to right starting
+	//	at 0.
+	//
+	//	top:
+	//	The row position of the cursor. Rows are numbered from top to bottom starting
+	//	at 0.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	left or top is less than zero.-or- left is greater than or equal to System.Console.BufferWidth.-or-
+	//	top is greater than or equal to System.Console.BufferHeight.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void SetCursorPosition (int left, int top)
+	{
+		CursorLeft = left;
+		CursorTop = top;
+		WindowLeft = Math.Max (Math.Min (left, BufferWidth - WindowWidth), 0);
+		WindowTop = Math.Max (Math.Min (top, BufferHeight - WindowHeight), 0);
+	}
 
-		//
-		// Summary:
-		//	Sets the position of the console window relative to the screen buffer.
-		//
-		// Parameters:
-		//	left:
-		//	The column position of the upper left corner of the console window.
-		//
-		//	top:
-		//	The row position of the upper left corner of the console window.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	left or top is less than zero.-or- left + System.Console.WindowWidth is greater
-		//	than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater
-		//	than System.Console.BufferHeight.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="left"></param>
-		/// <param name="top"></param>
-		public static void SetWindowPosition (int left, int top)
-		{
-			WindowLeft = left;
-			WindowTop = top;
-		}
+	//
+	// Summary:
+	//	Sets the System.Console.Error property to the specified System.IO.TextWriter
+	//	object.
+	//
+	// Parameters:
+	//	newError:
+	//	A stream that is the new standard error output.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	newError is null.
+	//
+	//	T:System.Security.SecurityException:
+	//	The caller does not have the required permission.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void SetError (TextWriter newError)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Sets the height and width of the console window to the specified values.
-		//
-		// Parameters:
-		//	width:
-		//	The width of the console window measured in columns.
-		//
-		//	height:
-		//	The height of the console window measured in rows.
-		//
-		// Exceptions:
-		//	T:System.ArgumentOutOfRangeException:
-		//	width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft
-		//	or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue.
-		//	-or- width or height is greater than the largest possible window width or height
-		//	for the current screen resolution and console font.
-		//
-		//	T:System.Security.SecurityException:
-		//	The user does not have permission to perform this action.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[SecuritySafeCritical]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="width"></param>
-		/// <param name="height"></param>
-		public static void SetWindowSize (int width, int height)
-		{
-			WindowWidth = width;
-			WindowHeight = height;
-		}
+	//
+	// Summary:
+	//	Sets the System.Console.In property to the specified System.IO.TextReader object.
+	//
+	// Parameters:
+	//	newIn:
+	//	A stream that is the new standard input.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	newIn is null.
+	//
+	//	T:System.Security.SecurityException:
+	//	The caller does not have the required permission.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void SetIn (TextReader newIn)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified string value to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (string value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the System.Console.Out property to the specified System.IO.TextWriter object.
+	//
+	// Parameters:
+	//	newOut:
+	//	A stream that is the new standard output.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	newOut is null.
+	//
+	//	T:System.Security.SecurityException:
+	//	The caller does not have the required permission.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="newOut"></param>
+	public static void SetOut (TextWriter newOut)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified object to the standard output
-		//	stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write, or null.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (object value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the position of the console window relative to the screen buffer.
+	//
+	// Parameters:
+	//	left:
+	//	The column position of the upper left corner of the console window.
+	//
+	//	top:
+	//	The row position of the upper left corner of the console window.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	left or top is less than zero.-or- left + System.Console.WindowWidth is greater
+	//	than System.Console.BufferWidth.-or- top + System.Console.WindowHeight is greater
+	//	than System.Console.BufferHeight.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="left"></param>
+	/// <param name="top"></param>
+	public static void SetWindowPosition (int left, int top)
+	{
+		WindowLeft = left;
+		WindowTop = top;
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 64-bit unsigned integer value
-		//	to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (ulong value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Sets the height and width of the console window to the specified values.
+	//
+	// Parameters:
+	//	width:
+	//	The width of the console window measured in columns.
+	//
+	//	height:
+	//	The height of the console window measured in rows.
+	//
+	// Exceptions:
+	//	T:System.ArgumentOutOfRangeException:
+	//	width or height is less than or equal to zero.-or- width plus System.Console.WindowLeft
+	//	or height plus System.Console.WindowTop is greater than or equal to System.Int16.MaxValue.
+	//	-or- width or height is greater than the largest possible window width or height
+	//	for the current screen resolution and console font.
+	//
+	//	T:System.Security.SecurityException:
+	//	The user does not have permission to perform this action.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[SecuritySafeCritical]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="width"></param>
+	/// <param name="height"></param>
+	public static void SetWindowSize (int width, int height)
+	{
+		WindowWidth = width;
+		WindowHeight = height;
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 64-bit signed integer value to
-		//	the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (long value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified string value to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (string value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified objects to the standard output
-		//	stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	The first object to write using format.
-		//
-		//	arg1:
-		//	The second object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		public static void Write (string format, object arg0, object arg1)
-		{
+	//
+	// Summary:
+	//	Writes the text representation of the specified object to the standard output
+	//	stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write, or null.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (object value)
+	{
+		if (value is Rune rune) {
+			Write ((char)rune.Value);
+		} else {
 			throw new NotImplementedException ();
 		}
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 32-bit signed integer value to
-		//	the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (int value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 64-bit unsigned integer value
+	//	to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (ulong value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified object to the standard output
-		//	stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	An object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		public static void Write (string format, object arg0)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 64-bit signed integer value to
+	//	the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (long value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 32-bit unsigned integer value
-		//	to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (uint value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified objects to the standard output
+	//	stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	The first object to write using format.
+	//
+	//	arg1:
+	//	The second object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	public static void Write (string format, object arg0, object arg1)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		/// <param name="arg2"></param>
-		/// <param name="arg3"></param>
-		public static void Write (string format, object arg0, object arg1, object arg2, object arg3)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 32-bit signed integer value to
+	//	the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (int value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified array of objects to the standard
-		//	output stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg:
-		//	An array of objects to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format or arg is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg"></param>
-		public static void Write (string format, params object [] arg)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified object to the standard output
+	//	stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	An object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	public static void Write (string format, object arg0)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified Boolean value to the standard
-		//	output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (bool value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 32-bit unsigned integer value
+	//	to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (uint value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified Unicode character value to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (char value)
-		{
-			_buffer [CursorLeft, CursorTop] = value;
-		}
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	/// <param name="arg2"></param>
+	/// <param name="arg3"></param>
+	public static void Write (string format, object arg0, object arg1, object arg2, object arg3)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified array of Unicode characters to the standard output stream.
-		//
-		// Parameters:
-		//	buffer:
-		//	A Unicode character array.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="buffer"></param>
-		public static void Write (char [] buffer)
-		{
-			_buffer [CursorLeft, CursorTop] = (char)0;
-			foreach (var ch in buffer) {
-				_buffer [CursorLeft, CursorTop] += ch;
-			}
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified array of objects to the standard
+	//	output stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg:
+	//	An array of objects to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format or arg is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg"></param>
+	public static void Write (string format, params object [] arg)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified subarray of Unicode characters to the standard output stream.
-		//
-		// Parameters:
-		//	buffer:
-		//	An array of Unicode characters.
-		//
-		//	index:
-		//	The starting position in buffer.
-		//
-		//	count:
-		//	The number of characters to write.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	buffer is null.
-		//
-		//	T:System.ArgumentOutOfRangeException:
-		//	index or count is less than zero.
-		//
-		//	T:System.ArgumentException:
-		//	index plus count specify a position that is not within buffer.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="buffer"></param>
-		/// <param name="index"></param>
-		/// <param name="count"></param>
-		public static void Write (char [] buffer, int index, int count)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified Boolean value to the standard
+	//	output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (bool value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified objects to the standard output
-		//	stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	The first object to write using format.
-		//
-		//	arg1:
-		//	The second object to write using format.
-		//
-		//	arg2:
-		//	The third object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		/// <param name="arg2"></param>
-		public static void Write (string format, object arg0, object arg1, object arg2)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified Unicode character value to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (char value)
+	{
+		_buffer [CursorLeft, CursorTop] = value;
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified System.Decimal value to the standard
-		//	output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (decimal value)
-		{
-			throw new NotImplementedException ();
+	//
+	// Summary:
+	//	Writes the specified array of Unicode characters to the standard output stream.
+	//
+	// Parameters:
+	//	buffer:
+	//	A Unicode character array.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="buffer"></param>
+	public static void Write (char [] buffer)
+	{
+		_buffer [CursorLeft, CursorTop] = (char)0;
+		foreach (var ch in buffer) {
+			_buffer [CursorLeft, CursorTop] += ch;
 		}
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified single-precision floating-point
-		//	value to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (float value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified subarray of Unicode characters to the standard output stream.
+	//
+	// Parameters:
+	//	buffer:
+	//	An array of Unicode characters.
+	//
+	//	index:
+	//	The starting position in buffer.
+	//
+	//	count:
+	//	The number of characters to write.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	buffer is null.
+	//
+	//	T:System.ArgumentOutOfRangeException:
+	//	index or count is less than zero.
+	//
+	//	T:System.ArgumentException:
+	//	index plus count specify a position that is not within buffer.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="buffer"></param>
+	/// <param name="index"></param>
+	/// <param name="count"></param>
+	public static void Write (char [] buffer, int index, int count)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified double-precision floating-point
-		//	value to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void Write (double value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified objects to the standard output
+	//	stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	The first object to write using format.
+	//
+	//	arg1:
+	//	The second object to write using format.
+	//
+	//	arg2:
+	//	The third object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	/// <param name="arg2"></param>
+	public static void Write (string format, object arg0, object arg1, object arg2)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the current line terminator to the standard output stream.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		public static void WriteLine ()
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified System.Decimal value to the standard
+	//	output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (decimal value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified single-precision floating-point
-		//	value, followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (float value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified single-precision floating-point
+	//	value to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (float value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 32-bit signed integer value,
-		//	followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (int value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified double-precision floating-point
+	//	value to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void Write (double value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 32-bit unsigned integer value,
-		//	followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (uint value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the current line terminator to the standard output stream.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	public static void WriteLine ()
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 64-bit signed integer value,
-		//	followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (long value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified single-precision floating-point
+	//	value, followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (float value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified 64-bit unsigned integer value,
-		//	followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (ulong value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 32-bit signed integer value,
+	//	followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (int value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified object, followed by the current
-		//	line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (object value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 32-bit unsigned integer value,
+	//	followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (uint value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified string value, followed by the current line terminator, to
-		//	the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (string value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 64-bit signed integer value,
+	//	followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (long value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified object, followed by the current
-		//	line terminator, to the standard output stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	An object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		public static void WriteLine (string format, object arg0)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified 64-bit unsigned integer value,
+	//	followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (ulong value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified objects, followed by the current
-		//	line terminator, to the standard output stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	The first object to write using format.
-		//
-		//	arg1:
-		//	The second object to write using format.
-		//
-		//	arg2:
-		//	The third object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		/// <param name="arg2"></param>
-		public static void WriteLine (string format, object arg0, object arg1, object arg2)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified object, followed by the current
+	//	line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (object value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//[CLSCompliant (false)]
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		/// <param name="arg2"></param>
-		/// <param name="arg3"></param>
-		public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified string value, followed by the current line terminator, to
+	//	the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (string value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified array of objects, followed by
-		//	the current line terminator, to the standard output stream using the specified
-		//	format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg:
-		//	An array of objects to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format or arg is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg"></param>
-		public static void WriteLine (string format, params object [] arg)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified object, followed by the current
+	//	line terminator, to the standard output stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	An object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	public static void WriteLine (string format, object arg0)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified subarray of Unicode characters, followed by the current
-		//	line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	buffer:
-		//	An array of Unicode characters.
-		//
-		//	index:
-		//	The starting position in buffer.
-		//
-		//	count:
-		//	The number of characters to write.
-		//
-		// Exceptions:
-		//	T:System.ArgumentNullException:
-		//	buffer is null.
-		//
-		//	T:System.ArgumentOutOfRangeException:
-		//	index or count is less than zero.
-		//
-		//	T:System.ArgumentException:
-		//	index plus count specify a position that is not within buffer.
-		//
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="buffer"></param>
-		/// <param name="index"></param>
-		/// <param name="count"></param>
-		public static void WriteLine (char [] buffer, int index, int count)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified objects, followed by the current
+	//	line terminator, to the standard output stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	The first object to write using format.
+	//
+	//	arg1:
+	//	The second object to write using format.
+	//
+	//	arg2:
+	//	The third object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	/// <param name="arg2"></param>
+	public static void WriteLine (string format, object arg0, object arg1, object arg2)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified System.Decimal value, followed
-		//	by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (decimal value)
-		{
-			throw new NotImplementedException ();
-		}
+	//[CLSCompliant (false)]
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	/// <param name="arg2"></param>
+	/// <param name="arg3"></param>
+	public static void WriteLine (string format, object arg0, object arg1, object arg2, object arg3)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified array of Unicode characters, followed by the current line
-		//	terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	buffer:
-		//	A Unicode character array.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="buffer"></param>
-		public static void WriteLine (char [] buffer)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified array of objects, followed by
+	//	the current line terminator, to the standard output stream using the specified
+	//	format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg:
+	//	An array of objects to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format or arg is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg"></param>
+	public static void WriteLine (string format, params object [] arg)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the specified Unicode character, followed by the current line terminator,
-		//	value to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (char value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified subarray of Unicode characters, followed by the current
+	//	line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	buffer:
+	//	An array of Unicode characters.
+	//
+	//	index:
+	//	The starting position in buffer.
+	//
+	//	count:
+	//	The number of characters to write.
+	//
+	// Exceptions:
+	//	T:System.ArgumentNullException:
+	//	buffer is null.
+	//
+	//	T:System.ArgumentOutOfRangeException:
+	//	index or count is less than zero.
+	//
+	//	T:System.ArgumentException:
+	//	index plus count specify a position that is not within buffer.
+	//
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="buffer"></param>
+	/// <param name="index"></param>
+	/// <param name="count"></param>
+	public static void WriteLine (char [] buffer, int index, int count)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified Boolean value, followed by the
-		//	current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (bool value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the text representation of the specified System.Decimal value, followed
+	//	by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (decimal value)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified objects, followed by the current
-		//	line terminator, to the standard output stream using the specified format information.
-		//
-		// Parameters:
-		//	format:
-		//	A composite format string (see Remarks).
-		//
-		//	arg0:
-		//	The first object to write using format.
-		//
-		//	arg1:
-		//	The second object to write using format.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		//
-		//	T:System.ArgumentNullException:
-		//	format is null.
-		//
-		//	T:System.FormatException:
-		//	The format specification in format is invalid.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="format"></param>
-		/// <param name="arg0"></param>
-		/// <param name="arg1"></param>
-		public static void WriteLine (string format, object arg0, object arg1)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified array of Unicode characters, followed by the current line
+	//	terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	buffer:
+	//	A Unicode character array.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="buffer"></param>
+	public static void WriteLine (char [] buffer)
+	{
+		throw new NotImplementedException ();
+	}
 
-		//
-		// Summary:
-		//	Writes the text representation of the specified double-precision floating-point
-		//	value, followed by the current line terminator, to the standard output stream.
-		//
-		// Parameters:
-		//	value:
-		//	The value to write.
-		//
-		// Exceptions:
-		//	T:System.IO.IOException:
-		//	An I/O error occurred.
-		/// <summary>
-		/// 
-		/// </summary>
-		/// <param name="value"></param>
-		public static void WriteLine (double value)
-		{
-			throw new NotImplementedException ();
-		}
+	//
+	// Summary:
+	//	Writes the specified Unicode character, followed by the current line terminator,
+	//	value to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (char value)
+	{
+		throw new NotImplementedException ();
+	}
 
+	//
+	// Summary:
+	//	Writes the text representation of the specified Boolean value, followed by the
+	//	current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (bool value)
+	{
+		throw new NotImplementedException ();
+	}
+
+	//
+	// Summary:
+	//	Writes the text representation of the specified objects, followed by the current
+	//	line terminator, to the standard output stream using the specified format information.
+	//
+	// Parameters:
+	//	format:
+	//	A composite format string (see Remarks).
+	//
+	//	arg0:
+	//	The first object to write using format.
+	//
+	//	arg1:
+	//	The second object to write using format.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	//
+	//	T:System.ArgumentNullException:
+	//	format is null.
+	//
+	//	T:System.FormatException:
+	//	The format specification in format is invalid.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="format"></param>
+	/// <param name="arg0"></param>
+	/// <param name="arg1"></param>
+	public static void WriteLine (string format, object arg0, object arg1)
+	{
+		throw new NotImplementedException ();
 	}
-}
+
+	//
+	// Summary:
+	//	Writes the text representation of the specified double-precision floating-point
+	//	value, followed by the current line terminator, to the standard output stream.
+	//
+	// Parameters:
+	//	value:
+	//	The value to write.
+	//
+	// Exceptions:
+	//	T:System.IO.IOException:
+	//	An I/O error occurred.
+	/// <summary>
+	/// 
+	/// </summary>
+	/// <param name="value"></param>
+	public static void WriteLine (double value)
+	{
+		throw new NotImplementedException ();
+	}
+
+}

+ 455 - 607
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -2,6 +2,7 @@
 // FakeDriver.cs: A fake ConsoleDriver for unit tests. 
 //
 using System;
+using System.Buffers;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
@@ -11,717 +12,564 @@ using System.Text;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Implements a mock ConsoleDriver for unit testing
-	/// </summary>
-	public class FakeDriver : ConsoleDriver {
+using Unix.Terminal;
+using static Terminal.Gui.WindowsConsole;
+using System.Drawing;
+
+namespace Terminal.Gui;
+/// <summary>
+/// Implements a mock ConsoleDriver for unit testing
+/// </summary>
+public class FakeDriver : ConsoleDriver {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 
-		public class Behaviors {
+	public class Behaviors {
 
-			public bool UseFakeClipboard { get; internal set; }
-			public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
-			public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
+		public bool UseFakeClipboard { get; internal set; }
+		public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
+		public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
 
-			public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
-			{
-				UseFakeClipboard = useFakeClipboard;
-				FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
-				FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
+		public Behaviors (bool useFakeClipboard = false, bool fakeClipboardAlwaysThrowsNotSupportedException = false, bool fakeClipboardIsSupportedAlwaysTrue = false)
+		{
+			UseFakeClipboard = useFakeClipboard;
+			FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
+			FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
 
-				// double check usage is correct
-				Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
-				Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
-			}
+			// double check usage is correct
+			Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
+			Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
 		}
+	}
 
-		public static FakeDriver.Behaviors FakeBehaviors = new Behaviors ();
-
-		int cols, rows, left, top;
-		public override int Cols => cols;
-		public override int Rows => rows;
-		// Only handling left here because not all terminals has a horizontal scroll bar.
-		public override int Left => 0;
-		public override int Top => 0;
-		public override bool EnableConsoleScrolling { get; set; }
-		private IClipboard clipboard = null;
-		public override IClipboard Clipboard => clipboard;
-
-		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
-		int [,,] contents;
-		bool [] dirtyLine;
-
-		/// <summary>
-		/// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
-		/// </summary>
-		public override int [,,] Contents => contents;
-
-		//void UpdateOffscreen ()
-		//{
-		//	int cols = Cols;
-		//	int rows = Rows;
-
-		//	contents = new int [rows, cols, 3];
-		//	for (int r = 0; r < rows; r++) {
-		//		for (int c = 0; c < cols; c++) {
-		//			contents [r, c, 0] = ' ';
-		//			contents [r, c, 1] = MakeColor (ConsoleColor.Gray, ConsoleColor.Black);
-		//			contents [r, c, 2] = 0;
-		//		}
-		//	}
-		//	dirtyLine = new bool [rows];
-		//	for (int row = 0; row < rows; row++)
-		//		dirtyLine [row] = true;
-		//}
-
-		static bool sync = false;
-
-		public FakeDriver ()
-		{
-			if (FakeBehaviors.UseFakeClipboard) {
-				clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
+	public static FakeDriver.Behaviors FakeBehaviors = new Behaviors ();
+
+	public FakeDriver ()
+	{
+		if (FakeBehaviors.UseFakeClipboard) {
+			Clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
+		} else {
+			if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+				Clipboard = new WindowsClipboard ();
+			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+				Clipboard = new MacOSXClipboard ();
 			} else {
-				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-					clipboard = new WindowsClipboard ();
-				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-					clipboard = new MacOSXClipboard ();
+				if (CursesDriver.Is_WSL_Platform ()) {
+					Clipboard = new WSLClipboard ();
 				} else {
-					if (CursesDriver.Is_WSL_Platform ()) {
-						clipboard = new WSLClipboard ();
-					} else {
-						clipboard = new CursesClipboard ();
-					}
+					Clipboard = new CursesClipboard ();
 				}
 			}
 		}
+	}
 
-		bool needMove;
-		// Current row, and current col, tracked by Move/AddCh only
-		int ccol, crow;
-		public override void Move (int col, int row)
-		{
-			ccol = col;
-			crow = row;
+	public override void End ()
+	{
+		FakeConsole.ResetColor ();
+		FakeConsole.Clear ();
+	}
+	
+	public override void Init (Action terminalResized)
+	{
+		FakeConsole.MockKeyPresses.Clear ();
+
+		TerminalResized = terminalResized;
+
+		Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+		Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
+		FakeConsole.Clear ();
+		ResizeScreen ();
+		// Call InitializeColorSchemes before UpdateOffScreen as it references Colors
+		CurrentAttribute = MakeColor (Color.White, Color.Black);
+		InitializeColorSchemes ();
+		ClearContents ();
+	}
 
-			if (Clip.Contains (col, row)) {
-				FakeConsole.CursorTop = row;
-				FakeConsole.CursorLeft = col;
-				needMove = false;
-			} else {
-				FakeConsole.CursorTop = Clip.Y;
-				FakeConsole.CursorLeft = Clip.X;
-				needMove = true;
-			}
-		}
 
-		public override void AddRune (Rune rune)
-		{
-			rune = rune.MakePrintable ();
-			var runeWidth = rune.GetColumns ();
-			var validClip = IsValidContent (ccol, crow, Clip);
-
-			if (validClip) {
-				if (needMove) {
-					//MockConsole.CursorLeft = ccol;
-					//MockConsole.CursorTop = crow;
-					needMove = false;
-				}
-				if (runeWidth == 0 && ccol > 0) {
-					var r = contents [crow, ccol - 1, 0];
-					var s = new string (new char [] { (char)r, (char)rune.Value });
-					string sn;
-					if (!s.IsNormalized ()) {
-						sn = s.Normalize ();
-					} else {
-						sn = s;
+	public override void UpdateScreen ()
+	{
+		var savedRow = FakeConsole.CursorTop;
+		var savedCol = FakeConsole.CursorLeft;
+		var savedCursorVisible = FakeConsole.CursorVisible;
+
+		var top = 0;
+		var left = 0;
+		var rows = Rows;
+		var cols = Cols;
+		System.Text.StringBuilder output = new System.Text.StringBuilder ();
+		Attribute redrawAttr = new Attribute ();
+		var lastCol = -1;
+
+		for (var row = top; row < rows; row++) {
+			if (!_dirtyLines [row]) {
+				continue;
+			}
+
+			FakeConsole.CursorTop = row;
+			FakeConsole.CursorLeft = 0;
+
+			_dirtyLines [row] = false;
+			output.Clear ();
+			for (var col = left; col < cols; col++) {
+				lastCol = -1;
+				var outputWidth = 0;
+				for (; col < cols; col++) {
+					if (!Contents [row, col].IsDirty) {
+						if (output.Length > 0) {
+							WriteToConsole (output, ref lastCol, row, ref outputWidth);
+						} else if (lastCol == -1) {
+							lastCol = col;
+						}
+						if (lastCol + 1 < cols)
+							lastCol++;
+						continue;
 					}
-					var c = sn [0];
-					contents [crow, ccol - 1, 0] = c;
-					contents [crow, ccol - 1, 1] = CurrentAttribute;
-					contents [crow, ccol - 1, 2] = 1;
 
-				} else {
-					if (runeWidth < 2 && ccol > 0
-					&& ((Rune)contents [crow, ccol - 1, 0]).GetColumns () > 1) {
-
-						contents [crow, ccol - 1, 0] = (int)(uint)' ';
-
-					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-						&& ((Rune)contents [crow, ccol, 0]).GetColumns () > 1) {
-
-						contents [crow, ccol + 1, 0] = (int)(uint)' ';
-						contents [crow, ccol + 1, 2] = 1;
+					if (lastCol == -1) {
+						lastCol = col;
+					}
 
+					Attribute attr = Contents [row, col].Attribute.Value;
+					// Performance: Only send the escape sequence if the attribute has changed.
+					if (attr != redrawAttr) {
+						redrawAttr = attr;
+						FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground;
+						FakeConsole.BackgroundColor = (ConsoleColor)attr.Background;
 					}
-					if (runeWidth > 1 && ccol == Clip.Right - 1) {
-						contents [crow, ccol, 0] = (int)(uint)' ';
-					} else {
-						contents [crow, ccol, 0] = (int)(uint)rune.Value;
+					outputWidth++;
+					var rune = (Rune)Contents [row, col].Runes [0];
+					output.Append (rune.ToString ());
+					if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
+						WriteToConsole (output, ref lastCol, row, ref outputWidth);
+						FakeConsole.CursorLeft--;
 					}
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 1;
-
-					dirtyLine [crow] = true;
+					Contents [row, col].IsDirty = false;
 				}
-			} else {
-				needMove = true;
-			}
-
-			if (runeWidth < 0 || runeWidth > 0) {
-				ccol++;
 			}
+			if (output.Length > 0) {
+				FakeConsole.CursorTop = row;
+				FakeConsole.CursorLeft = lastCol;
 
-			if (runeWidth > 1) {
-				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 0;
+				foreach (var c in output.ToString ()) {
+					FakeConsole.Write (c);
 				}
-				ccol++;
-			}
-
-			//if (ccol == Cols) {
-			//	ccol = 0;
-			//	if (crow + 1 < Rows)
-			//		crow++;
-			//}
-			if (sync) {
-				UpdateScreen ();
 			}
 		}
+		FakeConsole.CursorTop = 0;
+		FakeConsole.CursorLeft = 0;
 
-		public override void AddStr (string str)
-		{
-			foreach (var rune in str.EnumerateRunes ())
-				AddRune (rune);
-		}
+		//SetCursorVisibility (savedVisibitity);
 
-		public override void End ()
+		void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
 		{
-			FakeConsole.ResetColor ();
-			FakeConsole.Clear ();
+			FakeConsole.CursorTop = row;
+			FakeConsole.CursorLeft = lastCol;
+			foreach (var c in output.ToString ()) {
+				FakeConsole.Write (c);
+			}
+			
+			output.Clear ();
+			lastCol += outputWidth;
+			outputWidth = 0;
 		}
 
-		public override Attribute MakeColor (Color foreground, Color background)
-		{
-			return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background);
-		}
+		FakeConsole.CursorTop = savedRow;
+		FakeConsole.CursorLeft = savedCol;
+		FakeConsole.CursorVisible = savedCursorVisible;
+	}
 
-		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
-		{
-			// Encode the colors into the int value.
-			return new Attribute (
-				value: ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff),
-				foreground: (Color)f,
-				background: (Color)b
-				);
-		}
+	public override void Refresh ()
+	{
+		UpdateScreen ();
+		UpdateCursor ();
+	}
 
-		public override void Init (Action terminalResized)
-		{
-			FakeConsole.MockKeyPresses.Clear ();
-
-			TerminalResized = terminalResized;
-
-			cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-			rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-			FakeConsole.Clear ();
-			ResizeScreen ();
-			// Call InitalizeColorSchemes before UpdateOffScreen as it references Colors
-			CurrentAttribute = MakeColor (Color.White, Color.Black);
-			InitalizeColorSchemes ();
-			UpdateOffScreen ();
-		}
+	#region Color Handling
 
-		public override Attribute MakeAttribute (Color fore, Color back)
-		{
-			return MakeColor ((ConsoleColor)fore, (ConsoleColor)back);
-		}
+	// Cache the list of ConsoleColor values.
+	private static readonly HashSet<int> ConsoleColorValues = new HashSet<int> (
+		Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
+	);
 
-		int redrawColor = -1;
-		void SetColor (int color)
-		{
-			redrawColor = color;
-			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-				.OfType<ConsoleColor> ()
-				.Select (s => (int)s);
-			if (values.Contains (color & 0xffff)) {
-				FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
-			}
-			if (values.Contains ((color >> 16) & 0xffff)) {
-				FakeConsole.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff);
-			}
+	void SetColor (int color)
+	{
+		if (ConsoleColorValues.Contains (color & 0xffff)) {
+			FakeConsole.BackgroundColor = (ConsoleColor)(color & 0xffff);
 		}
+		if (ConsoleColorValues.Contains ((color >> 16) & 0xffff)) {
+			FakeConsole.ForegroundColor = (ConsoleColor)((color >> 16) & 0xffff);
+		}
+	}
 
-		public override void UpdateScreen ()
-		{
-			int top = Top;
-			int left = Left;
-			int rows = Math.Min (FakeConsole.WindowHeight + top, Rows);
-			int cols = Cols;
-
-			var savedRow = FakeConsole.CursorTop;
-			var savedCol = FakeConsole.CursorLeft;
-			var savedCursorVisible = FakeConsole.CursorVisible;
-			for (int row = top; row < rows; row++) {
-				if (!dirtyLine [row])
-					continue;
-				dirtyLine [row] = false;
-				for (int col = left; col < cols; col++) {
-					FakeConsole.CursorTop = row;
-					FakeConsole.CursorLeft = col;
-					for (; col < cols; col++) {
-						if (contents [row, col, 2] == 0) {
-							FakeConsole.CursorLeft++;
-							continue;
-						}
-
-						var color = contents [row, col, 1];
-						if (color != redrawColor)
-							SetColor (color);
+	/// <remarks>
+	/// In the FakeDriver, colors are encoded as an int; same as NetDriver
+	/// Extracts the foreground and background colors from the encoded value.
+	/// Assumes a 4-bit encoded value for both foreground and background colors.
+	/// </remarks>
+	internal override void GetColors (int value, out Color foreground, out Color background)
+	{
+		// Assume a 4-bit encoded value for both foreground and background colors.
+		foreground = (Color)((value >> 16) & 0xF);
+		background = (Color)(value & 0xF);
+	}
 
-						Rune rune = (Rune)contents [row, col, 0];
-						if (rune.DecodeSurrogatePair (out char [] spair)) {
-							FakeConsole.Write (spair);
-						} else {
-							FakeConsole.Write ((char)rune.Value);
-						}
-						contents [row, col, 2] = 0;
-					}
-				}
-			}
-			FakeConsole.CursorTop = savedRow;
-			FakeConsole.CursorLeft = savedCol;
-			FakeConsole.CursorVisible = savedCursorVisible;
-		}
+	/// <remarks>
+	/// In the FakeDriver, colors are encoded as an int; same as NetDriver
+	/// However, the foreground color is stored in the most significant 16 bits, 
+	/// and the background color is stored in the least significant 16 bits.
+	/// </remarks>
+	public override Attribute MakeColor (Color foreground, Color background)
+	{
+		// Encode the colors into the int value.
+		return new Attribute (
+			value: ((((int)foreground) & 0xffff) << 16) | (((int)background) & 0xffff),
+			foreground: foreground,
+			background: background
+		);
+	}
 
-		public override void Refresh ()
-		{
-			UpdateScreen ();
-			UpdateCursor ();
-		}
+	#endregion
 
-		public override void SetAttribute (Attribute c)
-		{
-			base.SetAttribute (c);
+	public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+			return consoleKeyInfo;
 		}
 
-		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-		{
-			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-				return consoleKeyInfo;
-			}
+		var mod = consoleKeyInfo.Modifiers;
+		var shift = (mod & ConsoleModifiers.Shift) != 0;
+		var alt = (mod & ConsoleModifiers.Alt) != 0;
+		var control = (mod & ConsoleModifiers.Control) != 0;
 
-			var mod = consoleKeyInfo.Modifiers;
-			var shift = (mod & ConsoleModifiers.Shift) != 0;
-			var alt = (mod & ConsoleModifiers.Alt) != 0;
-			var control = (mod & ConsoleModifiers.Control) != 0;
+		var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
 
-			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
+		return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+	}
 
-			return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+	Key MapKey (ConsoleKeyInfo keyInfo)
+	{
+		switch (keyInfo.Key) {
+		case ConsoleKey.Escape:
+			return MapKeyModifiers (keyInfo, Key.Esc);
+		case ConsoleKey.Tab:
+			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+		case ConsoleKey.Clear:
+			return MapKeyModifiers (keyInfo, Key.Clear);
+		case ConsoleKey.Home:
+			return MapKeyModifiers (keyInfo, Key.Home);
+		case ConsoleKey.End:
+			return MapKeyModifiers (keyInfo, Key.End);
+		case ConsoleKey.LeftArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+		case ConsoleKey.RightArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorRight);
+		case ConsoleKey.UpArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorUp);
+		case ConsoleKey.DownArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorDown);
+		case ConsoleKey.PageUp:
+			return MapKeyModifiers (keyInfo, Key.PageUp);
+		case ConsoleKey.PageDown:
+			return MapKeyModifiers (keyInfo, Key.PageDown);
+		case ConsoleKey.Enter:
+			return MapKeyModifiers (keyInfo, Key.Enter);
+		case ConsoleKey.Spacebar:
+			return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
+		case ConsoleKey.Backspace:
+			return MapKeyModifiers (keyInfo, Key.Backspace);
+		case ConsoleKey.Delete:
+			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+		case ConsoleKey.Insert:
+			return MapKeyModifiers (keyInfo, Key.InsertChar);
+		case ConsoleKey.PrintScreen:
+			return MapKeyModifiers (keyInfo, Key.PrintScreen);
+
+		case ConsoleKey.Oem1:
+		case ConsoleKey.Oem2:
+		case ConsoleKey.Oem3:
+		case ConsoleKey.Oem4:
+		case ConsoleKey.Oem5:
+		case ConsoleKey.Oem6:
+		case ConsoleKey.Oem7:
+		case ConsoleKey.Oem8:
+		case ConsoleKey.Oem102:
+		case ConsoleKey.OemPeriod:
+		case ConsoleKey.OemComma:
+		case ConsoleKey.OemPlus:
+		case ConsoleKey.OemMinus:
+			if (keyInfo.KeyChar == 0) {
+				return Key.Unknown;
+			}
+
+			return (Key)((uint)keyInfo.KeyChar);
+		}
+
+		var key = keyInfo.Key;
+		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+			var delta = key - ConsoleKey.A;
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
+			}
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
+			}
+			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+				return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
+			}
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0) {
+					return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
+				} else {
+					return (Key)((uint)keyInfo.KeyChar);
+				}
+			}
+			return (Key)((uint)keyInfo.KeyChar);
 		}
-
-		Key MapKey (ConsoleKeyInfo keyInfo)
-		{
-			switch (keyInfo.Key) {
-			case ConsoleKey.Escape:
-				return MapKeyModifiers (keyInfo, Key.Esc);
-			case ConsoleKey.Tab:
-				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
-			case ConsoleKey.Clear:
-				return MapKeyModifiers (keyInfo, Key.Clear);
-			case ConsoleKey.Home:
-				return MapKeyModifiers (keyInfo, Key.Home);
-			case ConsoleKey.End:
-				return MapKeyModifiers (keyInfo, Key.End);
-			case ConsoleKey.LeftArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorLeft);
-			case ConsoleKey.RightArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorRight);
-			case ConsoleKey.UpArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorUp);
-			case ConsoleKey.DownArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorDown);
-			case ConsoleKey.PageUp:
-				return MapKeyModifiers (keyInfo, Key.PageUp);
-			case ConsoleKey.PageDown:
-				return MapKeyModifiers (keyInfo, Key.PageDown);
-			case ConsoleKey.Enter:
-				return MapKeyModifiers (keyInfo, Key.Enter);
-			case ConsoleKey.Spacebar:
-				return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
-			case ConsoleKey.Backspace:
-				return MapKeyModifiers (keyInfo, Key.Backspace);
-			case ConsoleKey.Delete:
-				return MapKeyModifiers (keyInfo, Key.DeleteChar);
-			case ConsoleKey.Insert:
-				return MapKeyModifiers (keyInfo, Key.InsertChar);
-			case ConsoleKey.PrintScreen:
-				return MapKeyModifiers (keyInfo, Key.PrintScreen);
-
-			case ConsoleKey.Oem1:
-			case ConsoleKey.Oem2:
-			case ConsoleKey.Oem3:
-			case ConsoleKey.Oem4:
-			case ConsoleKey.Oem5:
-			case ConsoleKey.Oem6:
-			case ConsoleKey.Oem7:
-			case ConsoleKey.Oem8:
-			case ConsoleKey.Oem102:
-			case ConsoleKey.OemPeriod:
-			case ConsoleKey.OemComma:
-			case ConsoleKey.OemPlus:
-			case ConsoleKey.OemMinus:
-				if (keyInfo.KeyChar == 0)
-					return Key.Unknown;
-
-				return (Key)((uint)keyInfo.KeyChar);
+		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+			var delta = key - ConsoleKey.D0;
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
 			}
-
-			var key = keyInfo.Key;
-			if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-				var delta = key - ConsoleKey.A;
-				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
-				}
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
-				}
-				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
-				}
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0) {
-						return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
-					} else {
-						return (Key)((uint)keyInfo.KeyChar);
-					}
-				}
-				return (Key)((uint)keyInfo.KeyChar);
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 			}
-			if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-				var delta = key - ConsoleKey.D0;
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-					return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
-				}
-				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
-				}
-				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+				return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
+			}
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
 					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 				}
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
-						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-					}
-				}
-				return (Key)((uint)keyInfo.KeyChar);
 			}
-			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-				var delta = key - ConsoleKey.F1;
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
-				}
-
-				return (Key)((uint)Key.F1 + delta);
-			}
-			if (keyInfo.KeyChar != 0) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
+			return (Key)((uint)keyInfo.KeyChar);
+		}
+		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
+			var delta = key - ConsoleKey.F1;
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
 			}
 
-			return (Key)(0xffffffff);
+			return (Key)((uint)Key.F1 + delta);
 		}
-
-		KeyModifiers keyModifiers;
-
-		private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-		{
-			Key keyMod = new Key ();
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
-				keyMod = Key.ShiftMask;
-			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
-				keyMod |= Key.CtrlMask;
-			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0)
-				keyMod |= Key.AltMask;
-
-			return keyMod != Key.Null ? keyMod | key : key;
+		if (keyInfo.KeyChar != 0) {
+			return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
 		}
 
-		Action<KeyEvent> keyDownHandler;
-		Action<KeyEvent> keyHandler;
-		Action<KeyEvent> keyUpHandler;
-		private CursorVisibility savedCursorVisibility;
+		return (Key)(0xffffffff);
+	}
 
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
-		{
-			this.keyDownHandler = keyDownHandler;
-			this.keyHandler = keyHandler;
-			this.keyUpHandler = keyUpHandler;
+	KeyModifiers keyModifiers;
 
-			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
-			(mainLoop.Driver as FakeMainLoop).KeyPressed += (consoleKey) => ProcessInput (consoleKey);
+	private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
+	{
+		Key keyMod = new Key ();
+		if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
+			keyMod = Key.ShiftMask;
 		}
-
-		void ProcessInput (ConsoleKeyInfo consoleKey)
-		{
-			if (consoleKey.Key == ConsoleKey.Packet) {
-				consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey);
-			}
-			keyModifiers = new KeyModifiers ();
-			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
-				keyModifiers.Shift = true;
-			}
-			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
-				keyModifiers.Alt = true;
-			}
-			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) {
-				keyModifiers.Ctrl = true;
-			}
-			var map = MapKey (consoleKey);
-			if (map == (Key)0xffffffff) {
-				if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					keyDownHandler (new KeyEvent (map, keyModifiers));
-					keyUpHandler (new KeyEvent (map, keyModifiers));
-				}
-				return;
-			}
-
-			keyDownHandler (new KeyEvent (map, keyModifiers));
-			keyHandler (new KeyEvent (map, keyModifiers));
-			keyUpHandler (new KeyEvent (map, keyModifiers));
+		if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
+			keyMod |= Key.CtrlMask;
+		}
+		if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
+			keyMod |= Key.AltMask;
 		}
 
-		/// <inheritdoc/>
-		public override bool GetCursorVisibility (out CursorVisibility visibility)
-		{
-			visibility = FakeConsole.CursorVisible
-				? CursorVisibility.Default
-				: CursorVisibility.Invisible;
+		return keyMod != Key.Null ? keyMod | key : key;
+	}
 
-			return FakeConsole.CursorVisible;
-		}
+	Action<KeyEvent> _keyDownHandler;
+	Action<KeyEvent> _keyHandler;
+	Action<KeyEvent> _keyUpHandler;
+	private CursorVisibility _savedCursorVisibility;
 
-		/// <inheritdoc/>
-		public override bool SetCursorVisibility (CursorVisibility visibility)
-		{
-			savedCursorVisibility = visibility;
-			return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
-		}
+	public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+	{
+		_keyDownHandler = keyDownHandler;
+		_keyHandler = keyHandler;
+		_keyUpHandler = keyUpHandler;
 
-		/// <inheritdoc/>
-		public override bool EnsureCursorVisibility ()
-		{
-			if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) {
-				GetCursorVisibility (out CursorVisibility cursorVisibility);
-				savedCursorVisibility = cursorVisibility;
-				SetCursorVisibility (CursorVisibility.Invisible);
-				return false;
-			}
+		// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
+		(mainLoop.MainLoopDriver as FakeMainLoop).KeyPressed += (consoleKey) => ProcessInput (consoleKey);
+	}
 
-			SetCursorVisibility (savedCursorVisibility);
-			return FakeConsole.CursorVisible;
+	void ProcessInput (ConsoleKeyInfo consoleKey)
+	{
+		if (consoleKey.Key == ConsoleKey.Packet) {
+			consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey);
 		}
-
-		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
-		{
-			ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
+		keyModifiers = new KeyModifiers ();
+		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
+			keyModifiers.Shift = true;
 		}
-
-		public void SetBufferSize (int width, int height)
-		{
-			FakeConsole.SetBufferSize (width, height);
-			cols = width;
-			rows = height;
-			if (!EnableConsoleScrolling) {
-				SetWindowSize (width, height);
-			}
-			ProcessResize ();
+		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
+			keyModifiers.Alt = true;
 		}
-
-		public void SetWindowSize (int width, int height)
-		{
-			FakeConsole.SetWindowSize (width, height);
-			if (!EnableConsoleScrolling) {
-				if (width != cols || height != rows) {
-					SetBufferSize (width, height);
-					cols = width;
-					rows = height;
-				}
-			}
-			ProcessResize ();
+		if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+			keyModifiers.Ctrl = true;
 		}
-
-		public void SetWindowPosition (int left, int top)
-		{
-			if (EnableConsoleScrolling) {
-				this.left = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
-				this.top = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
-			} else if (this.left > 0 || this.top > 0) {
-				this.left = 0;
-				this.top = 0;
+		var map = MapKey (consoleKey);
+		if (map == (Key)0xffffffff) {
+			if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				_keyDownHandler (new KeyEvent (map, keyModifiers));
+				_keyUpHandler (new KeyEvent (map, keyModifiers));
 			}
-			FakeConsole.SetWindowPosition (this.left, this.top);
+			return;
 		}
 
-		void ProcessResize ()
-		{
-			ResizeScreen ();
-			UpdateOffScreen ();
-			TerminalResized?.Invoke ();
-		}
+		_keyDownHandler (new KeyEvent (map, keyModifiers));
+		_keyHandler (new KeyEvent (map, keyModifiers));
+		_keyUpHandler (new KeyEvent (map, keyModifiers));
+	}
 
-		public override void ResizeScreen ()
-		{
-			if (!EnableConsoleScrolling) {
-				if (FakeConsole.WindowHeight > 0) {
-					// Can raise an exception while is still resizing.
-					try {
-#pragma warning disable CA1416
-						FakeConsole.CursorTop = 0;
-						FakeConsole.CursorLeft = 0;
-						FakeConsole.WindowTop = 0;
-						FakeConsole.WindowLeft = 0;
-#pragma warning restore CA1416
-					} catch (System.IO.IOException) {
-						return;
-					} catch (ArgumentOutOfRangeException) {
-						return;
-					}
-				}
-			} else {
-				try {
-#pragma warning disable CA1416
-					FakeConsole.WindowLeft = Math.Max (Math.Min (left, Cols - FakeConsole.WindowWidth), 0);
-					FakeConsole.WindowTop = Math.Max (Math.Min (top, Rows - FakeConsole.WindowHeight), 0);
-#pragma warning restore CA1416
-				} catch (Exception) {
-					return;
-				}
-			}
+	/// <inheritdoc/>
+	public override bool GetCursorVisibility (out CursorVisibility visibility)
+	{
+		visibility = FakeConsole.CursorVisible
+			? CursorVisibility.Default
+			: CursorVisibility.Invisible;
 
-			Clip = new Rect (0, 0, Cols, Rows);
-		}
+		return FakeConsole.CursorVisible;
+	}
 
-		public override void UpdateOffScreen ()
-		{
-			contents = new int [Rows, Cols, 3];
-			dirtyLine = new bool [Rows];
+	/// <inheritdoc/>
+	public override bool SetCursorVisibility (CursorVisibility visibility)
+	{
+		_savedCursorVisibility = visibility;
+		return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
+	}
 
-			// Can raise an exception while is still resizing.
-			try {
-				for (int row = 0; row < rows; row++) {
-					for (int c = 0; c < cols; c++) {
-						contents [row, c, 0] = ' ';
-						contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
-						contents [row, c, 2] = 0;
-						dirtyLine [row] = true;
-					}
-				}
-			} catch (IndexOutOfRangeException) { }
+	/// <inheritdoc/>
+	public override bool EnsureCursorVisibility ()
+	{
+		if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) {
+			GetCursorVisibility (out CursorVisibility cursorVisibility);
+			_savedCursorVisibility = cursorVisibility;
+			SetCursorVisibility (CursorVisibility.Invisible);
+			return false;
 		}
 
-		public override bool GetColors (int value, out Color foreground, out Color background)
-		{
-			bool hasColor = false;
-			foreground = default;
-			background = default;
-			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-				.OfType<ConsoleColor> ()
-				.Select (s => (int)s);
-			if (values.Contains (value & 0xffff)) {
-				hasColor = true;
-				background = (Color)(ConsoleColor)(value & 0xffff);
-			}
-			if (values.Contains ((value >> 16) & 0xffff)) {
-				hasColor = true;
-				foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff);
-			}
-			return hasColor;
-		}
+		SetCursorVisibility (_savedCursorVisibility);
+		return FakeConsole.CursorVisible;
+	}
 
-		#region Unused
-		public override void UpdateCursor ()
-		{
-			if (!EnsureCursorVisibility ())
-				return;
+	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+	{
+		ProcessInput (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
+	}
 
-			// Prevents the exception of size changing during resizing.
-			try {
-				if (ccol >= 0 && ccol < FakeConsole.BufferWidth && crow >= 0 && crow < FakeConsole.BufferHeight) {
-					FakeConsole.SetCursorPosition (ccol, crow);
-				}
-			} catch (System.IO.IOException) {
-			} catch (ArgumentOutOfRangeException) {
-			}
-		}
+	public void SetBufferSize (int width, int height)
+	{
+		FakeConsole.SetBufferSize (width, height);
+		Cols = width;
+		Rows = height;
+		SetWindowSize (width, height);
+		ProcessResize ();
+	}
 
-		public override void StartReportingMouseMoves ()
-		{
+	public void SetWindowSize (int width, int height)
+	{
+		FakeConsole.SetWindowSize (width, height);
+		if (width != Cols || height != Rows) {
+			SetBufferSize (width, height);
+			Cols = width;
+			Rows = height;
 		}
+		ProcessResize ();
+	}
 
-		public override void StopReportingMouseMoves ()
-		{
+	public void SetWindowPosition (int left, int top)
+	{
+		if (Left > 0 || Top > 0) {
+			Left = 0;
+			Top = 0;
 		}
+		FakeConsole.SetWindowPosition (Left, Top);
+	}
 
-		public override void Suspend ()
-		{
-		}
+	void ProcessResize ()
+	{
+		ResizeScreen ();
+		ClearContents ();
+		TerminalResized?.Invoke ();
+	}
 
-		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-		{
+	public virtual void ResizeScreen ()
+	{
+		if (FakeConsole.WindowHeight > 0) {
+			// Can raise an exception while is still resizing.
+			try {
+				FakeConsole.CursorTop = 0;
+				FakeConsole.CursorLeft = 0;
+				FakeConsole.WindowTop = 0;
+				FakeConsole.WindowLeft = 0;
+			} catch (System.IO.IOException) {
+				return;
+			} catch (ArgumentOutOfRangeException) {
+				return;
+			}
 		}
 
-		public override void SetColors (short foregroundColorId, short backgroundColorId)
-		{
-			throw new NotImplementedException ();
-		}
+		Clip = new Rect (0, 0, Cols, Rows);
+	}
 
-		public override void CookMouse ()
-		{
+	public override void UpdateCursor ()
+	{
+		if (!EnsureCursorVisibility ()) {
+			return;
 		}
 
-		public override void UncookMouse ()
-		{
+		// Prevents the exception of size changing during resizing.
+		try {
+			// BUGBUG: Why is this using BufferWidth/Height and now Cols/Rows?
+			if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight) {
+				FakeConsole.SetCursorPosition (Col, Row);
+			}
+		} catch (System.IO.IOException) {
+		} catch (ArgumentOutOfRangeException) {
 		}
+	}
 
-		#endregion
+	#region Not Implemented
+	public override void Suspend ()
+	{
+		throw new NotImplementedException ();
+	}
+	#endregion
 
-		public class FakeClipboard : ClipboardBase {
-			public Exception FakeException = null;
+	public class FakeClipboard : ClipboardBase {
+		public Exception FakeException = null;
 
-			string contents = string.Empty;
+		string _contents = string.Empty;
 
-			bool isSupportedAlwaysFalse = false;
+		bool _isSupportedAlwaysFalse = false;
 
-			public override bool IsSupported => !isSupportedAlwaysFalse;
+		public override bool IsSupported => !_isSupportedAlwaysFalse;
 
-			public FakeClipboard (bool fakeClipboardThrowsNotSupportedException = false, bool isSupportedAlwaysFalse = false)
-			{
-				this.isSupportedAlwaysFalse = isSupportedAlwaysFalse;
-				if (fakeClipboardThrowsNotSupportedException) {
-					FakeException = new NotSupportedException ("Fake clipboard exception");
-				}
+		public FakeClipboard (bool fakeClipboardThrowsNotSupportedException = false, bool isSupportedAlwaysFalse = false)
+		{
+			_isSupportedAlwaysFalse = isSupportedAlwaysFalse;
+			if (fakeClipboardThrowsNotSupportedException) {
+				FakeException = new NotSupportedException ("Fake clipboard exception");
 			}
+		}
 
-			protected override string GetClipboardDataImpl ()
-			{
-				if (FakeException != null) {
-					throw FakeException;
-				}
-				return contents;
+		protected override string GetClipboardDataImpl ()
+		{
+			if (FakeException != null) {
+				throw FakeException;
 			}
+			return _contents;
+		}
 
-			protected override void SetClipboardDataImpl (string text)
-			{
-				if (FakeException != null) {
-					throw FakeException;
-				}
-				contents = text;
+		protected override void SetClipboardDataImpl (string text)
+		{
+			if (text == null) {
+				throw new ArgumentNullException (nameof (text));
+			}
+			if (FakeException != null) {
+				throw FakeException;
 			}
+			_contents = text;
 		}
+	}
 
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-	}
 }

+ 26 - 87
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -1,98 +1,37 @@
 using System;
-using System.Threading;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Mainloop intended to be used with the .NET System.Console API, and can
-	/// be used on Windows and Unix, it is cross platform but lacks things like
-	/// file descriptor monitoring.
-	/// </summary>
-	/// <remarks>
-	/// This implementation is used for FakeDriver.
-	/// </remarks>
-	public class FakeMainLoop : IMainLoopDriver {
-		AutoResetEvent keyReady = new AutoResetEvent (false);
-		AutoResetEvent waitForProbe = new AutoResetEvent (false);
-		ConsoleKeyInfo? keyResult = null;
-		MainLoop mainLoop;
-		//Func<ConsoleKeyInfo> consoleKeyReaderFn = () => ;
+namespace Terminal.Gui;
 
-		/// <summary>
-		/// Invoked when a Key is pressed.
-		/// </summary>
-		public Action<ConsoleKeyInfo> KeyPressed;
+internal class FakeMainLoop : IMainLoopDriver {
 
-		/// <summary>
-		/// Creates an instance of the FakeMainLoop. <paramref name="consoleDriver"/> is not used.
-		/// </summary>
-		/// <param name="consoleDriver"></param>
-		public FakeMainLoop (ConsoleDriver consoleDriver = null)
-		{
-			// consoleDriver is not needed/used in FakeConsole
-		}
-
-		void MockKeyReader ()
-		{
-			while (true) {
-				waitForProbe.WaitOne ();
-				keyResult = FakeConsole.ReadKey (true);
-				keyReady.Set ();
-			}
-		}
-
-		void IMainLoopDriver.Setup (MainLoop mainLoop)
-		{
-			this.mainLoop = mainLoop;
-			Thread readThread = new Thread (MockKeyReader);
-			readThread.Start ();
-		}
-
-		void IMainLoopDriver.Wakeup ()
-		{
-		}
-
-		bool IMainLoopDriver.EventsPending (bool wait)
-		{
-			keyResult = null;
-			waitForProbe.Set ();
-
-			if (CheckTimers (wait, out var waitTimeout)) {
-				return true;
-			}
+	public Action<ConsoleKeyInfo> KeyPressed;
 
-			keyReady.WaitOne (waitTimeout);
-			return keyResult.HasValue;
-		}
-
-		bool CheckTimers (bool wait, out int waitTimeout)
-		{
-			long now = DateTime.UtcNow.Ticks;
-
-			if (mainLoop.timeouts.Count > 0) {
-				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-				if (waitTimeout < 0)
-					return true;
-			} else {
-				waitTimeout = -1;
-			}
+	public FakeMainLoop (ConsoleDriver consoleDriver = null)
+	{
+		// No implementation needed for FakeMainLoop
+	}
 
-			if (!wait)
-				waitTimeout = 0;
+	public void Setup (MainLoop mainLoop)
+	{
+		// No implementation needed for FakeMainLoop
+	}
 
-			int ic;
-			lock (mainLoop.idleHandlers) {
-				ic = mainLoop.idleHandlers.Count;
-			}
+	public void Wakeup ()
+	{
+		// No implementation needed for FakeMainLoop
+	}
 
-			return ic > 0;
-		}
+	public bool EventsPending (bool wait)
+	{
+		// Always return true for FakeMainLoop
+		return true;
+	}
 
-		void IMainLoopDriver.Iteration ()
-		{
-			if (keyResult.HasValue) {
-				KeyPressed?.Invoke (keyResult.Value);
-				keyResult = null;
-			}
+	public void Iteration ()
+	{
+		if (FakeConsole.MockKeyPresses.Count > 0) {
+			KeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
 		}
 	}
-}
+}
+

+ 1164 - 1441
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -13,1638 +13,1361 @@ using System.Threading;
 using System.Threading.Tasks;
 using System.Text;
 
-namespace Terminal.Gui {
-	internal class NetWinVTConsole {
-		IntPtr InputHandle, OutputHandle, ErrorHandle;
-		uint originalInputConsoleMode, originalOutputConsoleMode, originalErrorConsoleMode;
+namespace Terminal.Gui;
+internal class NetWinVTConsole {
+	IntPtr _inputHandle, _outputHandle, _errorHandle;
+	uint _originalInputConsoleMode, _originalOutputConsoleMode, _originalErrorConsoleMode;
 
-		public NetWinVTConsole ()
-		{
-			InputHandle = GetStdHandle (STD_INPUT_HANDLE);
-			if (!GetConsoleMode (InputHandle, out uint mode)) {
-				throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}.");
-			}
-			originalInputConsoleMode = mode;
-			if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) {
-				mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
-				if (!SetConsoleMode (InputHandle, mode)) {
-					throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
-				}
-			}
-
-			OutputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
-			if (!GetConsoleMode (OutputHandle, out mode)) {
-				throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}.");
-			}
-			originalOutputConsoleMode = mode;
-			if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
-				mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
-				if (!SetConsoleMode (OutputHandle, mode)) {
-					throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}.");
-				}
+	public NetWinVTConsole ()
+	{
+		_inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+		if (!GetConsoleMode (_inputHandle, out uint mode)) {
+			throw new ApplicationException ($"Failed to get input console mode, error code: {GetLastError ()}.");
+		}
+		_originalInputConsoleMode = mode;
+		if ((mode & ENABLE_VIRTUAL_TERMINAL_INPUT) < ENABLE_VIRTUAL_TERMINAL_INPUT) {
+			mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+			if (!SetConsoleMode (_inputHandle, mode)) {
+				throw new ApplicationException ($"Failed to set input console mode, error code: {GetLastError ()}.");
 			}
+		}
 
-			ErrorHandle = GetStdHandle (STD_ERROR_HANDLE);
-			if (!GetConsoleMode (ErrorHandle, out mode)) {
-				throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
-			}
-			originalErrorConsoleMode = mode;
-			if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
-				mode |= DISABLE_NEWLINE_AUTO_RETURN;
-				if (!SetConsoleMode (ErrorHandle, mode)) {
-					throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
-				}
+		_outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
+		if (!GetConsoleMode (_outputHandle, out mode)) {
+			throw new ApplicationException ($"Failed to get output console mode, error code: {GetLastError ()}.");
+		}
+		_originalOutputConsoleMode = mode;
+		if ((mode & (ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
+			mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
+			if (!SetConsoleMode (_outputHandle, mode)) {
+				throw new ApplicationException ($"Failed to set output console mode, error code: {GetLastError ()}.");
 			}
 		}
 
-		public void Cleanup ()
-		{
-			if (!SetConsoleMode (InputHandle, originalInputConsoleMode)) {
-				throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
-			}
-			if (!SetConsoleMode (OutputHandle, originalOutputConsoleMode)) {
-				throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}.");
-			}
-			if (!SetConsoleMode (ErrorHandle, originalErrorConsoleMode)) {
-				throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}.");
+		_errorHandle = GetStdHandle (STD_ERROR_HANDLE);
+		if (!GetConsoleMode (_errorHandle, out mode)) {
+			throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
+		}
+		_originalErrorConsoleMode = mode;
+		if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
+			mode |= DISABLE_NEWLINE_AUTO_RETURN;
+			if (!SetConsoleMode (_errorHandle, mode)) {
+				throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
 			}
 		}
+	}
 
-		const int STD_INPUT_HANDLE = -10;
-		const int STD_OUTPUT_HANDLE = -11;
-		const int STD_ERROR_HANDLE = -12;
-
-		// Input modes.
-		const uint ENABLE_PROCESSED_INPUT = 1;
-		const uint ENABLE_LINE_INPUT = 2;
-		const uint ENABLE_ECHO_INPUT = 4;
-		const uint ENABLE_WINDOW_INPUT = 8;
-		const uint ENABLE_MOUSE_INPUT = 16;
-		const uint ENABLE_INSERT_MODE = 32;
-		const uint ENABLE_QUICK_EDIT_MODE = 64;
-		const uint ENABLE_EXTENDED_FLAGS = 128;
-		const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
-
-		// Output modes.
-		const uint ENABLE_PROCESSED_OUTPUT = 1;
-		const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
-		const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
-		const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
-		const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
-
-		[DllImport ("kernel32.dll", SetLastError = true)]
-		static extern IntPtr GetStdHandle (int nStdHandle);
-
-		[DllImport ("kernel32.dll")]
-		static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
-
-		[DllImport ("kernel32.dll")]
-		static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
-
-		[DllImport ("kernel32.dll")]
-		static extern uint GetLastError ();
+	public void Cleanup ()
+	{
+		if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode)) {
+			throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}.");
+		}
+		if (!SetConsoleMode (_outputHandle, _originalOutputConsoleMode)) {
+			throw new ApplicationException ($"Failed to restore output console mode, error code: {GetLastError ()}.");
+		}
+		if (!SetConsoleMode (_errorHandle, _originalErrorConsoleMode)) {
+			throw new ApplicationException ($"Failed to restore error console mode, error code: {GetLastError ()}.");
+		}
 	}
 
-	internal class NetEvents {
-		ManualResetEventSlim inputReady = new ManualResetEventSlim (false);
-		ManualResetEventSlim waitForStart = new ManualResetEventSlim (false);
-		ManualResetEventSlim winChange = new ManualResetEventSlim (false);
-		Queue<InputResult?> inputResultQueue = new Queue<InputResult?> ();
-		ConsoleDriver consoleDriver;
-		volatile ConsoleKeyInfo [] cki = null;
-		static volatile bool isEscSeq;
-		int lastWindowHeight;
-		bool stopTasks;
+	const int STD_INPUT_HANDLE = -10;
+	const int STD_OUTPUT_HANDLE = -11;
+	const int STD_ERROR_HANDLE = -12;
+
+	// Input modes.
+	const uint ENABLE_PROCESSED_INPUT = 1;
+	const uint ENABLE_LINE_INPUT = 2;
+	const uint ENABLE_ECHO_INPUT = 4;
+	const uint ENABLE_WINDOW_INPUT = 8;
+	const uint ENABLE_MOUSE_INPUT = 16;
+	const uint ENABLE_INSERT_MODE = 32;
+	const uint ENABLE_QUICK_EDIT_MODE = 64;
+	const uint ENABLE_EXTENDED_FLAGS = 128;
+	const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
+
+	// Output modes.
+	const uint ENABLE_PROCESSED_OUTPUT = 1;
+	const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
+	const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
+	const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
+	const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
+
+	[DllImport ("kernel32.dll", SetLastError = true)]
+	static extern IntPtr GetStdHandle (int nStdHandle);
+
+	[DllImport ("kernel32.dll")]
+	static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
+
+	[DllImport ("kernel32.dll")]
+	static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
+
+	[DllImport ("kernel32.dll")]
+	static extern uint GetLastError ();
+}
+
+internal class NetEvents {
+	ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
+	ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
+	ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
+	Queue<InputResult?> _inputResultQueue = new Queue<InputResult?> ();
+	ConsoleDriver _consoleDriver;
+	volatile ConsoleKeyInfo [] _cki = null;
+	volatile static bool _isEscSeq;
+	bool _stopTasks;
 #if PROCESS_REQUEST
-		bool neededProcessRequest;
+		bool _neededProcessRequest;
 #endif
-		public bool IsTerminalWithOptions { get; set; }
-		public EscSeqReqProc EscSeqReqProc { get; } = new EscSeqReqProc ();
+	public EscSeqRequests EscSeqRequests { get; } = new EscSeqRequests ();
 
-		public NetEvents (ConsoleDriver consoleDriver)
-		{
-			if (consoleDriver == null) {
-				throw new ArgumentNullException ("Console driver instance must be provided.");
-			}
-			this.consoleDriver = consoleDriver;
-			Task.Run (ProcessInputResultQueue);
-			Task.Run (CheckWinChange);
-		}
+	public NetEvents (ConsoleDriver consoleDriver)
+	{
+		_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
+		Task.Run (ProcessInputResultQueue);
+		Task.Run (CheckWindowSizeChange);
+	}
 
-		internal void StopTasks ()
-		{
-			stopTasks = true;
-		}
+	internal void StopTasks ()
+	{
+		_stopTasks = true;
+	}
 
-		public InputResult? ReadConsoleInput ()
-		{
-			while (true) {
-				if (stopTasks) {
-					return null;
-				}
-				waitForStart.Set ();
-				winChange.Set ();
+	public InputResult? ReadConsoleInput ()
+	{
+		while (true) {
+			if (_stopTasks) {
+				return null;
+			}
+			_waitForStart.Set ();
+			_winChange.Set ();
 
-				if (inputResultQueue.Count == 0) {
-					inputReady.Wait ();
-					inputReady.Reset ();
-				}
+			if (_inputResultQueue.Count == 0) {
+				_inputReady.Wait ();
+				_inputReady.Reset ();
+			}
 #if PROCESS_REQUEST
-				neededProcessRequest = false;
+				_neededProcessRequest = false;
 #endif
-				if (inputResultQueue.Count > 0) {
-					return inputResultQueue.Dequeue ();
-				}
-			}
-		}
-
-		void ProcessInputResultQueue ()
-		{
-			while (true) {
-				waitForStart.Wait ();
-				waitForStart.Reset ();
-
-				if (inputResultQueue.Count == 0) {
-					GetConsoleKey ();
-				}
-
-				inputReady.Set ();
+			if (_inputResultQueue.Count > 0) {
+				return _inputResultQueue.Dequeue ();
 			}
 		}
+	}
 
-		void GetConsoleKey ()
-		{
-			ConsoleKey key = 0;
-			ConsoleModifiers mod = 0;
-			ConsoleKeyInfo newConsoleKeyInfo = default;
-
-			while (true) {
-				ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true);
-				if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !isEscSeq)
-					|| (consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq)) {
-					if (cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && isEscSeq) {
-						cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
-							false, false, false), cki);
-					}
-					isEscSeq = true;
-					newConsoleKeyInfo = consoleKeyInfo;
-					cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
-					if (!Console.KeyAvailable) {
-						DecodeEscSeq (ref newConsoleKeyInfo, ref key, cki, ref mod);
-						cki = null;
-						isEscSeq = false;
+	void ProcessInputResultQueue ()
+	{
+		while (true) {
+			_waitForStart.Wait ();
+			_waitForStart.Reset ();
+
+			if (_inputResultQueue.Count == 0) {
+				ConsoleKey key = 0;
+				ConsoleModifiers mod = 0;
+				ConsoleKeyInfo newConsoleKeyInfo = default;
+
+				while (true) {
+					ConsoleKeyInfo consoleKeyInfo = Console.ReadKey (true);
+					if ((consoleKeyInfo.KeyChar == (char)Key.Esc && !_isEscSeq)
+					|| (consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq)) {
+						if (_cki == null && consoleKeyInfo.KeyChar != (char)Key.Esc && _isEscSeq) {
+							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)Key.Esc, 0,
+							    false, false, false), _cki);
+						}
+						_isEscSeq = true;
+						newConsoleKeyInfo = consoleKeyInfo;
+						_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
+						if (Console.KeyAvailable) continue;
+						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+						_cki = null;
+						_isEscSeq = false;
+						break;
+					} else if (consoleKeyInfo.KeyChar == (char)Key.Esc && _isEscSeq && _cki != null) {
+						if (_cki != null) {
+							ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+							_cki = null;
+						}
+						break;
+					} else {
+						_inputResultQueue.Enqueue (new InputResult {
+							EventType = EventType.Key,
+							ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
+						});
+						_isEscSeq = false;
 						break;
 					}
-				} else if (consoleKeyInfo.KeyChar == (char)Key.Esc && isEscSeq && cki != null) {
-					DecodeEscSeq (ref newConsoleKeyInfo, ref key, cki, ref mod);
-					cki = null;
-					break;
-				} else {
-					GetConsoleInputType (consoleKeyInfo);
-					isEscSeq = false;
-					break;
 				}
 			}
-		}
 
-		void CheckWinChange ()
-		{
-			while (true) {
-				if (stopTasks) {
-					return;
-				}
-				winChange.Wait ();
-				winChange.Reset ();
-				WaitWinChange ();
-				inputReady.Set ();
-			}
+			_inputReady.Set ();
 		}
+	}
 
-		void WaitWinChange ()
+	void CheckWindowSizeChange ()
+	{
+		void RequestWindowSize ()
 		{
 			while (true) {
-				// HACK: Sleep for 10ms to mitigate high CPU usage (see issue #1502). 10ms was tested to address the problem, but may not be correct.
-				Thread.Sleep (10);
-				if (stopTasks) {
-					return;
-				}
-				switch (IsTerminalWithOptions) {
-				case false:
-					int buffHeight, buffWidth;
-					if (((NetDriver)consoleDriver).IsWinPlatform) {
-						buffHeight = Math.Max (Console.BufferHeight, 0);
-						buffWidth = Math.Max (Console.BufferWidth, 0);
-					} else {
-						buffHeight = consoleDriver.Rows;
-						buffWidth = consoleDriver.Cols;
-					}
-					if (IsWinChanged (
-						Math.Max (Console.WindowHeight, 0),
-						Math.Max (Console.WindowWidth, 0),
-						buffHeight,
-						buffWidth)) {
+				// Wait for a while then check if screen has changed sizes
+				Task.Delay (500).Wait ();
 
-						return;
-					}
-					break;
-				case true:
-					//Request the size of the text area in characters.
-					EscSeqReqProc.Add ("t");
-					Console.Out.Write ("\x1b[18t");
-					break;
+				if (_stopTasks) {
+					return;
 				}
-			}
-		}
-
-		bool IsWinChanged (int winHeight, int winWidth, int buffHeight, int buffWidth)
-		{
-			if (!consoleDriver.EnableConsoleScrolling) {
-				if (winWidth != consoleDriver.Cols || winHeight != consoleDriver.Rows) {
-					var w = Math.Max (winWidth, 0);
-					var h = Math.Max (winHeight, 0);
-					GetWindowSizeEvent (new Size (w, h));
-					return true;
+				int buffHeight, buffWidth;
+				if (((NetDriver)_consoleDriver).IsWinPlatform) {
+					buffHeight = Math.Max (Console.BufferHeight, 0);
+					buffWidth = Math.Max (Console.BufferWidth, 0);
+				} else {
+					buffHeight = _consoleDriver.Rows;
+					buffWidth = _consoleDriver.Cols;
 				}
-			} else {
-				if (winWidth != consoleDriver.Cols || winHeight != lastWindowHeight
-					|| buffWidth != consoleDriver.Cols || buffHeight != consoleDriver.Rows) {
+				if (EnqueueWindowSizeEvent (
+				    Math.Max (Console.WindowHeight, 0),
+				    Math.Max (Console.WindowWidth, 0),
+				    buffHeight,
+				    buffWidth)) {
 
-					lastWindowHeight = Math.Max (winHeight, 0);
-					GetWindowSizeEvent (new Size (winWidth, lastWindowHeight));
-					return true;
+					return;
 				}
 			}
-			return false;
 		}
 
-		void GetWindowSizeEvent (Size size)
-		{
-			WindowSizeEvent windowSizeEvent = new WindowSizeEvent () {
-				Size = size
-			};
-
-			inputResultQueue.Enqueue (new InputResult () {
-				EventType = EventType.WindowSize,
-				WindowSizeEvent = windowSizeEvent
-			});
-		}
-
-		void GetConsoleInputType (ConsoleKeyInfo consoleKeyInfo)
-		{
-			InputResult inputResult = new InputResult {
-				EventType = EventType.Key
-			};
-			MouseEvent mouseEvent = new MouseEvent ();
-			ConsoleKeyInfo newConsoleKeyInfo = EscSeqUtils.GetConsoleInputKey (consoleKeyInfo);
-			if (inputResult.EventType == EventType.Key) {
-				inputResult.ConsoleKeyInfo = newConsoleKeyInfo;
-			} else {
-				inputResult.MouseEvent = mouseEvent;
-			}
-
-			inputResultQueue.Enqueue (inputResult);
-		}
-
-		void DecodeEscSeq (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
-		{
-			string c1Control, code, terminating;
-			string [] values;
-			// isKeyMouse is true if it's CSI<, false otherwise
-			bool isKeyMouse;
-			bool isReq;
-			List<MouseFlags> mouseFlags;
-			Point pos;
-			EscSeqUtils.DecodeEscSeq (EscSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
-
-			if (isKeyMouse) {
-				foreach (var mf in mouseFlags) {
-					GetMouseEvent (MapMouseFlags (mf), pos);
-				}
+		while (true) {
+			if (_stopTasks) {
 				return;
-			} else if (isReq) {
-				GetRequestEvent (c1Control, code, values, terminating);
-				return;
-			}
-			InputResult inputResult = new InputResult {
-				EventType = EventType.Key,
-				ConsoleKeyInfo = newConsoleKeyInfo
-			};
-
-			inputResultQueue.Enqueue (inputResult);
-		}
-
-		void ProcessContinuousButtonPressed (MouseFlags mouseFlag, Point pos)
-		{
-			GetMouseEvent (MapMouseFlags (mouseFlag), pos);
-		}
-
-		MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
-		{
-			MouseButtonState mbs = default;
-			foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) {
-				if (mouseFlags.HasFlag ((MouseFlags)flag)) {
-					switch (flag) {
-					case MouseFlags.Button1Pressed:
-						mbs |= MouseButtonState.Button1Pressed;
-						break;
-					case MouseFlags.Button1Released:
-						mbs |= MouseButtonState.Button1Released;
-						break;
-					case MouseFlags.Button1Clicked:
-						mbs |= MouseButtonState.Button1Clicked;
-						break;
-					case MouseFlags.Button1DoubleClicked:
-						mbs |= MouseButtonState.Button1DoubleClicked;
-						break;
-					case MouseFlags.Button1TripleClicked:
-						mbs |= MouseButtonState.Button1TripleClicked;
-						break;
-					case MouseFlags.Button2Pressed:
-						mbs |= MouseButtonState.Button2Pressed;
-						break;
-					case MouseFlags.Button2Released:
-						mbs |= MouseButtonState.Button2Released;
-						break;
-					case MouseFlags.Button2Clicked:
-						mbs |= MouseButtonState.Button2Clicked;
-						break;
-					case MouseFlags.Button2DoubleClicked:
-						mbs |= MouseButtonState.Button2DoubleClicked;
-						break;
-					case MouseFlags.Button2TripleClicked:
-						mbs |= MouseButtonState.Button2TripleClicked;
-						break;
-					case MouseFlags.Button3Pressed:
-						mbs |= MouseButtonState.Button3Pressed;
-						break;
-					case MouseFlags.Button3Released:
-						mbs |= MouseButtonState.Button3Released;
-						break;
-					case MouseFlags.Button3Clicked:
-						mbs |= MouseButtonState.Button3Clicked;
-						break;
-					case MouseFlags.Button3DoubleClicked:
-						mbs |= MouseButtonState.Button3DoubleClicked;
-						break;
-					case MouseFlags.Button3TripleClicked:
-						mbs |= MouseButtonState.Button3TripleClicked;
-						break;
-					case MouseFlags.WheeledUp:
-						mbs |= MouseButtonState.ButtonWheeledUp;
-						break;
-					case MouseFlags.WheeledDown:
-						mbs |= MouseButtonState.ButtonWheeledDown;
-						break;
-					case MouseFlags.WheeledLeft:
-						mbs |= MouseButtonState.ButtonWheeledLeft;
-						break;
-					case MouseFlags.WheeledRight:
-						mbs |= MouseButtonState.ButtonWheeledRight;
-						break;
-					case MouseFlags.Button4Pressed:
-						mbs |= MouseButtonState.Button4Pressed;
-						break;
-					case MouseFlags.Button4Released:
-						mbs |= MouseButtonState.Button4Released;
-						break;
-					case MouseFlags.Button4Clicked:
-						mbs |= MouseButtonState.Button4Clicked;
-						break;
-					case MouseFlags.Button4DoubleClicked:
-						mbs |= MouseButtonState.Button4DoubleClicked;
-						break;
-					case MouseFlags.Button4TripleClicked:
-						mbs |= MouseButtonState.Button4TripleClicked;
-						break;
-					case MouseFlags.ButtonShift:
-						mbs |= MouseButtonState.ButtonShift;
-						break;
-					case MouseFlags.ButtonCtrl:
-						mbs |= MouseButtonState.ButtonCtrl;
-						break;
-					case MouseFlags.ButtonAlt:
-						mbs |= MouseButtonState.ButtonAlt;
-						break;
-					case MouseFlags.ReportMousePosition:
-						mbs |= MouseButtonState.ReportMousePosition;
-						break;
-					case MouseFlags.AllEvents:
-						mbs |= MouseButtonState.AllEvents;
-						break;
-					}
-				}
 			}
-			return mbs;
+			_winChange.Wait ();
+			_winChange.Reset ();
+			RequestWindowSize ();
+			_inputReady.Set ();
 		}
+	}
 
-		Point lastCursorPosition;
+	/// <summary>
+	/// Enqueue a window size event if the window size has changed.
+	/// </summary>
+	/// <param name="winHeight"></param>
+	/// <param name="winWidth"></param>
+	/// <param name="buffHeight"></param>
+	/// <param name="buffWidth"></param>
+	/// <returns></returns>
+	bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
+	{
+		if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false;
+		var w = Math.Max (winWidth, 0);
+		var h = Math.Max (winHeight, 0);
+		_inputResultQueue.Enqueue (new InputResult () {
+			EventType = EventType.WindowSize,
+			WindowSizeEvent = new WindowSizeEvent () {
+				Size = new Size (w, h)
+			}
+		});
+		return true;
+	}
 
-		void GetRequestEvent (string c1Control, string code, string [] values, string terminating)
-		{
-			EventType eventType = new EventType ();
-			switch (terminating) {
-			case "R": // Reports cursor position as CSI r ; c R
-				Point point = new Point {
-					X = int.Parse (values [1]) - 1,
-					Y = int.Parse (values [0]) - 1
-				};
-				if (lastCursorPosition.Y != point.Y) {
-					lastCursorPosition = point;
-					eventType = EventType.WindowPosition;
-					var winPositionEv = new WindowPositionEvent () {
-						CursorPosition = point
-					};
-					inputResultQueue.Enqueue (new InputResult () {
-						EventType = eventType,
-						WindowPositionEvent = winPositionEv
-					});
-				} else {
-					return;
-				}
-				break;
-			case "c":
-				try {
-					var parent = EscSeqUtils.GetParentProcess (Process.GetCurrentProcess ());
-					if (parent == null) { Debug.WriteLine ("Not supported!"); }
-				} catch (Exception ex) {
-					Debug.WriteLine (ex.Message);
-				}
+	// Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
+	void ProcessRequestResponse (ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod)
+	{
+		// isMouse is true if it's CSI<, false otherwise
+		EscSeqUtils.DecodeEscSeq (EscSeqRequests, ref newConsoleKeyInfo, ref key, cki, ref mod,
+		    out var c1Control, out var code, out var values, out var terminating,
+		    out var isMouse, out var mouseFlags,
+		    out var pos, out var isReq,
+		    (f, p) => HandleMouseEvent (MapMouseFlags (f), p));
+
+		if (isMouse) {
+			foreach (var mf in mouseFlags) {
+				HandleMouseEvent (MapMouseFlags (mf), pos);
+			}
+			return;
+		} else if (isReq) {
+			HandleRequestResponseEvent (c1Control, code, values, terminating);
+			return;
+		}
+		HandleKeyboardEvent (newConsoleKeyInfo);
+	}
 
-				if (c1Control == "CSI" && values.Length == 2
-					&& values [0] == "1" && values [1] == "0") {
-					// Reports CSI?1;0c ("VT101 with No Options")
-					IsTerminalWithOptions = false;
-				} else {
-					IsTerminalWithOptions = true;
-				}
-				break;
-			case "t":
-				switch (values [0]) {
-				case "8":
-					IsWinChanged (
-						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));
+	MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
+	{
+		MouseButtonState mbs = default;
+		foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) {
+			if (mouseFlags.HasFlag ((MouseFlags)flag)) {
+				switch (flag) {
+				case MouseFlags.Button1Pressed:
+					mbs |= MouseButtonState.Button1Pressed;
+					break;
+				case MouseFlags.Button1Released:
+					mbs |= MouseButtonState.Button1Released;
+					break;
+				case MouseFlags.Button1Clicked:
+					mbs |= MouseButtonState.Button1Clicked;
+					break;
+				case MouseFlags.Button1DoubleClicked:
+					mbs |= MouseButtonState.Button1DoubleClicked;
+					break;
+				case MouseFlags.Button1TripleClicked:
+					mbs |= MouseButtonState.Button1TripleClicked;
+					break;
+				case MouseFlags.Button2Pressed:
+					mbs |= MouseButtonState.Button2Pressed;
+					break;
+				case MouseFlags.Button2Released:
+					mbs |= MouseButtonState.Button2Released;
+					break;
+				case MouseFlags.Button2Clicked:
+					mbs |= MouseButtonState.Button2Clicked;
+					break;
+				case MouseFlags.Button2DoubleClicked:
+					mbs |= MouseButtonState.Button2DoubleClicked;
+					break;
+				case MouseFlags.Button2TripleClicked:
+					mbs |= MouseButtonState.Button2TripleClicked;
 					break;
-				default:
-					SetRequestedEvent (c1Control, code, values, terminating);
+				case MouseFlags.Button3Pressed:
+					mbs |= MouseButtonState.Button3Pressed;
+					break;
+				case MouseFlags.Button3Released:
+					mbs |= MouseButtonState.Button3Released;
+					break;
+				case MouseFlags.Button3Clicked:
+					mbs |= MouseButtonState.Button3Clicked;
+					break;
+				case MouseFlags.Button3DoubleClicked:
+					mbs |= MouseButtonState.Button3DoubleClicked;
+					break;
+				case MouseFlags.Button3TripleClicked:
+					mbs |= MouseButtonState.Button3TripleClicked;
+					break;
+				case MouseFlags.WheeledUp:
+					mbs |= MouseButtonState.ButtonWheeledUp;
+					break;
+				case MouseFlags.WheeledDown:
+					mbs |= MouseButtonState.ButtonWheeledDown;
+					break;
+				case MouseFlags.WheeledLeft:
+					mbs |= MouseButtonState.ButtonWheeledLeft;
+					break;
+				case MouseFlags.WheeledRight:
+					mbs |= MouseButtonState.ButtonWheeledRight;
+					break;
+				case MouseFlags.Button4Pressed:
+					mbs |= MouseButtonState.Button4Pressed;
+					break;
+				case MouseFlags.Button4Released:
+					mbs |= MouseButtonState.Button4Released;
+					break;
+				case MouseFlags.Button4Clicked:
+					mbs |= MouseButtonState.Button4Clicked;
+					break;
+				case MouseFlags.Button4DoubleClicked:
+					mbs |= MouseButtonState.Button4DoubleClicked;
+					break;
+				case MouseFlags.Button4TripleClicked:
+					mbs |= MouseButtonState.Button4TripleClicked;
+					break;
+				case MouseFlags.ButtonShift:
+					mbs |= MouseButtonState.ButtonShift;
+					break;
+				case MouseFlags.ButtonCtrl:
+					mbs |= MouseButtonState.ButtonCtrl;
+					break;
+				case MouseFlags.ButtonAlt:
+					mbs |= MouseButtonState.ButtonAlt;
+					break;
+				case MouseFlags.ReportMousePosition:
+					mbs |= MouseButtonState.ReportMousePosition;
+					break;
+				case MouseFlags.AllEvents:
+					mbs |= MouseButtonState.AllEvents;
 					break;
 				}
-				break;
-			default:
-				SetRequestedEvent (c1Control, code, values, terminating);
-				break;
 			}
-
-			inputReady.Set ();
 		}
+		return mbs;
+	}
 
-		void SetRequestedEvent (string c1Control, string code, string [] values, string terminating)
-		{
-			EventType eventType = EventType.RequestResponse;
-			var requestRespEv = new RequestResponseEvent () {
-				ResultTuple = (c1Control, code, values, terminating)
-			};
-			inputResultQueue.Enqueue (new InputResult () {
-				EventType = eventType,
-				RequestResponseEvent = requestRespEv
-			});
-		}
+	Point _lastCursorPosition;
 
-		void GetMouseEvent (MouseButtonState buttonState, Point pos)
-		{
-			MouseEvent mouseEvent = new MouseEvent () {
-				Position = pos,
-				ButtonState = buttonState,
+	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:
+			Point point = new Point {
+				X = int.Parse (values [1]) - 1,
+				Y = int.Parse (values [0]) - 1
 			};
-
-			inputResultQueue.Enqueue (new InputResult () {
-				EventType = EventType.Mouse,
-				MouseEvent = mouseEvent
-			});
-
-			inputReady.Set ();
-		}
-
-		public enum EventType {
-			Key = 1,
-			Mouse = 2,
-			WindowSize = 3,
-			WindowPosition = 4,
-			RequestResponse = 5
-		}
-
-		[Flags]
-		public enum MouseButtonState {
-			Button1Pressed = 0x1,
-			Button1Released = 0x2,
-			Button1Clicked = 0x4,
-			Button1DoubleClicked = 0x8,
-			Button1TripleClicked = 0x10,
-			Button2Pressed = 0x20,
-			Button2Released = 0x40,
-			Button2Clicked = 0x80,
-			Button2DoubleClicked = 0x100,
-			Button2TripleClicked = 0x200,
-			Button3Pressed = 0x400,
-			Button3Released = 0x800,
-			Button3Clicked = 0x1000,
-			Button3DoubleClicked = 0x2000,
-			Button3TripleClicked = 0x4000,
-			ButtonWheeledUp = 0x8000,
-			ButtonWheeledDown = 0x10000,
-			ButtonWheeledLeft = 0x20000,
-			ButtonWheeledRight = 0x40000,
-			Button4Pressed = 0x80000,
-			Button4Released = 0x100000,
-			Button4Clicked = 0x200000,
-			Button4DoubleClicked = 0x400000,
-			Button4TripleClicked = 0x800000,
-			ButtonShift = 0x1000000,
-			ButtonCtrl = 0x2000000,
-			ButtonAlt = 0x4000000,
-			ReportMousePosition = 0x8000000,
-			AllEvents = -1
-		}
-
-		public struct MouseEvent {
-			public Point Position;
-			public MouseButtonState ButtonState;
-		}
-
-		public struct WindowSizeEvent {
-			public Size Size;
-		}
-
-		public struct WindowPositionEvent {
-			public int Top;
-			public int Left;
-			public Point CursorPosition;
-		}
-
-		public struct RequestResponseEvent {
-			public (string c1Control, string code, string [] values, string terminating) ResultTuple;
-		}
-
-		public struct InputResult {
-			public EventType EventType;
-			public ConsoleKeyInfo ConsoleKeyInfo;
-			public MouseEvent MouseEvent;
-			public WindowSizeEvent WindowSizeEvent;
-			public WindowPositionEvent WindowPositionEvent;
-			public RequestResponseEvent RequestResponseEvent;
-		}
-	}
-
-	internal class NetDriver : ConsoleDriver {
-		const int COLOR_BLACK = 30;
-		const int COLOR_RED = 31;
-		const int COLOR_GREEN = 32;
-		const int COLOR_YELLOW = 33;
-		const int COLOR_BLUE = 34;
-		const int COLOR_MAGENTA = 35;
-		const int COLOR_CYAN = 36;
-		const int COLOR_WHITE = 37;
-		const int COLOR_BRIGHT_BLACK = 90;
-		const int COLOR_BRIGHT_RED = 91;
-		const int COLOR_BRIGHT_GREEN = 92;
-		const int COLOR_BRIGHT_YELLOW = 93;
-		const int COLOR_BRIGHT_BLUE = 94;
-		const int COLOR_BRIGHT_MAGENTA = 95;
-		const int COLOR_BRIGHT_CYAN = 96;
-		const int COLOR_BRIGHT_WHITE = 97;
-
-		int cols, rows, left, top;
-
-		public override int Cols => cols;
-		public override int Rows => rows;
-		public override int Left => left;
-		public override int Top => top;
-		public override bool EnableConsoleScrolling { get; set; }
-		public NetWinVTConsole NetWinConsole { get; }
-		public bool IsWinPlatform { get; }
-		public override IClipboard Clipboard { get; }
-		public override int [,,] Contents => contents;
-
-		int largestBufferHeight;
-
-		public NetDriver ()
-		{
-			var p = Environment.OSVersion.Platform;
-			if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-				IsWinPlatform = true;
-				NetWinConsole = new NetWinVTConsole ();
-			}
-			if (IsWinPlatform) {
-				Clipboard = new WindowsClipboard ();
-			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				Clipboard = new MacOSXClipboard ();
+			if (_lastCursorPosition.Y != point.Y) {
+				_lastCursorPosition = point;
+				var eventType = EventType.WindowPosition;
+				var winPositionEv = new WindowPositionEvent () {
+					CursorPosition = point
+				};
+				_inputResultQueue.Enqueue (new InputResult () {
+					EventType = eventType,
+					WindowPositionEvent = winPositionEv
+				});
 			} else {
-				if (CursesDriver.Is_WSL_Platform ()) {
-					Clipboard = new WSLClipboard ();
-				} else {
-					Clipboard = new CursesClipboard ();
-				}
-			}
-		}
-
-		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
-		int [,,] contents;
-		bool [] dirtyLine;
-
-		static bool sync = false;
-
-		// Current row, and current col, tracked by Move/AddCh only
-		int ccol, crow;
-
-		public override void Move (int col, int row)
-		{
-			ccol = col;
-			crow = row;
-		}
-
-		public override void AddRune (Rune rune)
-		{
-			if (contents.Length != Rows * Cols * 3) {
 				return;
 			}
-			rune = rune.MakePrintable ();
-			var runeWidth = rune.GetColumns ();
-			var validClip = IsValidContent (ccol, crow, Clip);
-
-			if (validClip) {
-				if (runeWidth == 0 && ccol > 0) {
-					var r = contents [crow, ccol - 1, 0];
-					var s = new string (new char [] { (char)r, (char)rune.Value });
-					string sn;
-					if (!s.IsNormalized ()) {
-						sn = s.Normalize ();
-					} else {
-						sn = s;
-					}
-					var c = sn [0];
-					contents [crow, ccol - 1, 0] = c;
-					contents [crow, ccol - 1, 1] = CurrentAttribute;
-					contents [crow, ccol - 1, 2] = 1;
-
-				} else {
-					if (runeWidth < 2 && ccol > 0
-						&& ((Rune)(char)contents [crow, ccol - 1, 0]).GetColumns () > 1) {
-
-						contents [crow, ccol - 1, 0] = (int)(uint)' ';
-
-					} else if (runeWidth < 2 && ccol <= Clip.Right - 1
-						&& ((Rune)(char)contents [crow, ccol, 0]).GetColumns () > 1) {
-
-						contents [crow, ccol + 1, 0] = (int)(uint)' ';
-						contents [crow, ccol + 1, 2] = 1;
-
-					}
-					if (runeWidth > 1 && ccol == Clip.Right - 1) {
-						contents [crow, ccol, 0] = (int)(uint)' ';
-					} else {
-						contents [crow, ccol, 0] = (int)(uint)rune.Value;
-					}
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 1;
-
-				}
-				dirtyLine [crow] = true;
-			}
+			break;
 
-			if (runeWidth < 0 || runeWidth > 0) {
-				ccol++;
-			}
-
-			if (runeWidth > 1) {
-				if (validClip && ccol < Clip.Right) {
-					contents [crow, ccol, 1] = CurrentAttribute;
-					contents [crow, ccol, 2] = 0;
-				}
-				ccol++;
-			}
-
-			if (sync) {
-				UpdateScreen ();
+		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);
+				break;
 			}
+			break;
+		default:
+			EnqueueRequestResponseEvent (c1Control, code, values, terminating);
+			break;
 		}
 
-		public override void AddStr (string str)
-		{
-			foreach (var rune in str.EnumerateRunes ())
-				AddRune (rune);
-		}
+		_inputReady.Set ();
+	}
 
-		public override void End ()
-		{
-			mainLoop.netEvents.StopTasks ();
+	void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
+	{
+		EventType eventType = EventType.RequestResponse;
+		var requestRespEv = new RequestResponseEvent () {
+			ResultTuple = (c1Control, code, values, terminating)
+		};
+		_inputResultQueue.Enqueue (new InputResult () {
+			EventType = eventType,
+			RequestResponseEvent = requestRespEv
+		});
+	}
 
-			if (IsWinPlatform) {
-				NetWinConsole.Cleanup ();
-			}
+	void HandleMouseEvent (MouseButtonState buttonState, Point pos)
+	{
+		MouseEvent mouseEvent = new MouseEvent () {
+			Position = pos,
+			ButtonState = buttonState,
+		};
 
-			StopReportingMouseMoves ();
-			Console.ResetColor ();
+		_inputResultQueue.Enqueue (new InputResult () {
+			EventType = EventType.Mouse,
+			MouseEvent = mouseEvent
+		});
 
-			//Disable alternative screen buffer.
-			Console.Out.Write ("\x1b[?1049l");
+		_inputReady.Set ();
+	}
 
-			//Set cursor key to cursor.
-			Console.Out.Write ("\x1b[?25h");
+	public enum EventType {
+		Key = 1,
+		Mouse = 2,
+		WindowSize = 3,
+		WindowPosition = 4,
+		RequestResponse = 5
+	}
 
-			Console.Out.Close ();
-		}
+	[Flags]
+	public enum MouseButtonState {
+		Button1Pressed = 0x1,
+		Button1Released = 0x2,
+		Button1Clicked = 0x4,
+		Button1DoubleClicked = 0x8,
+		Button1TripleClicked = 0x10,
+		Button2Pressed = 0x20,
+		Button2Released = 0x40,
+		Button2Clicked = 0x80,
+		Button2DoubleClicked = 0x100,
+		Button2TripleClicked = 0x200,
+		Button3Pressed = 0x400,
+		Button3Released = 0x800,
+		Button3Clicked = 0x1000,
+		Button3DoubleClicked = 0x2000,
+		Button3TripleClicked = 0x4000,
+		ButtonWheeledUp = 0x8000,
+		ButtonWheeledDown = 0x10000,
+		ButtonWheeledLeft = 0x20000,
+		ButtonWheeledRight = 0x40000,
+		Button4Pressed = 0x80000,
+		Button4Released = 0x100000,
+		Button4Clicked = 0x200000,
+		Button4DoubleClicked = 0x400000,
+		Button4TripleClicked = 0x800000,
+		ButtonShift = 0x1000000,
+		ButtonCtrl = 0x2000000,
+		ButtonAlt = 0x4000000,
+		ReportMousePosition = 0x8000000,
+		AllEvents = -1
+	}
 
-		public override Attribute MakeColor (Color foreground, Color background)
-		{
-			return MakeColor ((ConsoleColor)foreground, (ConsoleColor)background);
-		}
+	public struct MouseEvent {
+		public Point Position;
+		public MouseButtonState ButtonState;
+	}
 
-		static Attribute MakeColor (ConsoleColor f, ConsoleColor b)
-		{
-			// Encode the colors into the int value.
-			return new Attribute (
-				value: ((((int)f) & 0xffff) << 16) | (((int)b) & 0xffff),
-				foreground: (Color)f,
-				background: (Color)b
-				);
-		}
+	public struct WindowSizeEvent {
+		public Size Size;
+	}
 
-		public override void Init (Action terminalResized)
-		{
-			TerminalResized = terminalResized;
+	public struct WindowPositionEvent {
+		public int Top;
+		public int Left;
+		public Point CursorPosition;
+	}
 
-			//Enable alternative screen buffer.
-			Console.Out.Write ("\x1b[?1049h");
+	public struct RequestResponseEvent {
+		public (string c1Control, string code, string [] values, string terminating) ResultTuple;
+	}
 
-			//Set cursor key to application.
-			Console.Out.Write ("\x1b[?25l");
+	public struct InputResult {
+		public EventType EventType;
+		public ConsoleKeyInfo ConsoleKeyInfo;
+		public MouseEvent MouseEvent;
+		public WindowSizeEvent WindowSizeEvent;
+		public WindowPositionEvent WindowPositionEvent;
+		public RequestResponseEvent RequestResponseEvent;
+	}
 
-			Console.TreatControlCAsInput = true;
+	void HandleKeyboardEvent (ConsoleKeyInfo cki)
+	{
+		InputResult inputResult = new InputResult {
+			EventType = EventType.Key,
+			ConsoleKeyInfo = cki
+		};
 
-			if (EnableConsoleScrolling) {
-				largestBufferHeight = Console.BufferHeight;
-			} else {
-				largestBufferHeight = Console.WindowHeight;
-			}
+		_inputResultQueue.Enqueue (inputResult);
+	}
+}
+
+internal class NetDriver : ConsoleDriver {
+	const int COLOR_BLACK = 30;
+	const int COLOR_RED = 31;
+	const int COLOR_GREEN = 32;
+	const int COLOR_YELLOW = 33;
+	const int COLOR_BLUE = 34;
+	const int COLOR_MAGENTA = 35;
+	const int COLOR_CYAN = 36;
+	const int COLOR_WHITE = 37;
+	const int COLOR_BRIGHT_BLACK = 90;
+	const int COLOR_BRIGHT_RED = 91;
+	const int COLOR_BRIGHT_GREEN = 92;
+	const int COLOR_BRIGHT_YELLOW = 93;
+	const int COLOR_BRIGHT_BLUE = 94;
+	const int COLOR_BRIGHT_MAGENTA = 95;
+	const int COLOR_BRIGHT_CYAN = 96;
+	const int COLOR_BRIGHT_WHITE = 97;
+
+	public NetWinVTConsole NetWinConsole { get; private set; }
+	public bool IsWinPlatform { get; private set; }
+
+	int _largestBufferHeight;
+
+	public NetDriver ()
+	{
+	}
 
-			cols = Console.WindowWidth;
-			rows = largestBufferHeight;
+	public override void End ()
+	{
+		_mainLoop._netEvents.StopTasks ();
 
-			CurrentAttribute = MakeColor (Color.White, Color.Black);
-			InitalizeColorSchemes ();
+		if (IsWinPlatform) {
+			NetWinConsole.Cleanup ();
+		}
 
-			CurrentAttribute = MakeColor (Color.White, Color.Black);
-			InitalizeColorSchemes ();
+		StopReportingMouseMoves ();
+		Console.ResetColor ();
 
-			ResizeScreen ();
-			UpdateOffScreen ();
+		//Disable alternative screen buffer.
+		Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndActivateAltBufferWithBackscroll);
 
-			StartReportingMouseMoves ();
-		}
+		//Set cursor key to cursor.
+		Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
 
-		public override void ResizeScreen ()
-		{
-			if (!EnableConsoleScrolling) {
-				if (Console.WindowHeight > 0) {
-					// Not supported on Unix.
-					if (IsWinPlatform) {
-						// Can raise an exception while is still resizing.
-						try {
-#pragma warning disable CA1416
-							Console.CursorTop = 0;
-							Console.CursorLeft = 0;
-							Console.WindowTop = 0;
-							Console.WindowLeft = 0;
-							if (Console.WindowHeight > Rows) {
-								Console.SetWindowSize (Cols, Rows);
-							}
-							Console.SetBufferSize (Cols, Rows);
-#pragma warning restore CA1416
-						} catch (System.IO.IOException) {
-							setClip ();
-						} catch (ArgumentOutOfRangeException) {
-							setClip ();
-						}
-					} else {
-						Console.Out.Write ($"\x1b[8;{Rows};{Cols}t");
-					}
-				}
-			} else {
-				if (IsWinPlatform) {
-					if (Console.WindowHeight > 0) {
-						// Can raise an exception while is still resizing.
-						try {
-#pragma warning disable CA1416
-							Console.CursorTop = 0;
-							Console.CursorLeft = 0;
-							if (Console.WindowHeight > Rows) {
-								Console.SetWindowSize (Cols, Rows);
-							}
-							Console.SetBufferSize (Cols, Rows);
-#pragma warning restore CA1416
-						} catch (System.IO.IOException) {
-							setClip ();
-						} catch (ArgumentOutOfRangeException) {
-							setClip ();
-						}
-					}
-				} else {
-					Console.Out.Write ($"\x1b[30;{Rows};{Cols}t");
-				}
-			}
-			setClip ();
+		Console.Out.Close ();
+	}
 
-			void setClip ()
-			{
-				Clip = new Rect (0, 0, Cols, Rows);
+	public override void Init (Action terminalResized)
+	{
+		var p = Environment.OSVersion.Platform;
+		if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
+			IsWinPlatform = true;
+			try {
+				NetWinConsole = new NetWinVTConsole ();
+			} catch (ApplicationException) {
+				// Likely running as a unit test, or in a non-interactive session.
 			}
 		}
-
-		public override void UpdateOffScreen ()
-		{
-			contents = new int [Rows, Cols, 3];
-			dirtyLine = new bool [Rows];
-
-			lock (contents) {
-				// Can raise an exception while is still resizing.
-				try {
-					for (int row = 0; row < rows; row++) {
-						for (int c = 0; c < cols; c++) {
-							contents [row, c, 0] = ' ';
-							contents [row, c, 1] = (ushort)Colors.TopLevel.Normal;
-							contents [row, c, 2] = 0;
-							dirtyLine [row] = true;
-						}
-					}
-				} catch (IndexOutOfRangeException) { }
+		if (IsWinPlatform) {
+			Clipboard = new WindowsClipboard ();
+		} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+			Clipboard = new MacOSXClipboard ();
+		} else {
+			if (CursesDriver.Is_WSL_Platform ()) {
+				Clipboard = new WSLClipboard ();
+			} else {
+				Clipboard = new CursesClipboard ();
 			}
 		}
 
-		public override Attribute MakeAttribute (Color fore, Color back)
-		{
-			return MakeColor ((ConsoleColor)fore, (ConsoleColor)back);
-		}
+		TerminalResized = terminalResized;
 
-		public override void Refresh ()
-		{
-			UpdateScreen ();
-			UpdateCursor ();
-		}
-
-		public override void UpdateScreen ()
-		{
-			if (winChanging || Console.WindowHeight < 1 || contents.Length != Rows * Cols * 3
-				|| (!EnableConsoleScrolling && Rows != Console.WindowHeight)
-				|| (EnableConsoleScrolling && Rows != largestBufferHeight)) {
-				return;
-			}
-
-			int top = 0;
-			int left = 0;
-			int rows = Rows;
-			int cols = Cols;
-			System.Text.StringBuilder output = new System.Text.StringBuilder ();
-			int redrawAttr = -1;
-			var lastCol = -1;
-
-			GetCursorVisibility (out CursorVisibility savedVisibitity);
-			SetCursorVisibility (CursorVisibility.Invisible);
-
-			for (int row = top; row < rows; row++) {
-				if (Console.WindowHeight < 1) {
-					return;
-				}
-				if (!dirtyLine [row]) {
-					continue;
-				}
-				if (!SetCursorPosition (0, row)) {
-					return;
-				}
-				dirtyLine [row] = false;
-				output.Clear ();
-				for (int col = left; col < cols; col++) {
-					lastCol = -1;
-					var outputWidth = 0;
-					for (; col < cols; col++) {
-						if (contents [row, col, 2] == 0) {
-							if (output.Length > 0) {
-								SetCursorPosition (lastCol, row);
-								Console.Write (output);
-								output.Clear ();
-								lastCol += outputWidth;
-								outputWidth = 0;
-							} else if (lastCol == -1) {
-								lastCol = col;
-							}
-							if (lastCol + 1 < cols)
-								lastCol++;
-							continue;
-						}
+		if (NetWinConsole != null) {
+			//Enable alternative screen buffer.
+			Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
 
-						if (lastCol == -1)
-							lastCol = col;
+			//Set cursor key to application.
+			Console.Out.Write (EscSeqUtils.CSI_HideCursor);
 
-						var attr = contents [row, col, 1];
-						if (attr != redrawAttr) {
-							redrawAttr = attr;
-							output.Append (WriteAttributes (attr));
-						}
-						outputWidth++;
-						var rune = (Rune)contents [row, col, 0];
-						char [] spair;
-						if (rune.DecodeSurrogatePair (out spair)) {
-							output.Append (spair);
-						} else {
-							output.Append ((char)rune.Value);
-						}
-						contents [row, col, 2] = 0;
-					}
-				}
-				if (output.Length > 0) {
-					SetCursorPosition (lastCol, row);
-					Console.Write (output);
-				}
-			}
-			SetCursorPosition (0, 0);
-			SetCursorVisibility (savedVisibitity);
-		}
+			Console.TreatControlCAsInput = true;
 
-		void SetVirtualCursorPosition (int col, int row)
-		{
-			Console.Out.Write ($"\x1b[{row + 1};{col + 1}H");
+			Cols = Console.WindowWidth;
+			Rows = Console.WindowHeight;
+		} else {
+			// Simluate
+			Cols = 80;
+			Rows = 25;
+			_largestBufferHeight = Rows;
 		}
 
-		System.Text.StringBuilder WriteAttributes (int attr)
-		{
-			const string CSI = "\x1b[";
-			int bg = 0;
-			int fg = 0;
-			System.Text.StringBuilder sb = new System.Text.StringBuilder ();
-
-			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-				  .OfType<ConsoleColor> ()
-				  .Select (s => (int)s);
-			if (values.Contains (attr & 0xffff)) {
-				bg = MapColors ((ConsoleColor)(attr & 0xffff), false);
-			}
-			if (values.Contains ((attr >> 16) & 0xffff)) {
-				fg = MapColors ((ConsoleColor)((attr >> 16) & 0xffff));
-			}
-			sb.Append ($"{CSI}{bg};{fg}m");
+		ResizeScreen ();
+		ClearContents ();
+		CurrentAttribute = MakeColor (Color.White, Color.Black);
+		InitializeColorSchemes ();
 
-			return sb;
-		}
+		StartReportingMouseMoves ();
+	}
 
-		int MapColors (ConsoleColor color, bool isForeground = true)
-		{
-			switch (color) {
-			case ConsoleColor.Black:
-				return isForeground ? COLOR_BLACK : COLOR_BLACK + 10;
-			case ConsoleColor.DarkBlue:
-				return isForeground ? COLOR_BLUE : COLOR_BLUE + 10;
-			case ConsoleColor.DarkGreen:
-				return isForeground ? COLOR_GREEN : COLOR_GREEN + 10;
-			case ConsoleColor.DarkCyan:
-				return isForeground ? COLOR_CYAN : COLOR_CYAN + 10;
-			case ConsoleColor.DarkRed:
-				return isForeground ? COLOR_RED : COLOR_RED + 10;
-			case ConsoleColor.DarkMagenta:
-				return isForeground ? COLOR_MAGENTA : COLOR_MAGENTA + 10;
-			case ConsoleColor.DarkYellow:
-				return isForeground ? COLOR_YELLOW : COLOR_YELLOW + 10;
-			case ConsoleColor.Gray:
-				return isForeground ? COLOR_WHITE : COLOR_WHITE + 10;
-			case ConsoleColor.DarkGray:
-				return isForeground ? COLOR_BRIGHT_BLACK : COLOR_BRIGHT_BLACK + 10;
-			case ConsoleColor.Blue:
-				return isForeground ? COLOR_BRIGHT_BLUE : COLOR_BRIGHT_BLUE + 10;
-			case ConsoleColor.Green:
-				return isForeground ? COLOR_BRIGHT_GREEN : COLOR_BRIGHT_GREEN + 10;
-			case ConsoleColor.Cyan:
-				return isForeground ? COLOR_BRIGHT_CYAN : COLOR_BRIGHT_CYAN + 10;
-			case ConsoleColor.Red:
-				return isForeground ? COLOR_BRIGHT_RED : COLOR_BRIGHT_RED + 10;
-			case ConsoleColor.Magenta:
-				return isForeground ? COLOR_BRIGHT_MAGENTA : COLOR_BRIGHT_MAGENTA + 10;
-			case ConsoleColor.Yellow:
-				return isForeground ? COLOR_BRIGHT_YELLOW : COLOR_BRIGHT_YELLOW + 10;
-			case ConsoleColor.White:
-				return isForeground ? COLOR_BRIGHT_WHITE : COLOR_BRIGHT_WHITE + 10;
-			}
-			return 0;
+	public virtual void ResizeScreen ()
+	{
+		if (NetWinConsole == null) {
+			return;
 		}
 
-		bool SetCursorPosition (int col, int row)
-		{
+		if (Console.WindowHeight > 0) {
+			// Not supported on Unix.
 			if (IsWinPlatform) {
-				// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+				// Can raise an exception while is still resizing.
 				try {
-					Console.SetCursorPosition (col, row);
-					return true;
-				} catch (Exception) {
-					return false;
+#pragma warning disable CA1416
+					Console.CursorTop = 0;
+					Console.CursorLeft = 0;
+					Console.WindowTop = 0;
+					Console.WindowLeft = 0;
+					if (Console.WindowHeight > Rows) {
+						Console.SetWindowSize (Cols, Rows);
+					}
+					Console.SetBufferSize (Cols, Rows);
+#pragma warning restore CA1416
+				} catch (System.IO.IOException) {
+					Clip = new Rect (0, 0, Cols, Rows);
+				} catch (ArgumentOutOfRangeException) {
+					Clip = new Rect (0, 0, Cols, Rows);
 				}
 			} else {
-				SetVirtualCursorPosition (col, row);
-				return true;
+				Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
 			}
 		}
 
-		private void SetWindowPosition (int col, int row)
-		{
-			if (IsWinPlatform && EnableConsoleScrolling) {
-				var winTop = Math.Max (Rows - Console.WindowHeight - row, 0);
-				winTop = Math.Min (winTop, Rows - Console.WindowHeight + 1);
-				winTop = Math.Max (winTop, 0);
-				if (winTop != Console.WindowTop) {
-					try {
-						if (!EnsureBufferSize ()) {
-							return;
-						}
-#pragma warning disable CA1416
-						Console.SetWindowPosition (col, winTop);
-#pragma warning restore CA1416
-					} catch (System.IO.IOException) {
+		Clip = new Rect (0, 0, Cols, Rows);
+	}
 
-					} catch (System.ArgumentOutOfRangeException) { }
-				}
-			}
-			top = Console.WindowTop;
-			left = Console.WindowLeft;
-		}
+	public override void Refresh ()
+	{
+		UpdateScreen ();
+		UpdateCursor ();
+	}
 
-		private bool EnsureBufferSize ()
-		{
-#pragma warning disable CA1416
-			if (IsWinPlatform && Console.BufferHeight < Rows) {
-				try {
-					Console.SetBufferSize (Console.WindowWidth, Rows);
-				} catch (Exception) {
-					return false;
-				}
-			}
-#pragma warning restore CA1416
-			return true;
+	public override void UpdateScreen ()
+	{
+		if (_winSizeChanging || Console.WindowHeight < 1 || Contents.Length != Rows * Cols || Rows != Console.WindowHeight) {
+			return;
 		}
 
-		private CursorVisibility? savedCursorVisibility;
+		var top = 0;
+		var left = 0;
+		var rows = Rows;
+		var cols = Cols;
+		System.Text.StringBuilder output = new System.Text.StringBuilder ();
+		Attribute redrawAttr = new Attribute ();
+		var lastCol = -1;
 
-		public override void UpdateCursor ()
-		{
-			EnsureCursorVisibility ();
-			//Debug.WriteLine ($"Before - CursorTop: {Console.CursorTop};CursorLeft: {Console.CursorLeft}");
+		//GetCursorVisibility (out CursorVisibility savedVisibitity);
+		//SetCursorVisibility (CursorVisibility.Invisible); 
 
-			if (ccol >= 0 && ccol < Cols && crow >= 0 && crow < Rows) {
-				SetCursorPosition (ccol, crow);
-				SetWindowPosition (0, crow);
+		for (var row = top; row < rows; row++) {
+			if (Console.WindowHeight < 1) {
+				return;
 			}
-			//Debug.WriteLine ($"WindowTop: {Console.WindowTop};WindowLeft: {Console.WindowLeft}");
-			//Debug.WriteLine ($"After - CursorTop: {Console.CursorTop};CursorLeft: {Console.CursorLeft}");
-		}
+			if (!_dirtyLines [row]) {
+				continue;
+			}
+			if (!SetCursorPosition (0, row)) {
+				return;
+			}
+			_dirtyLines [row] = false;
+			output.Clear ();
+			for (var col = left; col < cols; col++) {
+				lastCol = -1;
+				var outputWidth = 0;
+				for (; col < cols; col++) {
+					if (!Contents [row, col].IsDirty) {
+						if (output.Length > 0) {
+							WriteToConsole (output, ref lastCol, row, ref outputWidth);
+						} else if (lastCol == -1) {
+							lastCol = col;
+						}
+						if (lastCol + 1 < cols)
+							lastCol++;
+						continue;
+					}
 
-		public override void StartReportingMouseMoves ()
-		{
-			Console.Out.Write (EscSeqUtils.EnableMouseEvents);
-		}
+					if (lastCol == -1) {
+						lastCol = col;
+					}
 
-		public override void StopReportingMouseMoves ()
-		{
-			Console.Out.Write (EscSeqUtils.DisableMouseEvents);
+					Attribute attr = Contents [row, col].Attribute.Value;
+					// Performance: Only send the escape sequence if the attribute has changed.
+					if (attr != redrawAttr) {
+						redrawAttr = attr;
+						output.Append (EscSeqUtils.CSI_SetGraphicsRendition (
+						    MapColors ((ConsoleColor)attr.Background, false), MapColors ((ConsoleColor)attr.Foreground, true)));
+					}
+					outputWidth++;
+					var rune = (Rune)Contents [row, col].Runes [0];
+					output.Append (rune.ToString ());
+					if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
+						WriteToConsole (output, ref lastCol, row, ref outputWidth);
+						Console.CursorLeft--;
+					}
+					Contents [row, col].IsDirty = false;
+				}
+			}
+			if (output.Length > 0) {
+				SetCursorPosition (lastCol, row);
+				Console.Write (output);
+			}
 		}
+		SetCursorPosition (0, 0);
 
-		public override void Suspend ()
-		{
-		}
+		//SetCursorVisibility (savedVisibitity);
 
-		public override void SetAttribute (Attribute c)
+		void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
 		{
-			base.SetAttribute (c);
+			SetCursorPosition (lastCol, row);
+			Console.Write (output);
+			output.Clear ();
+			lastCol += outputWidth;
+			outputWidth = 0;
 		}
+	}
 
-		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-		{
-			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-				return consoleKeyInfo;
-			}
+	#region Color Handling
+
+	// Cache the list of ConsoleColor values.
+	private static readonly HashSet<int> ConsoleColorValues = new HashSet<int> (
+	    Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
+	);
+
+	// Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
+	private static Dictionary<ConsoleColor, int> colorMap = new Dictionary<ConsoleColor, int> {
+	{ ConsoleColor.Black, COLOR_BLACK },
+	{ ConsoleColor.DarkBlue, COLOR_BLUE },
+	{ ConsoleColor.DarkGreen, COLOR_GREEN },
+	{ ConsoleColor.DarkCyan, COLOR_CYAN },
+	{ ConsoleColor.DarkRed, COLOR_RED },
+	{ ConsoleColor.DarkMagenta, COLOR_MAGENTA },
+	{ ConsoleColor.DarkYellow, COLOR_YELLOW },
+	{ ConsoleColor.Gray, COLOR_WHITE },
+	{ ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
+	{ ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
+	{ ConsoleColor.Green, COLOR_BRIGHT_GREEN },
+	{ ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
+	{ ConsoleColor.Red, COLOR_BRIGHT_RED },
+	{ ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
+	{ ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
+	{ ConsoleColor.White, COLOR_BRIGHT_WHITE }
+    };
+
+	// Map a ConsoleColor to a platform dependent value.
+	int MapColors (ConsoleColor color, bool isForeground = true)
+	{
+		return colorMap.TryGetValue (color, out var colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
+	}
+
+	/// <remarks>
+	/// In the NetDriver, colors are encoded as an int. 
+	/// Extracts the foreground and background colors from the encoded value.
+	/// Assumes a 4-bit encoded value for both foreground and background colors.
+	/// </remarks>
+	internal override void GetColors (int value, out Color foreground, out Color background)
+	{
+		// Assume a 4-bit encoded value for both foreground and background colors.
+		foreground = (Color)((value >> 16) & 0xF);
+		background = (Color)(value & 0xF);
+	}
 
-			var mod = consoleKeyInfo.Modifiers;
-			var shift = (mod & ConsoleModifiers.Shift) != 0;
-			var alt = (mod & ConsoleModifiers.Alt) != 0;
-			var control = (mod & ConsoleModifiers.Control) != 0;
+	/// <remarks>
+	/// In the NetDriver, colors are encoded as an int. 
+	/// However, the foreground color is stored in the most significant 16 bits, 
+	/// and the background color is stored in the least significant 16 bits.
+	/// </remarks>
+	public override Attribute MakeColor (Color foreground, Color background)
+	{
+		// Encode the colors into the int value.
+		return new Attribute (
+		    value: ((((int)foreground) & 0xffff) << 16) | (((int)background) & 0xffff),
+		    foreground: foreground,
+		    background: background
+		);
+	}
 
-			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
+	#endregion
 
-			return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+	#region Cursor Handling
+	bool SetCursorPosition (int col, int row)
+	{
+		//if (IsWinPlatform) {
+		// Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
+		try {
+			Console.SetCursorPosition (col, row);
+			return true;
+		} catch (Exception) {
+			return false;
 		}
+		// BUGBUG: This breaks -usc on WSL; not sure why. But commenting out fixes.
+		//} else {
+		//	// TODO: Explain why + 1 is needed (and why we do this for non-Windows).
+		//	Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
+		//	return true;
+		//}
+	}
 
-		Key MapKey (ConsoleKeyInfo keyInfo)
-		{
-			MapKeyModifiers (keyInfo, (Key)keyInfo.Key);
-			switch (keyInfo.Key) {
-			case ConsoleKey.Escape:
-				return MapKeyModifiers (keyInfo, Key.Esc);
-			case ConsoleKey.Tab:
-				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
-			case ConsoleKey.Home:
-				return MapKeyModifiers (keyInfo, Key.Home);
-			case ConsoleKey.End:
-				return MapKeyModifiers (keyInfo, Key.End);
-			case ConsoleKey.LeftArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorLeft);
-			case ConsoleKey.RightArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorRight);
-			case ConsoleKey.UpArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorUp);
-			case ConsoleKey.DownArrow:
-				return MapKeyModifiers (keyInfo, Key.CursorDown);
-			case ConsoleKey.PageUp:
-				return MapKeyModifiers (keyInfo, Key.PageUp);
-			case ConsoleKey.PageDown:
-				return MapKeyModifiers (keyInfo, Key.PageDown);
-			case ConsoleKey.Enter:
-				return MapKeyModifiers (keyInfo, Key.Enter);
-			case ConsoleKey.Spacebar:
-				return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
-			case ConsoleKey.Backspace:
-				return MapKeyModifiers (keyInfo, Key.Backspace);
-			case ConsoleKey.Delete:
-				return MapKeyModifiers (keyInfo, Key.DeleteChar);
-			case ConsoleKey.Insert:
-				return MapKeyModifiers (keyInfo, Key.InsertChar);
-
-			case ConsoleKey.Oem1:
-			case ConsoleKey.Oem2:
-			case ConsoleKey.Oem3:
-			case ConsoleKey.Oem4:
-			case ConsoleKey.Oem5:
-			case ConsoleKey.Oem6:
-			case ConsoleKey.Oem7:
-			case ConsoleKey.Oem8:
-			case ConsoleKey.Oem102:
-			case ConsoleKey.OemPeriod:
-			case ConsoleKey.OemComma:
-			case ConsoleKey.OemPlus:
-			case ConsoleKey.OemMinus:
-				return (Key)((uint)keyInfo.KeyChar);
-			}
-
-			var key = keyInfo.Key;
-			if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-				var delta = key - ConsoleKey.A;
-				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
-				}
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
-				}
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-						return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
-					}
-				}
-				return (Key)((uint)keyInfo.KeyChar);
-			}
-			if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-				var delta = key - ConsoleKey.D0;
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-					return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
-				}
-				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
-				}
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
-						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
-					}
-				}
-				return (Key)((uint)keyInfo.KeyChar);
-			}
-			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-				var delta = key - ConsoleKey.F1;
-				if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
-				}
+	CursorVisibility? _cachedCursorVisibility;
 
-				return (Key)((uint)Key.F1 + delta);
-			}
-			if (keyInfo.KeyChar != 0) {
-				return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
-			}
+	public override void UpdateCursor ()
+	{
+		EnsureCursorVisibility ();
 
-			return (Key)(0xffffffff);
+		if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) {
+			SetCursorPosition (Col, Row);
+			SetWindowPosition (0, Row);
 		}
+	}
 
-		KeyModifiers keyModifiers;
+	public override bool GetCursorVisibility (out CursorVisibility visibility)
+	{
+		visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
+		return visibility == CursorVisibility.Default;
+	}
 
-		Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
-		{
-			if (keyModifiers == null) {
-				keyModifiers = new KeyModifiers ();
-			}
-			Key keyMod = new Key ();
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-				keyMod = Key.ShiftMask;
-				keyModifiers.Shift = true;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-				keyMod |= Key.CtrlMask;
-				keyModifiers.Ctrl = true;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-				keyMod |= Key.AltMask;
-				keyModifiers.Alt = true;
-			}
+	public override bool SetCursorVisibility (CursorVisibility visibility)
+	{
+		_cachedCursorVisibility = visibility;
+		var isVisible = Console.CursorVisible = visibility == CursorVisibility.Default;
+		//Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+		return isVisible;
+	}
 
-			return keyMod != Key.Null ? keyMod | key : key;
+	public override bool EnsureCursorVisibility ()
+	{
+		if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) {
+			GetCursorVisibility (out CursorVisibility cursorVisibility);
+			_cachedCursorVisibility = cursorVisibility;
+			SetCursorVisibility (CursorVisibility.Invisible);
+			return false;
 		}
 
-		Action<KeyEvent> keyHandler;
-		Action<KeyEvent> keyDownHandler;
-		Action<KeyEvent> keyUpHandler;
-		Action<MouseEvent> mouseHandler;
-		NetMainLoop mainLoop;
-
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
-		{
-			this.keyHandler = keyHandler;
-			this.keyDownHandler = keyDownHandler;
-			this.keyUpHandler = keyUpHandler;
-			this.mouseHandler = mouseHandler;
-
-			var mLoop = this.mainLoop = mainLoop.Driver as NetMainLoop;
+		SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
+		return _cachedCursorVisibility == CursorVisibility.Default;
+	}
+	#endregion
 
-			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
-			mLoop.ProcessInput = (e) => ProcessInput (e);
+	#region Size and Position Handling
 
-			// Check if terminal supports requests
-			this.mainLoop.netEvents.EscSeqReqProc.Add ("c");
-			Console.Out.Write ("\x1b[0c");
-		}
+	void SetWindowPosition (int col, int row)
+	{
+		Top = Console.WindowTop;
+		Left = Console.WindowLeft;
+	}
 
-		void ProcessInput (NetEvents.InputResult inputEvent)
-		{
-			switch (inputEvent.EventType) {
-			case NetEvents.EventType.Key:
-				ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
-				if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-					consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-				}
-				keyModifiers = new KeyModifiers ();
-				var map = MapKey (consoleKeyInfo);
-				if (map == (Key)0xffffffff) {
-					return;
-				}
-				if (map == Key.Null) {
-					keyDownHandler (new KeyEvent (map, keyModifiers));
-					keyUpHandler (new KeyEvent (map, keyModifiers));
-				} else {
-					keyDownHandler (new KeyEvent (map, keyModifiers));
-					keyHandler (new KeyEvent (map, keyModifiers));
-					keyUpHandler (new KeyEvent (map, keyModifiers));
-				}
-				break;
-			case NetEvents.EventType.Mouse:
-				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
-				break;
-			case NetEvents.EventType.WindowSize:
-				ChangeWin (inputEvent.WindowSizeEvent.Size);
-				break;
-			case NetEvents.EventType.RequestResponse:
-				Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple;
-				break;
+	private bool EnsureBufferSize ()
+	{
+#pragma warning disable CA1416
+		if (IsWinPlatform && Console.BufferHeight < Rows) {
+			try {
+				Console.SetBufferSize (Console.WindowWidth, Rows);
+			} catch (Exception) {
+				return false;
 			}
 		}
+#pragma warning restore CA1416
+		return true;
+	}
+	#endregion
 
-		volatile bool winChanging;
 
-		void ChangeWin (Size size)
-		{
-			winChanging = true;
-			if (!EnableConsoleScrolling) {
-				largestBufferHeight = Math.Max (size.Height, 0);
-			} else {
-				largestBufferHeight = Math.Max (size.Height, largestBufferHeight);
-			}
-			top = 0;
-			left = 0;
-			cols = size.Width;
-			rows = largestBufferHeight;
-			ResizeScreen ();
-			UpdateOffScreen ();
-			winChanging = false;
-			TerminalResized?.Invoke ();
+	public void StartReportingMouseMoves ()
+	{
+		Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+	}
+
+	public void StopReportingMouseMoves ()
+	{
+		Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+	}
+
+	ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+			return consoleKeyInfo;
 		}
 
-		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
-		{
-			//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+		var mod = consoleKeyInfo.Modifiers;
+		var shift = (mod & ConsoleModifiers.Shift) != 0;
+		var alt = (mod & ConsoleModifiers.Alt) != 0;
+		var control = (mod & ConsoleModifiers.Control) != 0;
 
-			MouseFlags mouseFlag = 0;
+		var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
 
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
-				mouseFlag |= MouseFlags.Button1Pressed;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) {
-				mouseFlag |= MouseFlags.Button1Released;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) {
-				mouseFlag |= MouseFlags.Button1Clicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button1DoubleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button1TripleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) {
-				mouseFlag |= MouseFlags.Button2Pressed;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) {
-				mouseFlag |= MouseFlags.Button2Released;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) {
-				mouseFlag |= MouseFlags.Button2Clicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button2DoubleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button2TripleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) {
-				mouseFlag |= MouseFlags.Button3Pressed;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) {
-				mouseFlag |= MouseFlags.Button3Released;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) {
-				mouseFlag |= MouseFlags.Button3Clicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button3DoubleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button3TripleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) {
-				mouseFlag |= MouseFlags.WheeledUp;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) {
-				mouseFlag |= MouseFlags.WheeledDown;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) {
-				mouseFlag |= MouseFlags.WheeledLeft;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) {
-				mouseFlag |= MouseFlags.WheeledRight;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) {
-				mouseFlag |= MouseFlags.Button4Pressed;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) {
-				mouseFlag |= MouseFlags.Button4Released;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) {
-				mouseFlag |= MouseFlags.Button4Clicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button4DoubleClicked;
-			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) {
-				mouseFlag |= MouseFlags.Button4TripleClicked;
+		return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+	}
+
+	Key MapKey (ConsoleKeyInfo keyInfo)
+	{
+		MapKeyModifiers (keyInfo, (Key)keyInfo.Key);
+		switch (keyInfo.Key) {
+		case ConsoleKey.Escape:
+			return MapKeyModifiers (keyInfo, Key.Esc);
+		case ConsoleKey.Tab:
+			return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+		case ConsoleKey.Home:
+			return MapKeyModifiers (keyInfo, Key.Home);
+		case ConsoleKey.End:
+			return MapKeyModifiers (keyInfo, Key.End);
+		case ConsoleKey.LeftArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorLeft);
+		case ConsoleKey.RightArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorRight);
+		case ConsoleKey.UpArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorUp);
+		case ConsoleKey.DownArrow:
+			return MapKeyModifiers (keyInfo, Key.CursorDown);
+		case ConsoleKey.PageUp:
+			return MapKeyModifiers (keyInfo, Key.PageUp);
+		case ConsoleKey.PageDown:
+			return MapKeyModifiers (keyInfo, Key.PageDown);
+		case ConsoleKey.Enter:
+			return MapKeyModifiers (keyInfo, Key.Enter);
+		case ConsoleKey.Spacebar:
+			return MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? Key.Space : (Key)keyInfo.KeyChar);
+		case ConsoleKey.Backspace:
+			return MapKeyModifiers (keyInfo, Key.Backspace);
+		case ConsoleKey.Delete:
+			return MapKeyModifiers (keyInfo, Key.DeleteChar);
+		case ConsoleKey.Insert:
+			return MapKeyModifiers (keyInfo, Key.InsertChar);
+
+		case ConsoleKey.Oem1:
+		case ConsoleKey.Oem2:
+		case ConsoleKey.Oem3:
+		case ConsoleKey.Oem4:
+		case ConsoleKey.Oem5:
+		case ConsoleKey.Oem6:
+		case ConsoleKey.Oem7:
+		case ConsoleKey.Oem8:
+		case ConsoleKey.Oem102:
+		case ConsoleKey.OemPeriod:
+		case ConsoleKey.OemComma:
+		case ConsoleKey.OemPlus:
+		case ConsoleKey.OemMinus:
+			return (Key)((uint)keyInfo.KeyChar);
+		}
+
+		var key = keyInfo.Key;
+		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
+			var delta = key - ConsoleKey.A;
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.A + delta));
+			}
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
+			}
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
+					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
+				}
 			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) {
-				mouseFlag |= MouseFlags.ReportMousePosition;
+			return (Key)((uint)keyInfo.KeyChar);
+		}
+		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
+			var delta = key - ConsoleKey.D0;
+			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
+				return (Key)(((uint)Key.AltMask) | ((uint)Key.D0 + delta));
 			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) {
-				mouseFlag |= MouseFlags.ButtonShift;
+			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
+				return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) {
-				mouseFlag |= MouseFlags.ButtonCtrl;
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
+					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
+				}
 			}
-			if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) {
-				mouseFlag |= MouseFlags.ButtonAlt;
+			return (Key)((uint)keyInfo.KeyChar);
+		}
+		if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) {
+			var delta = key - ConsoleKey.F1;
+			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+				return MapKeyModifiers (keyInfo, (Key)((uint)Key.F1 + delta));
 			}
 
-			return new MouseEvent () {
-				X = me.Position.X,
-				Y = me.Position.Y,
-				Flags = mouseFlag
-			};
+			return (Key)((uint)Key.F1 + delta);
 		}
-
-		/// <inheritdoc/>
-		public override bool GetCursorVisibility (out CursorVisibility visibility)
-		{
-			visibility = savedCursorVisibility ?? CursorVisibility.Default;
-			return visibility == CursorVisibility.Default;
+		if (keyInfo.KeyChar != 0) {
+			return MapKeyModifiers (keyInfo, (Key)((uint)keyInfo.KeyChar));
 		}
 
-		/// <inheritdoc/>
-		public override bool SetCursorVisibility (CursorVisibility visibility)
-		{
-			savedCursorVisibility = visibility;
-			var isVisible = Console.CursorVisible = visibility == CursorVisibility.Default;
-			if (isVisible) {
-				Console.Out.Write ("\x1b[?25h");
-			} else {
-				Console.Out.Write ("\x1b[?25l");
-			}
-			return isVisible;
-		}
+		return (Key)(0xffffffff);
+	}
 
-		/// <inheritdoc/>
-		public override bool EnsureCursorVisibility ()
-		{
-			if (!(ccol >= 0 && crow >= 0 && ccol < Cols && crow < Rows)) {
-				GetCursorVisibility (out CursorVisibility cursorVisibility);
-				savedCursorVisibility = cursorVisibility;
-				SetCursorVisibility (CursorVisibility.Invisible);
-				return false;
-			}
+	KeyModifiers _keyModifiers;
 
-			SetCursorVisibility (savedCursorVisibility ?? CursorVisibility.Default);
-			return savedCursorVisibility == CursorVisibility.Default;
+	Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
+	{
+		_keyModifiers ??= new KeyModifiers ();
+		Key keyMod = new Key ();
+		if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
+			keyMod = Key.ShiftMask;
+			_keyModifiers.Shift = true;
+		}
+		if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
+			keyMod |= Key.CtrlMask;
+			_keyModifiers.Ctrl = true;
+		}
+		if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
+			keyMod |= Key.AltMask;
+			_keyModifiers.Alt = true;
 		}
 
-		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
-		{
-			NetEvents.InputResult input = new NetEvents.InputResult ();
-			input.EventType = NetEvents.EventType.Key;
-			input.ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control);
+		return keyMod != Key.Null ? keyMod | key : key;
+	}
 
-			try {
-				ProcessInput (input);
-			} catch (OverflowException) { }
-		}
+	Action<KeyEvent> _keyHandler;
+	Action<KeyEvent> _keyDownHandler;
+	Action<KeyEvent> _keyUpHandler;
+	Action<MouseEvent> _mouseHandler;
+	NetMainLoop _mainLoop;
 
-		public override bool GetColors (int value, out Color foreground, out Color background)
-		{
-			bool hasColor = false;
-			foreground = default;
-			background = default;
-			IEnumerable<int> values = Enum.GetValues (typeof (ConsoleColor))
-				  .OfType<ConsoleColor> ()
-				  .Select (s => (int)s);
-			if (values.Contains (value & 0xffff)) {
-				hasColor = true;
-				background = (Color)(ConsoleColor)(value & 0xffff);
+	public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+	{
+		_keyHandler = keyHandler;
+		_keyDownHandler = keyDownHandler;
+		_keyUpHandler = keyUpHandler;
+		_mouseHandler = mouseHandler;
+
+		var mLoop = _mainLoop = mainLoop.MainLoopDriver as NetMainLoop;
+
+		// Note: .Net API doesn't support keydown/up events and thus any passed keyDown/UpHandlers will be simulated to be called.
+		mLoop.ProcessInput = (e) => ProcessInput (e);
+	}
+
+	volatile bool _winSizeChanging;
+
+	void ProcessInput (NetEvents.InputResult inputEvent)
+	{
+		switch (inputEvent.EventType) {
+		case NetEvents.EventType.Key:
+			ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 			}
-			if (values.Contains ((value >> 16) & 0xffff)) {
-				hasColor = true;
-				foreground = (Color)(ConsoleColor)((value >> 16) & 0xffff);
+			_keyModifiers = new KeyModifiers ();
+			var map = MapKey (consoleKeyInfo);
+			if (map == (Key)0xffffffff) {
+				return;
 			}
-			return hasColor;
+			if (map == Key.Null) {
+				_keyDownHandler (new KeyEvent (map, _keyModifiers));
+				_keyUpHandler (new KeyEvent (map, _keyModifiers));
+			} else {
+				_keyDownHandler (new KeyEvent (map, _keyModifiers));
+				_keyHandler (new KeyEvent (map, _keyModifiers));
+				_keyUpHandler (new KeyEvent (map, _keyModifiers));
+			}
+			break;
+		case NetEvents.EventType.Mouse:
+			_mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
+			break;
+		case NetEvents.EventType.WindowSize:
+			_winSizeChanging = true;
+			Top = 0;
+			Left = 0;
+			Cols = inputEvent.WindowSizeEvent.Size.Width;
+			Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); ;
+			ResizeScreen ();
+			ClearContents ();
+			_winSizeChanging = false;
+			TerminalResized?.Invoke ();
+			break;
+		case NetEvents.EventType.RequestResponse:
+			// BUGBUG: What is this for? It does not seem to be used anywhere. 
+			// It is also not clear what it does. View.Data is documented as "This property is not used internally"
+			Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple;
+			break;
+		case NetEvents.EventType.WindowPosition:
+			break;
+		default:
+			throw new ArgumentOutOfRangeException ();
 		}
+	}
 
-		#region Unused
-		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-		{
-		}
+	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
+	{
+		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
 
-		public override void SetColors (short foregroundColorId, short backgroundColorId)
-		{
-		}
+		MouseFlags mouseFlag = 0;
 
-		public override void CookMouse ()
-		{
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button1Pressed;
 		}
-
-		public override void UncookMouse ()
-		{
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) {
+			mouseFlag |= MouseFlags.Button1Released;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button1Clicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1DoubleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1TripleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button2Pressed;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) {
+			mouseFlag |= MouseFlags.Button2Released;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button2Clicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2DoubleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2TripleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button3Pressed;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) {
+			mouseFlag |= MouseFlags.Button3Released;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button3Clicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3DoubleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3TripleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) {
+			mouseFlag |= MouseFlags.WheeledUp;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) {
+			mouseFlag |= MouseFlags.WheeledDown;
 		}
-		#endregion
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) {
+			mouseFlag |= MouseFlags.WheeledLeft;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) {
+			mouseFlag |= MouseFlags.WheeledRight;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button4Pressed;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) {
+			mouseFlag |= MouseFlags.Button4Released;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button4Clicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4DoubleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4TripleClicked;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) {
+			mouseFlag |= MouseFlags.ReportMousePosition;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) {
+			mouseFlag |= MouseFlags.ButtonShift;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) {
+			mouseFlag |= MouseFlags.ButtonCtrl;
+		}
+		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) {
+			mouseFlag |= MouseFlags.ButtonAlt;
+		}
+
+		return new MouseEvent () {
+			X = me.Position.X,
+			Y = me.Position.Y,
+			Flags = mouseFlag
+		};
+	}
+
+	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+	{
+		NetEvents.InputResult input = new NetEvents.InputResult {
+			EventType = NetEvents.EventType.Key,
+			ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
+		};
+
+		try {
+			ProcessInput (input);
+		} catch (OverflowException) { }
+	}
 
-		//
-		// These are for the .NET driver, but running natively on Windows, wont run
-		// on the Mono emulation
-		//
 
+	#region Not Implemented
+	public override void Suspend ()
+	{
+		throw new NotImplementedException ();
 	}
+	#endregion
+
+}
+
+/// <summary>
+/// Mainloop intended to be used with the .NET System.Console API, and can
+/// be used on Windows and Unix, it is cross platform but lacks things like
+/// file descriptor monitoring.
+/// </summary>
+/// <remarks>
+/// This implementation is used for NetDriver.
+/// </remarks>
+internal class NetMainLoop : IMainLoopDriver {
+	ManualResetEventSlim _keyReady = new ManualResetEventSlim (false);
+	ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false);
+	Queue<NetEvents.InputResult?> _inputResult = new Queue<NetEvents.InputResult?> ();
+	MainLoop _mainLoop;
+	CancellationTokenSource _tokenSource = new CancellationTokenSource ();
+	internal NetEvents _netEvents;
 
 	/// <summary>
-	/// Mainloop intended to be used with the .NET System.Console API, and can
-	/// be used on Windows and Unix, it is cross platform but lacks things like
-	/// file descriptor monitoring.
+	/// Invoked when a Key is pressed.
+	/// </summary>
+	internal Action<NetEvents.InputResult> ProcessInput;
+
+	/// <summary>
+	/// Initializes the class with the console driver.
 	/// </summary>
 	/// <remarks>
-	/// This implementation is used for NetDriver.
+	///   Passing a consoleDriver is provided to capture windows resizing.
 	/// </remarks>
-	internal class NetMainLoop : IMainLoopDriver {
-		ManualResetEventSlim keyReady = new ManualResetEventSlim (false);
-		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
-		Queue<NetEvents.InputResult?> inputResult = new Queue<NetEvents.InputResult?> ();
-		MainLoop mainLoop;
-		CancellationTokenSource tokenSource = new CancellationTokenSource ();
-		internal NetEvents netEvents;
-
-		/// <summary>
-		/// Invoked when a Key is pressed.
-		/// </summary>
-		public Action<NetEvents.InputResult> ProcessInput;
-
-		/// <summary>
-		/// Initializes the class with the console driver.
-		/// </summary>
-		/// <remarks>
-		///   Passing a consoleDriver is provided to capture windows resizing.
-		/// </remarks>
-		/// <param name="consoleDriver">The console driver used by this Net main loop.</param>
-		public NetMainLoop (ConsoleDriver consoleDriver = null)
-		{
-			if (consoleDriver == null) {
-				throw new ArgumentNullException ("Console driver instance must be provided.");
-			}
-			netEvents = new NetEvents (consoleDriver);
-		}
+	/// <param name="consoleDriver">The console driver used by this Net main loop.</param>
+	/// <exception cref="ArgumentNullException"></exception>
+	public NetMainLoop (ConsoleDriver consoleDriver = null)
+	{
+		if (consoleDriver == null) {
+			throw new ArgumentNullException (nameof (consoleDriver));
+		}
+		_netEvents = new NetEvents (consoleDriver);
+	}
 
-		void NetInputHandler ()
-		{
-			while (true) {
-				waitForProbe.Wait ();
-				waitForProbe.Reset ();
-				if (inputResult.Count == 0) {
-					inputResult.Enqueue (netEvents.ReadConsoleInput ());
-				}
-				try {
-					while (inputResult.Peek () == null) {
-						inputResult.Dequeue ();
-					}
-					if (inputResult.Count > 0) {
-						keyReady.Set ();
-					}
-				} catch (InvalidOperationException) { }
+	void NetInputHandler ()
+	{
+		while (true) {
+			_waitForProbe.Wait ();
+			_waitForProbe.Reset ();
+			if (_inputResult.Count == 0) {
+				_inputResult.Enqueue (_netEvents.ReadConsoleInput ());
 			}
+			try {
+				while (_inputResult.Peek () == null) {
+					_inputResult.Dequeue ();
+				}
+				if (_inputResult.Count > 0) {
+					_keyReady.Set ();
+				}
+			} catch (InvalidOperationException) { }
 		}
+	}
 
-		void IMainLoopDriver.Setup (MainLoop mainLoop)
-		{
-			this.mainLoop = mainLoop;
-			Task.Run (NetInputHandler);
-		}
-
-		void IMainLoopDriver.Wakeup ()
-		{
-			keyReady.Set ();
-		}
+	void IMainLoopDriver.Setup (MainLoop mainLoop)
+	{
+		_mainLoop = mainLoop;
+		Task.Run (NetInputHandler);
+	}
 
-		bool IMainLoopDriver.EventsPending (bool wait)
-		{
-			waitForProbe.Set ();
+	void IMainLoopDriver.Wakeup ()
+	{
+		_keyReady.Set ();
+	}
 
-			if (CheckTimers (wait, out var waitTimeout)) {
-				return true;
-			}
+	bool IMainLoopDriver.EventsPending (bool wait)
+	{
+		_waitForProbe.Set ();
 
-			try {
-				if (!tokenSource.IsCancellationRequested) {
-					keyReady.Wait (waitTimeout, tokenSource.Token);
-				}
-			} catch (OperationCanceledException) {
-				return true;
-			} finally {
-				keyReady.Reset ();
-			}
+		if (CheckTimers (wait, out var waitTimeout)) {
+			return true;
+		}
 
-			if (!tokenSource.IsCancellationRequested) {
-				return inputResult.Count > 0 || CheckTimers (wait, out _);
+		try {
+			if (!_tokenSource.IsCancellationRequested) {
+				_keyReady.Wait (waitTimeout, _tokenSource.Token);
 			}
-
-			tokenSource.Dispose ();
-			tokenSource = new CancellationTokenSource ();
+		} catch (OperationCanceledException) {
 			return true;
+		} finally {
+			_keyReady.Reset ();
 		}
 
-		bool CheckTimers (bool wait, out int waitTimeout)
-		{
-			long now = DateTime.UtcNow.Ticks;
+		if (!_tokenSource.IsCancellationRequested) {
+			return _inputResult.Count > 0 || CheckTimers (wait, out _);
+		}
 
-			if (mainLoop.timeouts.Count > 0) {
-				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-				if (waitTimeout < 0)
-					return true;
-			} else {
-				waitTimeout = -1;
-			}
+		_tokenSource.Dispose ();
+		_tokenSource = new CancellationTokenSource ();
+		return true;
+	}
 
-			if (!wait)
-				waitTimeout = 0;
+	bool CheckTimers (bool wait, out int waitTimeout)
+	{
+		var now = DateTime.UtcNow.Ticks;
 
-			int ic;
-			lock (mainLoop.idleHandlers) {
-				ic = mainLoop.idleHandlers.Count;
+		if (_mainLoop.timeouts.Count > 0) {
+			waitTimeout = (int)((_mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+			if (waitTimeout < 0) {
+				return true;
 			}
+		} else {
+			waitTimeout = -1;
+		}
 
-			return ic > 0;
+		if (!wait) {
+			waitTimeout = 0;
 		}
 
-		void IMainLoopDriver.Iteration ()
-		{
-			while (inputResult.Count > 0) {
-				ProcessInput?.Invoke (inputResult.Dequeue ().Value);
-			}
+		int ic;
+		lock (_mainLoop.idleHandlers) {
+			ic = _mainLoop.idleHandlers.Count;
+		}
+
+		return ic > 0;
+	}
+
+	void IMainLoopDriver.Iteration ()
+	{
+		while (_inputResult.Count > 0) {
+			ProcessInput?.Invoke (_inputResult.Dequeue ().Value);
 		}
 	}
+	public void TearDown ()
+	{
+		//throw new NotImplementedException ();
+	}
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 561 - 506
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs


+ 14 - 5
Terminal.Gui/Drawing/Cell.cs

@@ -1,20 +1,29 @@
-using System.Text;
+using System.Collections.Generic;
+using System.Text;
 
 
 namespace Terminal.Gui; 
 
 /// <summary>
-/// Represents a single row/column within the <see cref="LineCanvas"/>. Includes the glyph and the foreground/background colors.
+/// Represents a single row/column in a Terminal.Gui rendering surface
+/// (e.g. <see cref="LineCanvas"/> and <see cref="ConsoleDriver"/>).
 /// </summary>
 public class Cell {
 	/// <summary>
-	/// The glyph to draw.
+	/// The list of Runes to draw in this cell. If the list is empty, the cell is blank. If the list contains
+	/// more than one Rune, the cell is a combining sequence.
+	/// (See #2616 - Support combining sequences that don't normalize)
 	/// </summary>
-	public Rune? Rune { get; set; }
+	public List<Rune> Runes { get; set; } = new List<Rune> ();
 
 	/// <summary>
-	/// The foreground color to draw the glyph with.
+	/// The attributes to use when drawing the Glyph.
 	/// </summary>
 	public Attribute? Attribute { get; set; }
 
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="T:Terminal.Gui.Cell"/> has
+	/// been modified since the last time it was drawn.
+	/// </summary>
+	public bool IsDirty { get; set; }
 }

+ 845 - 0
Terminal.Gui/Drawing/Color.cs

@@ -0,0 +1,845 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Colors that can be used to set the foreground and background colors in console applications.
+	/// </summary>
+	/// <remarks>
+	/// The <see cref="Attribute.HasValidColors"/> value indicates either no-color has been set or the color is invalid.
+	/// </remarks>
+	[JsonConverter (typeof (ColorJsonConverter))]
+	public enum Color {
+		/// <summary>
+		/// The black color.
+		/// </summary>
+		Black,
+		/// <summary>
+		/// The blue color.
+		/// </summary>
+		Blue,
+		/// <summary>
+		/// The green color.
+		/// </summary>
+		Green,
+		/// <summary>
+		/// The cyan color.
+		/// </summary>
+		Cyan,
+		/// <summary>
+		/// The red color.
+		/// </summary>
+		Red,
+		/// <summary>
+		/// The magenta color.
+		/// </summary>
+		Magenta,
+		/// <summary>
+		/// The brown color.
+		/// </summary>
+		Brown,
+		/// <summary>
+		/// The gray color.
+		/// </summary>
+		Gray,
+		/// <summary>
+		/// The dark gray color.
+		/// </summary>
+		DarkGray,
+		/// <summary>
+		/// The bright bBlue color.
+		/// </summary>
+		BrightBlue,
+		/// <summary>
+		/// The bright green color.
+		/// </summary>
+		BrightGreen,
+		/// <summary>
+		/// The bright cyan color.
+		/// </summary>
+		BrightCyan,
+		/// <summary>
+		/// The bright red color.
+		/// </summary>
+		BrightRed,
+		/// <summary>
+		/// The bright magenta color.
+		/// </summary>
+		BrightMagenta,
+		/// <summary>
+		/// The bright yellow color.
+		/// </summary>
+		BrightYellow,
+		/// <summary>
+		/// The White color.
+		/// </summary>
+		White
+	}
+
+	/// <summary>
+	/// Indicates the RGB for true colors.
+	/// </summary>
+	[JsonConverter (typeof (TrueColorJsonConverter))]
+	public readonly struct TrueColor : IEquatable<TrueColor> {
+		private static readonly ImmutableDictionary<TrueColor, Color> TrueColorToConsoleColorMap = new Dictionary<TrueColor, Color> () {
+			{ new TrueColor (0,0,0),Color.Black },
+			{ new TrueColor (0, 0, 0x80),Color.Blue },
+			{ new TrueColor (0, 0x80, 0),Color.Green},
+			{ new TrueColor (0, 0x80, 0x80),Color.Cyan},
+			{ new TrueColor (0x80, 0, 0),Color.Red},
+			{ new TrueColor (0x80, 0, 0x80),Color.Magenta},
+			{ new TrueColor (0xC1, 0x9C, 0x00),Color.Brown},  // TODO confirm this
+			{ new TrueColor (0xC0, 0xC0, 0xC0),Color.Gray},
+			{ new TrueColor (0x80, 0x80, 0x80),Color.DarkGray},
+			{ new TrueColor (0, 0, 0xFF),Color.BrightBlue},
+			{ new TrueColor (0, 0xFF, 0),Color.BrightGreen},
+			{ new TrueColor (0, 0xFF, 0xFF),Color.BrightCyan},
+			{ new TrueColor (0xFF, 0, 0),Color.BrightRed},
+			{ new TrueColor (0xFF, 0, 0xFF),Color.BrightMagenta },
+			{ new TrueColor (0xFF, 0xFF, 0),Color.BrightYellow},
+			{ new TrueColor (0xFF, 0xFF, 0xFF),Color.White},
+		}.ToImmutableDictionary ();
+
+		/// <summary>
+		/// Red color component.
+		/// </summary>
+		public int Red { get; }
+		/// <summary>
+		/// Green color component.
+		/// </summary>
+		public int Green { get; }
+		/// <summary>
+		/// Blue color component.
+		/// </summary>
+		public int Blue { get; }
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="TrueColor"/> struct.
+		/// </summary>
+		/// <param name="red"></param>
+		/// <param name="green"></param>
+		/// <param name="blue"></param>
+		public TrueColor (int red, int green, int blue)
+		{
+			Red = red;
+			Green = green;
+			Blue = blue;
+		}
+
+		/// <summary>
+		/// Converts the provided text to a <see cref="TrueColor"/>.
+		/// </summary>
+		/// <param name="text">The text to analyze.</param>
+		/// <param name="trueColor">The parsed value.</param>
+		/// <returns>A boolean value indcating whether it was successful.</returns>
+		public static bool TryParse (string text, [NotNullWhen (true)] out TrueColor? trueColor)
+		{
+			// empty color
+			if ((text == null) || (text.Length == 0)) {
+				trueColor = null;
+				return false;
+			}
+
+			// #RRGGBB or #RGB
+			if ((text [0] == '#') &&
+			    ((text.Length == 7) || (text.Length == 4))) {
+				if (text.Length == 7) {
+					var r = Convert.ToInt32 (text.Substring (1, 2), 16);
+					var g = Convert.ToInt32 (text.Substring (3, 2), 16);
+					var b = Convert.ToInt32 (text.Substring (5, 2), 16);
+					trueColor = new TrueColor (r, g, b);
+				} else {
+					var rText = char.ToString (text [1]);
+					var gText = char.ToString (text [2]);
+					var bText = char.ToString (text [3]);
+
+					var r = Convert.ToInt32 (rText + rText, 16);
+					var g = Convert.ToInt32 (gText + gText, 16);
+					var b = Convert.ToInt32 (bText + bText, 16);
+					trueColor = new TrueColor (r, g, b);
+				}
+				return true;
+			}
+
+			// rgb(XX,YY,ZZ)
+			var match = Regex.Match (text, @"rgb\((\d+),(\d+),(\d+)\)");
+			if (match.Success) {
+				var r = int.Parse (match.Groups [1].Value);
+				var g = int.Parse (match.Groups [2].Value);
+				var b = int.Parse (match.Groups [3].Value);
+				trueColor = new TrueColor (r, g, b);
+				return true;
+			}
+
+			trueColor = null;
+			return false;
+		}
+
+		/// <summary>
+		/// Converts a <see cref="Color"/> to a <see cref="TrueColor"/> using a default mapping.
+		/// </summary>
+		/// <param name="consoleColor">The <see cref="Color"/> to convert.</param>
+		/// <returns></returns>
+		public static TrueColor? FromConsoleColor (Color consoleColor)
+		{
+			return consoleColor switch {
+				Color.Black => new TrueColor (0, 0, 0),
+				Color.Blue => new TrueColor (0, 0, 0x80),
+				Color.Green => new TrueColor (0, 0x80, 0),
+				Color.Cyan => new TrueColor (0, 0x80, 0x80),
+				Color.Red => new TrueColor (0x80, 0, 0),
+				Color.Magenta => new TrueColor (0x80, 0, 0x80),
+				Color.Brown => new TrueColor (0xC1, 0x9C, 0x00) // TODO confirm this
+				,
+				Color.Gray => new TrueColor (0xC0, 0xC0, 0xC0),
+				Color.DarkGray => new TrueColor (0x80, 0x80, 0x80),
+				Color.BrightBlue => new TrueColor (0, 0, 0xFF),
+				Color.BrightGreen => new TrueColor (0, 0xFF, 0),
+				Color.BrightCyan => new TrueColor (0, 0xFF, 0xFF),
+				Color.BrightRed => new TrueColor (0xFF, 0, 0),
+				Color.BrightMagenta => new TrueColor (0xFF, 0, 0xFF),
+				Color.BrightYellow => new TrueColor (0xFF, 0xFF, 0),
+				Color.White => new TrueColor (0xFF, 0xFF, 0xFF),
+				var _ => null
+			};
+			;
+		}
+
+		/// <summary>
+		/// Converts the provided <see cref="TrueColor"/> to <see cref="Color"/> using a default mapping.
+		/// </summary>
+		/// <param name="trueColor"></param>
+		/// <returns></returns>
+		public static Color ToConsoleColor (TrueColor? trueColor)
+		{
+			if (trueColor.HasValue) {
+				return TrueColorToConsoleColorMap.MinBy (kv => CalculateDistance (kv.Key, trueColor.Value)).Value;
+			} else {
+				return (Color)(-1);
+			}
+		}
+
+		private static float CalculateDistance (TrueColor color1, TrueColor color2)
+		{
+			// use RGB distance
+			return
+				Math.Abs (color1.Red - color2.Red) +
+				Math.Abs (color1.Green - color2.Green) +
+				Math.Abs (color1.Blue - color2.Blue);
+		}
+
+		/// <inheritdoc/>
+		public static bool operator == (TrueColor left, TrueColor right)
+		{
+			return left.Equals (right);
+		}
+
+		/// <inheritdoc/>
+		public static bool operator != (TrueColor left, TrueColor right)
+		{
+			return !left.Equals (right);
+		}
+
+		/// <inheritdoc/>
+		public override bool Equals (object obj)
+		{
+			return obj is TrueColor other && Equals (other);
+		}
+
+		/// <inheritdoc/>
+		public bool Equals (TrueColor other)
+		{
+			return
+				Red == other.Red &&
+				Green == other.Green &&
+				Blue == other.Blue;
+		}
+
+		/// <inheritdoc/>
+		public override int GetHashCode ()
+		{
+			return HashCode.Combine (Red, Green, Blue);
+		}
+
+		/// <inheritdoc/>
+		public override string ToString ()
+		{
+			return $"#{Red:X2}{Green:X2}{Blue:X2}";
+		}
+	}
+
+	/// <summary>
+	/// Attributes represent how text is styled when displayed in the terminal. 
+	/// </summary>
+	/// <remarks>
+	///   <see cref="Attribute"/> provides a platform independent representation of colors (and someday other forms of text styling).
+	///   They encode both the foreground and the background color and are used in the <see cref="ColorScheme"/>
+	///   class to define color schemes that can be used in an application.
+	/// </remarks>
+	[JsonConverter (typeof (AttributeJsonConverter))]
+	public struct Attribute : IEquatable<Attribute> {
+
+		/// <summary>
+		/// Default empty attribute.
+		/// </summary>
+		public static readonly Attribute Default = new Attribute (Color.White, Color.Black);
+
+		/// <summary>
+		/// The <see cref="ConsoleDriver"/>-specific color value. If <see cref="Initialized"/> is <see langword="false"/> 
+		/// the value of this property is invalid (typically because the Attribute was created before a driver was loaded)
+		/// and the attribute should be re-made (see <see cref="Make(Color, Color)"/>) before it is used.
+		/// </summary>
+		[JsonIgnore (Condition = JsonIgnoreCondition.Always)]
+		internal int Value { get; }
+
+		/// <summary>
+		/// The foreground color.
+		/// </summary>
+		[JsonConverter (typeof (ColorJsonConverter))]
+		public Color Foreground { get; private init; }
+
+		/// <summary>
+		/// The background color.
+		/// </summary>
+		[JsonConverter (typeof (ColorJsonConverter))]
+		public Color Background { get; private init; }
+
+		/// <summary>
+		/// Gets the TrueColor foreground color.
+		/// </summary>
+		[JsonConverter (typeof (TrueColorJsonConverter))]
+		public TrueColor? TrueColorForeground { get; private init; }
+
+		/// <summary>
+		/// Gets the TrueColor background color.
+		/// </summary>
+		[JsonConverter (typeof (TrueColorJsonConverter))]
+		public TrueColor? TrueColorBackground { get; private init; }
+
+		/// <summary>
+		/// Initializes a new instance with a platform-specific color value.
+		/// </summary>
+		/// <param name="value">Value.</param>
+		internal Attribute (int value)
+		{
+			Color foreground = default;
+			Color background = default;
+
+			Initialized = false;
+			if (Application.Driver != null) {
+				Application.Driver.GetColors (value, out foreground, out background);
+				Initialized = true;
+			}
+			Value = value;
+			Foreground = foreground;
+			Background = background;
+			TrueColorForeground = TrueColor.FromConsoleColor (foreground);
+			TrueColorBackground = TrueColor.FromConsoleColor (background);
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Attribute"/> struct.
+		/// </summary>
+		/// <param name="value">platform-dependent color value.</param>
+		/// <param name="foreground">Foreground</param>
+		/// <param name="background">Background</param>
+		public Attribute (int value, Color foreground, Color background)
+		{
+			Foreground = foreground;
+			Background = background;
+			TrueColorForeground = TrueColor.FromConsoleColor (foreground);
+			TrueColorBackground = TrueColor.FromConsoleColor (background);
+			Value = value;
+			Initialized = true;
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Attribute"/> struct.
+		/// </summary>
+		/// <param name="foreground">Foreground</param>
+		/// <param name="background">Background</param>
+		public Attribute (Color foreground = new Color (), Color background = new Color ())
+		{
+			Foreground = foreground;
+			Background = background;
+			TrueColorForeground = TrueColor.FromConsoleColor (foreground);
+			TrueColorBackground = TrueColor.FromConsoleColor (background);
+
+			var make = Make (foreground, background);
+			Initialized = make.Initialized;
+			Value = make.Value;
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Attribute"/> class.  Populates
+		/// <see cref="TrueColorBackground"/> and <see cref="TrueColorForeground"/>. Also computes
+		/// <see cref="Foreground"/> and <see cref="Background"/> (basic console colors) in case
+		/// driver does not support true color rendering.
+		/// </summary>
+		/// <param name="trueColorForeground"></param>
+		/// <param name="trueColorBackground"></param>
+		public Attribute (TrueColor? trueColorForeground, TrueColor? trueColorBackground)
+		{
+			Foreground = TrueColor.ToConsoleColor (trueColorForeground);
+			Background = TrueColor.ToConsoleColor (trueColorBackground);
+			TrueColorForeground = trueColorForeground;
+			TrueColorBackground = trueColorBackground;
+			var make = Make (Foreground, Background);
+			Value = make.Value;
+			Initialized = make.Initialized;
+		}
+
+		/// <summary>
+		/// <para>
+		/// Initializes a new instance of the <see cref="Attribute"/> class.  Populates
+		/// <see cref="TrueColorBackground"/> and <see cref="TrueColorForeground"/> with explicit
+		/// fallback values for <see cref="Foreground"/> and <see cref="Background"/> (in case
+		/// driver does not support true color rendering). 
+		/// </para>
+		/// <remarks>If you do not want to manually specify the fallback colors use <see cref="Attribute(TrueColor?,TrueColor?)"/>
+		/// instead which auto calculates these.</remarks>
+		/// </summary>
+		/// <param name="trueColorForeground">True color RGB values you would like to use.</param>
+		/// <param name="trueColorBackground">True color RGB values you would like to use.</param>
+		/// <param name="foreground">Simple console color replacement if driver does not support true color.</param>
+		/// <param name="background">Simple console color replacement if driver does not support true color.</param>
+		public Attribute (TrueColor trueColorForeground, TrueColor trueColorBackground, Color foreground, Color background)
+		{
+			Foreground = foreground;
+			Background = background;
+			TrueColorForeground = trueColorForeground;
+			TrueColorBackground = trueColorBackground;
+			var make = Make (Foreground, Background);
+			Value = make.Value;
+			Initialized = make.Initialized;
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Attribute"/> struct
+		///  with the same colors for the foreground and background.
+		/// </summary>
+		/// <param name="color">The color.</param>
+		public Attribute (Color color) : this (color, color) { }
+
+
+		/// <summary>
+		/// Compares two attributes for equality.
+		/// </summary>
+		/// <param name="left"></param>
+		/// <param name="right"></param>
+		/// <returns></returns>
+		public static bool operator == (Attribute left, Attribute right) => left.Equals (right);
+
+		/// <summary>
+		/// Compares two attributes for inequality.
+		/// </summary>
+		/// <param name="left"></param>
+		/// <param name="right"></param>
+		/// <returns></returns>
+		public static bool operator != (Attribute left, Attribute right) => !(left == right);
+
+		/// <inheritdoc />
+		public override bool Equals (object obj)
+		{
+			return obj is Attribute other && Equals (other);
+		}
+
+		/// <inheritdoc />
+		public bool Equals (Attribute other)
+		{
+			if (TrueColorForeground.HasValue || TrueColorBackground.HasValue) {
+				return 
+					TrueColorForeground == other.TrueColorForeground &&
+					TrueColorBackground == other.TrueColorBackground;
+			}
+
+			return Value == other.Value &&
+				Foreground == other.Foreground &&
+				Background == other.Background;
+		}
+
+		/// <inheritdoc />
+		public override int GetHashCode () => HashCode.Combine (Value, Foreground, Background, TrueColorForeground, TrueColorBackground);
+
+		/// <summary>
+		/// Creates an <see cref="Attribute"/> from the specified foreground and background colors.
+		/// </summary>
+		/// <remarks>
+		/// If a <see cref="ConsoleDriver"/> has not been loaded (<c>Application.Driver == null</c>) this
+		/// method will return an attribute with <see cref="Initialized"/> set to  <see langword="false"/>.
+		/// </remarks>
+		/// <returns>The new attribute.</returns>
+		/// <param name="foreground">Foreground color to use.</param>
+		/// <param name="background">Background color to use.</param>
+		public static Attribute Make (Color foreground, Color background)
+		{
+			if (Application.Driver == null) {
+				// Create the attribute, but show it's not been initialized
+				return new Attribute () {
+					Initialized = false,
+					Foreground = foreground,
+					Background = background
+				};
+			}
+			return Application.Driver.MakeAttribute (foreground, background);
+		}
+
+		/// <summary>
+		/// Gets the current <see cref="Attribute"/> from the driver.
+		/// </summary>
+		/// <returns>The current attribute.</returns>
+		public static Attribute Get ()
+		{
+			if (Application.Driver == null) {
+				throw new InvalidOperationException ("The Application has not been initialized");
+			}
+			return Application.Driver.GetAttribute ();
+		}
+
+		/// <summary>
+		/// If <see langword="true"/> the attribute has been initialized by a <see cref="ConsoleDriver"/> and 
+		/// thus has <see cref="Value"/> that is valid for that driver. If <see langword="false"/> the <see cref="Foreground"/>
+		/// and <see cref="Background"/> colors may have been set '-1' but
+		/// the attribute has not been mapped to a <see cref="ConsoleDriver"/> specific color value.
+		/// </summary>
+		/// <remarks>
+		/// Attributes that have not been initialized must eventually be initialized before being passed to a driver.
+		/// </remarks>
+		[JsonIgnore]
+		public bool Initialized { get; internal set; }
+
+		/// <summary>
+		/// Returns <see langword="true"/> if the Attribute is valid (both foreground and background have valid color values).
+		/// </summary>
+		/// <returns></returns>
+		[JsonIgnore]
+		public bool HasValidColors => (int)Foreground > -1 && (int)Background > -1;
+
+		/// <inheritdoc />
+		public override string ToString ()
+		{
+			// Note, Unit tests are dependent on this format
+			return $"{Foreground},{Background}";
+		}
+	}
+
+	/// <summary>
+	/// Defines the color <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>. 
+	/// Containers such as <see cref="Window"/> and <see cref="FrameView"/> use <see cref="ColorScheme"/> to determine
+	/// the colors used by sub-views.
+	/// </summary>
+	/// <remarks>
+	/// See also: <see cref="Colors.ColorSchemes"/>.
+	/// </remarks>
+	[JsonConverter (typeof (ColorSchemeJsonConverter))]
+	public class ColorScheme : IEquatable<ColorScheme> {
+		Attribute _normal = Attribute.Default;
+		Attribute _focus = Attribute.Default;
+		Attribute _hotNormal = Attribute.Default;
+		Attribute _hotFocus = Attribute.Default;
+		Attribute _disabled = Attribute.Default;
+
+		/// <summary>
+		/// Used by <see cref="Colors.SetColorScheme(ColorScheme, string)"/> and <see cref="Colors.GetColorScheme(string)"/> to track which ColorScheme 
+		/// is being accessed.
+		/// </summary>
+		internal string schemeBeingSet = "";
+
+		/// <summary>
+		/// Creates a new instance.
+		/// </summary>
+		public ColorScheme () { }
+
+		/// <summary>
+		/// Creates a new instance, initialized with the values from <paramref name="scheme"/>.
+		/// </summary>
+		/// <param name="scheme">The scheme to initialize the new instance with.</param>
+		public ColorScheme (ColorScheme scheme) : base ()
+		{
+			if (scheme != null) {
+				_normal = scheme.Normal;
+				_focus = scheme.Focus;
+				_hotNormal = scheme.HotNormal;
+				_disabled = scheme.Disabled;
+				_hotFocus = scheme.HotFocus;
+			}
+		}
+
+		/// <summary>
+		/// Creates a new instance, initialized with the values from <paramref name="attribute"/>.
+		/// </summary>
+		/// <param name="attribute">The attribute to initialize the new instance with.</param>
+		public ColorScheme (Attribute attribute)
+		{
+			_normal = attribute;
+			_focus = attribute;
+			_hotNormal = attribute;
+			_disabled = attribute;
+			_hotFocus = attribute;
+		}
+
+		/// <summary>
+		/// The foreground and background color for text when the view is not focused, hot, or disabled.
+		/// </summary>
+		public Attribute Normal {
+			get { return _normal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_normal = value;
+			}
+		}
+
+		/// <summary>
+		/// The foreground and background color for text when the view has the focus.
+		/// </summary>
+		public Attribute Focus {
+			get { return _focus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_focus = value;
+			}
+		}
+
+		/// <summary>
+		/// The foreground and background color for text when the view is highlighted (hot).
+		/// </summary>
+		public Attribute HotNormal {
+			get { return _hotNormal; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotNormal = value;
+			}
+		}
+
+		/// <summary>
+		/// The foreground and background color for text when the view is highlighted (hot) and has focus.
+		/// </summary>
+		public Attribute HotFocus {
+			get { return _hotFocus; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_hotFocus = value;
+			}
+		}
+
+		/// <summary>
+		/// The default foreground and background color for text, when the view is disabled.
+		/// </summary>
+		public Attribute Disabled {
+			get { return _disabled; }
+			set {
+				if (!value.HasValidColors) {
+					return;
+				}
+				_disabled = value;
+			}
+		}
+
+		/// <summary>
+		/// Compares two <see cref="ColorScheme"/> objects for equality.
+		/// </summary>
+		/// <param name="obj"></param>
+		/// <returns>true if the two objects are equal</returns>
+		public override bool Equals (object obj)
+		{
+			return Equals (obj as ColorScheme);
+		}
+
+		/// <summary>
+		/// Compares two <see cref="ColorScheme"/> objects for equality.
+		/// </summary>
+		/// <param name="other"></param>
+		/// <returns>true if the two objects are equal</returns>
+		public bool Equals (ColorScheme other)
+		{
+			return other != null &&
+		       EqualityComparer<Attribute>.Default.Equals (_normal, other._normal) &&
+		       EqualityComparer<Attribute>.Default.Equals (_focus, other._focus) &&
+		       EqualityComparer<Attribute>.Default.Equals (_hotNormal, other._hotNormal) &&
+		       EqualityComparer<Attribute>.Default.Equals (_hotFocus, other._hotFocus) &&
+		       EqualityComparer<Attribute>.Default.Equals (_disabled, other._disabled);
+		}
+
+		/// <summary>
+		/// Returns a hashcode for this instance.
+		/// </summary>
+		/// <returns>hashcode for this instance</returns>
+		public override int GetHashCode ()
+		{
+			int hashCode = -1242460230;
+			hashCode = hashCode * -1521134295 + _normal.GetHashCode ();
+			hashCode = hashCode * -1521134295 + _focus.GetHashCode ();
+			hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode ();
+			hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode ();
+			hashCode = hashCode * -1521134295 + _disabled.GetHashCode ();
+			return hashCode;
+		}
+
+		/// <summary>
+		/// Compares two <see cref="ColorScheme"/> objects for equality.
+		/// </summary>
+		/// <param name="left"></param>
+		/// <param name="right"></param>
+		/// <returns><c>true</c> if the two objects are equivalent</returns>
+		public static bool operator == (ColorScheme left, ColorScheme right)
+		{
+			return EqualityComparer<ColorScheme>.Default.Equals (left, right);
+		}
+
+		/// <summary>
+		/// Compares two <see cref="ColorScheme"/> objects for inequality.
+		/// </summary>
+		/// <param name="left"></param>
+		/// <param name="right"></param>
+		/// <returns><c>true</c> if the two objects are not equivalent</returns>
+		public static bool operator != (ColorScheme left, ColorScheme right)
+		{
+			return !(left == right);
+		}
+
+		internal void Initialize ()
+		{
+			// If the new scheme was created before a driver was loaded, we need to re-make
+			// the attributes
+			if (!_normal.Initialized) {
+				_normal = new Attribute (_normal.Foreground, _normal.Background);
+			}
+			if (!_focus.Initialized) {
+				_focus = new Attribute (_focus.Foreground, _focus.Background);
+			}
+			if (!_hotNormal.Initialized) {
+				_hotNormal = new Attribute (_hotNormal.Foreground, _hotNormal.Background);
+			}
+			if (!_hotFocus.Initialized) {
+				_hotFocus = new Attribute (_hotFocus.Foreground, _hotFocus.Background);
+			}
+			if (!_disabled.Initialized) {
+				_disabled = new Attribute (_disabled.Foreground, _disabled.Background);
+			}
+		}
+	}
+
+	/// <summary>
+	/// The default <see cref="ColorScheme"/>s for the application.
+	/// </summary>
+	/// <remarks>
+	/// This property can be set in a Theme to change the default <see cref="Colors"/> for the application.
+	/// </remarks>
+	public static class Colors {
+		private class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
+			public bool Equals (string x, string y)
+			{
+				if (x != null && y != null) {
+					return string.Equals (x, y, StringComparison.InvariantCultureIgnoreCase);
+				}
+				return false;
+			}
+
+			public int GetHashCode (string obj)
+			{
+				return obj.ToLowerInvariant ().GetHashCode ();
+			}
+		}
+
+		static Colors ()
+		{
+			ColorSchemes = Create ();
+		}
+
+		/// <summary>
+		/// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
+		/// </summary>
+		public static Dictionary<string, ColorScheme> Create ()
+		{
+			// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
+			// by the class. 
+			return typeof (Colors).GetProperties ()
+				.Where (p => p.PropertyType == typeof (ColorScheme))
+				.Select (p => new KeyValuePair<string, ColorScheme> (p.Name, new ColorScheme ()))
+				.ToDictionary (t => t.Key, t => t.Value, comparer: new SchemeNameComparerIgnoreCase ());
+		}
+
+		/// <summary>
+		/// The application Toplevel color scheme, for the default Toplevel views.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["TopLevel"];</c>
+		/// </para>
+		/// </remarks>
+		public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); }
+
+		/// <summary>
+		/// The base color scheme, for the default Toplevel views.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Base"];</c>
+		/// </para>
+		/// </remarks>
+		public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); }
+
+		/// <summary>
+		/// The dialog color scheme, for standard popup dialog boxes
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Dialog"];</c>
+		/// </para>
+		/// </remarks>
+		public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); }
+
+		/// <summary>
+		/// The menu bar color
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Menu"];</c>
+		/// </para>
+		/// </remarks>
+		public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); }
+
+		/// <summary>
+		/// The color scheme for showing errors.
+		/// </summary>
+		/// <remarks>
+		/// <para>
+		///	This API will be deprecated in the future. Use <see cref="Colors.ColorSchemes"/> instead (e.g. <c>edit.ColorScheme = Colors.ColorSchemes["Error"];</c>
+		/// </para>
+		/// </remarks>
+		public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
+
+		static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null)
+		{
+			return ColorSchemes [schemeBeingSet];
+		}
+
+		static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
+		{
+			ColorSchemes [schemeBeingSet] = colorScheme;
+			colorScheme.schemeBeingSet = schemeBeingSet;
+		}
+
+		/// <summary>
+		/// Provides the defined <see cref="ColorScheme"/>s.
+		/// </summary>
+		[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
+		[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
+		public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
+	}
+
+}

+ 15 - 0
Terminal.Gui/Drawing/Glyphs.cs

@@ -139,6 +139,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public Rune Collapse { get; set; } = (Rune)'-';
 
+		/// <summary>
+		/// Identical To (U+226)
+		/// </summary>
+		public Rune IdenticalTo { get; set; } = (Rune)'≡';
+
 		/// <summary>
 		/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
 		/// </summary>
@@ -166,6 +171,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public Rune File { get; set; } = (Rune)'☰';
 
+		/// <summary>
+		/// Horizontal Ellipsis - … U+2026
+		/// </summary>
+		public Rune HorizontalEllipsis { get; set; } = (Rune)'…';
+
+		/// <summary>
+		/// Vertical Four Dots - ⁞ U+205e
+		/// </summary>
+		public Rune VerticalFourDots { get; set; } = (Rune)'⁞';
+
 		#region ----------------- Lines -----------------
 		/// <summary>
 		/// Box Drawings Horizontal Line - Light (U+2500) - ─

+ 9 - 5
Terminal.Gui/Drawing/LineCanvas.cs

@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
@@ -73,7 +74,7 @@ namespace Terminal.Gui {
 			ConfigurationManager.Applied += ConfigurationManager_Applied;
 		}
 
-		private void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs e)
+		private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e)
 		{
 			foreach (var irr in runeResolvers) {
 				irr.Value.SetGlyphs ();
@@ -142,7 +143,7 @@ namespace Terminal.Gui {
 				_lines.Remove(l);
 			}
 
-			return l;
+			return l!;
 		}
 
 		/// <summary>
@@ -553,14 +554,17 @@ namespace Terminal.Gui {
 
 		}
 
-		private Cell GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
+		private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition [] intersects)
 		{
 			if (!intersects.Any ()) {
 				return null;
 			}
 
 			var cell = new Cell ();
-			cell.Rune = GetRuneForIntersects (driver, intersects);
+			var rune = GetRuneForIntersects (driver, intersects);
+			if (rune.HasValue) {
+				cell.Runes.Add (rune.Value);
+			}
 			cell.Attribute = GetAttributeForIntersects (intersects);
 			return cell;
 		}

+ 1 - 1
Terminal.Gui/Input/Command.cs

@@ -340,7 +340,7 @@ namespace Terminal.Gui {
 		QuitToplevel,
 
 		/// <summary>
-		/// Suspend a application (used on Linux).
+		/// Suspend a application (Only implemented in <see cref="CursesDriver"/>).
 		/// </summary>
 		Suspend,
 

+ 0 - 109
Terminal.Gui/Input/EscSeqUtils/EscSeqReq.cs

@@ -1,109 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Represents the state of an ANSI escape sequence request.
-	/// </summary>
-	/// <remarks>
-	/// This is needed because there are some escape sequence requests responses that are equal
-	/// with some normal escape sequences and thus, will be only considered the responses to the
-	/// requests that were registered with this object.
-	/// </remarks>
-	public class EscSeqReqStatus {
-		/// <summary>
-		/// Gets the terminating.
-		/// </summary>
-		public string Terminating { get; }
-		/// <summary>
-		/// Gets the number of requests.
-		/// </summary>
-		public int NumRequests { get; }
-		/// <summary>
-		/// Gets information about unfinished requests.
-		/// </summary>
-		public int NumOutstanding { get; set; }
-
-		/// <summary>
-		/// Creates a new state of escape sequence request.
-		/// </summary>
-		/// <param name="terminating">The terminating.</param>
-		/// <param name="numOfReq">The number of requests.</param>
-		public EscSeqReqStatus (string terminating, int numOfReq)
-		{
-			Terminating = terminating;
-			NumRequests = NumOutstanding = numOfReq;
-		}
-	}
-
-	/// <summary>
-	/// Manages a list of <see cref="EscSeqReqStatus"/>.
-	/// </summary>
-	public class EscSeqReqProc {
-		/// <summary>
-		/// Gets the <see cref="EscSeqReqStatus"/> list.
-		/// </summary>
-		public List<EscSeqReqStatus> EscSeqReqStats { get; } = new List<EscSeqReqStatus> ();
-
-		/// <summary>
-		/// Adds a new <see cref="EscSeqReqStatus"/> instance to the <see cref="EscSeqReqStats"/> list.
-		/// </summary>
-		/// <param name="terminating">The terminating.</param>
-		/// <param name="numOfReq">The number of requests.</param>
-		public void Add (string terminating, int numOfReq = 1)
-		{
-			lock (EscSeqReqStats) {
-				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
-				if (found == null) {
-					EscSeqReqStats.Add (new EscSeqReqStatus (terminating, numOfReq));
-				} else if (found != null && found.NumOutstanding < found.NumRequests) {
-					found.NumOutstanding = Math.Min (found.NumOutstanding + numOfReq, found.NumRequests);
-				}
-			}
-		}
-
-		/// <summary>
-		/// Removes a <see cref="EscSeqReqStatus"/> instance from the <see cref="EscSeqReqStats"/> list.
-		/// </summary>
-		/// <param name="terminating">The terminating string.</param>
-		public void Remove (string terminating)
-		{
-			lock (EscSeqReqStats) {
-				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
-				if (found == null) {
-					return;
-				}
-				if (found != null && found.NumOutstanding == 0) {
-					EscSeqReqStats.Remove (found);
-				} else if (found != null && found.NumOutstanding > 0) {
-					found.NumOutstanding--;
-					if (found.NumOutstanding == 0) {
-						EscSeqReqStats.Remove (found);
-					}
-				}
-			}
-		}
-
-		/// <summary>
-		/// Indicates if a <see cref="EscSeqReqStatus"/> with the <paramref name="terminating"/> exist
-		/// in the <see cref="EscSeqReqStats"/> list.
-		/// </summary>
-		/// <param name="terminating"></param>
-		/// <returns><see langword="true"/> if exist, <see langword="false"/> otherwise.</returns>
-		public bool Requested (string terminating)
-		{
-			lock (EscSeqReqStats) {
-				var found = EscSeqReqStats.Find (x => x.Terminating == terminating);
-				if (found == null) {
-					return false;
-				}
-				if (found != null && found.NumOutstanding > 0) {
-					return true;
-				} else {
-					EscSeqReqStats.Remove (found);
-				}
-				return false;
-			}
-		}
-	}
-}

+ 0 - 907
Terminal.Gui/Input/EscSeqUtils/EscSeqUtils.cs

@@ -1,907 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Management;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides a platform-independent API for managing ANSI escape sequence codes.
-	/// </summary>
-	public static class EscSeqUtils {
-		/// <summary>
-		/// Represents the escape key.
-		/// </summary>
-		public static readonly char KeyEsc = (char)Key.Esc;
-		/// <summary>
-		/// Represents the CSI (Control Sequence Introducer).
-		/// </summary>
-		public static readonly string KeyCSI = $"{KeyEsc}[";
-		/// <summary>
-		/// Represents the CSI for enable any mouse event tracking.
-		/// </summary>
-		public static readonly string CSI_EnableAnyEventMouse = KeyCSI + "?1003h";
-		/// <summary>
-		/// Represents the CSI for enable SGR (Select Graphic Rendition).
-		/// </summary>
-		public static readonly string CSI_EnableSgrExtModeMouse = KeyCSI + "?1006h";
-		/// <summary>
-		/// Represents the CSI for enable URXVT (Unicode Extended Virtual Terminal).
-		/// </summary>
-		public static readonly string CSI_EnableUrxvtExtModeMouse = KeyCSI + "?1015h";
-		/// <summary>
-		/// Represents the CSI for disable any mouse event tracking.
-		/// </summary>
-		public static readonly string CSI_DisableAnyEventMouse = KeyCSI + "?1003l";
-		/// <summary>
-		/// Represents the CSI for disable SGR (Select Graphic Rendition).
-		/// </summary>
-		public static readonly string CSI_DisableSgrExtModeMouse = KeyCSI + "?1006l";
-		/// <summary>
-		/// Represents the CSI for disable URXVT (Unicode Extended Virtual Terminal).
-		/// </summary>
-		public static readonly string CSI_DisableUrxvtExtModeMouse = KeyCSI + "?1015l";
-
-		/// <summary>
-		/// Control sequence for enable mouse events.
-		/// </summary>
-		public static string EnableMouseEvents { get; set; } =
-			CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
-		/// <summary>
-		/// Control sequence for disable mouse events.
-		/// </summary>
-		public static string DisableMouseEvents { get; set; } =
-			CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
-
-		/// <summary>
-		/// Ensures a console key is mapped to one that works correctly with ANSI escape sequences.
-		/// </summary>
-		/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
-		/// <returns>The <see cref="ConsoleKeyInfo"/> modified.</returns>
-		public static ConsoleKeyInfo GetConsoleInputKey (ConsoleKeyInfo consoleKeyInfo)
-		{
-			ConsoleKeyInfo newConsoleKeyInfo = consoleKeyInfo;
-			ConsoleKey key;
-			var keyChar = consoleKeyInfo.KeyChar;
-			switch ((uint)keyChar) {
-			case 0:
-				if (consoleKeyInfo.Key == (ConsoleKey)64) {    // Ctrl+Space in Windows.
-					newConsoleKeyInfo = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
-				}
-				break;
-			case uint n when (n >= '\u0001' && n <= '\u001a'):
-				if (consoleKeyInfo.Key == 0 && consoleKeyInfo.KeyChar == '\r') {
-					key = ConsoleKey.Enter;
-					newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar,
-						key,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
-				} else if (consoleKeyInfo.Key == 0) {
-					key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
-					newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
-						key,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
-						(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
-						true);
-				}
-				break;
-			case 127:
-				newConsoleKeyInfo = new ConsoleKeyInfo (consoleKeyInfo.KeyChar, ConsoleKey.Backspace,
-					(consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
-					(consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
-					(consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0);
-				break;
-			default:
-				newConsoleKeyInfo = consoleKeyInfo;
-				break;
-			}
-
-			return newConsoleKeyInfo;
-		}
-
-		/// <summary>
-		/// A helper to resize the <see cref="ConsoleKeyInfo"/> as needed.
-		/// </summary>
-		/// <param name="consoleKeyInfo">The <see cref="ConsoleKeyInfo"/>.</param>
-		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array to resize.</param>
-		/// <returns>The <see cref="ConsoleKeyInfo"/> resized.</returns>
-		public static ConsoleKeyInfo [] ResizeArray (ConsoleKeyInfo consoleKeyInfo, ConsoleKeyInfo [] cki)
-		{
-			Array.Resize (ref cki, cki == null ? 1 : cki.Length + 1);
-			cki [cki.Length - 1] = consoleKeyInfo;
-			return cki;
-		}
-
-		/// <summary>
-		/// Decodes a escape sequence to been processed in the appropriate manner.
-		/// </summary>
-		/// <param name="escSeqReqProc">The <see cref="EscSeqReqProc"/> which may contain a request.</param>
-		/// <param name="newConsoleKeyInfo">The <see cref="ConsoleKeyInfo"/> which may changes.</param>
-		/// <param name="key">The <see cref="ConsoleKey"/> which may changes.</param>
-		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
-		/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
-		/// <param name="c1Control">The control returned by the <see cref="GetC1ControlChar(char)"/> method.</param>
-		/// <param name="code">The code returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
-		/// <param name="values">The values returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
-		/// <param name="terminating">The terminating returned by the <see cref="GetEscapeResult(char[])"/> method.</param>
-		/// <param name="isKeyMouse">Indicates if the escape sequence is a mouse key.</param>
-		/// <param name="buttonState">The <see cref="MouseFlags"/> button state.</param>
-		/// <param name="pos">The <see cref="MouseFlags"/> position.</param>
-		/// <param name="isReq">Indicates if the escape sequence is a response to a request.</param>
-		/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
-		public static void DecodeEscSeq (EscSeqReqProc escSeqReqProc, ref ConsoleKeyInfo newConsoleKeyInfo, ref ConsoleKey key, ConsoleKeyInfo [] cki, ref ConsoleModifiers mod, out string c1Control, out string code, out string [] values, out string terminating, out bool isKeyMouse, out List<MouseFlags> buttonState, out Point pos, out bool isReq, Action<MouseFlags, Point> continuousButtonPressedHandler)
-		{
-			char [] kChars = GetKeyCharArray (cki);
-			(c1Control, code, values, terminating) = GetEscapeResult (kChars);
-			isKeyMouse = false;
-			buttonState = new List<MouseFlags> () { 0 };
-			pos = default;
-			isReq = false;
-			switch (c1Control) {
-			case "ESC":
-				if (values == null && string.IsNullOrEmpty (terminating)) {
-					key = ConsoleKey.Escape;
-					newConsoleKeyInfo = new ConsoleKeyInfo (cki [0].KeyChar, key,
-						(mod & ConsoleModifiers.Shift) != 0,
-						(mod & ConsoleModifiers.Alt) != 0,
-						(mod & ConsoleModifiers.Control) != 0);
-				} else if ((uint)cki [1].KeyChar >= 1 && (uint)cki [1].KeyChar <= 26) {
-					key = (ConsoleKey)(char)(cki [1].KeyChar + (uint)ConsoleKey.A - 1);
-					newConsoleKeyInfo = new ConsoleKeyInfo (cki [1].KeyChar,
-						key,
-						false,
-						true,
-						true);
-				} else {
-					if (cki [1].KeyChar >= 97 && cki [1].KeyChar <= 122) {
-						key = (ConsoleKey)cki [1].KeyChar.ToString ().ToUpper () [0];
-					} else {
-						key = (ConsoleKey)cki [1].KeyChar;
-					}
-					newConsoleKeyInfo = new ConsoleKeyInfo ((char)key,
-						(ConsoleKey)Math.Min ((uint)key, 255),
-						false,
-						true,
-						false);
-				}
-				break;
-			case "SS3":
-				key = GetConsoleKey (terminating [0], values [0], ref mod);
-				newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
-					key,
-					(mod & ConsoleModifiers.Shift) != 0,
-					(mod & ConsoleModifiers.Alt) != 0,
-					(mod & ConsoleModifiers.Control) != 0);
-				break;
-			case "CSI":
-				if (!string.IsNullOrEmpty (code) && code == "<") {
-					GetMouse (cki, out buttonState, out pos, continuousButtonPressedHandler);
-					isKeyMouse = true;
-					return;
-				} else if (escSeqReqProc != null && escSeqReqProc.Requested (terminating)) {
-					isReq = true;
-					escSeqReqProc.Remove (terminating);
-					return;
-				}
-				key = GetConsoleKey (terminating [0], values [0], ref mod);
-				if (key != 0 && values.Length > 1) {
-					mod |= GetConsoleModifiers (values [1]);
-				}
-				newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
-					key,
-					(mod & ConsoleModifiers.Shift) != 0,
-					(mod & ConsoleModifiers.Alt) != 0,
-					(mod & ConsoleModifiers.Control) != 0);
-				break;
-			}
-		}
-
-		/// <summary>
-		/// Gets all the needed information about a escape sequence.
-		/// </summary>
-		/// <param name="kChar">The array with all chars.</param>
-		/// <returns>
-		/// The c1Control returned by <see cref="GetC1ControlChar(char)"/>, code, values and terminating.
-		/// </returns>
-		public static (string c1Control, string code, string [] values, string terminating) GetEscapeResult (char [] kChar)
-		{
-			if (kChar == null || kChar.Length == 0) {
-				return (null, null, null, null);
-			}
-			if (kChar [0] != '\x1b') {
-				throw new InvalidOperationException ("Invalid escape character!");
-			}
-			if (kChar.Length == 1) {
-				return ("ESC", null, null, null);
-			}
-			if (kChar.Length == 2) {
-				return ("ESC", null, null, kChar [1].ToString ());
-			}
-			string c1Control = GetC1ControlChar (kChar [1]);
-			string code = null;
-			int nSep = kChar.Count (x => x == ';') + 1;
-			string [] values = new string [nSep];
-			int valueIdx = 0;
-			string terminating = "";
-			for (int i = 2; i < kChar.Length; i++) {
-				var c = kChar [i];
-				if (char.IsDigit (c)) {
-					values [valueIdx] += c.ToString ();
-				} else if (c == ';') {
-					valueIdx++;
-				} else if (valueIdx == nSep - 1 || i == kChar.Length - 1) {
-					terminating += c.ToString ();
-				} else {
-					code += c.ToString ();
-				}
-			}
-
-			return (c1Control, code, values, terminating);
-		}
-
-		/// <summary>
-		/// Gets the c1Control used in the called escape sequence.
-		/// </summary>
-		/// <param name="c">The char used.</param>
-		/// <returns>The c1Control.</returns>
-		public static string GetC1ControlChar (char c)
-		{
-			// These control characters are used in the vtXXX emulation.
-			switch (c) {
-			case 'D':
-				return "IND"; // Index
-			case 'E':
-				return "NEL"; // Next Line
-			case 'H':
-				return "HTS"; // Tab Set
-			case 'M':
-				return "RI"; // Reverse Index
-			case 'N':
-				return "SS2"; // Single Shift Select of G2 Character Set: affects next character only
-			case 'O':
-				return "SS3"; // Single Shift Select of G3 Character Set: affects next character only
-			case 'P':
-				return "DCS"; // Device Control String
-			case 'V':
-				return "SPA"; // Start of Guarded Area
-			case 'W':
-				return "EPA"; // End of Guarded Area
-			case 'X':
-				return "SOS"; // Start of String
-			case 'Z':
-				return "DECID"; // Return Terminal ID Obsolete form of CSI c (DA)
-			case '[':
-				return "CSI"; // Control Sequence Introducer
-			case '\\':
-				return "ST"; // String Terminator
-			case ']':
-				return "OSC"; // Operating System Command
-			case '^':
-				return "PM"; // Privacy Message
-			case '_':
-				return "APC"; // Application Program Command
-			default:
-				return ""; // Not supported
-			}
-		}
-
-		/// <summary>
-		/// Gets the <see cref="ConsoleModifiers"/> from the value.
-		/// </summary>
-		/// <param name="value">The value.</param>
-		/// <returns>The <see cref="ConsoleModifiers"/> or zero.</returns>
-		public static ConsoleModifiers GetConsoleModifiers (string value)
-		{
-			switch (value) {
-			case "2":
-				return ConsoleModifiers.Shift;
-			case "3":
-				return ConsoleModifiers.Alt;
-			case "4":
-				return ConsoleModifiers.Shift | ConsoleModifiers.Alt;
-			case "5":
-				return ConsoleModifiers.Control;
-			case "6":
-				return ConsoleModifiers.Shift | ConsoleModifiers.Control;
-			case "7":
-				return ConsoleModifiers.Alt | ConsoleModifiers.Control;
-			case "8":
-				return ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control;
-			default:
-				return 0;
-			}
-		}
-
-		/// <summary>
-		/// Gets the <see cref="ConsoleKey"/> depending on terminating and value.
-		/// </summary>
-		/// <param name="terminating">The terminating.</param>
-		/// <param name="value">The value.</param>
-		/// <param name="mod">The <see cref="ConsoleModifiers"/> which may changes.</param>
-		/// <returns>The <see cref="ConsoleKey"/> and probably the <see cref="ConsoleModifiers"/>.</returns>
-		public static ConsoleKey GetConsoleKey (char terminating, string value, ref ConsoleModifiers mod)
-		{
-			ConsoleKey key;
-			switch (terminating) {
-			case 'A':
-				key = ConsoleKey.UpArrow;
-				break;
-			case 'B':
-				key = ConsoleKey.DownArrow;
-				break;
-			case 'C':
-				key = ConsoleKey.RightArrow;
-				break;
-			case 'D':
-				key = ConsoleKey.LeftArrow;
-				break;
-			case 'F':
-				key = ConsoleKey.End;
-				break;
-			case 'H':
-				key = ConsoleKey.Home;
-				break;
-			case 'P':
-				key = ConsoleKey.F1;
-				break;
-			case 'Q':
-				key = ConsoleKey.F2;
-				break;
-			case 'R':
-				key = ConsoleKey.F3;
-				break;
-			case 'S':
-				key = ConsoleKey.F4;
-				break;
-			case 'Z':
-				key = ConsoleKey.Tab;
-				mod |= ConsoleModifiers.Shift;
-				break;
-			case '~':
-				switch (value) {
-				case "2":
-					key = ConsoleKey.Insert;
-					break;
-				case "3":
-					key = ConsoleKey.Delete;
-					break;
-				case "5":
-					key = ConsoleKey.PageUp;
-					break;
-				case "6":
-					key = ConsoleKey.PageDown;
-					break;
-				case "15":
-					key = ConsoleKey.F5;
-					break;
-				case "17":
-					key = ConsoleKey.F6;
-					break;
-				case "18":
-					key = ConsoleKey.F7;
-					break;
-				case "19":
-					key = ConsoleKey.F8;
-					break;
-				case "20":
-					key = ConsoleKey.F9;
-					break;
-				case "21":
-					key = ConsoleKey.F10;
-					break;
-				case "23":
-					key = ConsoleKey.F11;
-					break;
-				case "24":
-					key = ConsoleKey.F12;
-					break;
-				default:
-					key = 0;
-					break;
-				}
-				break;
-			default:
-				key = 0;
-				break;
-			}
-
-			return key;
-		}
-
-		/// <summary>
-		/// A helper to get only the <see cref="ConsoleKeyInfo.KeyChar"/> from the <see cref="ConsoleKeyInfo"/> array.
-		/// </summary>
-		/// <param name="cki"></param>
-		/// <returns>The char array of the escape sequence.</returns>
-		public static char [] GetKeyCharArray (ConsoleKeyInfo [] cki)
-		{
-			char [] kChar = new char [] { };
-			var length = 0;
-			foreach (var kc in cki) {
-				length++;
-				Array.Resize (ref kChar, length);
-				kChar [length - 1] = kc.KeyChar;
-			}
-
-			return kChar;
-		}
-
-		private static MouseFlags? lastMouseButtonPressed;
-		//private static MouseFlags? lastMouseButtonReleased;
-		private static bool isButtonPressed;
-		//private static bool isButtonReleased;
-		private static bool isButtonClicked;
-		private static bool isButtonDoubleClicked;
-		private static bool isButtonTripleClicked;
-		private static Point point;
-
-		/// <summary>
-		/// Gets the <see cref="MouseFlags"/> mouse button flags and the position.
-		/// </summary>
-		/// <param name="cki">The <see cref="ConsoleKeyInfo"/> array.</param>
-		/// <param name="mouseFlags">The mouse button flags.</param>
-		/// <param name="pos">The mouse position.</param>
-		/// <param name="continuousButtonPressedHandler">The handler that will process the event.</param>
-		public static void GetMouse (ConsoleKeyInfo [] cki, out List<MouseFlags> mouseFlags, out Point pos, Action<MouseFlags, Point> continuousButtonPressedHandler)
-		{
-			MouseFlags buttonState = 0;
-			pos = new Point ();
-			int buttonCode = 0;
-			bool foundButtonCode = false;
-			int foundPoint = 0;
-			string value = "";
-			var kChar = GetKeyCharArray (cki);
-			//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
-			for (int i = 0; i < kChar.Length; i++) {
-				var c = kChar [i];
-				if (c == '<') {
-					foundButtonCode = true;
-				} else if (foundButtonCode && c != ';') {
-					value += c.ToString ();
-				} else if (c == ';') {
-					if (foundButtonCode) {
-						foundButtonCode = false;
-						buttonCode = int.Parse (value);
-					}
-					if (foundPoint == 1) {
-						pos.X = int.Parse (value) - 1;
-					}
-					value = "";
-					foundPoint++;
-				} else if (foundPoint > 0 && c != 'm' && c != 'M') {
-					value += c.ToString ();
-				} else if (c == 'm' || c == 'M') {
-					//pos.Y = int.Parse (value) + Console.WindowTop - 1;
-					pos.Y = int.Parse (value) - 1;
-
-					switch (buttonCode) {
-					case 0:
-					case 8:
-					case 16:
-					case 24:
-					case 32:
-					case 36:
-					case 40:
-					case 48:
-					case 56:
-						buttonState = c == 'M' ? MouseFlags.Button1Pressed
-							: MouseFlags.Button1Released;
-						break;
-					case 1:
-					case 9:
-					case 17:
-					case 25:
-					case 33:
-					case 37:
-					case 41:
-					case 45:
-					case 49:
-					case 53:
-					case 57:
-					case 61:
-						buttonState = c == 'M' ? MouseFlags.Button2Pressed
-							: MouseFlags.Button2Released;
-						break;
-					case 2:
-					case 10:
-					case 14:
-					case 18:
-					case 22:
-					case 26:
-					case 30:
-					case 34:
-					case 42:
-					case 46:
-					case 50:
-					case 54:
-					case 58:
-					case 62:
-						buttonState = c == 'M' ? MouseFlags.Button3Pressed
-							: MouseFlags.Button3Released;
-						break;
-					case 35:
-					//// Needed for Windows OS
-					//if (isButtonPressed && c == 'm'
-					//	&& (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
-					//	|| lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
-					//	|| lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
-
-					//	switch (lastMouseEvent.ButtonState) {
-					//	case MouseFlags.Button1Pressed:
-					//		buttonState = MouseFlags.Button1Released;
-					//		break;
-					//	case MouseFlags.Button2Pressed:
-					//		buttonState = MouseFlags.Button2Released;
-					//		break;
-					//	case MouseFlags.Button3Pressed:
-					//		buttonState = MouseFlags.Button3Released;
-					//		break;
-					//	}
-					//} else {
-					//	buttonState = MouseFlags.ReportMousePosition;
-					//}
-					//break;
-					case 39:
-					case 43:
-					case 47:
-					case 51:
-					case 55:
-					case 59:
-					case 63:
-						buttonState = MouseFlags.ReportMousePosition;
-						break;
-					case 64:
-						buttonState = MouseFlags.WheeledUp;
-						break;
-					case 65:
-						buttonState = MouseFlags.WheeledDown;
-						break;
-					case 68:
-					case 72:
-					case 80:
-						buttonState = MouseFlags.WheeledLeft;       // Shift/Ctrl+WheeledUp
-						break;
-					case 69:
-					case 73:
-					case 81:
-						buttonState = MouseFlags.WheeledRight;      // Shift/Ctrl+WheeledDown
-						break;
-					}
-					// Modifiers.
-					switch (buttonCode) {
-					case 8:
-					case 9:
-					case 10:
-					case 43:
-						buttonState |= MouseFlags.ButtonAlt;
-						break;
-					case 14:
-					case 47:
-						buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
-						break;
-					case 16:
-					case 17:
-					case 18:
-					case 51:
-						buttonState |= MouseFlags.ButtonCtrl;
-						break;
-					case 22:
-					case 55:
-						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
-						break;
-					case 24:
-					case 25:
-					case 26:
-					case 59:
-						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
-						break;
-					case 30:
-					case 63:
-						buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
-						break;
-					case 32:
-					case 33:
-					case 34:
-						buttonState |= MouseFlags.ReportMousePosition;
-						break;
-					case 36:
-					case 37:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
-						break;
-					case 39:
-					case 68:
-					case 69:
-						buttonState |= MouseFlags.ButtonShift;
-						break;
-					case 40:
-					case 41:
-					case 42:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
-						break;
-					case 45:
-					case 46:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
-						break;
-					case 48:
-					case 49:
-					case 50:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
-						break;
-					case 53:
-					case 54:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
-						break;
-					case 56:
-					case 57:
-					case 58:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
-						break;
-					case 61:
-					case 62:
-						buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
-						break;
-					}
-				}
-			}
-
-			mouseFlags = new List<MouseFlags> () { MouseFlags.AllEvents };
-
-			if (lastMouseButtonPressed != null && !isButtonPressed && !buttonState.HasFlag (MouseFlags.ReportMousePosition)
-				&& !buttonState.HasFlag (MouseFlags.Button1Released)
-				&& !buttonState.HasFlag (MouseFlags.Button2Released)
-				&& !buttonState.HasFlag (MouseFlags.Button3Released)
-				&& !buttonState.HasFlag (MouseFlags.Button4Released)) {
-
-				lastMouseButtonPressed = null;
-				isButtonPressed = false;
-			}
-
-			if (!isButtonClicked && !isButtonDoubleClicked && ((buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
-				  buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed) && lastMouseButtonPressed == null) ||
-				  isButtonPressed && lastMouseButtonPressed != null && buttonState.HasFlag (MouseFlags.ReportMousePosition)) {
-
-				mouseFlags [0] = buttonState;
-				lastMouseButtonPressed = buttonState;
-				isButtonPressed = true;
-
-				if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
-					point = new Point () {
-						X = pos.X,
-						Y = pos.Y
-					};
-
-					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
-						return false;
-					});
-				} else if (mouseFlags [0] == MouseFlags.ReportMousePosition) {
-					isButtonPressed = false;
-				}
-
-			} else if (isButtonDoubleClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
-				buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
-
-				mouseFlags [0] = GetButtonTripleClicked (buttonState);
-				isButtonDoubleClicked = false;
-				isButtonTripleClicked = true;
-
-			} else if (isButtonClicked && (buttonState == MouseFlags.Button1Pressed || buttonState == MouseFlags.Button2Pressed ||
-				buttonState == MouseFlags.Button3Pressed || buttonState == MouseFlags.Button4Pressed)) {
-
-				mouseFlags [0] = GetButtonDoubleClicked (buttonState);
-				isButtonClicked = false;
-				isButtonDoubleClicked = true;
-				Application.MainLoop.AddIdle (() => {
-					Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
-					return false;
-				});
-
-			}
-			//else if (isButtonReleased && !isButtonClicked && buttonState == MouseFlags.ReportMousePosition) {
-			//	mouseFlag [0] = GetButtonClicked ((MouseFlags)lastMouseButtonReleased);
-			//	lastMouseButtonReleased = null;
-			//	isButtonReleased = false;
-			//	isButtonClicked = true;
-			//	Application.MainLoop.AddIdle (() => {
-			//		Task.Run (async () => await ProcessButtonClickedAsync ());
-			//		return false;
-			//	});
-
-			//} 
-			else if (!isButtonClicked && !isButtonDoubleClicked && (buttonState == MouseFlags.Button1Released || buttonState == MouseFlags.Button2Released ||
-				  buttonState == MouseFlags.Button3Released || buttonState == MouseFlags.Button4Released)) {
-
-				mouseFlags [0] = buttonState;
-				isButtonPressed = false;
-
-				if (isButtonTripleClicked) {
-					isButtonTripleClicked = false;
-				} else if (pos.X == point.X && pos.Y == point.Y) {
-					mouseFlags.Add (GetButtonClicked (buttonState));
-					isButtonClicked = true;
-					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessButtonClickedAsync ());
-						return false;
-					});
-				}
-
-				point = pos;
-
-				//if ((lastMouseButtonPressed & MouseFlags.ReportMousePosition) == 0) {
-				//	lastMouseButtonReleased = buttonState;
-				//	isButtonPressed = false;
-				//	isButtonReleased = true;
-				//} else {
-				//	lastMouseButtonPressed = null;
-				//	isButtonPressed = false;
-				//}
-
-			} else if (buttonState == MouseFlags.WheeledUp) {
-
-				mouseFlags [0] = MouseFlags.WheeledUp;
-
-			} else if (buttonState == MouseFlags.WheeledDown) {
-
-				mouseFlags [0] = MouseFlags.WheeledDown;
-
-			} else if (buttonState == MouseFlags.WheeledLeft) {
-
-				mouseFlags [0] = MouseFlags.WheeledLeft;
-
-			} else if (buttonState == MouseFlags.WheeledRight) {
-
-				mouseFlags [0] = MouseFlags.WheeledRight;
-
-			} else if (buttonState == MouseFlags.ReportMousePosition) {
-				mouseFlags [0] = MouseFlags.ReportMousePosition;
-
-			} else {
-				mouseFlags [0] = buttonState;
-				//foreach (var flag in buttonState.GetUniqueFlags()) {
-				//	mouseFlag [0] |= flag;
-				//}
-			}
-
-			mouseFlags [0] = SetControlKeyStates (buttonState, mouseFlags [0]);
-			//buttonState = mouseFlags;
-
-			//System.Diagnostics.Debug.WriteLine ($"buttonState: {buttonState} X: {pos.X} Y: {pos.Y}");
-			//foreach (var mf in mouseFlags) {
-			//	System.Diagnostics.Debug.WriteLine ($"mouseFlags: {mf} X: {pos.X} Y: {pos.Y}");
-			//}
-		}
-
-		private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
-		{
-			while (isButtonPressed) {
-				await Task.Delay (100);
-				//var me = new MouseEvent () {
-				//	X = point.X,
-				//	Y = point.Y,
-				//	Flags = mouseFlag
-				//};
-
-				var view = Application.WantContinuousButtonPressedView;
-				if (view == null)
-					break;
-				if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-					Application.MainLoop.Invoke (() => continuousButtonPressedHandler (mouseFlag, point));
-				}
-			}
-		}
-
-		private static async Task ProcessButtonClickedAsync ()
-		{
-			await Task.Delay (300);
-			isButtonClicked = false;
-		}
-
-		private static async Task ProcessButtonDoubleClickedAsync ()
-		{
-			await Task.Delay (300);
-			isButtonDoubleClicked = false;
-		}
-
-		private static MouseFlags GetButtonClicked (MouseFlags mouseFlag)
-		{
-			MouseFlags mf = default;
-			switch (mouseFlag) {
-			case MouseFlags.Button1Released:
-				mf = MouseFlags.Button1Clicked;
-				break;
-
-			case MouseFlags.Button2Released:
-				mf = MouseFlags.Button2Clicked;
-				break;
-
-			case MouseFlags.Button3Released:
-				mf = MouseFlags.Button3Clicked;
-				break;
-			}
-			return mf;
-		}
-
-		private static MouseFlags GetButtonDoubleClicked (MouseFlags mouseFlag)
-		{
-			MouseFlags mf = default;
-			switch (mouseFlag) {
-			case MouseFlags.Button1Pressed:
-				mf = MouseFlags.Button1DoubleClicked;
-				break;
-
-			case MouseFlags.Button2Pressed:
-				mf = MouseFlags.Button2DoubleClicked;
-				break;
-
-			case MouseFlags.Button3Pressed:
-				mf = MouseFlags.Button3DoubleClicked;
-				break;
-			}
-			return mf;
-		}
-
-		private static MouseFlags GetButtonTripleClicked (MouseFlags mouseFlag)
-		{
-			MouseFlags mf = default;
-			switch (mouseFlag) {
-			case MouseFlags.Button1Pressed:
-				mf = MouseFlags.Button1TripleClicked;
-				break;
-
-			case MouseFlags.Button2Pressed:
-				mf = MouseFlags.Button2TripleClicked;
-				break;
-
-			case MouseFlags.Button3Pressed:
-				mf = MouseFlags.Button3TripleClicked;
-				break;
-			}
-			return mf;
-		}
-
-		private static MouseFlags SetControlKeyStates (MouseFlags buttonState, MouseFlags mouseFlag)
-		{
-			if ((buttonState & MouseFlags.ButtonCtrl) != 0 && (mouseFlag & MouseFlags.ButtonCtrl) == 0)
-				mouseFlag |= MouseFlags.ButtonCtrl;
-
-			if ((buttonState & MouseFlags.ButtonShift) != 0 && (mouseFlag & MouseFlags.ButtonShift) == 0)
-				mouseFlag |= MouseFlags.ButtonShift;
-
-			if ((buttonState & MouseFlags.ButtonAlt) != 0 && (mouseFlag & MouseFlags.ButtonAlt) == 0)
-				mouseFlag |= MouseFlags.ButtonAlt;
-			return mouseFlag;
-		}
-
-		/// <summary>
-		/// Get the terminal that holds the console driver.
-		/// </summary>
-		/// <param name="process">The process.</param>
-		/// <returns>If supported the executable console process, null otherwise.</returns>
-		public static Process GetParentProcess (Process process)
-		{
-			if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-				return null;
-			}
-
-			string query = "SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = " + process.Id;
-			using (ManagementObjectSearcher mos = new ManagementObjectSearcher (query)) {
-				foreach (ManagementObject mo in mos.Get ()) {
-					if (mo ["ParentProcessId"] != null) {
-						try {
-							var id = Convert.ToInt32 (mo ["ParentProcessId"]);
-							return Process.GetProcessById (id);
-						} catch {
-						}
-					}
-				}
-			}
-			return null;
-		}
-	}
-}

+ 24 - 15
Terminal.Gui/MainLoop.cs

@@ -77,8 +77,8 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// The current <see cref="IMainLoopDriver"/> in use.
 		/// </summary>
-		/// <value>The driver.</value>
-		public IMainLoopDriver Driver { get; }
+		/// <value>The main loop driver.</value>
+		public IMainLoopDriver MainLoopDriver { get; }
 
 		/// <summary>
 		/// Invoked when a new timeout is added. To be used in the case
@@ -93,7 +93,7 @@ namespace Terminal.Gui {
 		/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
 		public MainLoop (IMainLoopDriver driver)
 		{
-			Driver = driver;
+			MainLoopDriver = driver;
 			driver.Setup (this);
 		}
 
@@ -128,7 +128,7 @@ namespace Terminal.Gui {
 				idleHandlers.Add (idleHandler);
 			}
 
-			Driver.Wakeup ();
+			MainLoopDriver.Wakeup ();
 			return idleHandler;
 		}
 
@@ -140,8 +140,9 @@ namespace Terminal.Gui {
 		///  This method also returns <c>false</c> if the idle handler is not found.
 		public bool RemoveIdle (Func<bool> token)
 		{
-			lock (_idleHandlersLock)
+			lock (_idleHandlersLock) {
 				return idleHandlers.Remove (token);
+			}
 		}
 
 		void AddTimeout (TimeSpan time, Timeout timeout)
@@ -149,7 +150,7 @@ namespace Terminal.Gui {
 			lock (_timeoutsLockToken) {
 				var k = (DateTime.UtcNow + time).Ticks;
 				timeouts.Add (NudgeToUniqueKey (k), timeout);
-				TimeoutAdded?.Invoke (this, new TimeoutEventArgs(timeout, k));
+				TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
 			}
 		}
 
@@ -166,8 +167,9 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
 		{
-			if (callback == null)
+			if (callback == null) {
 				throw new ArgumentNullException (nameof (callback));
+			}
 			var timeout = new Timeout () {
 				Span = time,
 				Callback = callback
@@ -188,8 +190,9 @@ namespace Terminal.Gui {
 		{
 			lock (_timeoutsLockToken) {
 				var idx = timeouts.IndexOfValue (token as Timeout);
-				if (idx == -1)
+				if (idx == -1) {
 					return false;
+				}
 				timeouts.RemoveAt (idx);
 			}
 			return true;
@@ -213,8 +216,9 @@ namespace Terminal.Gui {
 				var k = t.Key;
 				var timeout = t.Value;
 				if (k < now) {
-					if (timeout.Callback (this))
+					if (timeout.Callback (this)) {
 						AddTimeout (timeout.Span, timeout);
+					}
 				} else {
 					lock (_timeoutsLockToken) {
 						timeouts.Add (NudgeToUniqueKey (k), timeout);
@@ -249,21 +253,25 @@ namespace Terminal.Gui {
 			}
 
 			foreach (var idle in iterate) {
-				if (idle ())
-					lock (_idleHandlersLock)
+				if (idle ()) {
+					lock (_idleHandlersLock) {
 						idleHandlers.Add (idle);
+					}
+				}
 			}
 		}
 
 		bool _running;
 
+		// BUGBUG: Stop is only called from MainLoopUnitTests.cs. As a result, the mainloop
+		// will never exit during other unit tests or normal execution.
 		/// <summary>
 		///   Stops the mainloop.
 		/// </summary>
 		public void Stop ()
 		{
 			_running = false;
-			Driver.Wakeup ();
+			MainLoopDriver.Wakeup ();
 		}
 
 		/// <summary>
@@ -276,7 +284,7 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public bool EventsPending (bool wait = false)
 		{
-			return Driver.EventsPending (wait);
+			return MainLoopDriver.EventsPending (wait);
 		}
 
 		/// <summary>
@@ -291,10 +299,11 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public void RunIteration ()
 		{
-			if (timeouts.Count > 0)
+			if (timeouts.Count > 0) {
 				RunTimers ();
+			}
 
-			Driver.Iteration ();
+			MainLoopDriver.Iteration ();
 
 			bool runIdle = false;
 			lock (_idleHandlersLock) {

+ 0 - 1
Terminal.Gui/Resources/config.json

@@ -28,7 +28,6 @@
       "Ctrl"
     ]
   },
-  "Application.EnableConsoleScrolling": false,
   "Application.QuitKey": {
     "Key": "Q",
     "Modifiers": [

+ 4 - 8
Terminal.Gui/Terminal.Gui.csproj

@@ -27,16 +27,12 @@
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
-    <PackageReference Include="System.IO.Abstractions" Version="19.2.29" />
-    <PackageReference Include="System.Text.Json" Version="7.0.2" />
-    <PackageReference Include="System.Management" Version="7.0.1" />
+    <PackageReference Include="System.IO.Abstractions" Version="19.2.51" />
+    <PackageReference Include="System.Text.Json" Version="7.0.3" />
+    <PackageReference Include="System.Management" Version="7.0.2" />
+    <PackageReference Include="Wcwidth" Version="1.0.0" />
     <InternalsVisibleTo Include="UnitTests" />
    </ItemGroup>
-  <!-- Uncomment the RestoreSources element to have dotnet restore pull NStack from a local dir for testing -->
-  <PropertyGroup>
-    <!-- See https://stackoverflow.com/a/44463578/297526 -->
-    <!--<RestoreSources>$(RestoreSources);..\..\NStack\NStack\bin\Debug;https://api.nuget.org/v3/index.json</RestoreSources>-->
-  </PropertyGroup>
   <!-- API Documentation -->
   <ItemGroup>
     <None Include="..\docfx\images\logo.png">

+ 2 - 144
Terminal.Gui/Text/RuneExtensions.cs

@@ -1,5 +1,6 @@
 using System.Globalization;
 using System.Text;
+using Wcwidth;
 
 namespace Terminal.Gui;
 
@@ -26,21 +27,7 @@ public static class RuneExtensions {
 	/// </returns>
 	public static int GetColumns (this Rune rune)
 	{
-		// TODO: I believe there is a way to do this without using our own tables, using Rune.
-		var codePoint = rune.Value;
-		switch (codePoint) {
-		case < 0x20:
-		case >= 0x7f and < 0xa0:
-			return -1;
-		case < 0x7f:
-			return 1;
-		}
-		/* binary search in table of non-spacing characters */
-		if (BiSearch (codePoint, _combining, _combining.GetLength (0) - 1) != 0) {
-			return 0;
-		}
-		/* if we arrive here, ucs is not a combining or C0/C1 control character */
-		return 1 + (BiSearch (codePoint, _combiningWideChars, _combiningWideChars.GetLength (0) - 1) != 0 ? 1 : 0);
+		return UnicodeCalculator.GetWidth (rune);
 	}
 
 	/// <summary>
@@ -179,133 +166,4 @@ public static class RuneExtensions {
 		}
 		return true;
 	}
-
-	// ---------------- implementation details ------------------
-	// TODO: Can this be handled by the new .NET 8 Rune type?
-	static readonly int [,] _combining = new int [,] {
-		{ 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
-		{ 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
-		{ 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
-		{ 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
-		{ 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
-		{ 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
-		{ 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
-		{ 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
-		{ 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
-		{ 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
-		{ 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
-		{ 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
-		{ 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
-		{ 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
-		{ 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
-		{ 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
-		{ 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
-		{ 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
-		{ 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
-		{ 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
-		{ 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
-		{ 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
-		{ 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
-		{ 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
-		{ 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
-		{ 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
-		{ 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
-		{ 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
-		{ 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
-		{ 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
-		{ 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
-		{ 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
-		{ 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
-		{ 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
-		{ 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
-		{ 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
-		{ 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
-		{ 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
-		{ 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
-		{ 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x2E9A, 0x2E9A },
-		{ 0x2EF4, 0x2EFF }, { 0x2FD6, 0x2FEF }, { 0x2FFC, 0x2FFF },
-		{ 0x31E4, 0x31EF }, { 0x321F, 0x321F }, { 0xA48D, 0xA48F },
-		{ 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 },
-		{ 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE1A, 0xFE1F },
-		{ 0xFE20, 0xFE23 }, { 0xFE53, 0xFE53 }, { 0xFE67, 0xFE67 },
-		{ 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
-		{ 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
-		{ 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
-		{ 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
-		{ 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
-		{ 0xE0100, 0xE01EF }
-	};
-
-	static readonly int [,] _combiningWideChars = new int [,] {
-		/* Hangul Jamo init. consonants - 0x1100, 0x11ff */
-		/* Miscellaneous Technical - 0x2300, 0x23ff */
-		/* Hangul Syllables - 0x11a8, 0x11c2 */
-		/* CJK Compatibility Ideographs - f900, fad9 */
-		/* Vertical forms - fe10, fe19 */
-		/* CJK Compatibility Forms - fe30, fe4f */
-		/* Fullwidth Forms - ff01, ffee */
-		/* Alphabetic Presentation Forms - 0xFB00, 0xFb4f */
-		/* Chess Symbols - 0x1FA00, 0x1FA0f */
-
-		{ 0x1100, 0x115f }, { 0x231a, 0x231b }, { 0x2329, 0x232a },
-		{ 0x23e9, 0x23ec }, { 0x23f0, 0x23f0 }, { 0x23f3, 0x23f3 },
-		{ 0x25fd, 0x25fe }, { 0x2614, 0x2615 }, { 0x2648, 0x2653 },
-		{ 0x267f, 0x267f }, { 0x2693, 0x2693 }, { 0x26a1, 0x26a1 },
-		{ 0x26aa, 0x26ab }, { 0x26bd, 0x26be }, { 0x26c4, 0x26c5 },
-		{ 0x26ce, 0x26ce }, { 0x26d4, 0x26d4 }, { 0x26ea, 0x26ea },
-		{ 0x26f2, 0x26f3 }, { 0x26f5, 0x26f5 }, { 0x26fa, 0x26fa },
-		{ 0x26fd, 0x26fd }, { 0x2705, 0x2705 }, { 0x270a, 0x270b },
-		{ 0x2728, 0x2728 }, { 0x274c, 0x274c }, { 0x274e, 0x274e },
-		{ 0x2753, 0x2755 }, { 0x2757, 0x2757 }, { 0x2795, 0x2797 },
-		{ 0x27b0, 0x27b0 }, { 0x27bf, 0x27bf }, { 0x2b1b, 0x2b1c },
-		{ 0x2b50, 0x2b50 }, { 0x2b55, 0x2b55 }, { 0x2e80, 0x303e },
-		{ 0x3041, 0x3096 }, { 0x3099, 0x30ff }, { 0x3105, 0x312f },
-		{ 0x3131, 0x318e }, { 0x3190, 0x3247 }, { 0x3250, 0x4dbf },
-		{ 0x4e00, 0xa4c6 }, { 0xa960, 0xa97c }, { 0xac00, 0xd7a3 },
-		{ 0xf900, 0xfaff }, { 0xfe10, 0xfe1f }, { 0xfe30, 0xfe6b },
-		{ 0xff01, 0xff60 }, { 0xffe0, 0xffe6 },
-		{ 0x16fe0, 0x16fe4 }, { 0x16ff0, 0x16ff1 }, { 0x17000, 0x187f7 },
-		{ 0x18800, 0x18cd5 }, { 0x18d00, 0x18d08 }, { 0x1aff0, 0x1affc },
-		{ 0x1b000, 0x1b122 }, { 0x1b150, 0x1b152 }, { 0x1b164, 0x1b167 }, { 0x1b170, 0x1b2fb }, { 0x1d538, 0x1d550 },
-		{ 0x1f004, 0x1f004 }, { 0x1f0cf, 0x1f0cf }, /*{ 0x1f100, 0x1f10a },*/
-		//{ 0x1f110, 0x1f12d }, { 0x1f130, 0x1f169 }, { 0x1f170, 0x1f1ac },
-		{ 0x1f18f, 0x1f199 },
-		{ 0x1f1e6, 0x1f1ff }, { 0x1f200, 0x1f202 }, { 0x1f210, 0x1f23b },
-		{ 0x1f240, 0x1f248 }, { 0x1f250, 0x1f251 }, { 0x1f260, 0x1f265 },
-		{ 0x1f300, 0x1f320 }, { 0x1f32d, 0x1f33e }, { 0x1f340, 0x1f37e },
-		{ 0x1f380, 0x1f393 }, { 0x1f3a0, 0x1f3ca }, { 0x1f3cf, 0x1f3d3 },
-		{ 0x1f3e0, 0x1f3f0 }, { 0x1f3f4, 0x1f3f4 }, { 0x1f3f8, 0x1f43e },
-		{ 0x1f440, 0x1f44e }, { 0x1f450, 0x1f4fc }, { 0x1f4ff, 0x1f53d },
-		{ 0x1f54b, 0x1f54e }, { 0x1f550, 0x1f567 }, { 0x1f57a, 0x1f57a },
-		{ 0x1f595, 0x1f596 }, { 0x1f5a4, 0x1f5a4 }, { 0x1f5fb, 0x1f606 },
-		{ 0x1f607, 0x1f64f }, { 0x1f680, 0x1f6c5 }, { 0x1f6cc, 0x1f6cc },
-		{ 0x1f6d0, 0x1f6d2 }, { 0x1f6d5, 0x1f6d7 }, { 0x1f6dd, 0x1f6df }, { 0x1f6eb, 0x1f6ec },
-		{ 0x1f6f4, 0x1f6fc }, { 0x1f7e0, 0x1f7eb }, { 0x1f7f0, 0x1f7f0 }, { 0x1f90c, 0x1f93a },
-		{ 0x1f93c, 0x1f945 }, { 0x1f947, 0x1f97f }, { 0x1f980, 0x1f9cc },
-		{ 0x1f9cd, 0x1f9ff }, { 0x1fa70, 0x1fa74 }, { 0x1fa78, 0x1fa7c }, { 0x1fa80, 0x1fa86 },
-		{ 0x1fa90, 0x1faac }, { 0x1fab0, 0x1faba }, { 0x1fac0, 0x1fac5 },
-		{ 0x1fad0, 0x1fad9 }, { 0x1fae0, 0x1fae7 }, { 0x1faf0, 0x1faf6 }, { 0x20000, 0x2fffd }, { 0x30000, 0x3fffd },
-		//{ 0xe0100, 0xe01ef }, { 0xf0000, 0xffffd }, { 0x100000, 0x10fffd }
-	};
-
-	static int BiSearch (int rune, int [,] table, int max)
-	{
-		var min = 0;
-
-		if (rune < table [0, 0] || rune > table [max, 1]) {
-			return 0;
-		}
-		while (max >= min) {
-			var mid = (min + max) / 2;
-			if (rune > table [mid, 1]) {
-				min = mid + 1;
-			} else if (rune < table [mid, 0]) {
-				max = mid - 1;
-			} else {
-				return 1;
-			}
-		}
-
-		return 0;
-	}
 }

+ 4 - 5
Terminal.Gui/View/Frame.cs

@@ -126,7 +126,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		public override void OnDrawContent (Rect contentArea)
 		{
-			if (Thickness == Thickness.Empty) return;
+			if (Thickness == Thickness.Empty) {
+				return;
+			}
 
 			if (ColorScheme != null) {
 				Driver.SetAttribute (GetNormalColor ());
@@ -139,9 +141,6 @@ namespace Terminal.Gui {
 			}
 
 			//Driver.SetAttribute (Colors.Error.Normal);
-
-			var prevClip = SetClip (Frame);
-
 			var screenBounds = ViewToScreen (Frame);
 
 			// This just draws/clears the thickness, not the insides.
@@ -305,7 +304,7 @@ namespace Terminal.Gui {
 				}
 			}
 
-			Driver.Clip = prevClip;
+			ClearNeedsDisplay ();
 		}
 
 		// TODO: v2 - Frame.BorderStyle is temporary - Eventually the border will be drawn by a "BorderView" that is a subview of the Frame.

+ 117 - 116
Terminal.Gui/View/ViewDrawing.cs

@@ -9,7 +9,7 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-		/// color scheme.
+		/// color scheme. 
 		/// </summary>
 		public virtual ColorScheme ColorScheme {
 			get {
@@ -34,7 +34,11 @@ namespace Terminal.Gui {
 		/// If it's overridden can return other values.</returns>
 		public virtual Attribute GetNormalColor ()
 		{
-			return Enabled ? ColorScheme.Normal : ColorScheme.Disabled;
+			ColorScheme cs = ColorScheme;
+			if (ColorScheme == null) {
+				cs = new ColorScheme ();
+			}
+			return Enabled ? cs.Normal : cs.Disabled;
 		}
 
 		/// <summary>
@@ -76,106 +80,126 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Removes the <see cref="_needsDisplay"/> and the <see cref="_subViewNeedsDisplay"/> setting on this view.
+		/// Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.
 		/// </summary>
 		protected void ClearNeedsDisplay ()
 		{
-			_needsDisplay = Rect.Empty;
+			_needsDisplayRect = Rect.Empty;
 			_subViewNeedsDisplay = false;
 		}
 
-		// The view-relative region that needs to be redrawn
-		internal Rect _needsDisplay { get; private set; } = Rect.Empty;
+		// The view-relative region that needs to be redrawn. Marked internal for unit tests.
+		internal Rect _needsDisplayRect = Rect.Empty;
+
+		/// <summary>
+		/// Gets or sets whether the view needs to be redrawn.
+		/// </summary>
+		public bool NeedsDisplay {
+			get => _needsDisplayRect != Rect.Empty;
+			set {
+				if (value) {
+					SetNeedsDisplay ();
+				} else {
+					ClearNeedsDisplay ();
+				}
+			}
+		}
 
 		/// <summary>
-		/// Sets a flag indicating this view needs to be redisplayed because its state has changed.
+		/// Sets the area of this view needing to be redrawn to <see cref="Bounds"/>.
 		/// </summary>
 		public void SetNeedsDisplay ()
 		{
-			if (!IsInitialized) return;
+			if (!IsInitialized) {
+				return;
+			}
 			SetNeedsDisplay (Bounds);
 		}
 
 		/// <summary>
-		/// Flags the view-relative region on this View as needing to be redrawn.
+		/// Expands the area of this view needing to be redrawn to include <paramref name="region"/>.
 		/// </summary>
 		/// <param name="region">The view-relative region that needs to be redrawn.</param>
 		public void SetNeedsDisplay (Rect region)
 		{
-			if (_needsDisplay.IsEmpty) {
-				_needsDisplay = region;
+			if (_needsDisplayRect.IsEmpty) {
+				_needsDisplayRect = region;
 			} else {
-				var x = Math.Min (_needsDisplay.X, region.X);
-				var y = Math.Min (_needsDisplay.Y, region.Y);
-				var w = Math.Max (_needsDisplay.Width, region.Width);
-				var h = Math.Max (_needsDisplay.Height, region.Height);
-				_needsDisplay = new Rect (x, y, w, h);
+				var x = Math.Min (_needsDisplayRect.X, region.X);
+				var y = Math.Min (_needsDisplayRect.Y, region.Y);
+				var w = Math.Max (_needsDisplayRect.Width, region.Width);
+				var h = Math.Max (_needsDisplayRect.Height, region.Height);
+				_needsDisplayRect = new Rect (x, y, w, h);
 			}
 			_superView?.SetSubViewNeedsDisplay ();
 
-			if (_subviews == null)
+			if (_needsDisplayRect.X < Bounds.X ||
+				_needsDisplayRect.Y < Bounds.Y ||
+				_needsDisplayRect.Width > Bounds.Width ||
+				_needsDisplayRect.Height > Bounds.Height) {
+				Margin?.SetNeedsDisplay (Margin.Bounds);
+				Border?.SetNeedsDisplay (Border.Bounds);
+				Padding?.SetNeedsDisplay (Padding.Bounds);
+			}
+
+			if (_subviews == null) {
 				return;
+			}
 
-			foreach (var view in _subviews)
-				if (view.Frame.IntersectsWith (region)) {
-					var childRegion = Rect.Intersect (view.Frame, region);
-					childRegion.X -= view.Frame.X;
-					childRegion.Y -= view.Frame.Y;
-					view.SetNeedsDisplay (childRegion);
+			foreach (var subview in _subviews) {
+				if (subview.Frame.IntersectsWith (region)) {
+					var subviewRegion = Rect.Intersect (subview.Frame, region);
+					subviewRegion.X -= subview.Frame.X;
+					subviewRegion.Y -= subview.Frame.Y;
+					subview.SetNeedsDisplay (subviewRegion);
 				}
+			}
+		}
+
+		/// <summary>
+		/// Gets whether any Subviews need to be redrawn.
+		/// </summary>
+		public bool SubViewNeedsDisplay {
+			get => _subViewNeedsDisplay;
 		}
 
-		internal bool _subViewNeedsDisplay { get; private set; }
+		bool _subViewNeedsDisplay;
 
 		/// <summary>
 		/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 		/// </summary>
 		public void SetSubViewNeedsDisplay ()
 		{
-			if (_subViewNeedsDisplay) {
-				return;
-			}
 			_subViewNeedsDisplay = true;
-			if (_superView != null && !_superView._subViewNeedsDisplay)
+			if (_superView != null && !_superView._subViewNeedsDisplay) {
 				_superView.SetSubViewNeedsDisplay ();
+			}
 		}
 
 		/// <summary>
-		///   Clears the view region with the current color.
+		///   Clears the <see cref="Bounds"/> with the normal background color.
 		/// </summary>
 		/// <remarks>
 		///   <para>
-		///     This clears the entire region used by this view.
+		///     This clears the Bounds used by this view.
 		///   </para>
 		/// </remarks>
-		public void Clear ()
-		{
-			var h = Frame.Height;
-			var w = Frame.Width;
-			for (var line = 0; line < h; line++) {
-				Move (0, line);
-				for (var col = 0; col < w; col++)
-					Driver.AddRune ((Rune)' ');
-			}
-		}
+		public void Clear () => Clear (ViewToScreen(Bounds));
 
-		// BUGBUG: Stupid that this takes screen-relative. We should have a tenet that says 
-		// "View APIs only deal with View-relative coords". 
+		// BUGBUG: This version of the Clear API should be removed. We should have a tenet that says 
+		// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
+		// be refactored to use the View-relative version.
 		/// <summary>
-		///   Clears the specified region with the current color. 
+		///   Clears the specified screen-relative rectangle with the normal background. 
 		/// </summary>
 		/// <remarks>
 		/// </remarks>
-		/// <param name="regionScreen">The screen-relative region to clear.</param>
+		/// <param name="regionScreen">The screen-relative rectangle to clear.</param>
 		public void Clear (Rect regionScreen)
 		{
-			var h = regionScreen.Height;
-			var w = regionScreen.Width;
-			for (var line = regionScreen.Y; line < regionScreen.Y + h; line++) {
-				Driver.Move (regionScreen.X, line);
-				for (var col = 0; col < w; col++)
-					Driver.AddRune ((Rune)' ');
-			}
+			var prev = Driver.SetAttribute (GetNormalColor ());
+			Driver.FillRect (regionScreen);
+			Driver.SetAttribute (prev);
 		}
 
 		// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
@@ -190,35 +214,18 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Sets the <see cref="ConsoleDriver"/>'s clip region to <see cref="Bounds"/>.
+		/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
 		/// </summary>
 		/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
 		/// <remarks>
 		/// <para>
-		/// <see cref="Bounds"/> is View-relative.
-		/// </para>
-		/// <para>
 		/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
 		/// </para>
 		/// </remarks>
 		public Rect ClipToBounds ()
-		{
-			return SetClip (Bounds);
-		}
-
-		// BUGBUG: v2 - SetClip should return VIEW-relative so that it can be used to reset it; using Driver.Clip directly should not be necessary. 
-		/// <summary>
-		/// Sets the clip region to the specified view-relative region.
-		/// </summary>
-		/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
-		/// <param name="region">View-relative clip region.</param>
-		/// <remarks>
-		/// If <see cref="ConsoleDriver.Clip"/> and <paramref name="region"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
-		/// </remarks>
-		public Rect SetClip (Rect region)
 		{
 			var previous = Driver.Clip;
-			Driver.Clip = Rect.Intersect (previous, ViewToScreen (region));
+			Driver.Clip = Rect.Intersect (previous, ViewToScreen (Bounds));
 			return previous;
 		}
 
@@ -304,22 +311,11 @@ namespace Terminal.Gui {
 				return false;
 			}
 
-			var prevClip = Driver.Clip;
-			Driver.Clip = ViewToScreen (Frame);
-
-			// TODO: Figure out what we should do if we have no superview
-			//if (SuperView != null) {
-			// TODO: Clipping is disabled for now to ensure we see errors
-			Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);// screenBounds;// SuperView.ClipToBounds ();
-										//}
-
 			// Each of these renders lines to either this View's LineCanvas 
 			// Those lines will be finally rendered in OnRenderLineCanvas
-			Margin?.OnDrawContent (Bounds);
-			Border?.OnDrawContent (Bounds);
-			Padding?.OnDrawContent (Bounds);
-
-			Driver.Clip = prevClip;
+			Margin?.OnDrawContent (Margin.Bounds);
+			Border?.OnDrawContent (Border.Bounds);
+			Padding?.OnDrawContent (Padding.Bounds);
 
 			return true;
 		}
@@ -346,7 +342,6 @@ namespace Terminal.Gui {
 			if (!CanBeVisible (this)) {
 				return;
 			}
-
 			OnDrawFrames ();
 
 			var prevClip = ClipToBounds ();
@@ -367,7 +362,6 @@ namespace Terminal.Gui {
 			Driver.Clip = prevClip;
 
 			OnRenderLineCanvas ();
-
 			// Invoke DrawContentCompleteEvent
 			OnDrawContentComplete (Bounds);
 
@@ -389,29 +383,29 @@ namespace Terminal.Gui {
 				return false;
 			}
 
-			//Driver.SetAttribute (new Attribute(Color.White, Color.Black));
-
 			// If we have a SuperView, it'll render our frames.
 			if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) {
 				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
-					Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal);
+					Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
 					Driver.Move (p.Key.X, p.Key.Y);
-					Driver.AddRune (p.Value.Rune.Value);
+					// TODO: #2616 - Support combining sequences that don't normalize
+					Driver.AddRune (p.Value.Runes [0]);
 				}
 				LineCanvas.Clear ();
 			}
 
-			if (Subviews.Where (s => s.SuperViewRendersLineCanvas).Count () > 0) {
+			if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
 				foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
-					// Combine the LineCavas'
+					// Combine the LineCanvas'
 					LineCanvas.Merge (subview.LineCanvas);
 					subview.LineCanvas.Clear ();
 				}
 
 				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
-					Driver.SetAttribute (p.Value.Attribute?.Value ?? ColorScheme.Normal);
+					Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
 					Driver.Move (p.Key.X, p.Key.Y);
-					Driver.AddRune (p.Value.Rune.Value);
+					// TODO: #2616 - Support combining sequences that don't normalize
+					Driver.AddRune (p.Value.Runes [0]);
 				}
 				LineCanvas.Clear ();
 			}
@@ -441,38 +435,45 @@ namespace Terminal.Gui {
 		/// </remarks>
 		public virtual void OnDrawContent (Rect contentArea)
 		{
-			if (SuperView != null) {
-				Clear (ViewToScreen (Bounds));
-			}
+			if (NeedsDisplay) {
+				if (SuperView != null) {
+					Clear (ViewToScreen (Bounds));
+				}
 
-			if (!string.IsNullOrEmpty (TextFormatter.Text)) {
-				if (TextFormatter != null) {
-					TextFormatter.NeedsFormat = true;
+				if (!string.IsNullOrEmpty (TextFormatter.Text)) {
+					if (TextFormatter != null) {
+						TextFormatter.NeedsFormat = true;
+					}
 				}
 				// This should NOT clear 
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
-				    HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
-				    Rect.Empty, false);
+					HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
+					Rect.Empty, false);
 				SetSubViewNeedsDisplay ();
 			}
 
 			// Draw subviews
 			// TODO: Implement OnDrawSubviews (cancelable);
-			if (_subviews != null) {
-				foreach (var view in _subviews) {
-					if (view.Visible) { //!view._needsDisplay.IsEmpty || view._childNeedsDisplay || view.LayoutNeeded) {
-						if (true) { //view.Frame.IntersectsWith (bounds)) { // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
-							if (view.LayoutNeeded) {
-								view.LayoutSubviews ();
-							}
-
-							// Draw the subview
-							// Use the view's bounds (view-relative; Location will always be (0,0)
-							//if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
-							view.Draw ();
-							//}
-						}
+			if (_subviews != null && SubViewNeedsDisplay) {
+				var subviewsNeedingDraw = _subviews.Where (
+					view => view.Visible &&
+						(view.NeedsDisplay ||
+						view.SubViewNeedsDisplay ||
+						view.LayoutNeeded)
+					);
+
+				foreach (var view in subviewsNeedingDraw) {
+					//view.Frame.IntersectsWith (bounds)) {
+					// && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
+					if (view.LayoutNeeded) {
+						view.LayoutSubviews ();
 					}
+
+					// Draw the subview
+					// Use the view's bounds (view-relative; Location will always be (0,0)
+					//if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
+					view.Draw ();
+					//}
 				}
 			}
 		}

+ 4 - 5
Terminal.Gui/View/ViewLayout.cs

@@ -470,16 +470,15 @@ namespace Terminal.Gui {
 
 		internal void SetNeedsLayout ()
 		{
-			if (LayoutNeeded)
+			if (LayoutNeeded) {
 				return;
+			}
 			LayoutNeeded = true;
 			foreach (var view in Subviews) {
 				view.SetNeedsLayout ();
 			}
 			TextFormatter.NeedsFormat = true;
-			if (SuperView == null)
-				return;
-			SuperView.SetNeedsLayout ();
+			SuperView?.SetNeedsLayout ();
 		}
 
 		/// <summary>
@@ -541,7 +540,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Converts a region in view-relative coordinates to screen-relative coordinates.
 		/// </summary>
-		internal Rect ViewToScreen (Rect region)
+		public Rect ViewToScreen (Rect region)
 		{
 			ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false);
 			return new Rect (x, y, region.Width, region.Height);

+ 11 - 6
Terminal.Gui/View/ViewSubViews.cs

@@ -5,7 +5,7 @@ using System.Linq;
 using System.Text;
 
 namespace Terminal.Gui {
-	public partial class View  {
+	public partial class View {
 		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 
 		View _superView = null;
@@ -128,7 +128,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets information if the view was already added to the <see cref="SuperView"/>.
+		/// Indicates whether the view was added to <see cref="SuperView"/>.
 		/// </summary>
 		public bool IsAdded { get; private set; }
 
@@ -420,10 +420,12 @@ namespace Terminal.Gui {
 		{
 			var args = new FocusEventArgs (view);
 			Enter?.Invoke (this, args);
-			if (args.Handled)
+			if (args.Handled) {
 				return true;
-			if (base.OnEnter (view))
+			}
+			if (base.OnEnter (view)) {
 				return true;
+			}
 
 			return false;
 		}
@@ -433,11 +435,14 @@ namespace Terminal.Gui {
 		{
 			var args = new FocusEventArgs (view);
 			Leave?.Invoke (this, args);
-			if (args.Handled)
+			if (args.Handled) {
 				return true;
-			if (base.OnLeave (view))
+			}
+			if (base.OnLeave (view)) {
 				return true;
+			}
 
+			Driver?.SetCursorVisibility (CursorVisibility.Invisible);
 			return false;
 		}
 

+ 6 - 8
Terminal.Gui/Views/CheckBox.cs

@@ -119,11 +119,11 @@ namespace Terminal.Gui {
 
 		Rune GetCheckedState ()
 		{
-			switch (Checked) {
-			case true: return charChecked;
-			case false: return charUnChecked;
-			default: return charNullChecked;
-			}
+			return Checked switch {
+				true => charChecked,
+				false => charUnChecked,
+				var _ => charNullChecked
+			};
 		}
 
 		string GetFormatterText ()
@@ -157,9 +157,7 @@ namespace Terminal.Gui {
 			get => allowNullChecked;
 			set {
 				allowNullChecked = value;
-				if (Checked == null) {
-					Checked = false;
-				}
+				Checked ??= false;
 			}
 		}
 

+ 1 - 8
Terminal.Gui/Views/ColorPicker.cs

@@ -68,14 +68,7 @@ namespace Terminal.Gui {
 				}
 			}
 		}
-		private int _boxHeight = 2;
-
-		// Cursor runes.
-		private static readonly Rune [] _cursorRunes = new Rune []
-		{
-			(Rune)0x250C, (Rune) 0x2500, (Rune) 0x2500, (Rune) 0x2510,
-			(Rune) 0x2514, (Rune) 0x2500, (Rune) 0x2500, (Rune) 0x2518
-		};
+		int _boxHeight = 2;
 
 		/// <summary>
 		/// Cursor for the selected color.

+ 2 - 2
Terminal.Gui/Views/HexView.cs

@@ -213,8 +213,8 @@ namespace Terminal.Gui {
 			Source.Position = displayStart;
 			var n = source.Read (data, 0, data.Length);
 
-			int activeColor = ColorScheme.HotNormal;
-			int trackingColor = ColorScheme.HotFocus;
+			var activeColor = ColorScheme.HotNormal;
+			var trackingColor = ColorScheme.HotFocus;
 
 			for (int line = 0; line < frame.Height; line++) {
 				var lineRect = new Rect (0, line, frame.Width, 1);

+ 0 - 1
Terminal.Gui/Views/Menu.cs

@@ -573,7 +573,6 @@ namespace Terminal.Gui {
 			}
 			var savedClip = Driver.Clip;
 			Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
-
 			Driver.SetAttribute (GetNormalColor ());
 
 			OnDrawFrames ();

+ 512 - 513
Terminal.Gui/Views/ScrollView.cs

@@ -14,597 +14,596 @@
 using System;
 using System.Linq;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Scrollviews are views that present a window into a virtual space where subviews are added.  Similar to the iOS UIScrollView.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The subviews that are added to this <see cref="Gui.ScrollView"/> are offset by the
-	///   <see cref="ContentOffset"/> property.  The view itself is a window into the 
-	///   space represented by the <see cref="ContentSize"/>.
-	/// </para>
-	/// <para>
-	///   Use the 
-	/// </para>
-	/// </remarks>
-	public class ScrollView : View {
-
-		// The ContentView is the view that contains the subviews  and content that are being scrolled
-		// The ContentView is the size of the ContentSize and is offset by the ContentOffset
-		private class ContentView : View {
-			public ContentView (Rect frame) : base (frame)
-			{
-				Id = "ScrollView.ContentView";
-				CanFocus = true;
-			}
+namespace Terminal.Gui;
+/// <summary>
+/// Scrollviews are views that present a window into a virtual space where subviews are added.  Similar to the iOS UIScrollView.
+/// </summary>
+/// <remarks>
+/// <para>
+///   The subviews that are added to this <see cref="Gui.ScrollView"/> are offset by the
+///   <see cref="ContentOffset"/> property.  The view itself is a window into the 
+///   space represented by the <see cref="ContentSize"/>.
+/// </para>
+/// <para>
+///   Use the 
+/// </para>
+/// </remarks>
+public class ScrollView : View {
+
+	// The ContentView is the view that contains the subviews  and content that are being scrolled
+	// The ContentView is the size of the ContentSize and is offset by the ContentOffset
+	private class ContentView : View {
+		public ContentView (Rect frame) : base (frame)
+		{
+			Id = "ScrollView.ContentView";
+			CanFocus = true;
 		}
+	}
 
-		ContentView _contentView;
-		ScrollBarView _vertical, _horizontal;
+	ContentView _contentView;
+	ScrollBarView _vertical, _horizontal;
 
-		/// <summary>
-		///  Initializes a new instance of the <see cref="Gui.ScrollView"/> class using <see cref="LayoutStyle.Absolute"/> positioning.
-		/// </summary>
-		/// <param name="frame"></param>
-		public ScrollView (Rect frame) : base (frame)
-		{
-			SetInitialProperties (frame);
-		}
+	/// <summary>
+	///  Initializes a new instance of the <see cref="Gui.ScrollView"/> class using <see cref="LayoutStyle.Absolute"/> positioning.
+	/// </summary>
+	/// <param name="frame"></param>
+	public ScrollView (Rect frame) : base (frame)
+	{
+		SetInitialProperties (frame);
+	}
 
-		/// <summary>
-		///  Initializes a new instance of the <see cref="Gui.ScrollView"/> class using <see cref="LayoutStyle.Computed"/> positioning.
-		/// </summary>
-		public ScrollView () : base ()
-		{
-			SetInitialProperties (Rect.Empty);
-		}
+	/// <summary>
+	///  Initializes a new instance of the <see cref="Gui.ScrollView"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+	/// </summary>
+	public ScrollView () : base ()
+	{
+		SetInitialProperties (Rect.Empty);
+	}
 
-		void SetInitialProperties (Rect frame)
-		{
-			_contentView = new ContentView (frame);
-			_vertical = new ScrollBarView (1, 0, isVertical: true) {
-				X = Pos.AnchorEnd (1),
-				Y = 0,
-				Width = 1,
-				Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0),
-				Host = this
+	void SetInitialProperties (Rect frame)
+	{
+		_contentView = new ContentView (frame);
+		_vertical = new ScrollBarView (1, 0, isVertical: true) {
+			X = Pos.AnchorEnd (1),
+			Y = 0,
+			Width = 1,
+			Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0),
+			Host = this
+		};
+
+		_horizontal = new ScrollBarView (1, 0, isVertical: false) {
+			X = 0,
+			Y = Pos.AnchorEnd (1),
+			Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0),
+			Height = 1,
+			Host = this
+		};
+
+		_vertical.OtherScrollBarView = _horizontal;
+		_horizontal.OtherScrollBarView = _vertical;
+		base.Add (_contentView);
+		CanFocus = true;
+
+		MouseEnter += View_MouseEnter;
+		MouseLeave += View_MouseLeave;
+		_contentView.MouseEnter += View_MouseEnter;
+		_contentView.MouseLeave += View_MouseLeave;
+
+		// Things this view knows how to do
+		AddCommand (Command.ScrollUp, () => ScrollUp (1));
+		AddCommand (Command.ScrollDown, () => ScrollDown (1));
+		AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
+		AddCommand (Command.ScrollRight, () => ScrollRight (1));
+		AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height));
+		AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height));
+		AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width));
+		AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width));
+		AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height));
+		AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height));
+		AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width));
+		AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width));
+
+		// Default keybindings for this view
+		AddKeyBinding (Key.CursorUp, Command.ScrollUp);
+		AddKeyBinding (Key.CursorDown, Command.ScrollDown);
+		AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
+		AddKeyBinding (Key.CursorRight, Command.ScrollRight);
+
+		AddKeyBinding (Key.PageUp, Command.PageUp);
+		AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp);
+
+		AddKeyBinding (Key.PageDown, Command.PageDown);
+		AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
+
+		AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft);
+		AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight);
+		AddKeyBinding (Key.Home, Command.TopHome);
+		AddKeyBinding (Key.End, Command.BottomEnd);
+		AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
+		AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
+
+		Initialized += (s, e) => {
+			if (!_vertical.IsInitialized) {
+				_vertical.BeginInit ();
+				_vertical.EndInit ();
+			}
+			if (!_horizontal.IsInitialized) {
+				_horizontal.BeginInit ();
+				_horizontal.EndInit ();
+			}
+			SetContentOffset (_contentOffset);
+			_contentView.Frame = new Rect (ContentOffset, ContentSize);
+			_vertical.ChangedPosition += delegate {
+				ContentOffset = new Point (ContentOffset.X, _vertical.Position);
 			};
-
-			_horizontal = new ScrollBarView (1, 0, isVertical: false) {
-				X = 0,
-				Y = Pos.AnchorEnd (1),
-				Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0),
-				Height = 1,
-				Host = this
+			_horizontal.ChangedPosition += delegate {
+				ContentOffset = new Point (_horizontal.Position, ContentOffset.Y);
 			};
+		};
+	}
 
-			_vertical.OtherScrollBarView = _horizontal;
-			_horizontal.OtherScrollBarView = _vertical;
-			base.Add (_contentView);
-			CanFocus = true;
+	//public override void BeginInit ()
+	//{
+	//	SetContentOffset (contentOffset);
+	//	base.BeginInit ();
+	//}
 
-			MouseEnter += View_MouseEnter;
-			MouseLeave += View_MouseLeave;
-			_contentView.MouseEnter += View_MouseEnter;
-			_contentView.MouseLeave += View_MouseLeave;
-
-			// Things this view knows how to do
-			AddCommand (Command.ScrollUp, () => ScrollUp (1));
-			AddCommand (Command.ScrollDown, () => ScrollDown (1));
-			AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
-			AddCommand (Command.ScrollRight, () => ScrollRight (1));
-			AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height));
-			AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height));
-			AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width));
-			AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width));
-			AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height));
-			AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height));
-			AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width));
-			AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width));
-
-			// Default keybindings for this view
-			AddKeyBinding (Key.CursorUp, Command.ScrollUp);
-			AddKeyBinding (Key.CursorDown, Command.ScrollDown);
-			AddKeyBinding (Key.CursorLeft, Command.ScrollLeft);
-			AddKeyBinding (Key.CursorRight, Command.ScrollRight);
-
-			AddKeyBinding (Key.PageUp, Command.PageUp);
-			AddKeyBinding ((Key)'v' | Key.AltMask, Command.PageUp);
-
-			AddKeyBinding (Key.PageDown, Command.PageDown);
-			AddKeyBinding (Key.V | Key.CtrlMask, Command.PageDown);
-
-			AddKeyBinding (Key.PageUp | Key.CtrlMask, Command.PageLeft);
-			AddKeyBinding (Key.PageDown | Key.CtrlMask, Command.PageRight);
-			AddKeyBinding (Key.Home, Command.TopHome);
-			AddKeyBinding (Key.End, Command.BottomEnd);
-			AddKeyBinding (Key.Home | Key.CtrlMask, Command.LeftHome);
-			AddKeyBinding (Key.End | Key.CtrlMask, Command.RightEnd);
-
-			Initialized += (s, e) => {
-				if (!_vertical.IsInitialized) {
-					_vertical.BeginInit ();
-					_vertical.EndInit ();
-				}
-				if (!_horizontal.IsInitialized) {
-					_horizontal.BeginInit ();
-					_horizontal.EndInit ();
-				}
-				SetContentOffset (_contentOffset);
-				_contentView.Frame = new Rect (ContentOffset, ContentSize);
-				_vertical.ChangedPosition += delegate {
-					ContentOffset = new Point (ContentOffset.X, _vertical.Position);
-				};
-				_horizontal.ChangedPosition += delegate {
-					ContentOffset = new Point (_horizontal.Position, ContentOffset.Y);
-				};
-			};
-		}
+	Size _contentSize;
+	Point _contentOffset;
+	bool _showHorizontalScrollIndicator;
+	bool _showVerticalScrollIndicator;
+	bool _keepContentAlwaysInViewport = true;
+	bool _autoHideScrollBars = true;
 
-		//public override void BeginInit ()
-		//{
-		//	SetContentOffset (contentOffset);
-		//	base.BeginInit ();
-		//}
-
-		Size _contentSize;
-		Point _contentOffset;
-		bool _showHorizontalScrollIndicator;
-		bool _showVerticalScrollIndicator;
-		bool _keepContentAlwaysInViewport = true;
-		bool _autoHideScrollBars = true;
-
-		/// <summary>
-		/// Represents the contents of the data shown inside the scrollview
-		/// </summary>
-		/// <value>The size of the content.</value>
-		public Size ContentSize {
-			get {
-				return _contentSize;
-			}
-			set {
-				if (_contentSize != value) {
-					_contentSize = value;
-					_contentView.Frame = new Rect (_contentOffset, value);
-					_vertical.Size = _contentSize.Height;
-					_horizontal.Size = _contentSize.Width;
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// Represents the contents of the data shown inside the scrollview
+	/// </summary>
+	/// <value>The size of the content.</value>
+	public Size ContentSize {
+		get {
+			return _contentSize;
+		}
+		set {
+			if (_contentSize != value) {
+				_contentSize = value;
+				_contentView.Frame = new Rect (_contentOffset, value);
+				_vertical.Size = _contentSize.Height;
+				_horizontal.Size = _contentSize.Width;
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Represents the top left corner coordinate that is displayed by the scrollview
-		/// </summary>
-		/// <value>The content offset.</value>
-		public Point ContentOffset {
-			get {
-				return _contentOffset;
+	/// <summary>
+	/// Represents the top left corner coordinate that is displayed by the scrollview
+	/// </summary>
+	/// <value>The content offset.</value>
+	public Point ContentOffset {
+		get {
+			return _contentOffset;
+		}
+		set {
+			if (!IsInitialized) {
+				// We're not initialized so we can't do anything fancy. Just cache value.
+				_contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); ;
+				return;
 			}
-			set {
-				if (!IsInitialized) {
-					// We're not initialized so we can't do anything fancy. Just cache value.
-					_contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y)); ;
-					return;
-				}
 
-				SetContentOffset (value);
-			}
+			SetContentOffset (value);
 		}
+	}
 
-		private void SetContentOffset (Point offset)
-		{
-			var co = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
-			_contentOffset = co;
-			_contentView.Frame = new Rect (_contentOffset, _contentSize);
-			var p = Math.Max (0, -_contentOffset.Y);
-			if (_vertical.Position != p) {
-				_vertical.Position = Math.Max (0, -_contentOffset.Y);
-			}
-			p = Math.Max (0, -_contentOffset.X);
-			if (_horizontal.Position != p) {
-				_horizontal.Position = Math.Max (0, -_contentOffset.X);
-			}
-			SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
-		/// </summary>
-		public bool AutoHideScrollBars {
-			get => _autoHideScrollBars;
-			set {
-				if (_autoHideScrollBars != value) {
-					_autoHideScrollBars = value;
-					if (Subviews.Contains (_vertical)) {
-						_vertical.AutoHideScrollBars = value;
-					}
-					if (Subviews.Contains (_horizontal)) {
-						_horizontal.AutoHideScrollBars = value;
-					}
-					SetNeedsDisplay ();
+	private void SetContentOffset (Point offset)
+	{
+		var co = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
+		_contentOffset = co;
+		_contentView.Frame = new Rect (_contentOffset, _contentSize);
+		var p = Math.Max (0, -_contentOffset.Y);
+		if (_vertical.Position != p) {
+			_vertical.Position = Math.Max (0, -_contentOffset.Y);
+		}
+		p = Math.Max (0, -_contentOffset.X);
+		if (_horizontal.Position != p) {
+			_horizontal.Position = Math.Max (0, -_contentOffset.X);
+		}
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+	/// </summary>
+	public bool AutoHideScrollBars {
+		get => _autoHideScrollBars;
+		set {
+			if (_autoHideScrollBars != value) {
+				_autoHideScrollBars = value;
+				if (Subviews.Contains (_vertical)) {
+					_vertical.AutoHideScrollBars = value;
+				}
+				if (Subviews.Contains (_horizontal)) {
+					_horizontal.AutoHideScrollBars = value;
 				}
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollView"/>
-		/// </summary>
-		public bool KeepContentAlwaysInViewport {
-			get { return _keepContentAlwaysInViewport; }
-			set {
-				if (_keepContentAlwaysInViewport != value) {
-					_keepContentAlwaysInViewport = value;
-					_vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
-					_horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
-					Point p = default;
-					if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width) {
-						p = new Point (_contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0), -_contentOffset.Y);
-					}
-					if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height) {
-						if (p == default) {
-							p = new Point (-_contentOffset.X, _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0));
-						} else {
-							p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0);
-						}
-					}
-					if (p != default) {
-						ContentOffset = p;
+	/// <summary>
+	/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollView"/>
+	/// </summary>
+	public bool KeepContentAlwaysInViewport {
+		get { return _keepContentAlwaysInViewport; }
+		set {
+			if (_keepContentAlwaysInViewport != value) {
+				_keepContentAlwaysInViewport = value;
+				_vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
+				_horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
+				Point p = default;
+				if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width) {
+					p = new Point (_contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0), -_contentOffset.Y);
+				}
+				if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height) {
+					if (p == default) {
+						p = new Point (-_contentOffset.X, _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0));
+					} else {
+						p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0);
 					}
 				}
+				if (p != default) {
+					ContentOffset = p;
+				}
 			}
 		}
+	}
 
-		View _contentBottomRightCorner;
+	View _contentBottomRightCorner;
 
-		/// <summary>
-		/// Adds the view to the scrollview.
-		/// </summary>
-		/// <param name="view">The view to add to the scrollview.</param>
-		public override void Add (View view)
-		{
-			if (view.Id == "contentBottomRightCorner") {
-				_contentBottomRightCorner = view;
-				base.Add (view);
-			} else {
-				if (!IsOverridden (view, "MouseEvent")) {
-					view.MouseEnter += View_MouseEnter;
-					view.MouseLeave += View_MouseLeave;
-				}
-				_contentView.Add (view);
-			}
-			SetNeedsLayout ();
+	/// <summary>
+	/// Adds the view to the scrollview.
+	/// </summary>
+	/// <param name="view">The view to add to the scrollview.</param>
+	public override void Add (View view)
+	{
+		if (view.Id == "contentBottomRightCorner") {
+			_contentBottomRightCorner = view;
+			base.Add (view);
+		} else {
+			if (!IsOverridden (view, "MouseEvent")) {
+				view.MouseEnter += View_MouseEnter;
+				view.MouseLeave += View_MouseLeave;
+			}
+			_contentView.Add (view);
 		}
+		SetNeedsLayout ();
+	}
 
-		void View_MouseLeave (object sender, MouseEventEventArgs e)
-		{
-			if (Application.MouseGrabView != null && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) {
-				Application.UngrabMouse ();
-			}
+	void View_MouseLeave (object sender, MouseEventEventArgs e)
+	{
+		if (Application.MouseGrabView != null && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal) {
+			Application.UngrabMouse ();
 		}
+	}
 
-		void View_MouseEnter (object sender, MouseEventEventArgs e)
-		{
-			Application.GrabMouse (this);
-		}
-
-		/// <summary>
-		/// Gets or sets the visibility for the horizontal scroll indicator.
-		/// </summary>
-		/// <value><c>true</c> if show horizontal scroll indicator; otherwise, <c>false</c>.</value>
-		public bool ShowHorizontalScrollIndicator {
-			get => _showHorizontalScrollIndicator;
-			set {
-				if (value != _showHorizontalScrollIndicator) {
-					_showHorizontalScrollIndicator = value;
-					SetNeedsLayout ();
-					if (value) {
-						_horizontal.OtherScrollBarView = _vertical;
-						base.Add (_horizontal);
-						_horizontal.ShowScrollIndicator = value;
-						_horizontal.AutoHideScrollBars = _autoHideScrollBars;
-						_horizontal.OtherScrollBarView.ShowScrollIndicator = value;
-						_horizontal.MouseEnter += View_MouseEnter;
-						_horizontal.MouseLeave += View_MouseLeave;
-					} else {
-						base.Remove (_horizontal);
-						_horizontal.OtherScrollBarView = null;
-						_horizontal.MouseEnter -= View_MouseEnter;
-						_horizontal.MouseLeave -= View_MouseLeave;
-					}
+	void View_MouseEnter (object sender, MouseEventEventArgs e)
+	{
+		Application.GrabMouse (this);
+	}
+
+	/// <summary>
+	/// Gets or sets the visibility for the horizontal scroll indicator.
+	/// </summary>
+	/// <value><c>true</c> if show horizontal scroll indicator; otherwise, <c>false</c>.</value>
+	public bool ShowHorizontalScrollIndicator {
+		get => _showHorizontalScrollIndicator;
+		set {
+			if (value != _showHorizontalScrollIndicator) {
+				_showHorizontalScrollIndicator = value;
+				SetNeedsLayout ();
+				if (value) {
+					_horizontal.OtherScrollBarView = _vertical;
+					base.Add (_horizontal);
+					_horizontal.ShowScrollIndicator = value;
+					_horizontal.AutoHideScrollBars = _autoHideScrollBars;
+					_horizontal.OtherScrollBarView.ShowScrollIndicator = value;
+					_horizontal.MouseEnter += View_MouseEnter;
+					_horizontal.MouseLeave += View_MouseLeave;
+				} else {
+					base.Remove (_horizontal);
+					_horizontal.OtherScrollBarView = null;
+					_horizontal.MouseEnter -= View_MouseEnter;
+					_horizontal.MouseLeave -= View_MouseLeave;
 				}
-				_vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0);
 			}
+			_vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0);
 		}
+	}
 
-		/// <summary>
-		///   Removes all widgets from this container.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public override void RemoveAll ()
-		{
-			_contentView.RemoveAll ();
-		}
-
-		/// <summary>
-		/// Gets or sets the visibility for the vertical scroll indicator.
-		/// </summary>
-		/// <value><c>true</c> if show vertical scroll indicator; otherwise, <c>false</c>.</value>
-		public bool ShowVerticalScrollIndicator {
-			get => _showVerticalScrollIndicator;
-			set {
-				if (value != _showVerticalScrollIndicator) {
-					_showVerticalScrollIndicator = value;
-					SetNeedsLayout ();
-					if (value) {
-						_vertical.OtherScrollBarView = _horizontal;
-						base.Add (_vertical);
-						_vertical.ShowScrollIndicator = value;
-						_vertical.AutoHideScrollBars = _autoHideScrollBars;
-						_vertical.OtherScrollBarView.ShowScrollIndicator = value;
-						_vertical.MouseEnter += View_MouseEnter;
-						_vertical.MouseLeave += View_MouseLeave;
-					} else {
-						Remove (_vertical);
-						_vertical.OtherScrollBarView = null;
-						_vertical.MouseEnter -= View_MouseEnter;
-						_vertical.MouseLeave -= View_MouseLeave;
-					}
+	/// <summary>
+	///   Removes all widgets from this container.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public override void RemoveAll ()
+	{
+		_contentView.RemoveAll ();
+	}
+
+	/// <summary>
+	/// Gets or sets the visibility for the vertical scroll indicator.
+	/// </summary>
+	/// <value><c>true</c> if show vertical scroll indicator; otherwise, <c>false</c>.</value>
+	public bool ShowVerticalScrollIndicator {
+		get => _showVerticalScrollIndicator;
+		set {
+			if (value != _showVerticalScrollIndicator) {
+				_showVerticalScrollIndicator = value;
+				SetNeedsLayout ();
+				if (value) {
+					_vertical.OtherScrollBarView = _horizontal;
+					base.Add (_vertical);
+					_vertical.ShowScrollIndicator = value;
+					_vertical.AutoHideScrollBars = _autoHideScrollBars;
+					_vertical.OtherScrollBarView.ShowScrollIndicator = value;
+					_vertical.MouseEnter += View_MouseEnter;
+					_vertical.MouseLeave += View_MouseLeave;
+				} else {
+					Remove (_vertical);
+					_vertical.OtherScrollBarView = null;
+					_vertical.MouseEnter -= View_MouseEnter;
+					_vertical.MouseLeave -= View_MouseLeave;
 				}
-				_horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0);
 			}
+			_horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0);
 		}
+	}
 
-		/// <inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			SetViewsNeedsDisplay ();
+	/// <inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		SetViewsNeedsDisplay ();
 
-			var savedClip = ClipToBounds ();
-			Driver.SetAttribute (GetNormalColor ());
-			Clear ();
+		var savedClip = ClipToBounds ();
+		// TODO: It's bad practice for views to always clear a view. It negates clipping.
+		Clear ();
 
-			if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) {
-				_contentView.Draw ();
-			}
+		if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) {
+			_contentView.Draw ();
+		}
 
-			DrawScrollBars ();
+		DrawScrollBars ();
 
-			Driver.Clip = savedClip;
-		}
+		Driver.Clip = savedClip;
+	}
 
-		private void DrawScrollBars ()
-		{
-			if (_autoHideScrollBars) {
-				ShowHideScrollBars ();
-			} else {
-				if (ShowVerticalScrollIndicator) {
-					_vertical.Draw ();
-				}
-				if (ShowHorizontalScrollIndicator) {
-					_horizontal.Draw ();
-				}
-				if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) {
-					SetContentBottomRightCornerVisibility ();
-					_contentBottomRightCorner.Draw ();
-				}
+	private void DrawScrollBars ()
+	{
+		if (_autoHideScrollBars) {
+			ShowHideScrollBars ();
+		} else {
+			if (ShowVerticalScrollIndicator) {
+				_vertical.Draw ();
+			}
+			if (ShowHorizontalScrollIndicator) {
+				_horizontal.Draw ();
+			}
+			if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) {
+				SetContentBottomRightCornerVisibility ();
+				_contentBottomRightCorner.Draw ();
 			}
 		}
+	}
 
-		private void SetContentBottomRightCornerVisibility ()
-		{
-			if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) {
-				_contentBottomRightCorner.Visible = true;
-			} else if (_horizontal.IsAdded || _vertical.IsAdded) {
-				_contentBottomRightCorner.Visible = false;
-			}
+	private void SetContentBottomRightCornerVisibility ()
+	{
+		if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator) {
+			_contentBottomRightCorner.Visible = true;
+		} else if (_horizontal.IsAdded || _vertical.IsAdded) {
+			_contentBottomRightCorner.Visible = false;
 		}
+	}
 
-		void ShowHideScrollBars ()
-		{
-			bool v = false, h = false; bool p = false;
+	void ShowHideScrollBars ()
+	{
+		bool v = false, h = false; bool p = false;
 
-			if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height) {
-				if (ShowVerticalScrollIndicator) {
-					ShowVerticalScrollIndicator = false;
-				}
-				v = false;
-			} else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height) {
-				p = true;
-			} else {
+		if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height) {
+			if (ShowVerticalScrollIndicator) {
+				ShowVerticalScrollIndicator = false;
+			}
+			v = false;
+		} else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height) {
+			p = true;
+		} else {
+			if (!ShowVerticalScrollIndicator) {
+				ShowVerticalScrollIndicator = true;
+			}
+			v = true;
+		}
+		if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width) {
+			if (ShowHorizontalScrollIndicator) {
+				ShowHorizontalScrollIndicator = false;
+			}
+			h = false;
+		} else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p) {
+			if (ShowHorizontalScrollIndicator) {
+				ShowHorizontalScrollIndicator = false;
+			}
+			h = false;
+			if (ShowVerticalScrollIndicator) {
+				ShowVerticalScrollIndicator = false;
+			}
+			v = false;
+		} else {
+			if (p) {
 				if (!ShowVerticalScrollIndicator) {
 					ShowVerticalScrollIndicator = true;
 				}
 				v = true;
 			}
-			if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width) {
-				if (ShowHorizontalScrollIndicator) {
-					ShowHorizontalScrollIndicator = false;
-				}
-				h = false;
-			} else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p) {
-				if (ShowHorizontalScrollIndicator) {
-					ShowHorizontalScrollIndicator = false;
-				}
-				h = false;
-				if (ShowVerticalScrollIndicator) {
-					ShowVerticalScrollIndicator = false;
-				}
-				v = false;
-			} else {
-				if (p) {
-					if (!ShowVerticalScrollIndicator) {
-						ShowVerticalScrollIndicator = true;
-					}
-					v = true;
-				}
-				if (!ShowHorizontalScrollIndicator) {
-					ShowHorizontalScrollIndicator = true;
-				}
-				h = true;
-			}
-			var dim = Dim.Fill (h ? 1 : 0);
-			if (!_vertical.Height.Equals (dim)) {
-				_vertical.Height = dim;
-			}
-			dim = Dim.Fill (v ? 1 : 0);
-			if (!_horizontal.Width.Equals (dim)) {
-				_horizontal.Width = dim;
-			}
-
-			if (v) {
-				_vertical.SetRelativeLayout (Bounds);
-				_vertical.Draw ();
-			}
-			if (h) {
-				_horizontal.SetRelativeLayout (Bounds);
-				_horizontal.Draw ();
-			}
-			SetContentBottomRightCornerVisibility ();
-			if (v && h) {
-				_contentBottomRightCorner.SetRelativeLayout (Bounds);
-				_contentBottomRightCorner.Draw ();
+			if (!ShowHorizontalScrollIndicator) {
+				ShowHorizontalScrollIndicator = true;
 			}
+			h = true;
+		}
+		var dim = Dim.Fill (h ? 1 : 0);
+		if (!_vertical.Height.Equals (dim)) {
+			_vertical.Height = dim;
+		}
+		dim = Dim.Fill (v ? 1 : 0);
+		if (!_horizontal.Width.Equals (dim)) {
+			_horizontal.Width = dim;
 		}
 
-		void SetViewsNeedsDisplay ()
-		{
-			foreach (View view in _contentView.Subviews) {
-				view.SetNeedsDisplay ();
-			}
+		if (v) {
+			_vertical.SetRelativeLayout (Bounds);
+			_vertical.Draw ();
+		}
+		if (h) {
+			_horizontal.SetRelativeLayout (Bounds);
+			_horizontal.Draw ();
+		}
+		SetContentBottomRightCornerVisibility ();
+		if (v && h) {
+			_contentBottomRightCorner.SetRelativeLayout (Bounds);
+			_contentBottomRightCorner.Draw ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			if (InternalSubviews.Count == 0)
-				Move (0, 0);
-			else
-				base.PositionCursor ();
-		}
-
-		/// <summary>
-		/// Scrolls the view up.
-		/// </summary>
-		/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
-		/// <param name="lines">Number of lines to scroll.</param>
-		public bool ScrollUp (int lines)
-		{
-			if (_contentOffset.Y < 0) {
-				ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0));
-				return true;
-			}
-			return false;
+	void SetViewsNeedsDisplay ()
+	{
+		foreach (View view in _contentView.Subviews) {
+			view.SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Scrolls the view to the left
-		/// </summary>
-		/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
-		/// <param name="cols">Number of columns to scroll by.</param>
-		public bool ScrollLeft (int cols)
-		{
-			if (_contentOffset.X < 0) {
-				ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y);
-				return true;
-			}
-			return false;
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		if (InternalSubviews.Count == 0)
+			Move (0, 0);
+		else
+			base.PositionCursor ();
+	}
+
+	/// <summary>
+	/// Scrolls the view up.
+	/// </summary>
+	/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
+	/// <param name="lines">Number of lines to scroll.</param>
+	public bool ScrollUp (int lines)
+	{
+		if (_contentOffset.Y < 0) {
+			ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0));
+			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Scrolls the view down.
-		/// </summary>
-		/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
-		/// <param name="lines">Number of lines to scroll.</param>
-		public bool ScrollDown (int lines)
-		{
-			if (_vertical.CanScroll (lines, out _, true)) {
-				ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines);
-				return true;
-			}
-			return false;
+	/// <summary>
+	/// Scrolls the view to the left
+	/// </summary>
+	/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
+	/// <param name="cols">Number of columns to scroll by.</param>
+	public bool ScrollLeft (int cols)
+	{
+		if (_contentOffset.X < 0) {
+			ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y);
+			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Scrolls the view to the right.
-		/// </summary>
-		/// <returns><c>true</c>, if right was scrolled, <c>false</c> otherwise.</returns>
-		/// <param name="cols">Number of columns to scroll by.</param>
-		public bool ScrollRight (int cols)
-		{
-			if (_horizontal.CanScroll (cols, out _)) {
-				ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y);
-				return true;
-			}
-			return false;
+	/// <summary>
+	/// Scrolls the view down.
+	/// </summary>
+	/// <returns><c>true</c>, if left was scrolled, <c>false</c> otherwise.</returns>
+	/// <param name="lines">Number of lines to scroll.</param>
+	public bool ScrollDown (int lines)
+	{
+		if (_vertical.CanScroll (lines, out _, true)) {
+			ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines);
+			return true;
 		}
+		return false;
+	}
 
-		///<inheritdoc/>
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			if (base.ProcessKey (kb))
-				return true;
+	/// <summary>
+	/// Scrolls the view to the right.
+	/// </summary>
+	/// <returns><c>true</c>, if right was scrolled, <c>false</c> otherwise.</returns>
+	/// <param name="cols">Number of columns to scroll by.</param>
+	public bool ScrollRight (int cols)
+	{
+		if (_horizontal.CanScroll (cols, out _)) {
+			ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y);
+			return true;
+		}
+		return false;
+	}
 
-			var result = InvokeKeybindings (kb);
-			if (result != null)
-				return (bool)result;
+	///<inheritdoc/>
+	public override bool ProcessKey (KeyEvent kb)
+	{
+		if (base.ProcessKey (kb))
+			return true;
 
-			return false;
-		}
+		var result = InvokeKeybindings (kb);
+		if (result != null)
+			return (bool)result;
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp &&
-				me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft &&
-				//				me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
-				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+		return false;
+	}
 
-				return false;
-			}
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp &&
+			me.Flags != MouseFlags.WheeledRight && me.Flags != MouseFlags.WheeledLeft &&
+			//				me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
+			!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
 
-			if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) {
-				ScrollDown (1);
-			} else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) {
-				ScrollUp (1);
-			} else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) {
-				ScrollRight (1);
-			} else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) {
-				ScrollLeft (1);
-			} else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator) {
-				_vertical.MouseEvent (me);
-			} else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) {
-				_horizontal.MouseEvent (me);
-			} else if (IsOverridden (me.View, "MouseEvent")) {
-				Application.UngrabMouse ();
-			}
-			return true;
+			return false;
 		}
 
-		///<inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			if (!_showVerticalScrollIndicator) {
-				// It was not added to SuperView, so it won't get disposed automatically
-				_vertical?.Dispose ();
-			}
-			if (!_showHorizontalScrollIndicator) {
-				// It was not added to SuperView, so it won't get disposed automatically
-				_horizontal?.Dispose ();
-			}
-			base.Dispose (disposing);
+		if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator) {
+			ScrollDown (1);
+		} else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator) {
+			ScrollUp (1);
+		} else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator) {
+			ScrollRight (1);
+		} else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator) {
+			ScrollLeft (1);
+		} else if (me.X == _vertical.Frame.X && ShowVerticalScrollIndicator) {
+			_vertical.MouseEvent (me);
+		} else if (me.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator) {
+			_horizontal.MouseEvent (me);
+		} else if (IsOverridden (me.View, "MouseEvent")) {
+			Application.UngrabMouse ();
 		}
+		return true;
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			if (Subviews.Count == 0 || !Subviews.Any (subview => subview.CanFocus)) {
-				Application.Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-			}
+	///<inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		if (!_showVerticalScrollIndicator) {
+			// It was not added to SuperView, so it won't get disposed automatically
+			_vertical?.Dispose ();
+		}
+		if (!_showHorizontalScrollIndicator) {
+			// It was not added to SuperView, so it won't get disposed automatically
+			_horizontal?.Dispose ();
+		}
+		base.Dispose (disposing);
+	}
 
-			return base.OnEnter (view);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		if (Subviews.Count == 0 || !Subviews.Any (subview => subview.CanFocus)) {
+			Application.Driver?.SetCursorVisibility (CursorVisibility.Invisible);
 		}
+
+		return base.OnEnter (view);
 	}
 }

+ 2 - 1
Terminal.Gui/Views/StatusBar.cs

@@ -121,8 +121,9 @@ namespace Terminal.Gui {
 		{
 			Move (0, 0);
 			Driver.SetAttribute (GetNormalColor ());
-			for (int i = 0; i < Frame.Width; i++)
+			for (int i = 0; i < Frame.Width; i++) {
 				Driver.AddRune ((Rune)' ');
+			}
 
 			Move (1, 0);
 			var scheme = GetNormalColor ();

+ 1 - 1
Terminal.Gui/Views/TabView.cs

@@ -171,7 +171,7 @@ namespace Terminal.Gui {
 				// Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
 				tabsBar.Y = Pos.Percent (0);
 			}
-
+			LayoutSubviews ();
 			SetNeedsDisplay ();
 		}
 

+ 10 - 16
Terminal.Gui/Views/TextField.cs

@@ -461,7 +461,7 @@ namespace Terminal.Gui {
 			var roc = GetReadOnlyColor ();
 			for (int idx = p; idx < tcount; idx++) {
 				var rune = _text [idx];
-				var cols = ((Rune)rune).GetColumns ();
+				var cols = rune.GetColumns ();
 				if (idx == _point && HasFocus && !Used && _length == 0 && !ReadOnly) {
 					Driver.SetAttribute (selColor);
 				} else if (ReadOnly) {
@@ -474,7 +474,7 @@ namespace Terminal.Gui {
 					Driver.SetAttribute (idx >= _start && _length > 0 && idx < _start + _length ? selColor : ColorScheme.Focus);
 				}
 				if (col + cols <= width) {
-					Driver.AddRune ((Rune)(Secret ? CM.Glyphs.Dot : rune));
+					Driver.AddRune ((Secret ? CM.Glyphs.Dot : rune));
 				}
 				if (!TextModel.SetCol (ref col, width, cols)) {
 					break;
@@ -567,7 +567,7 @@ namespace Terminal.Gui {
 				return;
 
 			int offB = OffSetBackground ();
-			bool need = !_needsDisplay.IsEmpty || !Used;
+			bool need = NeedsDisplay || !Used;
 			if (_point < _first) {
 				_first = _point;
 				need = true;
@@ -1233,14 +1233,11 @@ namespace Terminal.Gui {
 
 		List<Rune> DeleteSelectedText ()
 		{
-			string actualText = Text;
 			SetSelectedStartSelectedLength ();
 			int selStart = SelectedStart > -1 ? _start : _point;
-			(var size, var _) = TextModel.DisplaySize (_text, 0, selStart, false);
-			(var size2, var _) = TextModel.DisplaySize (_text, selStart, selStart + _length, false);
-			(var size3, var _) = TextModel.DisplaySize (_text, selStart + _length, actualText.GetRuneCount (), false);
-			var newText = actualText [..size] +
-				actualText.Substring (size + size2, size3);
+			var newText = StringExtensions.ToString (_text.GetRange (0, selStart)) +
+				 StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
+
 			ClearAllSelection ();
 			_point = selStart >= newText.GetRuneCount () ? newText.GetRuneCount () : selStart;
 			return newText.ToRuneList ();
@@ -1257,14 +1254,11 @@ namespace Terminal.Gui {
 
 			SetSelectedStartSelectedLength ();
 			int selStart = _start == -1 ? CursorPosition : _start;
-			string actualText = Text;
-			(int size, int _) = TextModel.DisplaySize (_text, 0, selStart, false);
-			(var size2, var _) = TextModel.DisplaySize (_text, selStart, selStart + _length, false);
-			(var size3, var _) = TextModel.DisplaySize (_text, selStart + _length, actualText.GetRuneCount (), false);
 			string cbTxt = Clipboard.Contents.Split ("\n") [0] ?? "";
-			Text = actualText [..size] +
+			Text = StringExtensions.ToString (_text.GetRange (0, selStart)) +
 				cbTxt +
-				actualText.Substring (size + size2, size3);
+				StringExtensions.ToString (_text.GetRange (selStart + _length, _text.Count - (selStart + _length)));
+
 			_point = selStart + cbTxt.GetRuneCount ();
 			ClearAllSelection ();
 			SetNeedsDisplay ();
@@ -1375,4 +1369,4 @@ namespace Terminal.Gui {
 			((TextField)HostControl).CursorPosition = column;
 		}
 	}
-}
+}

+ 12 - 8
Terminal.Gui/Views/TextView.cs

@@ -2407,7 +2407,11 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override Attribute GetNormalColor ()
 		{
-			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+			ColorScheme cs = ColorScheme;
+			if (ColorScheme == null) {
+				cs = new ColorScheme ();
+			}
+			return Enabled ? cs.Focus : cs.Disabled;
 		}
 
 		/// <summary>
@@ -3063,10 +3067,10 @@ namespace Terminal.Gui {
 				InsertText (new KeyEvent () { Key = key });
 			}
 
-			if (_needsDisplay.IsEmpty) {
-				PositionCursor ();
-			} else {
+			if (NeedsDisplay) {
 				Adjust ();
+			} else {
+				PositionCursor ();
 			}
 		}
 
@@ -3235,7 +3239,7 @@ namespace Terminal.Gui {
 		{
 			var offB = OffSetBackground ();
 			var line = GetCurrentLine ();
-			bool need = !_needsDisplay.IsEmpty || _wrapNeeded || !Used;
+			bool need = NeedsDisplay || _wrapNeeded || !Used;
 			var tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
 			var dSize = TextModel.DisplaySize (line, _leftColumn, _currentColumn, true, TabWidth);
 			if (!_wordWrap && _currentColumn < _leftColumn) {
@@ -4395,10 +4399,10 @@ namespace Terminal.Gui {
 
 		void DoNeededAction ()
 		{
-			if (_needsDisplay.IsEmpty) {
-				PositionCursor ();
-			} else {
+			if (NeedsDisplay) {
 				Adjust ();
+			} else {
+				PositionCursor ();
 			}
 		}
 

+ 1 - 1
Terminal.Gui/Views/TileView.cs

@@ -898,7 +898,7 @@ namespace Terminal.Gui {
 					// End Drag
 
 					Application.UngrabMouse ();
-					Driver.UncookMouse ();
+					//Driver.UncookMouse ();
 					FinalisePosition (
 						dragOrignalPos,
 						Orientation == Orientation.Horizontal ? Y : X);

+ 6 - 4
Terminal.Gui/Views/Toplevel.cs

@@ -757,9 +757,10 @@ namespace Terminal.Gui {
 				return;
 			}
 
-			if (!_needsDisplay.IsEmpty || _subViewNeedsDisplay || LayoutNeeded) {
-				Driver.SetAttribute (GetNormalColor ());
-				Clear (ViewToScreen (Bounds));
+			if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) {
+				//Driver.SetAttribute (GetNormalColor ());
+				// TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc...
+				Clear ();
 				LayoutSubviews ();
 				PositionToplevels ();
 
@@ -776,9 +777,10 @@ namespace Terminal.Gui {
 					}
 				}
 
+				// This should not be here, but in base
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
-						view.SetNeedsLayout ();
+						//view.SetNeedsLayout ();
 						view.SetNeedsDisplay (view.Bounds);
 						view.SetSubViewNeedsDisplay ();
 					}

+ 1 - 1
Terminal.Gui/Views/ToplevelOverlapped.cs

@@ -97,7 +97,7 @@ namespace Terminal.Gui {
 			}
 
 			foreach (var top in _toplevels) {
-				if (top != Current && top.Visible && (!top._needsDisplay.IsEmpty || top._subViewNeedsDisplay || top.LayoutNeeded)) {
+				if (top != Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded)) {
 					OverlappedTop.SetSubViewNeedsDisplay ();
 					return true;
 				}

+ 1 - 3
Terminal.sln

@@ -14,15 +14,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E143FB1F-0B88-48CB-9086-72CDCECFCD22}"
 	ProjectSection(SolutionItems) = preProject
-		.editorconfig = .editorconfig
 		.gitignore = .gitignore
+		.github\CODEOWNERS = .github\CODEOWNERS
 		CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
 		CONTRIBUTING.md = CONTRIBUTING.md
 		.github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml
-		.github\GitVersion.yml = .github\GitVersion.yml
 		.github\workflows\publish.yml = .github\workflows\publish.yml
 		README.md = README.md
-		Release.ps1 = Release.ps1
 		testenvironments.json = testenvironments.json
 	EndProjectSection
 EndProject

+ 3 - 0
UICatalog/Properties/launchSettings.json

@@ -50,6 +50,9 @@
     "Windows & FrameViews": {
       "commandName": "Project",
       "commandLineArgs": "\"Windows & FrameViews\""
+    },
+    "Profile 1": {
+      "commandName": "Executable"
     }
   }
 }

+ 2 - 2
UICatalog/Scenarios/BasicColors.cs

@@ -87,8 +87,8 @@ namespace UICatalog.Scenarios {
 
 			Application.RootMouseEvent = (e) => {
 				if (e.View != null) {
-					var colorValue = e.View.GetNormalColor ().Value;
-					Application.Driver.GetColors (colorValue, out Color fore, out Color back);
+					var fore = e.View.GetNormalColor ().Foreground;
+					var back = e.View.GetNormalColor ().Background;
 					lblForeground.Text = fore.ToString ();
 					viewForeground.ColorScheme.Normal = new Attribute (fore, fore);
 					lblBackground.Text = back.ToString ();

+ 290 - 73
UICatalog/Scenarios/CharacterMap.cs

@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Data;
 using System.Globalization;
 using System.Linq;
 using System.Net.Http;
@@ -11,6 +12,7 @@ using System.Text.Json;
 using System.Text.Unicode;
 using System.Threading.Tasks;
 using Terminal.Gui;
+using static Terminal.Gui.SpinnerStyle;
 using static Terminal.Gui.TableView;
 
 namespace UICatalog.Scenarios;
@@ -27,24 +29,31 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("ScrollView")]
 public class CharacterMap : Scenario {
 	CharMap _charMap;
-	Label _errorLabel;
+	public Label _errorLabel;
 	TableView _categoryList;
 
+	// Don't create a Window, just return the top-level view
+	public override void Init ()
+	{
+		Application.Init ();
+		Application.Top.ColorScheme = Colors.Base;
+	}
+
 	public override void Setup ()
 	{
 		_charMap = new CharMap () {
 			X = 0,
-			Y = 0,
+			Y = 1,
 			Height = Dim.Fill ()
 		};
-		Win.Add (_charMap);
+		Application.Top.Add (_charMap);
 
 		var jumpLabel = new Label ("Jump To Code Point:") { X = Pos.Right (_charMap) + 1, Y = Pos.Y (_charMap) };
-		Win.Add (jumpLabel);
+		Application.Top.Add (jumpLabel);
 		var jumpEdit = new TextField () { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" };
-		Win.Add (jumpEdit);
-		_errorLabel = new Label ("") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
-		Win.Add (_errorLabel);
+		Application.Top.Add (jumpEdit);
+		_errorLabel = new Label ("err") { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"] };
+		Application.Top.Add (_errorLabel);
 
 		jumpEdit.TextChanged += JumpEdit_TextChanged;
 
@@ -96,15 +105,42 @@ public class CharacterMap : Scenario {
 			_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
 		};
 
-		Win.Add (_categoryList);
+		Application.Top.Add (_categoryList);
 
 		_charMap.SelectedCodePoint = 0;
 		//jumpList.Refresh ();
 		_charMap.SetFocus ();
 
 		_charMap.Width = Dim.Fill () - _categoryList.Width;
+
+		var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_Quit", $"{Application.QuitKey}", () => Application.RequestStop ()),
+			}),
+			new MenuBarItem ("_Options", new MenuItem [] {
+				CreateMenuShowWidth (),
+			})
+		});
+		Application.Top.Add (menu);
+
+		//_charMap.Hover += (s, a) => {
+		//	_errorLabel.Text = $"U+{a.Item:x5} {(Rune)a.Item}";
+		//};
 	}
 
+	MenuItem CreateMenuShowWidth ()
+	{
+		var item = new MenuItem {
+			Title = "_Show Glyph Width",
+		};
+		item.CheckType |= MenuItemCheckStyle.Checked;
+		item.Checked = _charMap?.ShowGlyphWidths;
+		item.Action += () => {
+			_charMap.ShowGlyphWidths = (bool)(item.Checked = !item.Checked);
+		};
+
+		return item;
+	}
 
 	EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
 	{
@@ -177,7 +213,7 @@ public class CharacterMap : Scenario {
 			_errorLabel.Text = $"Beyond maximum codepoint";
 			return;
 		}
-		_errorLabel.Text = $"U+{result:x4}";
+		_errorLabel.Text = $"U+{result:x5}";
 
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		_categoryList.SelectedRow = table.Data
@@ -200,8 +236,7 @@ class CharMap : ScrollView {
 		get => _start;
 		set {
 			_start = value;
-			_selected = value;
-			ContentOffset = new Point (0, (int)(_start / 16));
+			SelectedCodePoint = value;
 			SetNeedsDisplay ();
 		}
 	}
@@ -216,15 +251,16 @@ class CharMap : ScrollView {
 		get => _selected;
 		set {
 			_selected = value;
-			var col = Cursor.X;
-			var row = Cursor.Y;
-			var height = (Bounds.Height / ROW_HEIGHT) - (ShowHorizontalScrollIndicator ? 2 : 1);
+			var row = (SelectedCodePoint / 16 * _rowHeight);
+			var col = (SelectedCodePoint % 16 * COLUMN_WIDTH);
+
+			var height = (Bounds.Height) - (ShowHorizontalScrollIndicator ? 2 : 1);
 			if (row + ContentOffset.Y < 0) {
 				// Moving up.
 				ContentOffset = new Point (ContentOffset.X, row);
 			} else if (row + ContentOffset.Y >= height) {
 				// Moving down.
-				ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + ROW_HEIGHT));
+				ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight));
 			}
 			var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
 			if (col + ContentOffset.X < 0) {
@@ -239,10 +275,15 @@ class CharMap : ScrollView {
 		}
 	}
 
+	public event EventHandler<ListViewItemEventArgs> Hover;
+
+	/// <summary>
+	/// Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates
+	/// </summary>
 	public Point Cursor {
 		get {
-			var row = SelectedCodePoint / 16;
-			var col = (SelectedCodePoint - row * 16) * COLUMN_WIDTH;
+			var row = (SelectedCodePoint / 16 * _rowHeight) + ContentOffset.Y + 1;
+			var col = (SelectedCodePoint % 16 * COLUMN_WIDTH) + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding
 			return new Point (col, row);
 		}
 		set => throw new NotImplementedException ();
@@ -250,35 +291,42 @@ class CharMap : ScrollView {
 
 	public override void PositionCursor ()
 	{
-		if (HasFocus && Cursor.X + ContentOffset.X + RowLabelWidth + 1 >= RowLabelWidth &&
-			Cursor.X + ContentOffset.X + RowLabelWidth + 1 < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
-			Cursor.Y + ContentOffset.Y + 1 > 0 &&
-			Cursor.Y + ContentOffset.Y + 1 < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
-
+		if (HasFocus &&
+			Cursor.X >= RowLabelWidth &&
+			Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
+			Cursor.Y > 0 &&
+			Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
 			Driver.SetCursorVisibility (CursorVisibility.Default);
-			Move (Cursor.X + ContentOffset.X + RowLabelWidth + 1, Cursor.Y + ContentOffset.Y + 1);
+			Move (Cursor.X, Cursor.Y);
 		} else {
 			Driver.SetCursorVisibility (CursorVisibility.Invisible);
 		}
 	}
 
+	public bool ShowGlyphWidths {
+		get => _rowHeight == 2;
+		set {
+			_rowHeight = value ? 2 : 1;
+			SetNeedsDisplay ();
+		}
+	}
 
 	int _start = 0;
 	int _selected = 0;
 
-	public const int COLUMN_WIDTH = 3;
-	public const int ROW_HEIGHT = 1;
+	const int COLUMN_WIDTH = 3;
+	int _rowHeight = 1;
 
 	public static int MaxCodePoint => 0x10FFFF;
 
-	public static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
-	public static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
+	static int RowLabelWidth => $"U+{MaxCodePoint:x5}".Length + 1;
+	static int RowWidth => RowLabelWidth + (COLUMN_WIDTH * 16);
 
 	public CharMap ()
 	{
 		ColorScheme = Colors.Dialog;
 		CanFocus = true;
-		ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)));
+		ContentSize = new Size (CharMap.RowWidth, (int)((MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight));
 
 		AddCommand (Command.ScrollUp, () => {
 			if (SelectedCodePoint >= 16) {
@@ -305,12 +353,12 @@ class CharMap : ScrollView {
 			return true;
 		});
 		AddCommand (Command.PageUp, () => {
-			var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+			var page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			return true;
 		});
 		AddCommand (Command.PageDown, () => {
-			var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+			var page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			return true;
 		});
@@ -336,26 +384,34 @@ class CharMap : ScrollView {
 
 	public override void OnDrawContent (Rect contentArea)
 	{
-		if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) {
-			ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2));
-			var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
-			if (Cursor.X + ContentOffset.X >= width) {
-				// Snap to the selected glyph.
-				ContentOffset = new Point (Math.Min (Cursor.X, Cursor.X - width + COLUMN_WIDTH), ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
-			} else {
-				ContentOffset = new Point (ContentOffset.X - Cursor.X, ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
-			}
-		} else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePoint / 16 + 1)) {
-			ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 1));
-			// Snap 1st column into view if it's been scrolled horizontally
-			ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
-		}
+		//if (ShowHorizontalScrollIndicator && ContentSize.Height < (int)(MaxCodePoint / 16 + 2)) {
+		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 2));
+		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16) * _rowHeight + 2);
+		//	var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+		//	if (Cursor.X + ContentOffset.X >= width) {
+		//		// Snap to the selected glyph.
+		//		ContentOffset = new Point (
+		//			Math.Min (Cursor.X, Cursor.X - width + COLUMN_WIDTH),
+		//			ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
+		//	} else {
+		//		ContentOffset = new Point (
+		//			ContentOffset.X - Cursor.X,
+		//			ContentOffset.Y == -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
+		//	}
+		//} else if (!ShowHorizontalScrollIndicator && ContentSize.Height > (int)(MaxCodePoint / 16 + 1)) {
+		//	//ContentSize = new Size (CharMap.RowWidth, (int)(MaxCodePoint / 16 + 1));
+		//	// Snap 1st column into view if it's been scrolled horizontally
+		//	ContentOffset = new Point (0, ContentOffset.Y < -ContentSize.Height + Bounds.Height ? ContentOffset.Y - 1 : ContentOffset.Y);
+		//}
 		base.OnDrawContent (contentArea);
 	}
 
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	public override void OnDrawContentComplete (Rect contentArea)
 	{
+		if (contentArea.Height == 0 || contentArea.Width == 0) {
+			return;
+		}
 		Rect viewport = new Rect (ContentOffset,
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 				Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0)));
@@ -370,8 +426,8 @@ class CharMap : ScrollView {
 			Driver.Clip = new Rect (Driver.Clip.Location, new Size (Driver.Clip.Width - 1, Driver.Clip.Height));
 		}
 
-		var cursorCol = Cursor.X;
-		var cursorRow = Cursor.Y;
+		var cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1;
+		var cursorRow = Cursor.Y - ContentOffset.Y - 1;
 
 		Driver.SetAttribute (GetHotNormalColor ());
 		Move (0, 0);
@@ -382,7 +438,7 @@ class CharMap : ScrollView {
 				Move (x, 0);
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
-				Driver.SetAttribute (HasFocus && (cursorCol + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ());
+				Driver.SetAttribute (HasFocus && (cursorCol + ContentOffset.X + RowLabelWidth == x) ? ColorScheme.HotFocus : GetHotNormalColor ());
 				Driver.AddStr ($"{hexDigit:x}");
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
@@ -390,7 +446,10 @@ class CharMap : ScrollView {
 		}
 
 		var firstColumnX = viewport.X + RowLabelWidth;
-		for (int row = -ContentOffset.Y, y = 1; row <= (-ContentOffset.Y) + (Bounds.Height / ROW_HEIGHT); row++, y += ROW_HEIGHT) {
+		for (int y = 1; y < Bounds.Height; y++) {
+			// What row is this?
+			var row = (y - ContentOffset.Y - 1) / _rowHeight;
+
 			var val = (row) * 16;
 			if (val > MaxCodePoint) {
 				continue;
@@ -398,16 +457,26 @@ class CharMap : ScrollView {
 			Move (firstColumnX + COLUMN_WIDTH, y);
 			Driver.SetAttribute (GetNormalColor ());
 			for (int col = 0; col < 16; col++) {
+
 				var x = firstColumnX + COLUMN_WIDTH * col + 1;
+
 				Move (x, y);
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 					Driver.SetAttribute (GetFocusColor ());
 				}
+				var scalar = val + col;
+				Rune rune = (Rune)'?';
+				if (Rune.IsValid (scalar)) {
+					rune = new Rune (scalar);
+				}
+				var width = rune.GetColumns ();
 
-				if (char.IsSurrogate ((char)(val + col))) {
-					Driver.AddRune (Rune.ReplacementChar);
+				// are we at first row of the row?
+				if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
+					Driver.AddRune (rune);
 				} else {
-					Driver.AddRune (new Rune (val + col));
+					Driver.SetAttribute (ColorScheme.HotNormal);
+					Driver.AddStr ($"{width}");
 				}
 
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
@@ -416,8 +485,11 @@ class CharMap : ScrollView {
 			}
 			Move (0, y);
 			Driver.SetAttribute (HasFocus && (cursorRow + ContentOffset.Y + 1 == y) ? ColorScheme.HotFocus : ColorScheme.HotNormal);
-			var rowLabel = $"U+{val / 16:x5}_ ";
-			Driver.AddStr (rowLabel);
+			if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) {
+				Driver.AddStr ($"U+{val / 16:x5}_ ");
+			} else {
+				Driver.AddStr (new string (' ', RowLabelWidth));
+			}
 		}
 		Driver.Clip = oldClip;
 	}
@@ -426,30 +498,38 @@ class CharMap : ScrollView {
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	{
 		var me = args.MouseEvent;
-		if (me.Flags == MouseFlags.ReportMousePosition || (me.Flags != MouseFlags.Button1Clicked &&
-			me.Flags != MouseFlags.Button1DoubleClicked)) { // && me.Flags != _contextMenu.MouseFlags)) {
+		if (me.Flags != MouseFlags.ReportMousePosition && me.Flags != MouseFlags.Button1Clicked &&
+			me.Flags != MouseFlags.Button1DoubleClicked) {
 			return;
 		}
 
-		if (me.X < RowLabelWidth) {
-			return;
+		if (me.Y == 0) {
+			me.Y = Cursor.Y;
 		}
 
-		if (me.Y < 1) {
-			return;
+		if (me.Y > 0) {
+		}
+
+		if (me.X < RowLabelWidth || me.X > RowLabelWidth + (16 * COLUMN_WIDTH) - 1) {
+			me.X = Cursor.X;
 		}
 
-		var row = me.Y - 1;
+		var row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header
 		var col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH;
-		if (row < 0 || row > Bounds.Height || col < 0 || col > 15) {
-			return;
+
+		if (col > 15) {
+			col = 15;
 		}
 
-		var val = (row - ContentOffset.Y) * 16 + col;
+		var val = (row) * 16 + col;
 		if (val > MaxCodePoint) {
 			return;
 		}
 
+		if (me.Flags == MouseFlags.ReportMousePosition) {
+			Hover?.Invoke (this, new ListViewItemEventArgs (val, null));
+		}
+
 		if (me.Flags == MouseFlags.Button1Clicked) {
 			SelectedCodePoint = val;
 			return;
@@ -535,6 +615,7 @@ class CharMap : ScrollView {
 		};
 		Application.Run (waitIndicator);
 
+
 		if (!string.IsNullOrEmpty (decResponse)) {
 			string name = string.Empty;
 
@@ -551,25 +632,156 @@ class CharMap : ScrollView {
 				//&& property3Element.TryGetProperty ("nestedProperty", out JsonElement nestedPropertyElement)) {
 				//	Console.WriteLine (nestedPropertyElement.GetString ());
 				//}
+				decResponse = JsonSerializer.Serialize (document.RootElement, new
+						JsonSerializerOptions {
+					WriteIndented = true
+				});
 			}
 
-			var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x4}";
-			switch (MessageBox.Query (title, decResponse, "Copy _Glyph", "Copy Code _Point", "Cancel")) {
-			case 0:
+			var title = $"{ToCamelCase (name)} - {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}";
+
+			var copyGlyph = new Button ("Copy _Glyph");
+			var copyCP = new Button ("Copy Code _Point");
+			var cancel = new Button ("Cancel");
+
+			var dlg = new Dialog (copyGlyph, copyCP, cancel) {
+				Title = title
+			};
+
+			copyGlyph.Clicked += (s, a) => {
 				CopyGlyph ();
-				break;
-			case 1:
+				dlg.RequestStop ();
+			};
+			copyCP.Clicked += (s, a) => {
 				CopyCodePoint ();
-				break;
-			}
+				dlg.RequestStop ();
+			};
+			cancel.Clicked += (s, a) => dlg.RequestStop ();
+
+			var rune = (Rune)SelectedCodePoint;
+			var label = new Label () {
+				Text = "IsAscii: ",
+				X = 0,
+				Y = 0
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.IsAscii}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = ", Bmp: ",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.IsBmp}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = ", CombiningMark: ",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.IsCombiningMark ()}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = ", SurrogatePair: ",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.IsSurrogatePair ()}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = ", Plane: ",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.Plane}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = "Columns: ",
+				X = 0,
+				Y = Pos.Bottom (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.GetColumns ()}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = ", Utf16SequenceLength: ",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+
+			label = new Label () {
+				Text = $"{rune.Utf16SequenceLength}",
+				X = Pos.Right (label),
+				Y = Pos.Top (label)
+			};
+			dlg.Add (label);
+			label = new Label () {
+				Text = $"Code Point Information from {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:",
+				X = 0,
+				Y = Pos.Bottom (label)
+			};
+			dlg.Add (label);
+
+			var json = new TextView () {
+				X = 0,
+				Y = Pos.Bottom (label),
+				Width = Dim.Fill (),
+				Height = Dim.Fill (2),
+				ReadOnly = true,
+				Text = decResponse
+			};
+			dlg.Add (json);
+
+			Application.Run (dlg);
+
 		} else {
-			MessageBox.ErrorQuery ("Code Point API", $"{UcdApiClient.BaseUrl} did not return a result.", "Ok");
+			MessageBox.ErrorQuery ("Code Point API", $"{UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint} did not return a result for\r\n {new Rune (SelectedCodePoint)} U+{SelectedCodePoint:x5}.", "Ok");
 		}
 		// BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
 		Application.GrabMouse (this);
 	}
 
-
 	public override bool OnEnter (View view)
 	{
 		if (IsInitialized) {
@@ -577,6 +789,12 @@ class CharMap : ScrollView {
 		}
 		return base.OnEnter (view);
 	}
+
+	public override bool OnLeave (View view)
+	{
+		Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return base.OnLeave (view);
+	}
 }
 
 public class UcdApiClient {
@@ -637,7 +855,6 @@ class UnicodeRange {
 
 			new UnicodeRange (0x1F130, 0x1F149   ,"Squared Latin Capital Letters"),
 			new UnicodeRange (0x12400, 0x1240f   ,"Cuneiform Numbers and Punctuation"),
-			new UnicodeRange (0x1FA00, 0x1FA0f   ,"Chess Symbols"),
 			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
 			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
 			new UnicodeRange (0x10100, 0x1013F   ,"Aegean Numbers"),

+ 3 - 4
UICatalog/Scenarios/Generic.cs

@@ -12,12 +12,12 @@ namespace UICatalog.Scenarios {
 			//    that reads "Press <hotkey> to Quit". Access this Window with `this.Win`.
 			//  - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`.
 			// To override this, implement an override of `Init`.
-			
+
 			//base.Init ();
-			
+
 			// A common, alternate, implementation where `this.Win` is not used is below. This code
 			// leverages ConfigurationManager to borrow the color scheme settings from UICatalog:
-			
+
 			Application.Init ();
 			ConfigurationManager.Themes.Theme = Theme;
 			ConfigurationManager.Apply ();
@@ -37,7 +37,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Center (),
 			};
 			Application.Top.Add (button);
-
 		}
 	}
 }

+ 136 - 0
UICatalog/Scenarios/Images.cs

@@ -0,0 +1,136 @@
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Collections.Concurrent;
+using System.IO;
+using Terminal.Gui;
+using Attribute = Terminal.Gui.Attribute;
+
+
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Images", Description: "Demonstration of how to render an image with/without true color support.")]
+	[ScenarioCategory ("Colors")]
+	public class Images : Scenario {
+		public override void Setup ()
+		{
+			base.Setup ();
+
+			var x = 0;
+			var y = 0;
+
+			var canTrueColor = Application.Driver.SupportsTrueColor;
+
+			var lblDriverName = new Label ($"Current driver is {Application.Driver.GetType ().Name}") {
+				X = x,
+				Y = y++
+			};
+			Win.Add (lblDriverName);
+			y++;
+
+			var cbSupportsTrueColor = new CheckBox ("Driver supports true color ") {
+				X = x,
+				Y = y++,
+				Checked = canTrueColor,
+				CanFocus = false
+			};
+			Win.Add (cbSupportsTrueColor);
+
+			var cbUseTrueColor = new CheckBox ("Use true color") {
+				X = x,
+				Y = y++,
+				Checked = Application.Driver.UseTrueColor,
+				Enabled = canTrueColor,
+			};
+			cbUseTrueColor.Toggled += (_, evt) => Application.Driver.UseTrueColor = evt.NewValue ?? false;
+			Win.Add (cbUseTrueColor);
+
+			var btnOpenImage = new Button ("Open Image") {
+				X = x,
+				Y = y++
+			};
+			Win.Add (btnOpenImage);
+
+			var imageView = new ImageView () {
+				X = x,
+				Y = y++,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+			};
+			Win.Add (imageView);
+
+
+			btnOpenImage.Clicked += (_, _) => {
+				var ofd = new OpenDialog ("Open Image") { AllowsMultipleSelection = false };
+				Application.Run (ofd);
+
+				if (ofd.Canceled)
+					return;
+
+				var path = ofd.FilePaths [0];
+
+				if (string.IsNullOrWhiteSpace (path)) {
+					return;
+				}
+
+				if (!File.Exists (path)) {
+					return;
+				}
+
+				Image<Rgba32> img;
+
+				try {
+					img = Image.Load<Rgba32> (File.ReadAllBytes (path));
+				} catch (Exception ex) {
+
+					MessageBox.ErrorQuery ("Could not open file", ex.Message, "Ok");
+					return;
+				}
+
+				imageView.SetImage (img);
+			};
+		}
+
+		class ImageView : View {
+
+			private Image<Rgba32> fullResImage;
+			private Image<Rgba32> matchSize;
+
+			ConcurrentDictionary<Rgba32, Attribute> cache = new ConcurrentDictionary<Rgba32, Attribute> ();
+
+			internal void SetImage (Image<Rgba32> image)
+			{
+				fullResImage = image;
+				this.SetNeedsDisplay ();
+			}
+
+			public override void OnDrawContent(Rect bounds)
+			{
+				base.OnDrawContent (bounds);
+
+				if (fullResImage == null) {
+					return;
+				}
+
+				// if we have not got a cached resized image of this size
+				if (matchSize == null || bounds.Width != matchSize.Width || bounds.Height != matchSize.Height) {
+
+					// generate one
+					matchSize = fullResImage.Clone (x => x.Resize (bounds.Width, bounds.Height));
+				}
+
+				for (int y = 0; y < bounds.Height; y++) {
+					for (int x = 0; x < bounds.Width; x++) {
+						var rgb = matchSize [x, y];
+
+						var attr = cache.GetOrAdd (rgb, (rgb) => new Attribute (new TrueColor (), new TrueColor (rgb.R, rgb.G, rgb.B)));
+
+						Driver.SetAttribute (attr);
+						AddRune (x, y, (System.Text.Rune)' ');
+					}
+				}
+			}
+		}
+	}
+}

+ 3 - 2
UICatalog/Scenarios/LineDrawing.cs

@@ -142,8 +142,9 @@ namespace UICatalog.Scenarios {
 				foreach (var canvas in _layers) {
 
 					foreach (var c in canvas.GetCellMap ()) {
-						Driver.SetAttribute (c.Value.Attribute?.Value ?? ColorScheme.Normal);
-						this.AddRune (c.Key.X, c.Key.Y, c.Value.Rune.Value);
+						Driver.SetAttribute (c.Value.Attribute ?? ColorScheme.Normal);
+						// TODO: #2616 - Support combining sequences that don't normalize
+						this.AddRune (c.Key.X, c.Key.Y, c.Value.Runes [0]);
 					}
 				}
 			}

+ 2 - 2
UICatalog/Scenarios/ProgressBarStyles.cs

@@ -79,7 +79,7 @@ namespace UICatalog.Scenarios {
 							_fractionTimer = null;
 							button.Enabled = true;
 						}
-						Application.MainLoop.Driver.Wakeup ();
+						Application.MainLoop.MainLoopDriver.Wakeup ();
 					}, null, 0, _timerTick);
 				}
 			};
@@ -128,7 +128,7 @@ namespace UICatalog.Scenarios {
 				marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
 				marqueesBlocksPB.Pulse ();
 				marqueesContinuousPB.Pulse ();
-				Application.MainLoop.Driver.Wakeup ();
+				Application.MainLoop.MainLoopDriver.Wakeup ();
 			}, null, 0, 300);
 
 			Application.Top.Unloaded += Top_Unloaded;

+ 0 - 1
UICatalog/Scenarios/TableEditor.cs

@@ -786,7 +786,6 @@ namespace UICatalog.Scenarios {
 			new UnicodeRange(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"),
 			new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
 			new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
-			new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"),
 			new UnicodeRange((uint)(CharMap.MaxCodePoint - 16), (uint)CharMap.MaxCodePoint,"End"),
 
 			new UnicodeRange (0x0020 ,0x007F        ,"Basic Latin"),

+ 116 - 0
UICatalog/Scenarios/TrueColors.cs

@@ -0,0 +1,116 @@
+using System;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+
+	[ScenarioMetadata (Name: "True Colors", Description: "Demonstration of true color support.")]
+	[ScenarioCategory ("Colors")]
+	public class TrueColors : Scenario {
+
+		public override void Setup ()
+		{
+			var x = 2;
+			var y = 1;
+
+			var canTrueColor = Application.Driver.SupportsTrueColor;
+
+			var lblDriverName = new Label ($"Current driver is {Application.Driver.GetType ().Name}") {
+				X = x,
+				Y = y++
+			};
+			Win.Add (lblDriverName);
+			y++;
+
+			var cbSupportsTrueColor = new CheckBox ("Driver supports true color ") {
+				X = x,
+				Y = y++,
+				Checked = canTrueColor,
+				CanFocus = false
+			};
+			Win.Add (cbSupportsTrueColor);
+
+			var cbUseTrueColor = new CheckBox ("Use true color") {
+				X = x,
+				Y = y++,
+				Checked = Application.Driver.UseTrueColor,
+				Enabled = canTrueColor,
+			};
+			cbUseTrueColor.Toggled += (_, evt) => Application.Driver.UseTrueColor = evt.NewValue ?? false;
+			Win.Add (cbUseTrueColor);
+
+			y += 2;
+			SetupGradient ("Red gradient", x, ref y, (i) => new TrueColor (i, 0, 0));
+			SetupGradient ("Green gradient", x, ref y, (i) => new TrueColor (0, i, 0));
+			SetupGradient ("Blue gradient", x, ref y, (i) => new TrueColor (0, 0, i));
+			SetupGradient ("Yellow gradient", x, ref y, (i) => new TrueColor (i, i, 0));
+			SetupGradient ("Magenta gradient", x, ref y, (i) => new TrueColor (i, 0, i));
+			SetupGradient ("Cyan gradient", x, ref y, (i) => new TrueColor (0, i, i));
+			SetupGradient ("Gray gradient", x, ref y, (i) => new TrueColor (i, i, i));
+
+			Win.Add (new Label ("Mouse over to get the gradient view color:") {
+				X = Pos.AnchorEnd (44),
+				Y = 2
+			});
+			Win.Add (new Label ("Red:") {
+				X = Pos.AnchorEnd (44),
+				Y = 4
+			});
+			Win.Add (new Label ("Green:") {
+				X = Pos.AnchorEnd (44),
+				Y = 5
+			});
+			Win.Add (new Label ("Blue:") {
+				X = Pos.AnchorEnd (44),
+				Y = 6
+			});
+
+			var lblRed = new Label ("na") {
+				X = Pos.AnchorEnd (32),
+				Y = 4
+			};
+			Win.Add (lblRed);
+			var lblGreen = new Label ("na") {
+				X = Pos.AnchorEnd (32),
+				Y = 5
+			};
+			Win.Add (lblGreen);
+			var lblBlue = new Label ("na") {
+				X = Pos.AnchorEnd (32),
+				Y = 6
+			};
+			Win.Add (lblBlue);
+
+			Application.RootMouseEvent = (e) => {
+				var normal = e.View.GetNormalColor ();
+				if (e.View != null) {
+					lblRed.Text = normal.TrueColorForeground.Value.Red.ToString ();
+					lblGreen.Text = normal.TrueColorForeground.Value.Green.ToString ();
+					lblBlue.Text = normal.TrueColorForeground.Value.Blue.ToString ();
+				}
+			};
+		}
+
+		private void SetupGradient (string name, int x, ref int y, Func<int, TrueColor> colorFunc)
+		{
+			var gradient = new Label (name) {
+				X = x,
+				Y = y++,
+			};
+			Win.Add (gradient);
+			for (int dx = x, i = 0; i <= 256; i += 4) {
+				var l = new Label (" ") {
+					X = dx++,
+					Y = y,
+					ColorScheme = new ColorScheme () {
+						Normal = new Terminal.Gui.Attribute (
+						colorFunc (i > 255 ? 255 : i),
+						colorFunc (i > 255 ? 255 : i)
+						)
+					}
+				};
+				Win.Add (l);
+			}
+			y += 2;
+		}
+	}
+}

+ 5 - 13
UICatalog/Scenarios/Unicode.cs

@@ -9,16 +9,9 @@ namespace UICatalog.Scenarios {
 	public class UnicodeInMenu : Scenario {
 		public override void Setup ()
 		{
-			const string IdenticalSign = "\u2261";
-			const string ArrowUpSign = "\u2191";
-			const string ArrowDownSign = "\u2193";
-			const string EllipsesSign = "\u2026";
-			const string StashSign = "\u205E";
-
-			//string text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line.";
 			string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου.";
 
-			string gitString = $"gui.cs master {IdenticalSign} {ArrowDownSign}18 {ArrowUpSign}10 {StashSign}1 {EllipsesSign}";
+			string gitString = $"gui.cs 糊 (hú) {ConfigurationManager.Glyphs.IdenticalTo} {ConfigurationManager.Glyphs.DownArrow}18 {ConfigurationManager.Glyphs.UpArrow}10 {ConfigurationManager.Glyphs.VerticalFourDots}1 {ConfigurationManager.Glyphs.HorizontalEllipsis}";
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_Файл", new MenuItem [] {
@@ -30,7 +23,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem ("_Edit", new MenuItem [] {
 					new MenuItem ("_Copy", "", null),
 					new MenuItem ("C_ut", "", null),
-					new MenuItem ("_Paste", "", null)
+					new MenuItem ("_糊", "hú (Paste)", null)
 				})
 			});
 			Application.Top.Add (menu);
@@ -60,11 +53,10 @@ namespace UICatalog.Scenarios {
 			label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			Win.Add (label);
 			var checkBox = new CheckBox (gitString) { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
-			var ckbAllowNull = new CheckBox ("Allow null checked") { X = Pos.Right (checkBox) + 1, Y = Pos.Y (label) };
-			ckbAllowNull.Toggled += (s,e) => checkBox.AllowNullChecked = (bool)!e.OldValue;
-			Win.Add (checkBox, ckbAllowNull);
+			var checkBoxRight = new CheckBox ($"Align Right - {gitString}") { X = 20, Y = Pos.Bottom (checkBox), Width = Dim.Percent (50), TextAlignment = TextAlignment.Right};
+			Win.Add (checkBox, checkBoxRight);
 
-			label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
+			label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (checkBoxRight) + 1 };
 			Win.Add (label);
 			var comboBox = new ComboBox () {
 				X = 20,

+ 4 - 27
UICatalog/UICatalog.cs

@@ -214,8 +214,6 @@ namespace UICatalog {
 				CM.Apply ();
 			}
 
-			//Application.EnableConsoleScrolling = _enableConsoleScrolling;
-
 			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 
@@ -239,7 +237,6 @@ namespace UICatalog {
 
 		static bool _useSystemConsole = false;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		//static bool _enableConsoleScrolling = false;
 		static bool _isFirstRunning = true;
 		static string _topLevelColorScheme = string.Empty;
 
@@ -254,7 +251,6 @@ namespace UICatalog {
 			public MenuItem? miUseSubMenusSingleFrame;
 			public MenuItem? miIsMenuBorderDisabled;
 			public MenuItem? miIsMouseDisabled;
-			public MenuItem? miEnableConsoleScrolling;
 
 			public ListView CategoryList;
 
@@ -383,8 +379,8 @@ namespace UICatalog {
 				ScenarioList.KeyDown += (s, a) => {
 					if (CollectionNavigator.IsCompatibleKey (a.KeyEvent)) {
 						var newItem = _scenarioCollectionNav?.GetNextMatchingItem (ScenarioList.SelectedRow, (char)a.KeyEvent.KeyValue);
-						if (newItem is int && newItem != -1) {
-							ScenarioList.SelectedRow = (int)newItem;
+						if (newItem is int v && newItem != -1) {
+							ScenarioList.SelectedRow = v;
 							ScenarioList.EnsureSelectedCellIsVisible ();
 							ScenarioList.SetNeedsDisplay ();
 							a.Handled = true;
@@ -426,8 +422,7 @@ namespace UICatalog {
 				ConfigChanged ();
 
 				miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
-				miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling;
-				DriverName.Title = $"Driver: {Driver.GetType ().Name}";
+				DriverName.Title = $"Driver: {Driver.GetVersionInfo()}";
 				OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
 
 				if (_selectedScenario != null) {
@@ -489,8 +484,7 @@ namespace UICatalog {
 			{
 				List<MenuItem []> menuItems = new List<MenuItem []> {
 					CreateDiagnosticFlagsMenuItems (),
-					new MenuItem [] { },
-					CreateEnableConsoleScrollingMenuItems (),
+					Array.Empty<MenuItem> (),
 					CreateDisabledEnabledMouseItems (),
 					CreateDisabledEnabledMenuBorder (),
 					CreateDisabledEnableUseSubMenusSingleFrame (),
@@ -567,22 +561,6 @@ namespace UICatalog {
 				return menuItems.ToArray ();
 			}
 
-			MenuItem [] CreateEnableConsoleScrollingMenuItems ()
-			{
-				List<MenuItem> menuItems = new List<MenuItem> ();
-				miEnableConsoleScrolling = new MenuItem ();
-				miEnableConsoleScrolling.Title = "_Enable Console Scrolling";
-				miEnableConsoleScrolling.Shortcut = Key.CtrlMask | Key.AltMask | (Key)miEnableConsoleScrolling.Title!.Substring (1, 1) [0];
-				miEnableConsoleScrolling.CheckType |= MenuItemCheckStyle.Checked;
-				miEnableConsoleScrolling.Action += () => {
-					miEnableConsoleScrolling.Checked = !miEnableConsoleScrolling.Checked;
-					Application.EnableConsoleScrolling = (bool)miEnableConsoleScrolling.Checked!;
-				};
-				menuItems.Add (miEnableConsoleScrolling);
-
-				return menuItems.ToArray ();
-			}
-
 			MenuItem [] CreateDiagnosticFlagsMenuItems ()
 			{
 				const string OFF = "Diagnostics: _Off";
@@ -759,7 +737,6 @@ namespace UICatalog {
 				StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
 
 				miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
-				miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling;
 
 				var height = (UICatalogApp.ShowStatusBar ? 1 : 0);// + (MenuBar.Visible ? 1 : 0);
 										  //ContentPane.Height = Dim.Fill (height);

+ 21 - 13
UnitTests/Application/ApplicationTests.cs

@@ -24,7 +24,6 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.Null (Application.Driver);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
@@ -36,11 +35,14 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Current);
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.NotNull (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.TerminalResized);
+			// FakeDriver is always 80x25
+			Assert.Equal (80, Application.Driver.Cols);
+			Assert.Equal (25, Application.Driver.Rows);
+
 		}
 
 		void Init ()
@@ -60,28 +62,24 @@ namespace Terminal.Gui.ApplicationTests {
 		public void Init_Shutdown_Cleans_Up ()
 		{
 			// Verify initial state is per spec
-			Pre_Init_State ();
+			//Pre_Init_State ();
 
 			Application.Init (new FakeDriver ());
 
 			// Verify post-Init state is correct
-			Post_Init_State ();
-
-			// MockDriver is always 80x25
-			Assert.Equal (80, Application.Driver.Cols);
-			Assert.Equal (25, Application.Driver.Rows);
+			//Post_Init_State ();
 
 			Application.Shutdown ();
 
 			// Verify state is back to initial
-			Pre_Init_State ();
+			//Pre_Init_State ();
 #if DEBUG_IDISPOSABLE
 			// Validate there are no outstanding Responder-based instances 
 			// after a scenario was selected to run. This proves the main UI Catalog
 			// 'app' closed cleanly.
-			foreach (var inst in Responder.Instances) {
-				Assert.True (inst.WasDisposed);
-			}
+			//foreach (var inst in Responder.Instances) {
+				//Assert.True (inst.WasDisposed);
+			//}
 #endif
 		}
 
@@ -100,7 +98,7 @@ namespace Terminal.Gui.ApplicationTests {
 		}
 
 		[Fact]
-		public void Init_Unbalanced_Throwss ()
+		public void Init_Unbalanced_Throws ()
 		{
 			Application.Init (new FakeDriver ());
 
@@ -131,6 +129,16 @@ namespace Terminal.Gui.ApplicationTests {
 			}
 		}
 
+		[Fact]
+		public void Init_Null_Driver_Should_Pick_A_Driver ()
+		{
+			Application.Init (null);
+
+			Assert.NotNull (Application.Driver);
+
+			Shutdown ();
+		}
+
 		[Fact]
 		public void Init_Begin_End_Cleans_Up ()
 		{

+ 8 - 2
UnitTests/Application/MainLoopTests.cs

@@ -25,7 +25,7 @@ namespace Terminal.Gui.ApplicationTests {
 		public void Constructor_Setups_Driver ()
 		{
 			var ml = new MainLoop (new FakeMainLoop ());
-			Assert.NotNull (ml.Driver);
+			Assert.NotNull (ml.MainLoopDriver);
 		}
 
 		// Idle Handler tests
@@ -525,6 +525,10 @@ namespace Terminal.Gui.ApplicationTests {
 			{
 				throw new NotImplementedException ();
 			}
+			public void TearDown ()
+			{
+				throw new NotImplementedException ();
+			}
 
 			public void Setup (MainLoop mainLoop)
 			{
@@ -659,7 +663,9 @@ namespace Terminal.Gui.ApplicationTests {
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (one, total);
-				} else if (taskCompleted) 					Application.RequestStop ();
+				} else if (taskCompleted) {
+					Application.RequestStop ();
+				}
 			};
 
 			Application.Run ();

+ 0 - 10
UnitTests/AssemblyInfo.cs

@@ -1,11 +1 @@
 global using CM = Terminal.Gui.ConfigurationManager;
-
-using System;
-using System.Diagnostics;
-using System.Reflection;
-using Terminal.Gui;
-using Xunit;
-
-// Since Application is a singleton we can't run tests in parallel
-[assembly: CollectionBehavior (DisableTestParallelization = true)]
-

+ 0 - 4
UnitTests/Clipboard/ClipboardTests.cs

@@ -1,10 +1,6 @@
 using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
-using static AutoInitShutdownAttribute;
 
 namespace Terminal.Gui.ClipboardTests {
 	public class ClipboardTests {

+ 24 - 34
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -13,7 +13,7 @@ using Attribute = Terminal.Gui.Attribute;
 namespace Terminal.Gui.ConfigurationTests {
 	public class ConfigurationManagerTests {
 
-		public static readonly JsonSerializerOptions _jsonOptions = new() {
+		public static readonly JsonSerializerOptions _jsonOptions = new () {
 			Converters = {
 				new AttributeJsonConverter (),
 				new ColorJsonConverter (),
@@ -65,44 +65,43 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (boolSrc, boolCopy);
 
 			// Structs
-			var attrDest = new Attribute (1);
-			var attrSrc = new Attribute (2);
+			var attrDest = new Attribute (Color.Black);
+			var attrSrc = new Attribute (Color.White);
 			var attrCopy = DeepMemberwiseCopy (attrSrc, attrDest);
 			Assert.Equal (attrSrc, attrCopy);
 
 			// Classes
-			var colorschemeDest = new ColorScheme () { Disabled = new Attribute (1) };
-			var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (2) };
+			var colorschemeDest = new ColorScheme () { Disabled = new Attribute (Color.Black) };
+			var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (Color.White) };
 			var colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest);
 			Assert.Equal (colorschemeSrc, colorschemeCopy);
 
 			// Dictionaries
-			var dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
-			var dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) } };
+			var dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+			var dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
 			var dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (dictSrc, dictCopy);
 
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } };
+			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (dictSrc, dictCopy);
 
 			// src adds an item
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (2) }, { "Normal", new Attribute (3) } };
+			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
 
 			// src updates only one item
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (1) }, { "Normal", new Attribute (2) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (3) } };
+			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) }, { "Normal", new Attribute (Color.White) } };
+			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
-
 		}
 
 		//[Fact ()]
@@ -207,7 +206,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		}
 
 		[Fact]
-		public void Reset_Resets()
+		public void Reset_Resets ()
 		{
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
 			ConfigurationManager.Reset ();
@@ -225,7 +224,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 			ConfigurationManager.Settings.Apply ();
 
 			// assert apply worked
@@ -233,7 +231,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.True (Application.IsMouseDisabled);
-			Assert.True (Application.EnableConsoleScrolling);
 
 			//act
 			ConfigurationManager.Reset ();
@@ -245,14 +242,12 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.False (Application.IsMouseDisabled);
-			Assert.False (Application.EnableConsoleScrolling);
 
 			// arrange
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 			ConfigurationManager.Settings.Apply ();
 
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
@@ -268,7 +263,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.False (Application.IsMouseDisabled);
-			Assert.False (Application.EnableConsoleScrolling);
 
 		}
 
@@ -312,7 +306,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Reset ();
 			ConfigurationManager.GetHardCodedDefaults ();
 			var stream = ConfigurationManager.ToStream ();
-			
+
 			ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson");
 		}
 
@@ -320,7 +314,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		public void TestConfigurationManagerInitDriver_NoLocations ()
 		{
 
-			
+
 		}
 
 		[Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
@@ -336,7 +330,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 			// Change Base
 			var json = ConfigurationManager.ToStream ();
-			
+
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver");
 
 			var colorSchemes = ((Dictionary<string, ColorScheme>)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue);
@@ -507,7 +501,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 			ConfigurationManager.Reset ();
 			ConfigurationManager.ThrowOnJsonErrors = true;
-			
+
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
 
 			Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
@@ -518,7 +512,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Color.White, Colors.ColorSchemes ["Base"].Normal.Foreground);
 			Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background);
 
-			var colorSchemes = (Dictionary<string, ColorScheme>)Themes.First().Value ["ColorSchemes"].PropertyValue;
+			var colorSchemes = (Dictionary<string, ColorScheme>)Themes.First ().Value ["ColorSchemes"].PropertyValue;
 			Assert.Equal (Color.White, colorSchemes ["Base"].Normal.Foreground);
 			Assert.Equal (Color.Blue, colorSchemes ["Base"].Normal.Background);
 
@@ -571,7 +565,7 @@ namespace Terminal.Gui.ConfigurationTests {
 								""UserDefined"": {
 									""AbNormal"": {
 										""foreground"": ""green"",
-										""background"": ""1234""
+										""background"": ""black""
 									}
 								}
 							}
@@ -615,7 +609,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
 			Assert.StartsWith ("Unknown property", jsonException.Message);
-			
+
 			Assert.Equal (0, ConfigurationManager.jsonErrors.Length);
 
 			ConfigurationManager.ThrowOnJsonErrors = false;
@@ -661,7 +655,7 @@ namespace Terminal.Gui.ConfigurationTests {
 								""UserDefined"": {
 									""AbNormal"": {
 										""foreground"": ""green"",
-										""background"": ""1234""
+										""background"": ""black""
 									}
 								}
 							}
@@ -696,7 +690,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings.Update (json, "test");
 
 			ConfigurationManager.Settings.Update ("{}}", "test");
-			
+
 			Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length);
 
 			Application.Shutdown ();
@@ -743,12 +737,11 @@ namespace Terminal.Gui.ConfigurationTests {
 		public void Load_FiresUpdated ()
 		{
 			ConfigurationManager.Reset ();
-			
+
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 			ConfigurationManager.Updated += ConfigurationManager_Updated;
 			bool fired = false;
@@ -760,7 +753,6 @@ namespace Terminal.Gui.ConfigurationTests {
 				Assert.Equal (Key.PageDown | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
 				Assert.Equal (Key.PageUp | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue);
 				Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
-				Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue);
 			}
 
 			ConfigurationManager.Load (true);
@@ -785,7 +777,6 @@ namespace Terminal.Gui.ConfigurationTests {
 				Assert.Equal (Key.F, Application.AlternateForwardKey);
 				Assert.Equal (Key.B, Application.AlternateBackwardKey);
 				Assert.True (Application.IsMouseDisabled);
-				Assert.True (Application.EnableConsoleScrolling);
 			}
 
 			// act
@@ -793,7 +784,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 			ConfigurationManager.Apply ();
 

+ 53 - 10
UnitTests/Configuration/JsonConverterTests.cs

@@ -1,12 +1,5 @@
-using Xunit;
-using Terminal.Gui;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Text.Json;
-using Attribute = Terminal.Gui.Attribute;
+using System.Text.Json;
+using Xunit;
 
 namespace Terminal.Gui.ConfigurationTests {
 	public class ColorJsonConverterTests {
@@ -132,6 +125,56 @@ namespace Terminal.Gui.ConfigurationTests {
 		}
 	}
 
+	public class TrueColorJsonConverterTests {
+		[Theory]
+		[InlineData (0,0,0, "\"#000000\"")]
+		public void SerializesToHexCode (int r, int g, int b, string expected)
+		{
+			// Arrange
+
+			// Act
+			var actual = JsonSerializer.Serialize (new TrueColor (r, g, b), new JsonSerializerOptions {
+				Converters = { new TrueColorJsonConverter () }
+			});
+
+			//Assert
+			Assert.Equal (expected, actual);
+
+		}
+
+		[Theory]
+		[InlineData ("\"#000000\"", 0, 0, 0)]
+		public void DeserializesFromHexCode (string hexCode, int r, int g, int b)
+		{
+			// Arrange
+			TrueColor expected = new TrueColor (r, g, b);
+
+			// Act
+			var actual = JsonSerializer.Deserialize<TrueColor> (hexCode, new JsonSerializerOptions {
+				Converters = { new TrueColorJsonConverter () }
+			});
+
+			//Assert
+			Assert.Equal (expected, actual);
+		}
+
+		[Theory]
+		[InlineData ("\"rgb(0,0,0)\"", 0, 0, 0)]
+		public void DeserializesFromRgb (string rgb, int r, int g, int b)
+		{
+			// Arrange
+			TrueColor expected = new TrueColor (r, g, b);
+
+			// Act
+			var actual = JsonSerializer.Deserialize<TrueColor> (rgb, new JsonSerializerOptions {
+				Converters = { new TrueColorJsonConverter () }
+			});
+
+			//Assert
+			Assert.Equal (expected, actual);
+		}
+	}
+
 	public class AttributeJsonConverterTests {
 		[Fact, AutoInitShutdown]
 		public void TestDeserialize ()
@@ -155,7 +198,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// Test serializing to human-readable color names
 			var attribute = new Attribute (Color.Blue, Color.Green);
 			var json = JsonSerializer.Serialize<Attribute> (attribute, ConfigurationManagerTests._jsonOptions);
-			Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\"}", json);
+			Assert.Equal ("{\"Foreground\":\"Blue\",\"Background\":\"Green\",\"TrueColorForeground\":\"#000080\",\"TrueColorBackground\":\"#008000\"}", json);
 		}
 	}
 

+ 0 - 6
UnitTests/Configuration/SettingsScopeTests.cs

@@ -25,7 +25,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.True (ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue is Key);
 			Assert.True (ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue is Key);
 			Assert.True (ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue is bool);
-			Assert.True (ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue is bool);
 
 			Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string);
 			Assert.Equal ("Default", ConfigurationManager.Settings ["Theme"].PropertyValue as string);
@@ -43,14 +42,12 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.PageDown | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue);
 			Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
-			Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue);
 
 			// act
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 			ConfigurationManager.Settings.Apply ();
 
@@ -59,7 +56,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.True (Application.IsMouseDisabled);
-			Assert.True (Application.EnableConsoleScrolling);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -73,14 +69,12 @@ namespace Terminal.Gui.ConfigurationTests {
 			updatedSettings["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			updatedSettings["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			updatedSettings["Application.IsMouseDisabled"].PropertyValue = true;
-			updatedSettings["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 			ConfigurationManager.Settings.Update (updatedSettings);
 			Assert.Equal (Key.End, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
 			Assert.Equal (Key.F, updatedSettings ["Application.AlternateForwardKey"].PropertyValue);
 			Assert.Equal (Key.B, updatedSettings ["Application.AlternateBackwardKey"].PropertyValue);
 			Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].PropertyValue);
-			Assert.True ((bool)updatedSettings ["Application.EnableConsoleScrolling"].PropertyValue);
 		}
 	}
 }

+ 196 - 0
UnitTests/ConsoleDrivers/AddRuneTests.cs

@@ -0,0 +1,196 @@
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+using System.Buffers;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+using static Terminal.Gui.SpinnerStyle;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+
+namespace Terminal.Gui.DriverTests;
+public class AddRuneTests {
+	readonly ITestOutputHelper _output;
+
+	public AddRuneTests (ITestOutputHelper output)
+	{
+		this._output = output;
+	}
+
+	[Fact]
+	public void AddRune ()
+	{
+
+		var driver = new FakeDriver ();
+		Application.Init (driver);
+		driver.Init (() => { });
+
+		driver.AddRune (new Rune ('a'));
+		Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]);
+
+		driver.End ();
+		Application.Shutdown ();
+	}
+
+	[Fact]
+	public void AddRune_InvalidLocation_DoesNothing ()
+	{
+		var driver = new FakeDriver ();
+		Application.Init (driver);
+		driver.Init (() => { });
+
+		driver.Move (driver.Cols, driver.Rows);
+		driver.AddRune ('a');
+
+		for (var col = 0; col < driver.Cols; col++) {
+			for (var row = 0; row < driver.Rows; row++) {
+				Assert.Equal ((Rune)' ', driver.Contents [row, col].Runes [0]);
+			}
+		}
+
+		driver.End ();
+		Application.Shutdown ();
+	}
+
+	[Fact]
+	public void AddRune_MovesToNextColumn ()
+	{
+		var driver = new FakeDriver ();
+		Application.Init (driver);
+		driver.Init (() => { });
+
+		driver.AddRune ('a');
+		Assert.Equal ((Rune)'a', driver.Contents [0, 0].Runes [0]);
+		Assert.Equal (0, driver.Row);
+		Assert.Equal (1, driver.Col);
+
+		driver.AddRune ('b');
+		Assert.Equal ((Rune)'b', driver.Contents [0, 1].Runes [0]);
+		Assert.Equal (0, driver.Row);
+		Assert.Equal (2, driver.Col);
+
+		// Move to the last column of the first row
+		var lastCol = driver.Cols - 1;
+		driver.Move (lastCol, 0);
+		Assert.Equal (0, driver.Row);
+		Assert.Equal (lastCol, driver.Col);
+
+		// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid
+		driver.AddRune ('c');
+		Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Runes [0]);
+		Assert.Equal (lastCol + 1, driver.Col);
+
+		// Add a rune; should succeed but do nothing as it's outside of Contents
+		driver.AddRune ('d');
+		Assert.Equal (lastCol + 2, driver.Col);
+		for (var col = 0; col < driver.Cols; col++) {
+			for (var row = 0; row < driver.Rows; row++) {
+				Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Runes [0]);
+			}
+		}
+
+		driver.End ();
+		Application.Shutdown ();
+	}
+
+	[Fact]
+	public void AddRune_MovesToNextColumn_Wide ()
+	{
+		var driver = new FakeDriver ();
+		Application.Init (driver);
+		driver.Init (() => { });
+
+		// 🍕 Slice of Pizza "\U0001F355"
+		var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed);
+		Assert.Equal (OperationStatus.Done, operationStatus);
+		Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
+		Assert.Equal (2, rune.GetColumns ());
+
+		driver.AddRune (rune);
+		Assert.Equal (rune, driver.Contents [0, 0].Runes [0]);
+		Assert.Equal (0, driver.Row);
+		Assert.Equal (2, driver.Col);
+
+		//driver.AddRune ('b');
+		//Assert.Equal ((Rune)'b', driver.Contents [0, 1].Runes [0]);
+		//Assert.Equal (0, driver.Row);
+		//Assert.Equal (2, driver.Col);
+
+		//// Move to the last column of the first row
+		//var lastCol = driver.Cols - 1;
+		//driver.Move (lastCol, 0);
+		//Assert.Equal (0, driver.Row);
+		//Assert.Equal (lastCol, driver.Col);
+
+		//// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid
+		//driver.AddRune ('c');
+		//Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Runes [0]);
+		//Assert.Equal (lastCol + 1, driver.Col);
+
+		//// Add a rune; should succeed but do nothing as it's outside of Contents
+		//driver.AddRune ('d');
+		//Assert.Equal (lastCol + 2, driver.Col);
+		//for (var col = 0; col < driver.Cols; col++) {
+		//	for (var row = 0; row < driver.Rows; row++) {
+		//		Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Runes [0]);
+		//	}
+		//}
+
+		driver.End ();
+		Application.Shutdown ();
+	}
+
+
+	[Fact]
+	public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars ()
+	{
+		var driver = new FakeDriver ();
+		Application.Init (driver);
+		driver.Init (() => { });
+
+		var expected = new Rune ('ắ');
+
+		var text = "\u1eaf";
+		driver.AddStr (text);
+		Assert.Equal (expected, driver.Contents [0, 0].Runes [0]);
+		Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]);
+
+		driver.ClearContents ();
+		driver.Move (0, 0);
+
+		text = "\u0103\u0301";
+		driver.AddStr (text);
+		Assert.Equal (expected, driver.Contents [0, 0].Runes [0]);
+		Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]);
+
+		driver.ClearContents ();
+		driver.Move (0, 0);
+
+		text = "\u0061\u0306\u0301";
+		driver.AddStr (text);
+		Assert.Equal (expected, driver.Contents [0, 0].Runes [0]);
+		Assert.Equal ((Rune)' ', driver.Contents [0, 1].Runes [0]);
+
+		//		var s = "a\u0301\u0300\u0306";
+
+
+		//		TestHelpers.AssertDriverContentsWithFrameAre (@"
+		//ắ", output);
+
+		//		tf.Text = "\u1eaf";
+		//		Application.Refresh ();
+		//		TestHelpers.AssertDriverContentsWithFrameAre (@"
+		//ắ", output);
+
+		//		tf.Text = "\u0103\u0301";
+		//		Application.Refresh ();
+		//		TestHelpers.AssertDriverContentsWithFrameAre (@"
+		//ắ", output);
+
+		//		tf.Text = "\u0061\u0306\u0301";
+		//		Application.Refresh ();
+		//		TestHelpers.AssertDriverContentsWithFrameAre (@"
+		//ắ", output);
+		driver.End ();
+		Application.Shutdown ();
+	}
+}

+ 50 - 39
UnitTests/ConsoleDrivers/AttributeTests.cs

@@ -23,31 +23,29 @@ namespace Terminal.Gui.DriverTests {
 			Assert.Equal (default (Color), attr.Foreground);
 			Assert.Equal (default (Color), attr.Background);
 
-			// Test value, foreground, background
-			var value = 42;
+			// Test foreground, background
 			var fg = new Color ();
 			fg = Color.Red;
 
 			var bg = new Color ();
 			bg = Color.Blue;
 
-			attr = new Attribute (value, fg, bg);
-
-			Assert.Equal (value, attr.Value);
-			Assert.Equal (fg, attr.Foreground);
-			Assert.Equal (bg, attr.Background);
-
-			// value, foreground, background
 			attr = new Attribute (fg, bg);
 
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (bg, attr.Background);
 
 			attr = new Attribute (fg);
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (fg, attr.Background);
 
 			attr = new Attribute (bg);
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (bg, attr.Foreground);
 			Assert.Equal (bg, attr.Background);
 
@@ -73,44 +71,15 @@ namespace Terminal.Gui.DriverTests {
 
 			// Test conversion to int
 			attr = new Attribute (value, fg, bg);
-			int value_implicit = (int)attr.Value;
+			int value_implicit = attr.Value;
 			Assert.Equal (value, value_implicit);
 
-			// Test conversion from int
-			attr = value;
 			Assert.Equal (value, attr.Value);
 
 			driver.End ();
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void Implicit_Assign_NoDriver ()
-		{
-
-			var attr = new Attribute ();
-
-			var fg = new Color ();
-			fg = Color.Red;
-
-			var bg = new Color ();
-			bg = Color.Blue;
-
-			// Test conversion to int
-			attr = new Attribute (fg, bg);
-			int value_implicit = (int)attr.Value;
-			Assert.False (attr.Initialized);
-
-			Assert.Equal (-1, value_implicit);
-			Assert.False (attr.Initialized);
-
-			// Test conversion from int
-			attr = -1;
-			Assert.Equal (-1, attr.Value);
-			Assert.False (attr.Initialized);
-
-		}
-
 		[Fact]
 		public void Make_SetsNotInitialized_NoDriver ()
 		{
@@ -227,5 +196,47 @@ namespace Terminal.Gui.DriverTests {
 			attr = new Attribute ((Color)(-1), (Color)(-1));
 			Assert.False (attr.HasValidColors);
 		}
+
+		[Fact]
+		public void Equals_NotInitialized()
+		{
+			var attr1 = new Attribute (Color.Red, Color.Green);
+			var attr2 = new Attribute (Color.Red, Color.Green);
+
+			Assert.True (attr1.Equals (attr2));
+			Assert.True (attr2.Equals (attr1));
+		}
+
+		[Fact]
+		public void NotEquals_NotInitialized ()
+		{
+			var attr1 = new Attribute (Color.Red, Color.Green);
+			var attr2 = new Attribute (Color.Green, Color.Red);
+
+			Assert.False (attr1.Equals (attr2));
+			Assert.False (attr2.Equals (attr1));
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Equals_Initialized ()
+		{
+			Assert.NotNull(Application.Driver);
+
+			var attr1 = new Attribute (Color.Red, Color.Green);
+			var attr2 = new Attribute (Color.Red, Color.Green);
+
+			Assert.True (attr1.Equals (attr2));
+			Assert.True (attr2.Equals (attr1));
+		}
+
+		[Fact,AutoInitShutdown]
+		public void NotEquals_Initialized ()
+		{
+			var attr1 = new Attribute (Color.Red, Color.Green);
+			var attr2 = new Attribute (Color.Green, Color.Red);
+
+			Assert.False (attr1.Equals (attr2));
+			Assert.False (attr2.Equals (attr1));
+		}
 	}
 }

+ 116 - 0
UnitTests/ConsoleDrivers/ClipRegionTests.cs

@@ -0,0 +1,116 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.DriverTests {
+	public class ClipRegionTests {
+		readonly ITestOutputHelper output;
+
+		public ClipRegionTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+		
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void IsValidLocation (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			// positive
+			Assert.True (driver.IsValidLocation (0, 0));
+			Assert.True (driver.IsValidLocation (1, 1));
+			Assert.True (driver.IsValidLocation (driver.Cols - 1, driver.Rows - 1));
+			// negative
+			Assert.False (driver.IsValidLocation (-1, 0));
+			Assert.False (driver.IsValidLocation (0, -1));
+			Assert.False (driver.IsValidLocation (-1, -1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+			// Define a clip rectangle
+			driver.Clip = new Rect (5, 5, 5, 5);
+			// positive
+			Assert.True (driver.IsValidLocation (5, 5));
+			Assert.True (driver.IsValidLocation (9, 9));
+			// negative
+			Assert.False (driver.IsValidLocation (4, 5));
+			Assert.False (driver.IsValidLocation (5, 4));
+			Assert.False (driver.IsValidLocation (10, 9));
+			Assert.False (driver.IsValidLocation (9, 10));
+			Assert.False (driver.IsValidLocation (-1, 0));
+			Assert.False (driver.IsValidLocation (0, -1));
+			Assert.False (driver.IsValidLocation (-1, -1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void Clip_Set_To_Empty_AllInvalid (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			// Define a clip rectangle
+			driver.Clip = Rect.Empty;
+
+			// negative
+			Assert.False (driver.IsValidLocation (4, 5));
+			Assert.False (driver.IsValidLocation (5, 4));
+			Assert.False (driver.IsValidLocation (10, 9));
+			Assert.False (driver.IsValidLocation (9, 10));
+			Assert.False (driver.IsValidLocation (-1, 0));
+			Assert.False (driver.IsValidLocation (0, -1));
+			Assert.False (driver.IsValidLocation (-1, -1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (typeof (FakeDriver))]
+		public void AddRune_Is_Clipped (Type driverType)
+		{
+			var driver = (FakeDriver)Activator.CreateInstance (driverType);
+			Application.Init (driver);
+
+			driver.Move (0, 0);
+			driver.AddRune ('x');
+			Assert.Equal ((Rune)'x', driver.Contents [0, 0].Runes [0]);
+
+			driver.Move (5, 5);
+			driver.AddRune ('x');
+			Assert.Equal ((Rune)'x', driver.Contents [5, 5].Runes [0]);
+
+			// Clear the contents
+			driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' ');
+			Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes [0]);
+
+			// Setup the region with a single rectangle, fill screen with 'x'
+			driver.Clip = new Rect (5, 5, 5, 5);
+			driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), 'x');
+			Assert.Equal ((Rune)' ', driver.Contents [0, 0].Runes [0]);
+			Assert.Equal ((Rune)' ', driver.Contents [4, 9].Runes [0]);
+			Assert.Equal ((Rune)'x', driver.Contents [5, 5].Runes [0]);
+			Assert.Equal ((Rune)'x', driver.Contents [9, 9].Runes [0]);
+			Assert.Equal ((Rune)' ', driver.Contents [10, 10].Runes [0]);
+
+			Application.Shutdown ();
+		}
+	}
+}

+ 0 - 13
UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -57,8 +57,6 @@ namespace Terminal.Gui.DriverTests {
 			Console.BackgroundColor = ConsoleColor.Green;
 			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
 			driver.Move (2, 3);
-			Assert.Equal (2, Console.CursorLeft);
-			Assert.Equal (3, Console.CursorTop);
 
 			driver.End ();
 			Assert.Equal (0, Console.CursorLeft);
@@ -225,17 +223,6 @@ namespace Terminal.Gui.DriverTests {
 			Assert.Equal (40, Application.Driver.Rows);
 			Assert.True (wasTerminalResized);
 
-			// MockDriver will still be 120x40
-			wasTerminalResized = false;
-			Application.EnableConsoleScrolling = true;
-			driver.SetWindowSize (40, 20);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (40, Console.WindowWidth);
-			Assert.Equal (20, Console.WindowHeight);
-			Assert.True (wasTerminalResized);
 
 			Application.Shutdown ();
 		}

+ 2 - 120
UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs

@@ -19,12 +19,11 @@ namespace Terminal.Gui.DriverTests {
 
 		[Theory]
 		[InlineData (typeof (FakeDriver))]
-		public void EnableConsoleScrolling_Is_False_Left_And_Top_Is_Always_Zero (Type driverType)
+		public void Left_And_Top_Is_Always_Zero (Type driverType)
 		{
 			var driver = (FakeDriver)Activator.CreateInstance (driverType);
 			Application.Init (driver);
 
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowTop);
 
@@ -34,123 +33,6 @@ namespace Terminal.Gui.DriverTests {
 
 			Application.Shutdown ();
 		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_WindowWidth (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.EnableConsoleScrolling = true;
-			Assert.True (Application.EnableConsoleScrolling);
-
-			driver.SetWindowPosition (81, 25);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void EnableConsoleScrolling_Is_True_Left_Cannot_Be_Greater_Than_BufferWidth_Minus_WindowWidth (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.EnableConsoleScrolling = true;
-			Assert.True (Application.EnableConsoleScrolling);
-
-			driver.SetWindowPosition (81, 25);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			// MockDriver will now be sets to 120x25
-			driver.SetBufferSize (120, 25);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (25, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			driver.SetWindowPosition (121, 25);
-			Assert.Equal (40, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			driver.SetWindowSize (90, 25);
-			Assert.Equal (120, Application.Driver.Cols);
-			Assert.Equal (25, Application.Driver.Rows);
-			Assert.Equal (120, Console.BufferWidth);
-			Assert.Equal (25, Console.BufferHeight);
-			Assert.Equal (90, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			driver.SetWindowPosition (121, 25);
-			Assert.Equal (30, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_WindowHeight (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.EnableConsoleScrolling = true;
-			Assert.True (Application.EnableConsoleScrolling);
-
-			driver.SetWindowPosition (80, 26);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void EnableConsoleScrolling_Is_True_Top_Cannot_Be_Greater_Than_BufferHeight_Minus_WindowHeight (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Application.EnableConsoleScrolling = true;
-			Assert.True (Application.EnableConsoleScrolling);
-
-			driver.SetWindowPosition (80, 26);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			// MockDriver will now be sets to 80x40
-			driver.SetBufferSize (80, 40);
-			Assert.Equal (80, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (25, Console.WindowHeight);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-			driver.SetWindowPosition (80, 40);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (15, Console.WindowTop);
-
-			driver.SetWindowSize (80, 20);
-			Assert.Equal (80, Application.Driver.Cols);
-			Assert.Equal (40, Application.Driver.Rows);
-			Assert.Equal (80, Console.BufferWidth);
-			Assert.Equal (40, Console.BufferHeight);
-			Assert.Equal (80, Console.WindowWidth);
-			Assert.Equal (20, Console.WindowHeight);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (15, Console.WindowTop);
-			driver.SetWindowPosition (80, 41);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (20, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
+		
 	}
 }

+ 129 - 0
UnitTests/ConsoleDrivers/ContentsTests.cs

@@ -0,0 +1,129 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+
+namespace Terminal.Gui.DriverTests;
+public class ContentsTests {
+	readonly ITestOutputHelper output;
+
+	public ContentsTests (ITestOutputHelper output)
+	{
+		this.output = output;
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	//[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (CursesDriver))]
+	//[InlineData (typeof (WindowsDriver))]
+	public void AddStr_With_Combining_Characters (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+		// driver.Init (null);
+
+		var acuteaccent = new System.Text.Rune (0x0301); // Combining acute accent (é)
+		var combined = "e" + acuteaccent;
+		var expected = "é";
+
+		driver.AddStr (combined);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+#if false // Disabled Until #2616 is fixed
+
+		// 3 char combine
+		// a + ogonek + acute = <U+0061, U+0328, U+0301> ( ǫ́ )
+		var ogonek = new System.Text.Rune (0x0328); // Combining ogonek (a small hook or comma shape)
+		combined = "a" + ogonek + acuteaccent;
+		expected = "ǫ́";
+
+		driver.Move (0, 0);
+		driver.AddStr (combined);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+
+#endif
+		
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	//[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (CursesDriver))]
+	//[InlineData (typeof (WindowsDriver))]
+	public void Move_Bad_Coordinates (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+
+		Assert.Equal (0, driver.Col);
+		Assert.Equal (0, driver.Row);
+
+		driver.Move (-1, 0);
+		Assert.Equal (-1, driver.Col);
+		Assert.Equal (0, driver.Row);
+
+		driver.Move (0, -1);
+		Assert.Equal (0, driver.Col);
+		Assert.Equal (-1, driver.Row);
+
+		driver.Move (driver.Cols, 0);
+		Assert.Equal (driver.Cols, driver.Col);
+		Assert.Equal (0, driver.Row);
+
+		driver.Move (0, driver.Rows);
+		Assert.Equal (0, driver.Col);
+		Assert.Equal (driver.Rows, driver.Row);
+
+		driver.Move (500, 500);
+		Assert.Equal (500, driver.Col);
+		Assert.Equal (500, driver.Row);
+
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
+	}
+
+	// TODO: Add these unit tests
+	
+	// AddRune moves correctly
+
+	// AddRune with wide characters are handled correctly
+
+	// AddRune with wide characters and Col < 0 are handled correctly
+
+	// AddRune with wide characters and Col == Cols - 1 are handled correctly
+
+	// AddRune with wide characters and Col == Cols are handled correctly
+
+	// AddStr moves correctly
+
+	// AddStr with wide characters moves correctly
+
+	// AddStr where Col is negative works
+
+	// AddStr where Col is negative and characters include wide / combining characters works
+
+	// AddStr where Col is near Cols and characters include wide / combining characters works
+
+	// Clipping works correctly
+
+	// Clipping works correctly with wide characters
+
+	// Clipping works correctly with combining characters
+
+	// Clipping works correctly with combining characters and wide characters
+
+	// ResizeScreen works correctly
+
+	// Refresh works correctly
+
+	// IsDirty tests
+}
+

+ 36 - 34
UnitTests/ConsoleDrivers/KeyTests.cs

@@ -184,41 +184,43 @@ namespace Terminal.Gui.InputTests {
 		[ClassData (typeof (PacketTest))]
 		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 		{
-			var modifiers = new ConsoleModifiers ();
-			if (shift) modifiers |= ConsoleModifiers.Shift;
-			if (alt) modifiers |= ConsoleModifiers.Alt;
-			if (control) modifiers |= ConsoleModifiers.Control;
-			var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
-
-			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey);
-			else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar);
-			Assert.Equal (scanCode, initialScanCode);
-
-			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
-
-			//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
-			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar);
-			else Assert.Equal (keyChar, unicodeCharacter);
-			Assert.Equal (consoleKey, expectedVirtualKey);
-			Assert.Equal (scanCode, expectedScanCode);
-
-			var top = Application.Top;
-
-			top.KeyPress += (s, e) => {
-				var after = ShortcutHelper.GetModifiersKey (e.KeyEvent);
-				Assert.Equal (expectedRemapping, after);
-				e.Handled = true;
-				Application.RequestStop ();
-			};
-
-			var iterations = -1;
-
-			Application.Iteration += () => {
-				iterations++;
-				if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
-			};
-
 			lock (packetLock) {
+				Application._forceFakeConsole = true;
+				Application.Init ();
+
+				var modifiers = new ConsoleModifiers ();
+				if (shift) modifiers |= ConsoleModifiers.Shift;
+				if (alt) modifiers |= ConsoleModifiers.Alt;
+				if (control) modifiers |= ConsoleModifiers.Control;
+				var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
+
+				if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) Assert.Equal (mappedConsoleKey, initialVirtualKey);
+				else Assert.Equal (mappedConsoleKey, outputChar < 0xff ? outputChar & 0xff | 0xff << 8 : outputChar);
+				Assert.Equal (scanCode, initialScanCode);
+
+				var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
+
+				//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
+				if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) Assert.Equal (0, (double)keyChar);
+				else Assert.Equal (keyChar, unicodeCharacter);
+				Assert.Equal (consoleKey, expectedVirtualKey);
+				Assert.Equal (scanCode, expectedScanCode);
+
+				var top = Application.Top;
+
+				top.KeyPress += (s, e) => {
+					var after = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+					Assert.Equal (expectedRemapping, after);
+					e.Handled = true;
+					Application.RequestStop ();
+				};
+
+				var iterations = -1;
+
+				Application.Iteration += () => {
+					iterations++;
+					if (iterations == 0) Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
+				};
 				Application.Run ();
 				Application.Shutdown ();
 			}

+ 9 - 9
UnitTests/FileServices/FileDialogTests.cs

@@ -217,14 +217,14 @@ namespace Terminal.Gui.FileServicesTests {
 			Assert.Multiple (
 				() => {
 					// Only the subfolder should be selected
-					Assert.Equal (1, dlg.MultiSelected.Count);
+					Assert.Single (dlg.MultiSelected);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 				},
 				() => {
 					// Event should also agree with the final state
 					Assert.NotNull (eventMultiSelected);
-					Assert.Equal (1, eventMultiSelected.Count);
+					Assert.Single (eventMultiSelected);
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 				}
 			);
@@ -330,14 +330,14 @@ namespace Terminal.Gui.FileServicesTests {
 			Assert.Multiple (
 				() => {
 					// Only the subfolder should be selected
-					Assert.Equal (1, dlg.MultiSelected.Count);
+					Assert.Single (dlg.MultiSelected);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 				},
 				() => {
 					// Event should also agree with the final state
 					Assert.NotNull (eventMultiSelected);
-					Assert.Equal (1, eventMultiSelected.Count);
+					Assert.Single (eventMultiSelected);
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 				}
 			);
@@ -382,7 +382,7 @@ namespace Terminal.Gui.FileServicesTests {
 			fd.Style.Culture = new CultureInfo ("en-US");
 
 			fd.Draw ();
-			
+
 			string expected =
 			@$"
  ┌──────────────────────────────────────────────────────────────────┐
@@ -467,7 +467,7 @@ namespace Terminal.Gui.FileServicesTests {
 
 			fd.Draw ();
 
-			TestHelpers.AssertDriverUsedColors (other,dir,img,exe);
+			TestHelpers.AssertDriverUsedColors (other, dir, img, exe);
 		}
 
 		private ColorScheme GetColorScheme (Attribute a)
@@ -487,7 +487,7 @@ namespace Terminal.Gui.FileServicesTests {
 		[InlineData (".csv", "c:\\MyFile.csv", true)]
 		[InlineData (".csv", "c:\\MyFile.CSV", true)]
 		[InlineData (".csv", "c:\\MyFile.csv.bak", false)]
-		public void TestAllowedType_Basic(string allowed, string candidate, bool expected)
+		public void TestAllowedType_Basic (string allowed, string candidate, bool expected)
 		{
 			Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate));
 		}
@@ -496,7 +496,7 @@ namespace Terminal.Gui.FileServicesTests {
 		[InlineData ("Dockerfile", "c:\\temp\\Dockerfile", true)]
 		[InlineData ("Dockerfile", "Dockerfile", true)]
 		[InlineData ("Dockerfile", "someimg.Dockerfile", true)]
-		public void TestAllowedType_SpecificFile(string allowed, string candidate, bool expected)
+		public void TestAllowedType_SpecificFile (string allowed, string candidate, bool expected)
 		{
 			Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate));
 		}
@@ -504,7 +504,7 @@ namespace Terminal.Gui.FileServicesTests {
 		[Theory]
 		[InlineData (".Designer.cs", "c:\\MyView.Designer.cs", true)]
 		[InlineData (".Designer.cs", "c:\\temp/MyView.Designer.cs", true)]
-		[InlineData(".Designer.cs","MyView.Designer.cs",true)]
+		[InlineData (".Designer.cs", "MyView.Designer.cs", true)]
 		[InlineData (".Designer.cs", "c:\\MyView.DESIGNER.CS", true)]
 		[InlineData (".Designer.cs", "MyView.cs", false)]
 		public void TestAllowedType_DoubleBarreled (string allowed, string candidate, bool expected)

+ 32 - 32
UnitTests/Input/EscSeqReqTests.cs

@@ -10,69 +10,69 @@ namespace Terminal.Gui.InputTests {
 		[Fact]
 		public void Constructor_Defaults ()
 		{
-			var escSeqReq = new EscSeqReqProc ();
-			Assert.NotNull (escSeqReq.EscSeqReqStats);
-			Assert.Empty (escSeqReq.EscSeqReqStats);
+			var escSeqReq = new EscSeqRequests ();
+			Assert.NotNull (escSeqReq.Statuses);
+			Assert.Empty (escSeqReq.Statuses);
 		}
 
 		[Fact]
 		public void Add_Tests ()
 		{
-			var escSeqReq = new EscSeqReqProc ();
+			var escSeqReq = new EscSeqRequests ();
 			escSeqReq.Add ("t");
-			Assert.Single (escSeqReq.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
-			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests);
-			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+			Assert.Single (escSeqReq.Statuses);
+			Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator);
+			Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding);
 
 			escSeqReq.Add ("t", 2);
-			Assert.Single (escSeqReq.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
-			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumRequests);
-			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+			Assert.Single (escSeqReq.Statuses);
+			Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator);
+			Assert.Equal (1, escSeqReq.Statuses [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding);
 
-			escSeqReq = new EscSeqReqProc ();
+			escSeqReq = new EscSeqRequests ();
 			escSeqReq.Add ("t", 2);
-			Assert.Single (escSeqReq.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
-			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
-			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+			Assert.Single (escSeqReq.Statuses);
+			Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator);
+			Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests);
+			Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding);
 
 			escSeqReq.Add ("t", 3);
-			Assert.Single (escSeqReq.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
-			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
-			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+			Assert.Single (escSeqReq.Statuses);
+			Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator);
+			Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests);
+			Assert.Equal (2, escSeqReq.Statuses [^1].NumOutstanding);
 		}
 
 		[Fact]
 		public void Remove_Tests ()
 		{
-			var escSeqReq = new EscSeqReqProc ();
+			var escSeqReq = new EscSeqRequests ();
 			escSeqReq.Add ("t");
 			escSeqReq.Remove ("t");
-			Assert.Empty (escSeqReq.EscSeqReqStats);
+			Assert.Empty (escSeqReq.Statuses);
 
 			escSeqReq.Add ("t", 2);
 			escSeqReq.Remove ("t");
-			Assert.Single (escSeqReq.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReq.EscSeqReqStats [^1].Terminating);
-			Assert.Equal (2, escSeqReq.EscSeqReqStats [^1].NumRequests);
-			Assert.Equal (1, escSeqReq.EscSeqReqStats [^1].NumOutstanding);
+			Assert.Single (escSeqReq.Statuses);
+			Assert.Equal ("t", escSeqReq.Statuses [^1].Terminator);
+			Assert.Equal (2, escSeqReq.Statuses [^1].NumRequests);
+			Assert.Equal (1, escSeqReq.Statuses [^1].NumOutstanding);
 
 			escSeqReq.Remove ("t");
-			Assert.Empty (escSeqReq.EscSeqReqStats);
+			Assert.Empty (escSeqReq.Statuses);
 		}
 
 		[Fact]
 		public void Requested_Tests ()
 		{
-			var escSeqReq = new EscSeqReqProc ();
-			Assert.False (escSeqReq.Requested ("t"));
+			var escSeqReq = new EscSeqRequests ();
+			Assert.False (escSeqReq.HasResponse ("t"));
 
 			escSeqReq.Add ("t");
-			Assert.False (escSeqReq.Requested ("r"));
-			Assert.True (escSeqReq.Requested ("t"));
+			Assert.False (escSeqReq.HasResponse ("r"));
+			Assert.True (escSeqReq.HasResponse ("t"));
 		}
 	}
 }

+ 20 - 20
UnitTests/Input/EscSeqUtilsTests.cs

@@ -10,15 +10,15 @@ namespace Terminal.Gui.InputTests {
 		public void Defaults_Values ()
 		{
 			Assert.Equal ('\x1b', EscSeqUtils.KeyEsc);
-			Assert.Equal ("\x1b[", EscSeqUtils.KeyCSI);
+			Assert.Equal ("\x1b[", EscSeqUtils.CSI);
 			Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse);
 			Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse);
 			Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse);
 			Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse);
-			Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.EnableMouseEvents);
-			Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.DisableMouseEvents);
+			Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.CSI_EnableMouseEvents);
+			Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.CSI_DisableMouseEvents);
 		}
 
 		[Fact]
@@ -26,51 +26,51 @@ namespace Terminal.Gui.InputTests {
 		{
 			var cki = new ConsoleKeyInfo ('r', 0, false, false, false);
 			var expectedCki = new ConsoleKeyInfo ('r', 0, false, false, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, true, false, false);
 			expectedCki = new ConsoleKeyInfo ('r', 0, true, false, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, false, true, false);
 			expectedCki = new ConsoleKeyInfo ('r', 0, false, true, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, false, false, true);
 			expectedCki = new ConsoleKeyInfo ('r', 0, false, false, true);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, true, true, false);
 			expectedCki = new ConsoleKeyInfo ('r', 0, true, true, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, false, true, true);
 			expectedCki = new ConsoleKeyInfo ('r', 0, false, true, true);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('r', 0, true, true, true);
 			expectedCki = new ConsoleKeyInfo ('r', 0, true, true, true);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('\u0012', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('R', ConsoleKey.R, false, false, true);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('\0', (ConsoleKey)64, false, false, true);
 			expectedCki = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, false, false, true);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('\r', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('\r', ConsoleKey.Enter, false, false, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('\u007f', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('\u007f', ConsoleKey.Backspace, false, false, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 
 			cki = new ConsoleKeyInfo ('R', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('R', 0, false, false, false);
-			Assert.Equal (expectedCki, EscSeqUtils.GetConsoleInputKey (cki));
+			Assert.Equal (expectedCki, EscSeqUtils.MapConsoleKeyInfo (cki));
 		}
 
 		[Fact]
@@ -83,7 +83,7 @@ namespace Terminal.Gui.InputTests {
 			Assert.Equal (cki, expectedCkInfos [0]);
 		}
 
-		private EscSeqReqProc escSeqReqProc;
+		private EscSeqRequests escSeqReqProc;
 		private ConsoleKeyInfo newConsoleKeyInfo;
 		private ConsoleKey key;
 		private ConsoleKeyInfo [] cki;
@@ -617,7 +617,7 @@ namespace Terminal.Gui.InputTests {
 			ClearAll ();
 
 			Assert.Null (escSeqReqProc);
-			escSeqReqProc = new EscSeqReqProc ();
+			escSeqReqProc = new EscSeqRequests ();
 			escSeqReqProc.Add ("t");
 
 			cki = new ConsoleKeyInfo [] {
@@ -633,10 +633,10 @@ namespace Terminal.Gui.InputTests {
 				new ConsoleKeyInfo ('t', 0, false, false, false)
 			};
 			expectedCki = default;
-			Assert.Single (escSeqReqProc.EscSeqReqStats);
-			Assert.Equal ("t", escSeqReqProc.EscSeqReqStats [^1].Terminating);
+			Assert.Single (escSeqReqProc.Statuses);
+			Assert.Equal ("t", escSeqReqProc.Statuses [^1].Terminator);
 			EscSeqUtils.DecodeEscSeq (escSeqReqProc, ref newConsoleKeyInfo, ref key, cki, ref mod, out c1Control, out code, out values, out terminating, out isKeyMouse, out mouseFlags, out pos, out isReq, ProcessContinuousButtonPressed);
-			Assert.Empty (escSeqReqProc.EscSeqReqStats);
+			Assert.Empty (escSeqReqProc.Statuses);
 			Assert.Equal (expectedCki, newConsoleKeyInfo);
 			Assert.Equal (0, (int)key);
 			Assert.Equal (0, (int)mod);

+ 102 - 124
UnitTests/TestHelpers.cs

@@ -4,7 +4,6 @@ using System.Linq;
 using System.Text;
 using Xunit.Abstractions;
 using Xunit;
-using Terminal.Gui;
 using System.Text.RegularExpressions;
 using System.Reflection;
 using System.Diagnostics;
@@ -12,7 +11,9 @@ using System.Diagnostics;
 using Attribute = Terminal.Gui.Attribute;
 using Microsoft.VisualStudio.TestPlatform.Utilities;
 using Xunit.Sdk;
+using System.Globalization;
 
+namespace Terminal.Gui;
 // This class enables test functions annotated with the [AutoInitShutdown] attribute to 
 // automatically call Application.Init at start of the test and Application.Shutdown after the
 // test exits. 
@@ -31,54 +32,45 @@ public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 	/// CursesDriver, NetDriver) will be used when Application.Init is called. If null FakeDriver will be used.
 	/// Only valid if <paramref name="autoInit"/> is true.</param>
 	/// <param name="useFakeClipboard">If true, will force the use of <see cref="FakeDriver.FakeClipboard"/>. 
-	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	/// Only valid if <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	/// <param name="fakeClipboardAlwaysThrowsNotSupportedException">Only valid if <paramref name="autoInit"/> is true.
-	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	/// Only valid if <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	/// <param name="fakeClipboardIsSupportedAlwaysTrue">Only valid if <paramref name="autoInit"/> is true.
-	/// Only valid if <see cref="consoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
+	/// Only valid if <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.</param>
 	/// <param name="configLocation">Determines what config file locations <see cref="ConfigurationManager"/> will 
 	/// load from.</param>
-	public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
+	public AutoInitShutdownAttribute (bool autoInit = true,
 		Type consoleDriverType = null,
 		bool useFakeClipboard = true,
 		bool fakeClipboardAlwaysThrowsNotSupportedException = false,
 		bool fakeClipboardIsSupportedAlwaysTrue = false,
 		ConfigurationManager.ConfigLocations configLocation = ConfigurationManager.ConfigLocations.DefaultOnly)
 	{
-		//Assert.True (autoInit == false && consoleDriverType == null);
-
 		AutoInit = autoInit;
-		AutoShutdown = autoShutdown;
-		DriverType = consoleDriverType ?? typeof (FakeDriver);
+		CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+		_driverType = consoleDriverType ?? typeof (FakeDriver);
 		FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
 		FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 		FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
 		ConfigurationManager.Locations = configLocation;
 	}
 
-	static bool _init = false;
 	bool AutoInit { get; }
-	bool AutoShutdown { get; }
-	Type DriverType;
+	Type _driverType;
 
 	public override void Before (MethodInfo methodUnderTest)
 	{
 		Debug.WriteLine ($"Before: {methodUnderTest.Name}");
-		if (AutoShutdown && _init) {
-			throw new InvalidOperationException ("After did not run when AutoShutdown was specified.");
-		}
 		if (AutoInit) {
-			Application.Init ((ConsoleDriver)Activator.CreateInstance (DriverType));
-			_init = true;
+			Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
 		}
 	}
-
+	
 	public override void After (MethodInfo methodUnderTest)
 	{
 		Debug.WriteLine ($"After: {methodUnderTest.Name}");
-		if (AutoShutdown) {
+		if (AutoInit) {
 			Application.Shutdown ();
-			_init = false;
 		}
 	}
 }
@@ -107,20 +99,26 @@ public class SetupFakeDriverAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 	}
 }
 
-class TestHelpers {
+partial class TestHelpers {
+	[GeneratedRegex ("\\s+$", RegexOptions.Multiline)]
+	private static partial Regex TrailingWhiteSpaceRegEx ();
+	[GeneratedRegex ("^\\s+", RegexOptions.Multiline)]
+	private static partial Regex LeadingWhitespaceRegEx ();
+
 #pragma warning disable xUnit1013 // Public method should be marked as test
 	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, bool ignoreLeadingWhitespace = false)
 	{
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
 		var sb = new StringBuilder ();
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = (FakeDriver)Application.Driver;
 
 		var contents = driver.Contents;
 
 		for (int r = 0; r < driver.Rows; r++) {
 			for (int c = 0; c < driver.Cols; c++) {
-				Rune rune = (Rune)contents [r, c, 0];
+				// TODO: Remove hard-coded [0] once combining pairs is supported
+				Rune rune = contents [r, c].Runes[0];
 				if (rune.DecodeSurrogatePair (out char [] spair)) {
 					sb.Append (spair);
 				} else {
@@ -135,51 +133,47 @@ class TestHelpers {
 
 		var actualLook = sb.ToString ();
 
-		if (!string.Equals (expectedLook, actualLook)) {
-
-			// ignore trailing whitespace on each line
-			var trailingWhitespace = new Regex (@"\s+$", RegexOptions.Multiline);
-			var leadingWhitespace = new Regex (@"^\s+", RegexOptions.Multiline);
-
-			// get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
-			expectedLook = trailingWhitespace.Replace (expectedLook, "").Trim ();
-			actualLook = trailingWhitespace.Replace (actualLook, "").Trim ();
-
-			if (ignoreLeadingWhitespace) {
-				expectedLook = leadingWhitespace.Replace (expectedLook, "").Trim ();
-				actualLook = leadingWhitespace.Replace (actualLook, "").Trim ();
-			}
+		if (string.Equals (expectedLook, actualLook)) return;
+		
+		// get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
+		expectedLook = TrailingWhiteSpaceRegEx ().Replace (expectedLook, "").Trim ();
+		actualLook = TrailingWhiteSpaceRegEx ().Replace (actualLook, "").Trim ();
 
-			// standardize line endings for the comparison
-			expectedLook = expectedLook.Replace ("\r\n", "\n");
-			actualLook = actualLook.Replace ("\r\n", "\n");
+		if (ignoreLeadingWhitespace) {
+			expectedLook = LeadingWhitespaceRegEx().Replace (expectedLook, "").Trim ();
+			actualLook = LeadingWhitespaceRegEx().Replace (actualLook, "").Trim ();
+		}
 
-			// If test is about to fail show user what things looked like
-			if (!string.Equals (expectedLook, actualLook)) {
-				output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
-				output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
-			}
+		// standardize line endings for the comparison
+		expectedLook = expectedLook.Replace ("\r\n", "\n");
+		actualLook = actualLook.Replace ("\r\n", "\n");
 
-			Assert.Equal (expectedLook, actualLook);
+		// If test is about to fail show user what things looked like
+		if (!string.Equals (expectedLook, actualLook)) {
+			output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+			output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
 		}
+
+		Assert.Equal (expectedLook, actualLook);
 	}
 
 	public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
 	{
 		var lines = new List<List<Rune>> ();
 		var sb = new StringBuilder ();
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = Application.Driver;
 		var x = -1;
 		var y = -1;
-		int w = -1;
-		int h = -1;
-
+		var w = -1;
+		var h = -1;
+		
 		var contents = driver.Contents;
 
-		for (int r = 0; r < driver.Rows; r++) {
+		for (var r = 0; r < driver.Rows; r++) {
 			var runes = new List<Rune> ();
-			for (int c = 0; c < driver.Cols; c++) {
-				var rune = (Rune)contents [r, c, 0];
+			for (var c = 0; c < driver.Cols; c++) {
+				// TODO: Remove hard-coded [0] once combining pairs is supported
+				Rune rune = contents [r, c].Runes [0];
 				if (rune != (Rune)' ') {
 					if (x == -1) {
 						x = c;
@@ -196,26 +190,19 @@ class TestHelpers {
 					}
 					h = r - y + 1;
 				}
-				if (x > -1) {
-					runes.Add (rune);
-				}
-			}
-			if (runes.Count > 0) {
-				lines.Add (runes);
+				if (x > -1) runes.Add (rune);
 			}
+			if (runes.Count > 0) lines.Add (runes);
 		}
 
 		// Remove unnecessary empty lines
 		if (lines.Count > 0) {
-			for (int r = lines.Count - 1; r > h - 1; r--) {
-				lines.RemoveAt (r);
-			}
+			for (var r = lines.Count - 1; r > h - 1; r--) lines.RemoveAt (r);
 		}
 
 		// Remove trailing whitespace on each line
-		for (int r = 0; r < lines.Count; r++) {
-			List<Rune> row = lines [r];
-			for (int c = row.Count - 1; c >= 0; c--) {
+		foreach (var row in lines) {
+			for (var c = row.Count - 1; c >= 0; c--) {
 				var rune = row [c];
 				if (rune != (Rune)' ' || (row.Sum (x => x.GetColumns ()) == w)) {
 					break;
@@ -236,25 +223,22 @@ class TestHelpers {
 
 		var actualLook = sb.ToString ();
 
-		if (!string.Equals (expectedLook, actualLook)) {
-
-			// standardize line endings for the comparison
-			expectedLook = expectedLook.Replace ("\r\n", "\n");
-			actualLook = actualLook.Replace ("\r\n", "\n");
+		if (string.Equals (expectedLook, actualLook)) {
+			return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+		}
+		
+		// standardize line endings for the comparison
+		expectedLook = expectedLook.Replace ("\r\n", "\n");
+		actualLook = actualLook.Replace ("\r\n", "\n");
 
-			// Remove the first and the last line ending from the expectedLook
-			if (expectedLook.StartsWith ("\n")) {
-				expectedLook = expectedLook [1..];
-			}
-			if (expectedLook.EndsWith ("\n")) {
-				expectedLook = expectedLook [..^1];
-			}
+		// Remove the first and the last line ending from the expectedLook
+		if (expectedLook.StartsWith ("\n")) expectedLook = expectedLook [1..];
+		if (expectedLook.EndsWith ("\n")) expectedLook = expectedLook [..^1];
 
-			output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
-			output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
+		output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+		output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
 
-			Assert.Equal (expectedLook, actualLook);
-		}
+		Assert.Equal (expectedLook, actualLook);
 		return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
 	}
 
@@ -270,35 +254,32 @@ class TestHelpers {
 	{
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
-		if (expectedColors.Length > 10) {
-			throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
-		}
+		if (expectedColors.Length > 10) throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
 
 		expectedLook = expectedLook.Trim ();
-		var driver = ((FakeDriver)Application.Driver);
-
+		var driver = (FakeDriver)Application.Driver;
+		
 		var contents = driver.Contents;
 
-		int r = 0;
+		var r = 0;
 		foreach (var line in expectedLook.Split ('\n').Select (l => l.Trim ())) {
 
-			for (int c = 0; c < line.Length; c++) {
+			for (var c = 0; c < line.Length; c++) {
 
-				int val = contents [r, c, 1];
+				var val = contents [r, c].Attribute;
 
-				var match = expectedColors.Where (e => e.Value == val).ToList ();
-				if (match.Count == 0) {
-					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => DescribeColor (c.Value)))})");
-				} else if (match.Count > 1) {
+				var match = expectedColors.Where (e => e == val).ToList ();
+				switch (match.Count) {
+				case 0:
+					throw new Exception ($"Unexpected color {val} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value.ToString()))})");
+				case > 1:
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 				}
 
 				var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0];
 				var userExpected = line [c];
 
-				if (colorUsed != userExpected) {
-					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {colorUsed} ({DescribeColor (val)}) but test expected {userExpected} ({DescribeColor (expectedColors [int.Parse (userExpected.ToString ())].Value)}) (these are indexes into the expectedColors array)");
-				}
+				if (colorUsed != userExpected) throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {colorUsed} ({val}) but test expected {userExpected} ({expectedColors [int.Parse (userExpected.ToString ())].Value}) (these are indexes into the expectedColors array)");
 			}
 
 			r++;
@@ -312,40 +293,37 @@ class TestHelpers {
 	/// <param name="expectedColors"></param>
 	internal static void AssertDriverUsedColors (params Attribute [] expectedColors)
 	{
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = (FakeDriver)Application.Driver;
 
 		var contents = driver.Contents;
 
 		var toFind = expectedColors.ToList ();
 
-		var colorsUsed = new HashSet<int> ();
+		// Contents 3rd column is an Attribute
+		var colorsUsed = new HashSet<Attribute> ();
 
-		for (int r = 0; r < driver.Rows; r++) {
-			for (int c = 0; c < driver.Cols; c++) {
-				int val = contents [r, c, 1];
-				
-				colorsUsed.Add (val);
+		for (var r = 0; r < driver.Rows; r++) {
+			for (var c = 0; c < driver.Cols; c++) {
+				var val = contents [r, c].Attribute;
+				if (val.HasValue) {
+					colorsUsed.Add (val.Value);
 
-				var match = toFind.FirstOrDefault (e => e.Value == val);
+					var match = toFind.FirstOrDefault (e => e == val);
 
-				// need to check twice because Attribute is a struct and therefore cannot be null
-				if (toFind.Any (e => e.Value == val)) {
-					toFind.Remove (match);
+					// need to check twice because Attribute is a struct and therefore cannot be null
+					if (toFind.Any (e => e == val)) {
+						toFind.Remove (match);
+					}
 				}
-			}
-		}
+			}}
 
-		if(toFind.Any()) {
-			var sb = new StringBuilder ();
-			sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => DescribeColor (a))));
-			sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (DescribeColor)));
-			throw new Exception (sb.ToString());
+		if (!toFind.Any ()) {
+			return;
 		}
-	}
-	private static object DescribeColor (int userExpected)
-	{
-		var a = new Attribute (userExpected);
-		return $"{a.Foreground},{a.Background}";
+		var sb = new StringBuilder ();
+		sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString())));
+		sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString())));
+		throw new Exception (sb.ToString ());
 	}
 
 #pragma warning disable xUnit1013 // Public method should be marked as test
@@ -376,11 +354,11 @@ class TestHelpers {
 	{
 		var replaced = toReplace;
 
-		if (Environment.NewLine.Length == 2 && !replaced.Contains ("\r\n")) {
-			replaced = replaced.Replace ("\n", Environment.NewLine);
-		} else if (Environment.NewLine.Length == 1) {
-			replaced = replaced.Replace ("\r\n", Environment.NewLine);
-		}
+		replaced = Environment.NewLine.Length switch {
+			2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine),
+			1 => replaced.Replace ("\r\n", Environment.NewLine),
+			var _ => replaced
+		};
 
 		return replaced;
 	}

+ 119 - 27
UnitTests/Text/RuneTests.cs

@@ -5,6 +5,7 @@ using System.Globalization;
 using System.Linq;
 using System.Text;
 using Xunit;
+using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui.TextTests;
 
@@ -19,7 +20,10 @@ public class RuneTests {
 	[InlineData (0x0328, true)] //  Combining ogonek (a small hook or comma shape) U+0328
 	[InlineData (0x00E9, false)] // Latin Small Letter E with Acute, Unicode U+00E9 é 
 	[InlineData (0x0061, false)] // Latin Small Letter A is U+0061.
-	public void TestIsCombiningMark (int codepoint, bool expected)
+	[InlineData ('\uFE20', true)]   // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	[InlineData ('\uFE21', true)] // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+
+	public void IsCombiningMark (int codepoint, bool expected)
 	{
 		var rune = new Rune (codepoint);
 		Assert.Equal (expected, rune.IsCombiningMark ());
@@ -53,6 +57,8 @@ public class RuneTests {
 	[InlineData (0x0301)] // Combining acute accent (é)
 	[InlineData (0x0302)] // Combining Circumflex Accent
 	[InlineData (0x0061)] // Combining ogonek (a small hook or comma shape)
+	[InlineData ('\uFE20')]   // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	[InlineData ('\uFE21')] // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
 	public void MakePrintable_Combining_Character_Is_Not_Printable (int code)
 	{
 		var rune = new Rune (code);
@@ -61,30 +67,65 @@ public class RuneTests {
 	}
 
 	[Theory]
+	[InlineData (0, "\0", 0, 1, 1)]
+	[InlineData ('\u1dc0', "᷀", 0, 1, 3)] // ◌᷀ Combining Dotted Grave Accent
+	[InlineData ('\u20D0', "⃐", 0, 1, 3)]   // ◌⃐ Combining Left Harpoon Above
+
+	[InlineData (1, "\u0001", -1, 1, 1)]
+	[InlineData (2, "\u0002", -1, 1, 1)]
+	[InlineData (31, "\u001f", -1, 1, 1)]   // non printable character - Information Separator One
+	[InlineData (127, "\u007f", -1, 1, 1)]  // non printable character - Delete
+
+	[InlineData (32, " ", 1, 1, 1)]    // space
 	[InlineData ('a', "a", 1, 1, 1)]
 	[InlineData ('b', "b", 1, 1, 1)]
 	[InlineData (123, "{", 1, 1, 1)]        // { Left Curly Bracket
-	[InlineData ('\u1150', "ᅐ", 2, 1, 3)]   // ᅐ Hangul Choseong Ceongchieumcieuc
-	[InlineData ('\u1161', "ᅡ", 0, 1, 3)]   // ᅡ Hangul Jungseong A - Unicode Hangul Jamo for join with column width equal to 0 alone.
-	[InlineData (31, "\u001f", -1, 1, 1)]   // non printable character - Information Separator One
-	[InlineData (127, "\u007f", -1, 1, 1)]  // non printable character - Delete
-	[InlineData ('\u20D0', "⃐", 0, 1, 3)]   // ◌⃐ Combining Left Harpoon Above
+	[InlineData ('\u231c', "⌜", 1, 1, 3)] // ⌜ Top Left Corner
+
+	// BUGBUG: These are CLEARLY wide glyphs, but GetColumns() returns 1
+	// However, most terminals treat these as narrow and they overlap the next cell when drawn (including Windows Terminal)
+	[InlineData ('\u1161', "ᅡ", 1, 1, 3)]   // ᅡ Hangul Jungseong A - Unicode Hangul Jamo for join with column width equal to 0 alone.
+	[InlineData ('\u2103', "℃", 1, 1, 3)]   // ℃ Degree Celsius
+	[InlineData ('\u2501', "━", 1, 1, 3)]   // ━ Box Drawings Heavy Horizontal
 	[InlineData ('\u25a0', "■", 1, 1, 3)]   // ■ Black Square
 	[InlineData ('\u25a1', "□", 1, 1, 3)]   // □ White Square
+	[InlineData ('\u277f', "❿", 1, 1, 3)] //Dingbat Negative Circled Number Ten - ❿ U+277f 
+	[InlineData ('\u4dc0', "䷀", 1, 1, 3)]   // ䷀Hexagram For The Creative Heaven -  U+4dc0 - https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	[InlineData ('\ud7b0', "ힰ", 1, 1, 3)] // ힰ ┤Hangul Jungseong O-Yeo - ힰ U+d7b0')]
 	[InlineData ('\uf61e', "", 1, 1, 3)]   // Private Use Area
-	[InlineData ('\u2103', "℃", 1, 1, 3)]   // ℃ Degree Celsius
+
+	[InlineData ('\u23f0', "⏰", 2, 1, 3)] // Alarm Clock - ⏰ U+23f0
 	[InlineData ('\u1100', "ᄀ", 2, 1, 3)]   // ᄀ Hangul Choseong Kiyeok
-	[InlineData ('\u2501', "━", 1, 1, 3)]   // ━ Box Drawings Heavy Horizontal
+	[InlineData ('\u1150', "ᅐ", 2, 1, 3)]   // ᅐ Hangul Choseong Ceongchieumcieuc
 	[InlineData ('\u2615', "☕", 2, 1, 3)] // ☕ Hot Beverage
 	[InlineData ('\u231a', "⌚", 2, 1, 3)] // ⌚ Watch
 	[InlineData ('\u231b', "⌛", 2, 1, 3)] // ⌛ Hourglass
-	[InlineData ('\u231c', "⌜", 1, 1, 3)] // ⌜ Top Left Corner
-	[InlineData ('\u1dc0', "᷀", 0, 1, 3)] // ◌᷀ Combining Dotted Grave Accent
-	public void GetColumns_With_Single_Code (int code, string str, int runeLength, int stringLength, int utf8Length)
+
+	// From WindowsTerminal's CodepointWidthDetector tests (https://github.com/microsoft/terminal/blob/main/src/types/CodepointWidthDetector.cpp)
+	//static constexpr std::wstring_view emoji = L"\xD83E\xDD22"; // U+1F922 nauseated face
+	//static constexpr std::wstring_view ambiguous = L"\x414"; // U+0414 cyrillic capital de
+
+	//{ 0x414, L"\x414", CodepointWidth::Narrow }, // U+0414 cyrillic capital de
+	[InlineData ('\u0414', "Д", 1, 1, 2)] // U+0414 cyrillic capital de
+
+	//{ 0x1104, L"\x1104", CodepointWidth::Wide }, // U+1104 hangul choseong ssangtikeut
+	[InlineData ('\u1104', "ᄄ", 2, 1, 3)]
+
+	//{ 0x306A, L"\x306A", CodepointWidth::Wide }, // U+306A hiragana na な
+	[InlineData (0x306A, "な", 2, 1, 3)]
+
+	//{ 0x30CA, L"\x30CA", CodepointWidth::Wide }, // U+30CA katakana na ナ
+	[InlineData (0x30CA, "ナ", 2, 1, 3)]
+
+	//{ 0x72D7, L"\x72D7", CodepointWidth::Wide }, // U+72D7
+	[InlineData (0x72D7, "狗", 2, 1, 3)]
+	
+
+	public void GetColumns_With_Single_Code (int code, string str, int columns, int stringLength, int utf8Length)
 	{
 		var rune = new Rune (code);
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
@@ -97,12 +138,12 @@ public class RuneTests {
 	[InlineData (new byte [] { 0xf0, 0x9f, 0xa4, 0x96 }, "🤖", 2, 2)] // 🤖 Robot Face
 	[InlineData (new byte [] { 0xf0, 0x90, 0x90, 0xa1 }, "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData (new byte [] { 0xf0, 0x9f, 0x8c, 0xb9 }, "🌹", 2, 2)] // 🌹 Rose
-	public void GetColumns_Utf8_Encode (byte [] code, string str, int runeLength, int stringLength)
+	public void GetColumns_Utf8_Encode (byte [] code, string str, int columns, int stringLength)
 	{
 		var operationStatus = Rune.DecodeFromUtf8 (code, out Rune rune, out int bytesConsumed);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (bytesConsumed, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
@@ -116,11 +157,13 @@ public class RuneTests {
 	[InlineData (new char [] { '\ud83e', '\udde0' }, "🧠", 2, 2, 4)] // 🧠 Brain
 	[InlineData (new char [] { '\ud801', '\udc21' }, "𐐡", 1, 2, 4)]  // 𐐡 Deseret Capital Letter Er
 	[InlineData (new char [] { '\ud83c', '\udf39' }, "🌹", 2, 2, 4)] // 🌹 Rose
-	public void GetColumns_Utf16_Encode (char [] code, string str, int runeLength, int stringLength, int utf8Length)
+	[InlineData (new char [] { '\uD83D', '\uDC7E' }, "👾", 2, 2, 4)] //   U+1F47E alien monster (CodepointWidth::Wide)
+	[InlineData (new char [] { '\uD83D', '\uDD1C' }, "🔜", 2, 2, 4)] //  🔜 Soon With Rightwards Arrow Above (CodepointWidth::Wide)
+	public void GetColumns_Utf16_Encode (char [] code, string str, int columns, int stringLength, int utf8Length)
 	{
 		var rune = new Rune (code [0], code [1]);
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
@@ -134,12 +177,14 @@ public class RuneTests {
 	[InlineData ("\U0001f9e0", "🧠", 2, 2)] // 🧠 Brain
 	[InlineData ("\U00010421", "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData ("\U0001f339", "🌹", 2, 2)] // 🌹 Rose
-	public void GetColumns_Utf32_Encode (string code, string str, int runeLength, int stringLength)
+	//[InlineData ("\uFE20FE21", "", 1, 1)]   // Combining Ligature Left Half - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	// Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	public void GetColumns_Utf32_Encode (string code, string str, int columns, int stringLength)
 	{
 		var operationStatus = Rune.DecodeFromUtf16 (code, out Rune rune, out int charsConsumed);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
@@ -147,7 +192,7 @@ public class RuneTests {
 		// with DecodeRune
 		(var nrune, var size) = code.DecodeRune ();
 		Assert.Equal (str, nrune.ToString ());
-		Assert.Equal (runeLength, nrune.GetColumns ());
+		Assert.Equal (columns, nrune.GetColumns ());
 		Assert.Equal (stringLength, nrune.ToString ().Length);
 		Assert.Equal (size, nrune.Utf8SequenceLength);
 		for (int x = 0; x < code.Length - 1; x++) {
@@ -165,12 +210,12 @@ public class RuneTests {
 	[InlineData ("\u0915\u093f", "कि", 2, 2, 2)] // Hindi कि with DEVANAGARI LETTER KA and DEVANAGARI VOWEL SIGN I
 	[InlineData ("\u0e4d\u0e32", "ํา", 2, 1, 2)] // Decomposition: ํ (U+0E4D) - า (U+0E32) = U+0E33 ำ Thai Character Sara Am
 	[InlineData ("\u0e33", "ำ", 1, 1, 1)] // Decomposition: ํ (U+0E4D) - า (U+0E32) = U+0E33 ำ Thai Character Sara Am
-	public void GetColumns_String_Without_SurrogatePair (string code, string str, int codeLength, int runesLength, int stringLength)
+	public void GetColumns_String_Without_SurrogatePair (string code, string str, int codeLength, int columns, int stringLength)
 	{
 		Assert.Equal (str, code.Normalize ());
 		Assert.Equal (codeLength, code.Length);
-		Assert.Equal (runesLength, code.EnumerateRunes ().Sum (x => x.GetColumns ()));
-		Assert.Equal (runesLength, str.GetColumns ());
+		//Assert.Equal (columns, code.EnumerateRunes ().Sum (x => x.GetColumns ()));
+		Assert.Equal (columns, str.GetColumns ());
 		Assert.Equal (stringLength, str.Length);
 	}
 
@@ -539,7 +584,7 @@ public class RuneTests {
 
 	[Theory]
 	[InlineData ("Hello, 世界", 13, 11, 9)]   // Without Surrogate Pairs
-	[InlineData ("Hello, 𝔹𝕆𝔹", 19, 13, 13)] // With Surrogate Pairs
+	[InlineData ("Hello, 𝔹𝕆𝔹", 19, 10, 13)] // With Surrogate Pairs
 	public void Test_DecodeRune_Extension (string text, int bytesLength, int colsLength, int textLength)
 	{
 		List<Rune> runes = new List<Rune> ();
@@ -558,7 +603,7 @@ public class RuneTests {
 
 	[Theory]
 	[InlineData ("Hello, 世界", 13, 11, 9, "界世 ,olleH")]   // Without Surrogate Pairs
-	[InlineData ("Hello, 𝔹𝕆𝔹", 19, 13, 13, "𝔹𝕆𝔹 ,olleH")] // With Surrogate Pairs
+	[InlineData ("Hello, 𝔹𝕆𝔹", 19, 10, 13, "𝔹𝕆𝔹 ,olleH")] // With Surrogate Pairs
 	public void Test_DecodeLastRune_Extension (string text, int bytesLength, int colsLength, int textLength, string encoded)
 	{
 		List<Rune> runes = new List<Rune> ();
@@ -641,17 +686,64 @@ public class RuneTests {
 		Assert.False (d.SequenceEqual (b [1]));
 	}
 
+	/// <summary>
+	/// Shows the difference between using Wcwidth.UnicodeCalculator and our
+	/// own port of wcwidth. Specifically, the UnicodeCalculator is more accurate to spec
+	/// where null has a width of 0, and our port says it's -1.
+	/// </summary>
+	/// <param name="expectedColumns"></param>
+	/// <param name="scalar"></param>
+	[Theory]
+	[InlineData (0, 0)]
+	[InlineData (-1, 1)]
+	[InlineData (-1, 2)]
+	[InlineData (-1, 3)]
+	[InlineData (-1, 4)]
+	[InlineData (-1, 5)]
+	[InlineData (-1, 6)]
+	[InlineData (-1, 7)]
+	[InlineData (-1, 8)]
+	[InlineData (-1, 9)]
+	[InlineData (-1, 10)]
+	[InlineData (-1, 11)]
+	[InlineData (-1, 12)]
+	[InlineData (-1, 13)]
+	[InlineData (-1, 14)]
+	[InlineData (-1, 15)]
+	[InlineData (-1, 16)]
+	[InlineData (-1, 17)]
+	[InlineData (-1, 18)]
+	[InlineData (-1, 19)]
+	[InlineData (-1, 20)]
+	[InlineData (-1, 21)]
+	[InlineData (-1, 22)]
+	[InlineData (-1, 23)]
+	[InlineData (-1, 24)]
+	[InlineData (-1, 25)]
+	[InlineData (-1, 26)]
+	[InlineData (-1, 27)]
+	[InlineData (-1, 28)]
+	[InlineData (-1, 29)]
+	[InlineData (-1, 30)]
+	[InlineData (-1, 31)]
+	public void Rune_GetColumns_Non_Printable (int expectedColumns, int scalar)
+	{
+		var rune = new Rune (scalar);
+		Assert.Equal (expectedColumns, rune.GetColumns());
+		Assert.Equal (0, rune.ToString().GetColumns());
+	}
+
 	[Fact]
 	public void Rune_GetColumns_Versus_String_GetColumns_With_Non_Printable_Characters ()
 	{
 		int sumRuneWidth = 0;
 		int sumConsoleWidth = 0;
 		for (uint i = 0; i < 32; i++) {
-			sumRuneWidth += ((Rune)i).GetColumns ();
-			sumConsoleWidth += ((Rune)i).ToString ().GetColumns ();
+			sumRuneWidth += ((Rune)(i)).GetColumns ();
+			sumConsoleWidth += ((Rune)(i)).ToString ().GetColumns ();
 		}
 
-		Assert.Equal (-32, sumRuneWidth);
+		Assert.Equal (-31, sumRuneWidth);
 		Assert.Equal (0, sumConsoleWidth);
 	}
 

+ 3 - 1
UnitTests/Text/StringTests.cs

@@ -16,7 +16,7 @@ public class StringTests {
 	public void TestGetColumns_Null ()
 	{
 		string? str = null;
-		Assert.Equal (0, str.GetColumns ());
+		Assert.Equal (0, str!.GetColumns ());
 	}
 
 	[Fact]
@@ -51,6 +51,8 @@ public class StringTests {
 	[InlineData ("👨‍👩‍👦‍👦👨‍👩‍👦‍👦", 16)]
 	[InlineData ("山", 2)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
 	[InlineData ("山🙂", 4)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
+	//[InlineData ("\ufe20\ufe21", 2)] // Combining Ligature Left Half ︠ - U+fe20 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
+	//				 // Combining Ligature Right Half - U+fe21 -https://github.com/microsoft/terminal/blob/main/src/types/unicode_width_overrides.xml
 	public void TestGetColumns_MultiRune_WideBMP (string str, int expected)
 	{
 		Assert.Equal (expected, str.GetColumns ());

+ 6 - 5
UnitTests/Text/UnicodeTests.cs

@@ -1,4 +1,5 @@
-using Xunit;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
@@ -41,10 +42,10 @@ public class UnicodeTests {
 ┌────────────────────────────┐
 │これは広いルーンラインです。│
 │これは広いルーンラインです。│
-│これは ┌────────────┐ です。│
-│これは │ワイドルーン│ です。│
-│これは │  {CM.Glyphs.LeftBracket} 選ぶ {CM.Glyphs.RightBracket}  │ です。│
-│これは └────────────┘ です。│
+│これは�┌────────────┐�です。│
+│これは�│ワイドルーン│�です。│
+│これは�│  {CM.Glyphs.LeftBracket} 選ぶ {CM.Glyphs.RightBracket}  │�です。│
+│これは�└────────────┘�です。│
 │これは広いルーンラインです。│
 │これは広いルーンラインです。│
 └────────────────────────────┘

+ 63 - 0
UnitTests/UnitTests - Backup.csproj

@@ -0,0 +1,63 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <!-- https://stackoverflow.com/questions/294216/why-does-c-sharp-forbid-generic-attribute-types -->
+    <!-- for AutoInitShutdown attribute -->
+    <LangVersion>Preview</LangVersion>
+    <IsPackable>false</IsPackable>
+    <UseDataCollector />
+    <!-- Version numbers are automatically updated by gitversion when a release is released -->
+    <!-- In the source tree the version will always be 2.0 for all projects. -->
+    <!-- Do not modify these. -->
+    <AssemblyVersion>2.0</AssemblyVersion>
+    <FileVersion>2.0</FileVersion>
+    <Version>2.0</Version>
+    <InformationalVersion>2.0</InformationalVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="dotnet-xunit" Version="2.3.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
+    <PackageReference Include="ReportGenerator" Version="5.1.18" />
+    <PackageReference Include="System.Collections" Version="4.3.0" />
+    <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="19.2.4" />
+    <PackageReference Include="xunit" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="coverlet.collector" Version="3.2.0">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+    <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
+  </ItemGroup>
+  <PropertyGroup Label="FineCodeCoverage">
+    <Enabled>
+      False
+    </Enabled>
+    <Exclude>
+      [UICatalog]*
+    </Exclude>
+    <Include></Include>
+    <ExcludeByFile>
+      <!--**/Migrations/*
+      **/Hacks/*.cs-->
+    </ExcludeByFile>
+    <ExcludeByAttribute>
+      <!--MyCustomExcludeFromCodeCoverage-->
+    </ExcludeByAttribute>
+    <IncludeTestAssembly>
+      False
+    </IncludeTestAssembly>
+    <RunSettingsFilePath>C:\Users\charlie\s\gui-cs\Terminal.Gui\UnitTests\bin\Debug\net7.0\fine-code-coverage\coverage-tool-output\UnitTests-fcc-mscodecoverage-generated.runsettings</RunSettingsFilePath>
+  </PropertyGroup>
+</Project>

+ 9 - 4
UnitTests/UnitTests.csproj

@@ -21,12 +21,12 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
-    <PackageReference Include="ReportGenerator" Version="5.1.21" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
+    <PackageReference Include="ReportGenerator" Version="5.1.23" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="19.2.29" />
-    <PackageReference Include="xunit" Version="2.4.2" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
+    <PackageReference Include="xunit" Version="2.5.0" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
@@ -39,6 +39,11 @@
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
     <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
   </ItemGroup>
+  <ItemGroup>
+    <None Update="xunit.runner.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
   <PropertyGroup Label="FineCodeCoverage">
     <Enabled>
       False

+ 6 - 6
UnitTests/View/DrawTests.cs

@@ -23,8 +23,8 @@ namespace Terminal.Gui.ViewsTests {
 			Assert.Equal ("𝔹", r.ToString ());
 			Assert.Equal (us, r.ToString ());
 
-			Assert.Equal (2, us.GetColumns ());
-			Assert.Equal (2, r.GetColumns ());
+			Assert.Equal (1, us.GetColumns ());
+			Assert.Equal (1, r.GetColumns ());
 
 			var win = new Window () { Title = us };
 			var label = new Label (r.ToString ());
@@ -37,9 +37,9 @@ namespace Terminal.Gui.ViewsTests {
 			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
 			var expected = @"
-┌┤𝔹├────┐
-│𝔹      │
-│𝔹      │
+┌┤𝔹├────
+│𝔹       
+│𝔹       
 └────────┘";
 			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
@@ -55,7 +55,7 @@ namespace Terminal.Gui.ViewsTests {
 			};
 
 			TestHelpers.AssertDriverColorsAre (@"
-0022000000
+0020000000
 0000000000
 0111000000
 0000000000", expectedColors);

+ 1 - 1
UnitTests/View/KeyboardTests.cs

@@ -160,7 +160,7 @@ namespace Terminal.Gui.ViewTests {
 
 			Application.Top.Add (view);
 
-			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', shift, alt, control));
+			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', 0, shift, alt, control));
 
 			Application.Iteration += () => Application.RequestStop ();
 

+ 3 - 3
UnitTests/View/Layout/LayoutTests.cs

@@ -540,8 +540,8 @@ Y
 			// that was set on the OnAdded method with the text length of 3
 			// and height 1 because wasn't set and the text has 1 line
 			Assert.Equal (new Rect (0, 0, 3, 1), lbl.Frame);
-			Assert.Equal (new Rect (0, 0, 3, 1), lbl._needsDisplay);
-			Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 3, 1), lbl._needsDisplayRect);
+			Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView._needsDisplayRect);
 			Assert.True (lbl.SuperView.LayoutNeeded);
 			lbl.SuperView.Draw ();
 			Assert.Equal ("12  ", GetContents ());
@@ -550,7 +550,7 @@ Y
 			{
 				var text = "";
 				for (int i = 0; i < 4; i++) {
-					text += (char)Application.Driver.Contents [0, i, 0];
+					text += Application.Driver.Contents [0, i].Runes[0];
 				}
 				return text;
 			}

+ 25 - 20
UnitTests/View/ViewTests.cs

@@ -534,11 +534,11 @@ namespace Terminal.Gui.ViewTests {
 				Application.Begin (Application.Top);
 
 				// should have the initial text
-				Assert.Equal ('t', driver.Contents [0, 0, 0]);
-				Assert.Equal ('e', driver.Contents [0, 1, 0]);
-				Assert.Equal ('s', driver.Contents [0, 2, 0]);
-				Assert.Equal ('t', driver.Contents [0, 3, 0]);
-				Assert.Equal (' ', driver.Contents [0, 4, 0]);
+				Assert.Equal ((Rune)'t', driver.Contents [0, 0].Runes [0]);
+				Assert.Equal ((Rune)'e', driver.Contents [0, 1].Runes [0]);
+				Assert.Equal ((Rune)'s', driver.Contents [0, 2].Runes [0]);
+				Assert.Equal ((Rune)'t', driver.Contents [0, 3].Runes [0]);
+				Assert.Equal ((Rune)' ', driver.Contents [0, 4].Runes [0]);
 			} finally {
 				Application.Shutdown ();
 			}
@@ -563,7 +563,7 @@ namespace Terminal.Gui.ViewTests {
 			// BUGBUG: v2 - _needsDisplay needs debugging - test disabled for now.
 			//Assert.Equal (new Rect (new Point (0, 0), rect.Size), view._needsDisplay);
 			Assert.True (view.LayoutNeeded);
-			Assert.False (view._subViewNeedsDisplay);
+			Assert.False (view.SubViewNeedsDisplay);
 			Assert.False (view._addingView);
 			view._addingView = true;
 			Assert.True (view._addingView);
@@ -680,7 +680,7 @@ namespace Terminal.Gui.ViewTests {
 
 				for (int i = 0; i < Application.Driver.Rows; i++) {
 					for (int j = 0; j < Application.Driver.Cols; j++) {
-						if (contents [i, j, 0] != ' ') {
+						if (contents [i, j].Runes[0] != (Rune)' ') {
 							runesCount++;
 						}
 					}
@@ -888,7 +888,7 @@ namespace Terminal.Gui.ViewTests {
 			win.Add (label);
 			var top = Application.Top;
 			top.Add (win);
-			Application.Begin (top);
+			var rs = Application.Begin (top);
 
 			Assert.True (label.Visible);
 			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
@@ -901,6 +901,9 @@ namespace Terminal.Gui.ViewTests {
 ", output);
 
 			label.Visible = false;
+
+			bool firstIteration = false;
+			Application.RunMainLoopIteration (ref rs, true, ref firstIteration);
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────────────────────────┐
 │                            │
@@ -1092,7 +1095,7 @@ At 0,0
 			Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
 			view.LayoutStyle = LayoutStyle.Absolute;
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect);
 			top.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0     
@@ -1127,7 +1130,7 @@ At 0,0
 			view.Height = 1;
 			Assert.Equal (new Rect (1, 1, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect);
 			top.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0     
@@ -1161,7 +1164,7 @@ At 0,0
 			Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
 			view.LayoutStyle = LayoutStyle.Absolute;
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect);
 			view.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0                       
@@ -1198,7 +1201,7 @@ At 0,0
 			view.Height = 1;
 			Assert.Equal (new Rect (1, 1, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect);
 			view.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0                       
@@ -1233,7 +1236,7 @@ At 0,0
 			Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
 			view.LayoutStyle = LayoutStyle.Absolute;
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect);
 			top.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0       
@@ -1270,7 +1273,7 @@ At 0,0
 			view.Height = 1;
 			Assert.Equal (new Rect (3, 3, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect);
 			top.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0       
@@ -1303,7 +1306,7 @@ At 0,0
 
 			view.Frame = new Rect (3, 3, 10, 1);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 1), view._needsDisplayRect);
 			view.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0                       
@@ -1340,7 +1343,7 @@ At 0,0
 			view.Height = 1;
 			Assert.Equal (new Rect (3, 3, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Bounds);
-			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 30, 2), view._needsDisplayRect);
 			view.Draw ();
 			TestHelpers.AssertDriverContentsWithFrameAre (@"
 At 0,0                       
@@ -1363,6 +1366,8 @@ At 0,0
 			bottom.Add (new Label ("222"));
 			v.Add (bottom);
 
+			v.BeginInit ();
+			v.EndInit ();
 			v.LayoutSubviews ();
 			v.Draw ();
 
@@ -1407,19 +1412,19 @@ At 0,0
 			Application.Begin (top);
 
 			top.LayoutComplete += (s, e) => {
-				Assert.Equal (new Rect (0, 0, 80, 25), top._needsDisplay);
+				Assert.Equal (new Rect (0, 0, 80, 25), top._needsDisplayRect);
 			};
 
 			frame.LayoutComplete += (s, e) => {
-				Assert.Equal (new Rect (0, 0, 40, 8), frame._needsDisplay);
+				Assert.Equal (new Rect (0, 0, 40, 8), frame._needsDisplayRect);
 			};
 
 			label.LayoutComplete += (s, e) => {
-				Assert.Equal (new Rect (0, 0, 38, 1), label._needsDisplay);
+				Assert.Equal (new Rect (0, 0, 38, 1), label._needsDisplayRect);
 			};
 
 			button.LayoutComplete += (s, e) => {
-				Assert.Equal (new Rect (0, 0, 13, 1), button._needsDisplay);
+				Assert.Equal (new Rect (0, 0, 13, 1), button._needsDisplayRect);
 			};
 
 			Assert.True (label.AutoSize);

+ 1 - 1
UnitTests/Views/ListViewTests.cs

@@ -240,7 +240,7 @@ namespace Terminal.Gui.ViewsTests {
 			{
 				var item = "";
 				for (int i = 0; i < 7; i++) {
-					item += (char)Application.Driver.Contents [line, i, 0];
+					item += Application.Driver.Contents [line, i].Runes[0];
 				}
 				return item;
 			}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 570 - 570
UnitTests/Views/ProgressBarTests.cs


+ 4 - 1
UnitTests/Views/TabViewTests.cs

@@ -25,10 +25,13 @@ namespace Terminal.Gui.ViewsTests {
 
 		private TabView GetTabView (out Tab tab1, out Tab tab2, bool initFakeDriver = true)
 		{
-			if (initFakeDriver)
+			if (initFakeDriver) {
 				InitFakeDriver ();
+			}
 
 			var tv = new TabView ();
+			tv.BeginInit ();
+			tv.EndInit ();
 			tv.ColorScheme = new ColorScheme ();
 			tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false);
 			tv.AddTab (tab2 = new Tab ("Tab2", new Label ("hi2")), false);

+ 1 - 1
UnitTests/Views/TableViewTests.cs

@@ -1901,7 +1901,7 @@ namespace Terminal.Gui.ViewsTests {
 			// Now test making the width too small for the MinAcceptableWidth
 			// the Column won't fit so should not be rendered
 			var driver = ((FakeDriver)Application.Driver);
-			driver.UpdateOffScreen ();
+			driver.ClearContents ();
 
 
 			tableView.Bounds = new Rect (0, 0, 9, 5);

+ 1 - 1
UnitTests/Views/TextFieldTests.cs

@@ -1137,7 +1137,7 @@ namespace Terminal.Gui.ViewsTests {
 			{
 				var item = "";
 				for (int i = 0; i < 16; i++) {
-					item += (char)Application.Driver.Contents [0, i, 0];
+					item += Application.Driver.Contents [0, i].Runes [0];
 				}
 				return item;
 			}

+ 4 - 4
UnitTests/Views/ToplevelTests.cs

@@ -1012,7 +1012,7 @@ namespace Terminal.Gui.ViewsTests {
 
 			void view_LayoutStarted (object sender, LayoutEventArgs e)
 			{
-				Assert.Equal (new Rect (0, 0, 20, 10), view._needsDisplay);
+				Assert.Equal (new Rect (0, 0, 20, 10), view._needsDisplayRect);
 				view.LayoutStarted -= view_LayoutStarted;
 			}
 
@@ -1024,12 +1024,12 @@ namespace Terminal.Gui.ViewsTests {
 
 			view.Frame = new Rect (1, 3, 10, 5);
 			Assert.Equal (new Rect (1, 3, 10, 5), view.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplayRect);
 
 			view.OnDrawContent (view.Bounds);
 			view.Frame = new Rect (1, 3, 10, 5);
 			Assert.Equal (new Rect (1, 3, 10, 5), view.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplay);
+			Assert.Equal (new Rect (0, 0, 10, 5), view._needsDisplayRect);
 		}
 
 		// BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why
@@ -1284,7 +1284,7 @@ namespace Terminal.Gui.ViewsTests {
 		}
 
 		[Fact, AutoInitShutdown]
-		public void Single_Smaller_Top_Will_Have_Cleaning_Trails_Chunk_On_Move ()
+		public void Modal_As_Top_Will_Drag_Cleanly ()
 		{
 			var dialog = new Dialog () { Width = 30, Height = 10 };
 			dialog.Add (new Label (

+ 5 - 4
UnitTests/Views/TreeViewTests.cs

@@ -848,13 +848,15 @@ namespace Terminal.Gui.ViewsTests {
 			tv.AddObject (n2);
 			tv.Expand (n1);
 
-			var pink = new Attribute (Color.Magenta, Color.Black);
-			var hotpink = new Attribute (Color.BrightMagenta, Color.Black);
-
 			tv.ColorScheme = new ColorScheme ();
 			tv.LayoutSubviews ();
 			tv.Draw ();
 
+			// create a new color scheme
+			var pink = new Attribute (Color.Magenta, Color.Black);
+			var hotpink = new Attribute (Color.BrightMagenta, Color.Black);
+
+
 			// Normal drawing of the tree view
 			TestHelpers.AssertDriverContentsAre (
 @"├-normal
@@ -871,7 +873,6 @@ namespace Terminal.Gui.ViewsTests {
 ",
 				new [] { tv.ColorScheme.Normal, pink });
 
-			// create a new color scheme
 			var pinkScheme = new ColorScheme {
 				Normal = pink,
 				Focus = hotpink

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio