Forráskód Böngészése

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 2 éve
szülő
commit
0df485a890
100 módosított fájl, 10919 hozzáadás és 9934 törlés
  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,
 # the repo. Unless a later match takes precedence,
 # @global-owner1 and @global-owner2 will be requested for
 # @global-owner1 and @global-owner2 will be requested for
 # review when someone opens a pull request.
 # review when someone opens a pull request.
-*       @migueldeicaza @tig
+* @tig
+
+/docs/ @tig @bdisp @tznind
 
 
 # Order is important; the last matching pattern takes the most
 # Order is important; the last matching pattern takes the most
 # precedence. When someone opens a pull request that only
 # precedence. When someone opens a pull request that only

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

@@ -29,6 +29,7 @@ jobs:
 
 
     - name: Test
     - name: Test
       run: |
       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
         dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage"  --settings UnitTests/coverlet.runsettings
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
         mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 
 

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

@@ -50,6 +50,7 @@ jobs:
 
 
     #- name: Test to generate Code Coverage Report
     #- name: Test to generate Code Coverage Report
     #  run: |
     #  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
     #    dotnet test --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings
     #    mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
     #    mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/
 
 

+ 1 - 0
.gitignore

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

+ 29 - 67
Terminal.Gui/Application.cs

@@ -6,7 +6,6 @@ using System.Globalization;
 using System.Reflection;
 using System.Reflection;
 using System.IO;
 using System.IO;
 using System.Text.Json.Serialization;
 using System.Text.Json.Serialization;
-using static Terminal.Gui.ConfigurationManager;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
@@ -55,42 +54,7 @@ namespace Terminal.Gui {
 
 
 		// For Unit testing - ignores UseSystemConsole
 		// For Unit testing - ignores UseSystemConsole
 		internal static bool _forceFakeConsole;
 		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;
 		private static List<CultureInfo> _cachedSupportedCultures;
 
 
 		/// <summary>
 		/// <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.
 		// 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)
 		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) {
 			if (_initialized) {
 				throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
 				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);
 			MainLoop = new MainLoop (mainLoopDriver);
 
 
 			try {
 			try {
-				Driver.EnableConsoleScrolling = EnableConsoleScrolling;
 				Driver.Init (OnTerminalResized);
 				Driver.Init (OnTerminalResized);
 			} catch (InvalidOperationException ex) {
 			} catch (InvalidOperationException ex) {
 				// This is a case where the driver is unable to initialize the console.
 				// 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?
 			// BUGBUG: OverlappedTop is not cleared here, but it should be?
 
 
+			MainLoop?.Stop();
 			MainLoop = null;
 			MainLoop = null;
 			Driver?.End ();
 			Driver?.End ();
 			Driver = null;
 			Driver = null;
@@ -289,7 +255,6 @@ namespace Terminal.Gui {
 			NotifyStopRunState = null;
 			NotifyStopRunState = null;
 			_initialized = false;
 			_initialized = false;
 			_mouseGrabView = null;
 			_mouseGrabView = null;
-			_enableConsoleScrolling = false;
 			_lastMouseOwnerView = null;
 			_lastMouseOwnerView = null;
 
 
 			// Reset synchronization context to allow the user to run async/await,
 			// Reset synchronization context to allow the user to run async/await,
@@ -552,7 +517,8 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public static void Refresh ()
 		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;
 			View last = null;
 			foreach (var v in _toplevels.Reverse ()) {
 			foreach (var v in _toplevels.Reverse ()) {
 				if (v.Visible) {
 				if (v.Visible) {
@@ -674,13 +640,14 @@ namespace Terminal.Gui {
 				Iteration?.Invoke ();
 				Iteration?.Invoke ();
 
 
 				EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
 				EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-				if ((state.Toplevel != Current && Current?.Modal == true)
-					|| (state.Toplevel != Current && Current?.Modal == false)) {
+				if (state.Toplevel != Current) {
 					OverlappedTop?.OnDeactivate (state.Toplevel);
 					OverlappedTop?.OnDeactivate (state.Toplevel);
 					state.Toplevel = Current;
 					state.Toplevel = Current;
 					OverlappedTop?.OnActivate (state.Toplevel);
 					OverlappedTop?.OnActivate (state.Toplevel);
 					Top.SetSubViewNeedsDisplay ();
 					Top.SetSubViewNeedsDisplay ();
 					Refresh ();
 					Refresh ();
+				} else if (Current.SuperView == null && Current?.Modal == true) {
+					Refresh ();
 				}
 				}
 				if (Driver.EnsureCursorVisibility ()) {
 				if (Driver.EnsureCursorVisibility ()) {
 					state.Toplevel.SetNeedsDisplay ();
 					state.Toplevel.SetNeedsDisplay ();
@@ -690,42 +657,41 @@ namespace Terminal.Gui {
 			}
 			}
 			firstIteration = false;
 			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 ();
 				Top.Draw ();
 				foreach (var top in _toplevels.Reverse ()) {
 				foreach (var top in _toplevels.Reverse ()) {
 					if (top != Top && top != state.Toplevel) {
 					if (top != Top && top != state.Toplevel) {
 						top.SetNeedsDisplay ();
 						top.SetNeedsDisplay ();
 						top.SetSubViewNeedsDisplay ();
 						top.SetSubViewNeedsDisplay ();
+						top.Clear ();
 						top.Draw ();
 						top.Draw ();
 					}
 					}
 				}
 				}
 			}
 			}
 			if (_toplevels.Count == 1 && state.Toplevel == Top
 			if (_toplevels.Count == 1 && state.Toplevel == Top
 				&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
 				&& (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 ();
 				state.Toplevel.Draw ();
-				//if (state.Toplevel.SuperView != null) {
-				//	state.Toplevel.SuperView?.OnRenderLineCanvas ();
-				//} else {
-				//	state.Toplevel.OnRenderLineCanvas ();
-				//}
 				state.Toplevel.PositionCursor ();
 				state.Toplevel.PositionCursor ();
 				Driver.Refresh ();
 				Driver.Refresh ();
 			} else {
 			} else {
 				Driver.UpdateCursor ();
 				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 ();
 				Top.Draw ();
 			}
 			}
 		}
 		}
@@ -735,7 +701,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public static void DoEvents ()
 		public static void DoEvents ()
 		{
 		{
-			MainLoop.Driver.Wakeup ();
+			MainLoop.MainLoopDriver.Wakeup ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -1011,7 +977,6 @@ namespace Terminal.Gui {
 		{
 		{
 			var full = new Rect (0, 0, Driver.Cols, Driver.Rows);
 			var full = new Rect (0, 0, Driver.Cols, Driver.Rows);
 			TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height });
 			TerminalResized?.Invoke (new ResizedEventArgs () { Cols = full.Width, Rows = full.Height });
-			Driver.Clip = full;
 			foreach (var t in _toplevels) {
 			foreach (var t in _toplevels) {
 				t.SetRelativeLayout (full);
 				t.SetRelativeLayout (full);
 				t.LayoutSubviews ();
 				t.LayoutSubviews ();
@@ -1073,7 +1038,7 @@ namespace Terminal.Gui {
 			if (!OnGrabbingMouse (view)) {
 			if (!OnGrabbingMouse (view)) {
 				OnGrabbedMouse (view);
 				OnGrabbedMouse (view);
 				_mouseGrabView = view;
 				_mouseGrabView = view;
-				Driver.UncookMouse ();
+				//Driver.UncookMouse ();
 			}
 			}
 		}
 		}
 
 
@@ -1087,7 +1052,7 @@ namespace Terminal.Gui {
 			if (!OnUnGrabbingMouse (_mouseGrabView)) {
 			if (!OnUnGrabbingMouse (_mouseGrabView)) {
 				OnUnGrabbedMouse (_mouseGrabView);
 				OnUnGrabbedMouse (_mouseGrabView);
 				_mouseGrabView = null;
 				_mouseGrabView = null;
-				Driver.CookMouse ();
+				//Driver.CookMouse ();
 			}
 			}
 		}
 		}
 
 
@@ -1132,10 +1097,7 @@ namespace Terminal.Gui {
 
 
 		static void ProcessMouseEvent (MouseEvent me)
 		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) {
 			if (IsMouseDisabled) {
 				return;
 				return;

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

@@ -1,31 +1,34 @@
 using System.Text;
 using System.Text;
 using System;
 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>
 		/// <summary>
 		/// Gets (copies from) or sets (pastes to) the contents of the OS clipboard.
 		/// 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");
 							// throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
 							clipData = string.Empty;
 							clipData = string.Empty;
 						}
 						}
-						contents = clipData;
+						_contents = clipData;
 					}
 					}
 				} catch (Exception) {
 				} catch (Exception) {
-					contents = string.Empty;
+					_contents = string.Empty;
 				}
 				}
-				return contents;
+				return _contents;
 			}
 			}
 			set {
 			set {
 				try {
 				try {
@@ -54,51 +57,120 @@ namespace Terminal.Gui {
 						}
 						}
 						Application.Driver.Clipboard.SetClipboardData (value);
 						Application.Driver.Clipboard.SetClipboardData (value);
 					}
 					}
-					contents = value;
+					_contents = value;
 				} catch (NotSupportedException e) {
 				} catch (NotSupportedException e) {
 					throw e;
 					throw e;
 				} catch (Exception) {
 				} 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 ()
 		public string GetClipboardData ()
 		{
 		{
 			try {
 			try {
+				var result = GetClipboardDataImpl ();
+				if (result == null) {
+					return string.Empty;
+				}
 				return GetClipboardDataImpl ();
 				return GetClipboardDataImpl ();
 			} catch (NotSupportedException ex) {
 			} catch (NotSupportedException ex) {
 				throw new NotSupportedException ("Failed to copy from the OS clipboard.", 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>
 		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
 		public void SetClipboardData (string text)
 		public void SetClipboardData (string text)
 		{
 		{
+			if (text == null) {
+				throw new ArgumentNullException (nameof (text));
+			}
+
 			try {
 			try {
 				SetClipboardDataImpl (text);
 				SetClipboardDataImpl (text);
 			} catch (NotSupportedException ex) {
 			} catch (NotSupportedException ex) {
@@ -59,25 +67,21 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// Copies the contents of the OS clipboard to <paramref name="result"/> if possible.
 		/// </summary>
 		/// </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>
 		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
 		public bool TryGetClipboardData (out string result)
 		public bool TryGetClipboardData (out string result)
 		{
 		{
+			result = string.Empty;
 			// Don't even try to read because environment is not set up.
 			// Don't even try to read because environment is not set up.
 			if (!IsSupported) {
 			if (!IsSupported) {
-				result = null;
 				return false;
 				return false;
 			}
 			}
 
 
 			try {
 			try {
 				result = GetClipboardDataImpl ();
 				result = GetClipboardDataImpl ();
-				while (result == null) {
-					result = GetClipboardDataImpl ();
-				}
 				return true;
 				return true;
 			} catch (NotSupportedException ex) {
 			} catch (NotSupportedException ex) {
 				System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
 				System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
-				result = null;
 				return false;
 				return false;
 			}
 			}
 		}
 		}

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

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

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

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

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

@@ -2,7 +2,7 @@
 
 
 #nullable enable
 #nullable enable
 
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 
 /// <summary>
 /// <summary>
 /// The root object for a Theme. A Theme is a set of settings that are applied to the running <see cref="Application"/>
 /// 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 ()
 	internal override bool Apply ()
 	{
 	{
 		var ret = base.Apply ();
 		var ret = base.Apply ();
-		Application.Driver?.InitalizeColorSchemes ();
+		Application.Driver?.InitializeColorSchemes ();
 		return ret;
 		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.
 // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
 //
 using System.Text;
 using System.Text;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Diagnostics;
 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>
 	/// <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>
 	/// </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>
 	/// <summary>
-	/// Indicates the RGB for true colors.
+	/// The handler fired when the terminal is resized.
 	/// </summary>
 	/// </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>
 	/// <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>
 	/// </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>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <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>
 	/// </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>
 	/// <summary>
-	/// Cursors Visibility that are displayed
+	/// Tests whether the specified coordinate are valid for drawing. 
 	/// </summary>
 	/// </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>
 	/// <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>
 	/// </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>
 		/// <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>
 		/// </summary>
-		public abstract void UncookMouse ();
-
+		Off = 0b_0000_0000,
 		/// <summary>
 		/// <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>
 		/// </summary>
-		public abstract void CookMouse ();
-
-		private Attribute currentAttribute;
-
+		FrameRuler = 0b_0000_0001,
 		/// <summary>
 		/// <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>
 		/// </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>
 	/// <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>
 	/// </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;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
-using System.Threading.Tasks;
 using System.Text;
 using System.Text;
 using Unix.Terminal;
 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 ((char)Key.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 0, false, false, false),
 							new ConsoleKeyInfo ('[', 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;
 				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 ((char)Key.Esc, 0, false, false, false),
 							new ConsoleKeyInfo ('[', 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 {
 					} 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 {
 			} 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 {
 				} 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 (shift) {
-				if (keyChar == 0) {
-					key |= Key.ShiftMask;
-				}
-				km.Shift = shift;
+				mod |= ConsoleModifiers.Shift;
 			}
 			}
 			if (alt) {
 			if (alt) {
-				key |= Key.AltMask;
-				km.Alt = alt;
+				mod |= ConsoleModifiers.Alt;
 			}
 			}
 			if (control) {
 			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>
 	/// <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>
 	/// </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;
 			this.mainLoop = mainLoop;
 			pipe (wakeupPipes);
 			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;
 				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_LAZY = 1;
 		const int RTLD_GLOBAL = 8;
 		const int RTLD_GLOBAL = 8;
 
 
-		readonly string libraryPath;
+		public readonly string LibraryPath;
 		readonly IntPtr handle;
 		readonly IntPtr handle;
 
 
 		public IntPtr NativeLibraryHandle => handle;
 		public IntPtr NativeLibraryHandle => handle;
@@ -104,13 +104,15 @@ namespace Unix.Terminal {
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
 		{
 		{
 			if (isFullPath) {
 			if (isFullPath) {
-				this.libraryPath = FirstValidLibraryPath (libraryPathAlternatives);
-				this.handle = PlatformSpecificLoadLibrary (this.libraryPath);
+				this.LibraryPath = FirstValidLibraryPath (libraryPathAlternatives);
+				this.handle = PlatformSpecificLoadLibrary (this.LibraryPath);
 			} else {
 			} else {
 				foreach (var lib in libraryPathAlternatives) {
 				foreach (var lib in libraryPathAlternatives) {
 					this.handle = PlatformSpecificLoadLibrary (lib);
 					this.handle = PlatformSpecificLoadLibrary (lib);
-					if (this.handle != IntPtr.Zero)
+					if (this.handle != IntPtr.Zero) {
+						this.LibraryPath = lib;
 						break;
 						break;
+					}
 				}
 				}
 			}
 			}
 
 

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

@@ -71,6 +71,7 @@ namespace Unix.Terminal {
 		//static bool use_naked_driver;
 		//static bool use_naked_driver;
 
 
 		static UnmanagedLibrary curses_library;
 		static UnmanagedLibrary curses_library;
+
 		static NativeMethods methods;
 		static NativeMethods methods;
 
 
 		[DllImport ("libc")]
 		[DllImport ("libc")]
@@ -97,6 +98,13 @@ namespace Unix.Terminal {
 			cols_ptr = get_ptr ("COLS");
 			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 ()
 		static public Window initscr ()
 		{
 		{
 			setlocale (LC_ALL, "");
 			setlocale (LC_ALL, "");
@@ -116,6 +124,9 @@ namespace Unix.Terminal {
 							 "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig");
 							 "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig");
 				Environment.Exit (1);
 				Environment.Exit (1);
 			}
 			}
+
+			//Console.Error.WriteLine ($"using curses {Curses.curses_version ()}");
+
 			return main_window;
 			return main_window;
 		}
 		}
 
 
@@ -141,7 +152,10 @@ namespace Unix.Terminal {
 
 
 			console_sharp_get_dims (out l, out c);
 			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;
 				lines = l;
 				cols = c;
 				cols = c;
 				return true;
 				return true;
@@ -250,7 +264,35 @@ namespace Unix.Terminal {
 
 
 		public static int StartColor () => methods.start_color ();
 		public static int StartColor () => methods.start_color ();
 		public static bool HasColors => methods.has_colors ();
 		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);
 		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 UseDefaultColors () => methods.use_default_colors ();
 		public static int ColorPairs => methods.COLOR_PAIRS ();
 		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 move (int line, int col) => methods.move (line, col);
 		static public int curs_set (int visibility) => methods.curs_set (visibility);
 		static public int curs_set (int visibility) => methods.curs_set (visibility);
 		//static public int addch (int ch) => methods.addch (ch);
 		//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 addwstr (string s) => methods.addwstr (s);
 		static public int mvaddwstr (int y, int x, string s) => methods.mvaddwstr (y, x, 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 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 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 attron (int attrs) => methods.attron (attrs);
 		static public int attroff (int attrs) => methods.attroff (attrs);
 		static public int attroff (int attrs) => methods.attroff (attrs);
 		static public int attrset (int attrs) => methods.attrset (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 move (int line, int col);
 		public delegate int curs_set (int visibility);
 		public delegate int curs_set (int visibility);
 		public delegate int addch (int ch);
 		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 mvaddch (int y, int x, int ch);
 		public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
 		public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
 		public delegate int mvaddwstr (int y, int x, [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 savetty ();
 		public delegate int resetty ();
 		public delegate int resetty ();
 		public delegate int set_escdelay (int size);
 		public delegate int set_escdelay (int size);
+		public delegate IntPtr curses_version ();
 	}
 	}
 
 
 	internal class NativeMethods {
 	internal class NativeMethods {
@@ -443,11 +489,13 @@ namespace Unix.Terminal {
 		public readonly Delegates.move move;
 		public readonly Delegates.move move;
 		public readonly Delegates.curs_set curs_set;
 		public readonly Delegates.curs_set curs_set;
 		public readonly Delegates.addch addch;
 		public readonly Delegates.addch addch;
+		public readonly Delegates.echochar echochar;
 		public readonly Delegates.mvaddch mvaddch;
 		public readonly Delegates.mvaddch mvaddch;
 		public readonly Delegates.addwstr addwstr;
 		public readonly Delegates.addwstr addwstr;
 		public readonly Delegates.mvaddwstr mvaddwstr;
 		public readonly Delegates.mvaddwstr mvaddwstr;
 		public readonly Delegates.wmove wmove;
 		public readonly Delegates.wmove wmove;
 		public readonly Delegates.waddch waddch;
 		public readonly Delegates.waddch waddch;
+		//public readonly Delegates.wechochar wechochar;
 		public readonly Delegates.attron attron;
 		public readonly Delegates.attron attron;
 		public readonly Delegates.attroff attroff;
 		public readonly Delegates.attroff attroff;
 		public readonly Delegates.attrset attrset;
 		public readonly Delegates.attrset attrset;
@@ -476,6 +524,7 @@ namespace Unix.Terminal {
 		public readonly Delegates.savetty savetty;
 		public readonly Delegates.savetty savetty;
 		public readonly Delegates.resetty resetty;
 		public readonly Delegates.resetty resetty;
 		public readonly Delegates.set_escdelay set_escdelay;
 		public readonly Delegates.set_escdelay set_escdelay;
+		public readonly Delegates.curses_version curses_version;
 		public UnmanagedLibrary UnmanagedLibrary;
 		public UnmanagedLibrary UnmanagedLibrary;
 
 
 		public NativeMethods (UnmanagedLibrary lib)
 		public NativeMethods (UnmanagedLibrary lib)
@@ -519,6 +568,7 @@ namespace Unix.Terminal {
 			move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
 			move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
 			curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
 			curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
 			addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
 			addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
+			echochar = lib.GetNativeMethodDelegate<Delegates.echochar> ("echochar");
 			mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
 			mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
 			addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
 			addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
 			mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
 			mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
@@ -552,6 +602,7 @@ namespace Unix.Terminal {
 			savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
 			savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
 			resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
 			resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
 			set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
 			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
 #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);
 				return Curses.waddch (Handle, ch);
 			}
 			}
-	
+
+			//public int echochar (char ch)
+			//{
+			//	return Curses.wechochar (Handle, ch);
+			//}
+
 			public int refresh ()
 			public int refresh ()
 			{
 			{
 				return Curses.wrefresh (Handle);
 				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.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
+using Rune = System.Text.Rune;
 
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
 
 #pragma warning disable RCS1138 // Add summary to documentation comment.
 #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>
 	/// 
 	/// 
 	/// </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 ();
 			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. 
 // FakeDriver.cs: A fake ConsoleDriver for unit tests. 
 //
 //
 using System;
 using System;
+using System.Buffers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Linq;
 using System.Linq;
@@ -11,717 +12,564 @@ using System.Text;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 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
 #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 {
 			} 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 {
 				} 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));
 					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
 #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;
-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.Threading.Tasks;
 using System.Text;
 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
 #if PROCESS_REQUEST
-		bool neededProcessRequest;
+		bool _neededProcessRequest;
 #endif
 #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
 #if PROCESS_REQUEST
-				neededProcessRequest = false;
+				_neededProcessRequest = false;
 #endif
 #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;
 						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) {
 			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;
 				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;
 					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;
 				}
 				}
-				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 {
 			} 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;
 				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) {
 			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 {
 				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 {
 			} 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>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
-	/// This implementation is used for NetDriver.
+	///   Passing a consoleDriver is provided to capture windows resizing.
 	/// </remarks>
 	/// </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;
 			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 különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 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; 
 namespace Terminal.Gui; 
 
 
 /// <summary>
 /// <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>
 /// </summary>
 public class Cell {
 public class Cell {
 	/// <summary>
 	/// <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>
 	/// </summary>
-	public Rune? Rune { get; set; }
+	public List<Rune> Runes { get; set; } = new List<Rune> ();
 
 
 	/// <summary>
 	/// <summary>
-	/// The foreground color to draw the glyph with.
+	/// The attributes to use when drawing the Glyph.
 	/// </summary>
 	/// </summary>
 	public Attribute? Attribute { get; set; }
 	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>
 		/// </summary>
 		public Rune Collapse { get; set; } = (Rune)'-';
 		public Rune Collapse { get; set; } = (Rune)'-';
 
 
+		/// <summary>
+		/// Identical To (U+226)
+		/// </summary>
+		public Rune IdenticalTo { get; set; } = (Rune)'≡';
+
 		/// <summary>
 		/// <summary>
 		/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
 		/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
 		/// </summary>
 		/// </summary>
@@ -166,6 +171,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public Rune File { get; set; } = (Rune)'☰';
 		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 -----------------
 		#region ----------------- Lines -----------------
 		/// <summary>
 		/// <summary>
 		/// Box Drawings Horizontal Line - Light (U+2500) - ─
 		/// 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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
@@ -73,7 +74,7 @@ namespace Terminal.Gui {
 			ConfigurationManager.Applied += ConfigurationManager_Applied;
 			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) {
 			foreach (var irr in runeResolvers) {
 				irr.Value.SetGlyphs ();
 				irr.Value.SetGlyphs ();
@@ -142,7 +143,7 @@ namespace Terminal.Gui {
 				_lines.Remove(l);
 				_lines.Remove(l);
 			}
 			}
 
 
-			return l;
+			return l!;
 		}
 		}
 
 
 		/// <summary>
 		/// <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 ()) {
 			if (!intersects.Any ()) {
 				return null;
 				return null;
 			}
 			}
 
 
 			var cell = new Cell ();
 			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);
 			cell.Attribute = GetAttributeForIntersects (intersects);
 			return cell;
 			return cell;
 		}
 		}

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

@@ -340,7 +340,7 @@ namespace Terminal.Gui {
 		QuitToplevel,
 		QuitToplevel,
 
 
 		/// <summary>
 		/// <summary>
-		/// Suspend a application (used on Linux).
+		/// Suspend a application (Only implemented in <see cref="CursesDriver"/>).
 		/// </summary>
 		/// </summary>
 		Suspend,
 		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>
 		/// <summary>
 		/// The current <see cref="IMainLoopDriver"/> in use.
 		/// The current <see cref="IMainLoopDriver"/> in use.
 		/// </summary>
 		/// </summary>
-		/// <value>The driver.</value>
-		public IMainLoopDriver Driver { get; }
+		/// <value>The main loop driver.</value>
+		public IMainLoopDriver MainLoopDriver { get; }
 
 
 		/// <summary>
 		/// <summary>
 		/// Invoked when a new timeout is added. To be used in the case
 		/// 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>
 		/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
 		public MainLoop (IMainLoopDriver driver)
 		public MainLoop (IMainLoopDriver driver)
 		{
 		{
-			Driver = driver;
+			MainLoopDriver = driver;
 			driver.Setup (this);
 			driver.Setup (this);
 		}
 		}
 
 
@@ -128,7 +128,7 @@ namespace Terminal.Gui {
 				idleHandlers.Add (idleHandler);
 				idleHandlers.Add (idleHandler);
 			}
 			}
 
 
-			Driver.Wakeup ();
+			MainLoopDriver.Wakeup ();
 			return idleHandler;
 			return idleHandler;
 		}
 		}
 
 
@@ -140,8 +140,9 @@ namespace Terminal.Gui {
 		///  This method also returns <c>false</c> if the idle handler is not found.
 		///  This method also returns <c>false</c> if the idle handler is not found.
 		public bool RemoveIdle (Func<bool> token)
 		public bool RemoveIdle (Func<bool> token)
 		{
 		{
-			lock (_idleHandlersLock)
+			lock (_idleHandlersLock) {
 				return idleHandlers.Remove (token);
 				return idleHandlers.Remove (token);
+			}
 		}
 		}
 
 
 		void AddTimeout (TimeSpan time, Timeout timeout)
 		void AddTimeout (TimeSpan time, Timeout timeout)
@@ -149,7 +150,7 @@ namespace Terminal.Gui {
 			lock (_timeoutsLockToken) {
 			lock (_timeoutsLockToken) {
 				var k = (DateTime.UtcNow + time).Ticks;
 				var k = (DateTime.UtcNow + time).Ticks;
 				timeouts.Add (NudgeToUniqueKey (k), timeout);
 				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>
 		/// </remarks>
 		public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
 		public object AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
 		{
 		{
-			if (callback == null)
+			if (callback == null) {
 				throw new ArgumentNullException (nameof (callback));
 				throw new ArgumentNullException (nameof (callback));
+			}
 			var timeout = new Timeout () {
 			var timeout = new Timeout () {
 				Span = time,
 				Span = time,
 				Callback = callback
 				Callback = callback
@@ -188,8 +190,9 @@ namespace Terminal.Gui {
 		{
 		{
 			lock (_timeoutsLockToken) {
 			lock (_timeoutsLockToken) {
 				var idx = timeouts.IndexOfValue (token as Timeout);
 				var idx = timeouts.IndexOfValue (token as Timeout);
-				if (idx == -1)
+				if (idx == -1) {
 					return false;
 					return false;
+				}
 				timeouts.RemoveAt (idx);
 				timeouts.RemoveAt (idx);
 			}
 			}
 			return true;
 			return true;
@@ -213,8 +216,9 @@ namespace Terminal.Gui {
 				var k = t.Key;
 				var k = t.Key;
 				var timeout = t.Value;
 				var timeout = t.Value;
 				if (k < now) {
 				if (k < now) {
-					if (timeout.Callback (this))
+					if (timeout.Callback (this)) {
 						AddTimeout (timeout.Span, timeout);
 						AddTimeout (timeout.Span, timeout);
+					}
 				} else {
 				} else {
 					lock (_timeoutsLockToken) {
 					lock (_timeoutsLockToken) {
 						timeouts.Add (NudgeToUniqueKey (k), timeout);
 						timeouts.Add (NudgeToUniqueKey (k), timeout);
@@ -249,21 +253,25 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			foreach (var idle in iterate) {
 			foreach (var idle in iterate) {
-				if (idle ())
-					lock (_idleHandlersLock)
+				if (idle ()) {
+					lock (_idleHandlersLock) {
 						idleHandlers.Add (idle);
 						idleHandlers.Add (idle);
+					}
+				}
 			}
 			}
 		}
 		}
 
 
 		bool _running;
 		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>
 		/// <summary>
 		///   Stops the mainloop.
 		///   Stops the mainloop.
 		/// </summary>
 		/// </summary>
 		public void Stop ()
 		public void Stop ()
 		{
 		{
 			_running = false;
 			_running = false;
-			Driver.Wakeup ();
+			MainLoopDriver.Wakeup ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -276,7 +284,7 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public bool EventsPending (bool wait = false)
 		public bool EventsPending (bool wait = false)
 		{
 		{
-			return Driver.EventsPending (wait);
+			return MainLoopDriver.EventsPending (wait);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -291,10 +299,11 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public void RunIteration ()
 		public void RunIteration ()
 		{
 		{
-			if (timeouts.Count > 0)
+			if (timeouts.Count > 0) {
 				RunTimers ();
 				RunTimers ();
+			}
 
 
-			Driver.Iteration ();
+			MainLoopDriver.Iteration ();
 
 
 			bool runIdle = false;
 			bool runIdle = false;
 			lock (_idleHandlersLock) {
 			lock (_idleHandlersLock) {

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

@@ -28,7 +28,6 @@
       "Ctrl"
       "Ctrl"
     ]
     ]
   },
   },
-  "Application.EnableConsoleScrolling": false,
   "Application.QuitKey": {
   "Application.QuitKey": {
     "Key": "Q",
     "Key": "Q",
     "Modifiers": [
     "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.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
     <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
     <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" />
     <InternalsVisibleTo Include="UnitTests" />
    </ItemGroup>
    </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 -->
   <!-- API Documentation -->
   <ItemGroup>
   <ItemGroup>
     <None Include="..\docfx\images\logo.png">
     <None Include="..\docfx\images\logo.png">

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

@@ -1,5 +1,6 @@
 using System.Globalization;
 using System.Globalization;
 using System.Text;
 using System.Text;
+using Wcwidth;
 
 
 namespace Terminal.Gui;
 namespace Terminal.Gui;
 
 
@@ -26,21 +27,7 @@ public static class RuneExtensions {
 	/// </returns>
 	/// </returns>
 	public static int GetColumns (this Rune rune)
 	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>
 	/// <summary>
@@ -179,133 +166,4 @@ public static class RuneExtensions {
 		}
 		}
 		return true;
 		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>
 		/// </summary>
 		public override void OnDrawContent (Rect contentArea)
 		public override void OnDrawContent (Rect contentArea)
 		{
 		{
-			if (Thickness == Thickness.Empty) return;
+			if (Thickness == Thickness.Empty) {
+				return;
+			}
 
 
 			if (ColorScheme != null) {
 			if (ColorScheme != null) {
 				Driver.SetAttribute (GetNormalColor ());
 				Driver.SetAttribute (GetNormalColor ());
@@ -139,9 +141,6 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			//Driver.SetAttribute (Colors.Error.Normal);
 			//Driver.SetAttribute (Colors.Error.Normal);
-
-			var prevClip = SetClip (Frame);
-
 			var screenBounds = ViewToScreen (Frame);
 			var screenBounds = ViewToScreen (Frame);
 
 
 			// This just draws/clears the thickness, not the insides.
 			// 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.
 		// 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>
 		/// <summary>
 		/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
 		/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-		/// color scheme.
+		/// color scheme. 
 		/// </summary>
 		/// </summary>
 		public virtual ColorScheme ColorScheme {
 		public virtual ColorScheme ColorScheme {
 			get {
 			get {
@@ -34,7 +34,11 @@ namespace Terminal.Gui {
 		/// If it's overridden can return other values.</returns>
 		/// If it's overridden can return other values.</returns>
 		public virtual Attribute GetNormalColor ()
 		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>
 		/// <summary>
@@ -76,106 +80,126 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Removes the <see cref="_needsDisplay"/> and the <see cref="_subViewNeedsDisplay"/> setting on this view.
+		/// Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.
 		/// </summary>
 		/// </summary>
 		protected void ClearNeedsDisplay ()
 		protected void ClearNeedsDisplay ()
 		{
 		{
-			_needsDisplay = Rect.Empty;
+			_needsDisplayRect = Rect.Empty;
 			_subViewNeedsDisplay = false;
 			_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>
 		/// <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>
 		/// </summary>
 		public void SetNeedsDisplay ()
 		public void SetNeedsDisplay ()
 		{
 		{
-			if (!IsInitialized) return;
+			if (!IsInitialized) {
+				return;
+			}
 			SetNeedsDisplay (Bounds);
 			SetNeedsDisplay (Bounds);
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <param name="region">The view-relative region that needs to be redrawn.</param>
 		/// <param name="region">The view-relative region that needs to be redrawn.</param>
 		public void SetNeedsDisplay (Rect region)
 		public void SetNeedsDisplay (Rect region)
 		{
 		{
-			if (_needsDisplay.IsEmpty) {
-				_needsDisplay = region;
+			if (_needsDisplayRect.IsEmpty) {
+				_needsDisplayRect = region;
 			} else {
 			} 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 ();
 			_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;
 				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>
 		/// <summary>
 		/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 		/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 		/// </summary>
 		/// </summary>
 		public void SetSubViewNeedsDisplay ()
 		public void SetSubViewNeedsDisplay ()
 		{
 		{
-			if (_subViewNeedsDisplay) {
-				return;
-			}
 			_subViewNeedsDisplay = true;
 			_subViewNeedsDisplay = true;
-			if (_superView != null && !_superView._subViewNeedsDisplay)
+			if (_superView != null && !_superView._subViewNeedsDisplay) {
 				_superView.SetSubViewNeedsDisplay ();
 				_superView.SetSubViewNeedsDisplay ();
+			}
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		///   Clears the view region with the current color.
+		///   Clears the <see cref="Bounds"/> with the normal background color.
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
 		///   <para>
 		///   <para>
-		///     This clears the entire region used by this view.
+		///     This clears the Bounds used by this view.
 		///   </para>
 		///   </para>
 		/// </remarks>
 		/// </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>
 		/// <summary>
-		///   Clears the specified region with the current color. 
+		///   Clears the specified screen-relative rectangle with the normal background. 
 		/// </summary>
 		/// </summary>
 		/// <remarks>
 		/// <remarks>
 		/// </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)
 		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
 		// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
@@ -190,35 +214,18 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
 		/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
 		/// <remarks>
 		/// <remarks>
 		/// <para>
 		/// <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"/>.
 		/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public Rect ClipToBounds ()
 		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;
 			var previous = Driver.Clip;
-			Driver.Clip = Rect.Intersect (previous, ViewToScreen (region));
+			Driver.Clip = Rect.Intersect (previous, ViewToScreen (Bounds));
 			return previous;
 			return previous;
 		}
 		}
 
 
@@ -304,22 +311,11 @@ namespace Terminal.Gui {
 				return false;
 				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 
 			// Each of these renders lines to either this View's LineCanvas 
 			// Those lines will be finally rendered in OnRenderLineCanvas
 			// 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;
 			return true;
 		}
 		}
@@ -346,7 +342,6 @@ namespace Terminal.Gui {
 			if (!CanBeVisible (this)) {
 			if (!CanBeVisible (this)) {
 				return;
 				return;
 			}
 			}
-
 			OnDrawFrames ();
 			OnDrawFrames ();
 
 
 			var prevClip = ClipToBounds ();
 			var prevClip = ClipToBounds ();
@@ -367,7 +362,6 @@ namespace Terminal.Gui {
 			Driver.Clip = prevClip;
 			Driver.Clip = prevClip;
 
 
 			OnRenderLineCanvas ();
 			OnRenderLineCanvas ();
-
 			// Invoke DrawContentCompleteEvent
 			// Invoke DrawContentCompleteEvent
 			OnDrawContentComplete (Bounds);
 			OnDrawContentComplete (Bounds);
 
 
@@ -389,29 +383,29 @@ namespace Terminal.Gui {
 				return false;
 				return false;
 			}
 			}
 
 
-			//Driver.SetAttribute (new Attribute(Color.White, Color.Black));
-
 			// If we have a SuperView, it'll render our frames.
 			// If we have a SuperView, it'll render our frames.
 			if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) {
 			if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) {
 				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
 				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.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 ();
 				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)) {
 				foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
-					// Combine the LineCavas'
+					// Combine the LineCanvas'
 					LineCanvas.Merge (subview.LineCanvas);
 					LineCanvas.Merge (subview.LineCanvas);
 					subview.LineCanvas.Clear ();
 					subview.LineCanvas.Clear ();
 				}
 				}
 
 
 				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
 				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.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 ();
 				LineCanvas.Clear ();
 			}
 			}
@@ -441,38 +435,45 @@ namespace Terminal.Gui {
 		/// </remarks>
 		/// </remarks>
 		public virtual void OnDrawContent (Rect contentArea)
 		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 
 				// This should NOT clear 
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
-				    HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
-				    Rect.Empty, false);
+					HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
+					Rect.Empty, false);
 				SetSubViewNeedsDisplay ();
 				SetSubViewNeedsDisplay ();
 			}
 			}
 
 
 			// Draw subviews
 			// Draw subviews
 			// TODO: Implement OnDrawSubviews (cancelable);
 			// 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 ()
 		internal void SetNeedsLayout ()
 		{
 		{
-			if (LayoutNeeded)
+			if (LayoutNeeded) {
 				return;
 				return;
+			}
 			LayoutNeeded = true;
 			LayoutNeeded = true;
 			foreach (var view in Subviews) {
 			foreach (var view in Subviews) {
 				view.SetNeedsLayout ();
 				view.SetNeedsLayout ();
 			}
 			}
 			TextFormatter.NeedsFormat = true;
 			TextFormatter.NeedsFormat = true;
-			if (SuperView == null)
-				return;
-			SuperView.SetNeedsLayout ();
+			SuperView?.SetNeedsLayout ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -541,7 +540,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Converts a region in view-relative coordinates to screen-relative coordinates.
 		/// Converts a region in view-relative coordinates to screen-relative coordinates.
 		/// </summary>
 		/// </summary>
-		internal Rect ViewToScreen (Rect region)
+		public Rect ViewToScreen (Rect region)
 		{
 		{
 			ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false);
 			ViewToScreen (region.X, region.Y, out var x, out var y, clamped: false);
 			return new Rect (x, y, region.Width, region.Height);
 			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;
 using System.Text;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
-	public partial class View  {
+	public partial class View {
 		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 
 
 		View _superView = null;
 		View _superView = null;
@@ -128,7 +128,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public bool IsAdded { get; private set; }
 		public bool IsAdded { get; private set; }
 
 
@@ -420,10 +420,12 @@ namespace Terminal.Gui {
 		{
 		{
 			var args = new FocusEventArgs (view);
 			var args = new FocusEventArgs (view);
 			Enter?.Invoke (this, args);
 			Enter?.Invoke (this, args);
-			if (args.Handled)
+			if (args.Handled) {
 				return true;
 				return true;
-			if (base.OnEnter (view))
+			}
+			if (base.OnEnter (view)) {
 				return true;
 				return true;
+			}
 
 
 			return false;
 			return false;
 		}
 		}
@@ -433,11 +435,14 @@ namespace Terminal.Gui {
 		{
 		{
 			var args = new FocusEventArgs (view);
 			var args = new FocusEventArgs (view);
 			Leave?.Invoke (this, args);
 			Leave?.Invoke (this, args);
-			if (args.Handled)
+			if (args.Handled) {
 				return true;
 				return true;
-			if (base.OnLeave (view))
+			}
+			if (base.OnLeave (view)) {
 				return true;
 				return true;
+			}
 
 
+			Driver?.SetCursorVisibility (CursorVisibility.Invisible);
 			return false;
 			return false;
 		}
 		}
 
 

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

@@ -119,11 +119,11 @@ namespace Terminal.Gui {
 
 
 		Rune GetCheckedState ()
 		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 ()
 		string GetFormatterText ()
@@ -157,9 +157,7 @@ namespace Terminal.Gui {
 			get => allowNullChecked;
 			get => allowNullChecked;
 			set {
 			set {
 				allowNullChecked = value;
 				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>
 		/// <summary>
 		/// Cursor for the selected color.
 		/// Cursor for the selected color.

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

@@ -213,8 +213,8 @@ namespace Terminal.Gui {
 			Source.Position = displayStart;
 			Source.Position = displayStart;
 			var n = source.Read (data, 0, data.Length);
 			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++) {
 			for (int line = 0; line < frame.Height; line++) {
 				var lineRect = new Rect (0, line, frame.Width, 1);
 				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;
 			var savedClip = Driver.Clip;
 			Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
 			Driver.Clip = new Rect (0, 0, Driver.Cols, Driver.Rows);
-
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
 
 
 			OnDrawFrames ();
 			OnDrawFrames ();

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

@@ -14,597 +14,596 @@
 using System;
 using System;
 using System.Linq;
 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) {
 				if (!ShowVerticalScrollIndicator) {
 					ShowVerticalScrollIndicator = true;
 					ShowVerticalScrollIndicator = true;
 				}
 				}
 				v = 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);
 			Move (0, 0);
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
-			for (int i = 0; i < Frame.Width; i++)
+			for (int i = 0; i < Frame.Width; i++) {
 				Driver.AddRune ((Rune)' ');
 				Driver.AddRune ((Rune)' ');
+			}
 
 
 			Move (1, 0);
 			Move (1, 0);
 			var scheme = GetNormalColor ();
 			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
 				// 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);
 				tabsBar.Y = Pos.Percent (0);
 			}
 			}
-
+			LayoutSubviews ();
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
 
 

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

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

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

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

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

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

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

@@ -757,9 +757,10 @@ namespace Terminal.Gui {
 				return;
 				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 ();
 				LayoutSubviews ();
 				PositionToplevels ();
 				PositionToplevels ();
 
 
@@ -776,9 +777,10 @@ namespace Terminal.Gui {
 					}
 					}
 				}
 				}
 
 
+				// This should not be here, but in base
 				foreach (var view in Subviews) {
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
 					if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
-						view.SetNeedsLayout ();
+						//view.SetNeedsLayout ();
 						view.SetNeedsDisplay (view.Bounds);
 						view.SetNeedsDisplay (view.Bounds);
 						view.SetSubViewNeedsDisplay ();
 						view.SetSubViewNeedsDisplay ();
 					}
 					}

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

@@ -97,7 +97,7 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			foreach (var top in _toplevels) {
 			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 ();
 					OverlappedTop.SetSubViewNeedsDisplay ();
 					return true;
 					return true;
 				}
 				}

+ 1 - 3
Terminal.sln

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

+ 3 - 0
UICatalog/Properties/launchSettings.json

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

+ 2 - 2
UICatalog/Scenarios/BasicColors.cs

@@ -87,8 +87,8 @@ namespace UICatalog.Scenarios {
 
 
 			Application.RootMouseEvent = (e) => {
 			Application.RootMouseEvent = (e) => {
 				if (e.View != null) {
 				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 ();
 					lblForeground.Text = fore.ToString ();
 					viewForeground.ColorScheme.Normal = new Attribute (fore, fore);
 					viewForeground.ColorScheme.Normal = new Attribute (fore, fore);
 					lblBackground.Text = back.ToString ();
 					lblBackground.Text = back.ToString ();

+ 290 - 73
UICatalog/Scenarios/CharacterMap.cs

@@ -3,6 +3,7 @@
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Data;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Net.Http;
 using System.Net.Http;
@@ -11,6 +12,7 @@ using System.Text.Json;
 using System.Text.Unicode;
 using System.Text.Unicode;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Terminal.Gui;
 using Terminal.Gui;
+using static Terminal.Gui.SpinnerStyle;
 using static Terminal.Gui.TableView;
 using static Terminal.Gui.TableView;
 
 
 namespace UICatalog.Scenarios;
 namespace UICatalog.Scenarios;
@@ -27,24 +29,31 @@ namespace UICatalog.Scenarios;
 [ScenarioCategory ("ScrollView")]
 [ScenarioCategory ("ScrollView")]
 public class CharacterMap : Scenario {
 public class CharacterMap : Scenario {
 	CharMap _charMap;
 	CharMap _charMap;
-	Label _errorLabel;
+	public Label _errorLabel;
 	TableView _categoryList;
 	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 ()
 	public override void Setup ()
 	{
 	{
 		_charMap = new CharMap () {
 		_charMap = new CharMap () {
 			X = 0,
 			X = 0,
-			Y = 0,
+			Y = 1,
 			Height = Dim.Fill ()
 			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) };
 		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" };
 		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;
 		jumpEdit.TextChanged += JumpEdit_TextChanged;
 
 
@@ -96,15 +105,42 @@ public class CharacterMap : Scenario {
 			_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
 			_charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start;
 		};
 		};
 
 
-		Win.Add (_categoryList);
+		Application.Top.Add (_categoryList);
 
 
 		_charMap.SelectedCodePoint = 0;
 		_charMap.SelectedCodePoint = 0;
 		//jumpList.Refresh ();
 		//jumpList.Refresh ();
 		_charMap.SetFocus ();
 		_charMap.SetFocus ();
 
 
 		_charMap.Width = Dim.Fill () - _categoryList.Width;
 		_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)
 	EnumerableTableSource<UnicodeRange> CreateCategoryTable (int sortByColumn, bool descending)
 	{
 	{
@@ -177,7 +213,7 @@ public class CharacterMap : Scenario {
 			_errorLabel.Text = $"Beyond maximum codepoint";
 			_errorLabel.Text = $"Beyond maximum codepoint";
 			return;
 			return;
 		}
 		}
-		_errorLabel.Text = $"U+{result:x4}";
+		_errorLabel.Text = $"U+{result:x5}";
 
 
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		var table = (EnumerableTableSource<UnicodeRange>)_categoryList.Table;
 		_categoryList.SelectedRow = table.Data
 		_categoryList.SelectedRow = table.Data
@@ -200,8 +236,7 @@ class CharMap : ScrollView {
 		get => _start;
 		get => _start;
 		set {
 		set {
 			_start = value;
 			_start = value;
-			_selected = value;
-			ContentOffset = new Point (0, (int)(_start / 16));
+			SelectedCodePoint = value;
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 		}
 		}
 	}
 	}
@@ -216,15 +251,16 @@ class CharMap : ScrollView {
 		get => _selected;
 		get => _selected;
 		set {
 		set {
 			_selected = value;
 			_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) {
 			if (row + ContentOffset.Y < 0) {
 				// Moving up.
 				// Moving up.
 				ContentOffset = new Point (ContentOffset.X, row);
 				ContentOffset = new Point (ContentOffset.X, row);
 			} else if (row + ContentOffset.Y >= height) {
 			} else if (row + ContentOffset.Y >= height) {
 				// Moving down.
 				// 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);
 			var width = (Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH) - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
 			if (col + ContentOffset.X < 0) {
 			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 {
 	public Point Cursor {
 		get {
 		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);
 			return new Point (col, row);
 		}
 		}
 		set => throw new NotImplementedException ();
 		set => throw new NotImplementedException ();
@@ -250,35 +291,42 @@ class CharMap : ScrollView {
 
 
 	public override void PositionCursor ()
 	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);
 			Driver.SetCursorVisibility (CursorVisibility.Default);
-			Move (Cursor.X + ContentOffset.X + RowLabelWidth + 1, Cursor.Y + ContentOffset.Y + 1);
+			Move (Cursor.X, Cursor.Y);
 		} else {
 		} else {
 			Driver.SetCursorVisibility (CursorVisibility.Invisible);
 			Driver.SetCursorVisibility (CursorVisibility.Invisible);
 		}
 		}
 	}
 	}
 
 
+	public bool ShowGlyphWidths {
+		get => _rowHeight == 2;
+		set {
+			_rowHeight = value ? 2 : 1;
+			SetNeedsDisplay ();
+		}
+	}
 
 
 	int _start = 0;
 	int _start = 0;
 	int _selected = 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 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 ()
 	public CharMap ()
 	{
 	{
 		ColorScheme = Colors.Dialog;
 		ColorScheme = Colors.Dialog;
 		CanFocus = true;
 		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, () => {
 		AddCommand (Command.ScrollUp, () => {
 			if (SelectedCodePoint >= 16) {
 			if (SelectedCodePoint >= 16) {
@@ -305,12 +353,12 @@ class CharMap : ScrollView {
 			return true;
 			return true;
 		});
 		});
 		AddCommand (Command.PageUp, () => {
 		AddCommand (Command.PageUp, () => {
-			var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+			var page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			SelectedCodePoint -= Math.Min (page, SelectedCodePoint);
 			return true;
 			return true;
 		});
 		});
 		AddCommand (Command.PageDown, () => {
 		AddCommand (Command.PageDown, () => {
-			var page = (Bounds.Height / ROW_HEIGHT - 1) * 16;
+			var page = (Bounds.Height / _rowHeight - 1) * 16;
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint);
 			return true;
 			return true;
 		});
 		});
@@ -336,26 +384,34 @@ class CharMap : ScrollView {
 
 
 	public override void OnDrawContent (Rect contentArea)
 	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);
 		base.OnDrawContent (contentArea);
 	}
 	}
 
 
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	//public void CharMap_DrawContent (object s, DrawEventArgs a)
 	public override void OnDrawContentComplete (Rect contentArea)
 	public override void OnDrawContentComplete (Rect contentArea)
 	{
 	{
+		if (contentArea.Height == 0 || contentArea.Width == 0) {
+			return;
+		}
 		Rect viewport = new Rect (ContentOffset,
 		Rect viewport = new Rect (ContentOffset,
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 			new Size (Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0),
 				Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 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));
 			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 ());
 		Driver.SetAttribute (GetHotNormalColor ());
 		Move (0, 0);
 		Move (0, 0);
@@ -382,7 +438,7 @@ class CharMap : ScrollView {
 				Move (x, 0);
 				Move (x, 0);
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
 				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.AddStr ($"{hexDigit:x}");
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.SetAttribute (GetHotNormalColor ());
 				Driver.AddStr (" ");
 				Driver.AddStr (" ");
@@ -390,7 +446,10 @@ class CharMap : ScrollView {
 		}
 		}
 
 
 		var firstColumnX = viewport.X + RowLabelWidth;
 		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;
 			var val = (row) * 16;
 			if (val > MaxCodePoint) {
 			if (val > MaxCodePoint) {
 				continue;
 				continue;
@@ -398,16 +457,26 @@ class CharMap : ScrollView {
 			Move (firstColumnX + COLUMN_WIDTH, y);
 			Move (firstColumnX + COLUMN_WIDTH, y);
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
 			for (int col = 0; col < 16; col++) {
 			for (int col = 0; col < 16; col++) {
+
 				var x = firstColumnX + COLUMN_WIDTH * col + 1;
 				var x = firstColumnX + COLUMN_WIDTH * col + 1;
+
 				Move (x, y);
 				Move (x, y);
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
 					Driver.SetAttribute (GetFocusColor ());
 					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 {
 				} 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) {
 				if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) {
@@ -416,8 +485,11 @@ class CharMap : ScrollView {
 			}
 			}
 			Move (0, y);
 			Move (0, y);
 			Driver.SetAttribute (HasFocus && (cursorRow + ContentOffset.Y + 1 == y) ? ColorScheme.HotFocus : ColorScheme.HotNormal);
 			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;
 		Driver.Clip = oldClip;
 	}
 	}
@@ -426,30 +498,38 @@ class CharMap : ScrollView {
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	void Handle_MouseClick (object sender, MouseEventEventArgs args)
 	{
 	{
 		var me = args.MouseEvent;
 		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;
 			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;
 		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) {
 		if (val > MaxCodePoint) {
 			return;
 			return;
 		}
 		}
 
 
+		if (me.Flags == MouseFlags.ReportMousePosition) {
+			Hover?.Invoke (this, new ListViewItemEventArgs (val, null));
+		}
+
 		if (me.Flags == MouseFlags.Button1Clicked) {
 		if (me.Flags == MouseFlags.Button1Clicked) {
 			SelectedCodePoint = val;
 			SelectedCodePoint = val;
 			return;
 			return;
@@ -535,6 +615,7 @@ class CharMap : ScrollView {
 		};
 		};
 		Application.Run (waitIndicator);
 		Application.Run (waitIndicator);
 
 
+
 		if (!string.IsNullOrEmpty (decResponse)) {
 		if (!string.IsNullOrEmpty (decResponse)) {
 			string name = string.Empty;
 			string name = string.Empty;
 
 
@@ -551,25 +632,156 @@ class CharMap : ScrollView {
 				//&& property3Element.TryGetProperty ("nestedProperty", out JsonElement nestedPropertyElement)) {
 				//&& property3Element.TryGetProperty ("nestedProperty", out JsonElement nestedPropertyElement)) {
 				//	Console.WriteLine (nestedPropertyElement.GetString ());
 				//	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 ();
 				CopyGlyph ();
-				break;
-			case 1:
+				dlg.RequestStop ();
+			};
+			copyCP.Clicked += (s, a) => {
 				CopyCodePoint ();
 				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 {
 		} 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
 		// BUGBUG: This is a workaround for some weird ScrollView related mouse grab bug
 		Application.GrabMouse (this);
 		Application.GrabMouse (this);
 	}
 	}
 
 
-
 	public override bool OnEnter (View view)
 	public override bool OnEnter (View view)
 	{
 	{
 		if (IsInitialized) {
 		if (IsInitialized) {
@@ -577,6 +789,12 @@ class CharMap : ScrollView {
 		}
 		}
 		return base.OnEnter (view);
 		return base.OnEnter (view);
 	}
 	}
+
+	public override bool OnLeave (View view)
+	{
+		Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return base.OnLeave (view);
+	}
 }
 }
 
 
 public class UcdApiClient {
 public class UcdApiClient {
@@ -637,7 +855,6 @@ class UnicodeRange {
 
 
 			new UnicodeRange (0x1F130, 0x1F149   ,"Squared Latin Capital Letters"),
 			new UnicodeRange (0x1F130, 0x1F149   ,"Squared Latin Capital Letters"),
 			new UnicodeRange (0x12400, 0x1240f   ,"Cuneiform Numbers and Punctuation"),
 			new UnicodeRange (0x12400, 0x1240f   ,"Cuneiform Numbers and Punctuation"),
-			new UnicodeRange (0x1FA00, 0x1FA0f   ,"Chess Symbols"),
 			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
 			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
 			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
 			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
 			new UnicodeRange (0x10100, 0x1013F   ,"Aegean Numbers"),
 			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`.
 			//    that reads "Press <hotkey> to Quit". Access this Window with `this.Win`.
 			//  - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`.
 			//  - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`.
 			// To override this, implement an override of `Init`.
 			// To override this, implement an override of `Init`.
-			
+
 			//base.Init ();
 			//base.Init ();
-			
+
 			// A common, alternate, implementation where `this.Win` is not used is below. This code
 			// A common, alternate, implementation where `this.Win` is not used is below. This code
 			// leverages ConfigurationManager to borrow the color scheme settings from UICatalog:
 			// leverages ConfigurationManager to borrow the color scheme settings from UICatalog:
-			
+
 			Application.Init ();
 			Application.Init ();
 			ConfigurationManager.Themes.Theme = Theme;
 			ConfigurationManager.Themes.Theme = Theme;
 			ConfigurationManager.Apply ();
 			ConfigurationManager.Apply ();
@@ -37,7 +37,6 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Center (),
 				Y = Pos.Center (),
 			};
 			};
 			Application.Top.Add (button);
 			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 canvas in _layers) {
 
 
 					foreach (var c in canvas.GetCellMap ()) {
 					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;
 							_fractionTimer = null;
 							button.Enabled = true;
 							button.Enabled = true;
 						}
 						}
-						Application.MainLoop.Driver.Wakeup ();
+						Application.MainLoop.MainLoopDriver.Wakeup ();
 					}, null, 0, _timerTick);
 					}, null, 0, _timerTick);
 				}
 				}
 			};
 			};
@@ -128,7 +128,7 @@ namespace UICatalog.Scenarios {
 				marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
 				marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
 				marqueesBlocksPB.Pulse ();
 				marqueesBlocksPB.Pulse ();
 				marqueesContinuousPB.Pulse ();
 				marqueesContinuousPB.Pulse ();
-				Application.MainLoop.Driver.Wakeup ();
+				Application.MainLoop.MainLoopDriver.Wakeup ();
 			}, null, 0, 300);
 			}, null, 0, 300);
 
 
 			Application.Top.Unloaded += Top_Unloaded;
 			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(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"),
 			new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
 			new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
 			new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
 			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((uint)(CharMap.MaxCodePoint - 16), (uint)CharMap.MaxCodePoint,"End"),
 
 
 			new UnicodeRange (0x0020 ,0x007F        ,"Basic Latin"),
 			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 class UnicodeInMenu : Scenario {
 		public override void Setup ()
 		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 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 [] {
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_Файл", new MenuItem [] {
 				new MenuBarItem ("_Файл", new MenuItem [] {
@@ -30,7 +23,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuBarItem ("_Edit", new MenuItem [] {
 					new MenuItem ("_Copy", "", null),
 					new MenuItem ("_Copy", "", null),
 					new MenuItem ("C_ut", "", null),
 					new MenuItem ("C_ut", "", null),
-					new MenuItem ("_Paste", "", null)
+					new MenuItem ("_糊", "hú (Paste)", null)
 				})
 				})
 			});
 			});
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
@@ -60,11 +53,10 @@ namespace UICatalog.Scenarios {
 			label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 };
 			Win.Add (label);
 			Win.Add (label);
 			var checkBox = new CheckBox (gitString) { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) };
 			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);
 			Win.Add (label);
 			var comboBox = new ComboBox () {
 			var comboBox = new ComboBox () {
 				X = 20,
 				X = 20,

+ 4 - 27
UICatalog/UICatalog.cs

@@ -214,8 +214,6 @@ namespace UICatalog {
 				CM.Apply ();
 				CM.Apply ();
 			}
 			}
 
 
-			//Application.EnableConsoleScrolling = _enableConsoleScrolling;
-
 			Application.Run<UICatalogTopLevel> ();
 			Application.Run<UICatalogTopLevel> ();
 			Application.Shutdown ();
 			Application.Shutdown ();
 
 
@@ -239,7 +237,6 @@ namespace UICatalog {
 
 
 		static bool _useSystemConsole = false;
 		static bool _useSystemConsole = false;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
-		//static bool _enableConsoleScrolling = false;
 		static bool _isFirstRunning = true;
 		static bool _isFirstRunning = true;
 		static string _topLevelColorScheme = string.Empty;
 		static string _topLevelColorScheme = string.Empty;
 
 
@@ -254,7 +251,6 @@ namespace UICatalog {
 			public MenuItem? miUseSubMenusSingleFrame;
 			public MenuItem? miUseSubMenusSingleFrame;
 			public MenuItem? miIsMenuBorderDisabled;
 			public MenuItem? miIsMenuBorderDisabled;
 			public MenuItem? miIsMouseDisabled;
 			public MenuItem? miIsMouseDisabled;
-			public MenuItem? miEnableConsoleScrolling;
 
 
 			public ListView CategoryList;
 			public ListView CategoryList;
 
 
@@ -383,8 +379,8 @@ namespace UICatalog {
 				ScenarioList.KeyDown += (s, a) => {
 				ScenarioList.KeyDown += (s, a) => {
 					if (CollectionNavigator.IsCompatibleKey (a.KeyEvent)) {
 					if (CollectionNavigator.IsCompatibleKey (a.KeyEvent)) {
 						var newItem = _scenarioCollectionNav?.GetNextMatchingItem (ScenarioList.SelectedRow, (char)a.KeyEvent.KeyValue);
 						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.EnsureSelectedCellIsVisible ();
 							ScenarioList.SetNeedsDisplay ();
 							ScenarioList.SetNeedsDisplay ();
 							a.Handled = true;
 							a.Handled = true;
@@ -426,8 +422,7 @@ namespace UICatalog {
 				ConfigChanged ();
 				ConfigChanged ();
 
 
 				miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
 				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}";
 				OS.Title = $"OS: {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystem} {Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.OperatingSystemVersion}";
 
 
 				if (_selectedScenario != null) {
 				if (_selectedScenario != null) {
@@ -489,8 +484,7 @@ namespace UICatalog {
 			{
 			{
 				List<MenuItem []> menuItems = new List<MenuItem []> {
 				List<MenuItem []> menuItems = new List<MenuItem []> {
 					CreateDiagnosticFlagsMenuItems (),
 					CreateDiagnosticFlagsMenuItems (),
-					new MenuItem [] { },
-					CreateEnableConsoleScrollingMenuItems (),
+					Array.Empty<MenuItem> (),
 					CreateDisabledEnabledMouseItems (),
 					CreateDisabledEnabledMouseItems (),
 					CreateDisabledEnabledMenuBorder (),
 					CreateDisabledEnabledMenuBorder (),
 					CreateDisabledEnableUseSubMenusSingleFrame (),
 					CreateDisabledEnableUseSubMenusSingleFrame (),
@@ -567,22 +561,6 @@ namespace UICatalog {
 				return menuItems.ToArray ();
 				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 ()
 			MenuItem [] CreateDiagnosticFlagsMenuItems ()
 			{
 			{
 				const string OFF = "Diagnostics: _Off";
 				const string OFF = "Diagnostics: _Off";
@@ -759,7 +737,6 @@ namespace UICatalog {
 				StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
 				StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
 
 
 				miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
 				miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
-				miEnableConsoleScrolling!.Checked = Application.EnableConsoleScrolling;
 
 
 				var height = (UICatalogApp.ShowStatusBar ? 1 : 0);// + (MenuBar.Visible ? 1 : 0);
 				var height = (UICatalogApp.ShowStatusBar ? 1 : 0);// + (MenuBar.Visible ? 1 : 0);
 										  //ContentPane.Height = Dim.Fill (height);
 										  //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.Driver);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Top);
 			Assert.Null (Application.Current);
 			Assert.Null (Application.Current);
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.RootMouseEvent);
@@ -36,11 +35,14 @@ namespace Terminal.Gui.ApplicationTests {
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Driver);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Top);
 			Assert.NotNull (Application.Current);
 			Assert.NotNull (Application.Current);
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.NotNull (Application.MainLoop);
 			Assert.NotNull (Application.MainLoop);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.Iteration);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.RootMouseEvent);
 			Assert.Null (Application.TerminalResized);
 			Assert.Null (Application.TerminalResized);
+			// FakeDriver is always 80x25
+			Assert.Equal (80, Application.Driver.Cols);
+			Assert.Equal (25, Application.Driver.Rows);
+
 		}
 		}
 
 
 		void Init ()
 		void Init ()
@@ -60,28 +62,24 @@ namespace Terminal.Gui.ApplicationTests {
 		public void Init_Shutdown_Cleans_Up ()
 		public void Init_Shutdown_Cleans_Up ()
 		{
 		{
 			// Verify initial state is per spec
 			// Verify initial state is per spec
-			Pre_Init_State ();
+			//Pre_Init_State ();
 
 
 			Application.Init (new FakeDriver ());
 			Application.Init (new FakeDriver ());
 
 
 			// Verify post-Init state is correct
 			// 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 ();
 			Application.Shutdown ();
 
 
 			// Verify state is back to initial
 			// Verify state is back to initial
-			Pre_Init_State ();
+			//Pre_Init_State ();
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 			// Validate there are no outstanding Responder-based instances 
 			// Validate there are no outstanding Responder-based instances 
 			// after a scenario was selected to run. This proves the main UI Catalog
 			// after a scenario was selected to run. This proves the main UI Catalog
 			// 'app' closed cleanly.
 			// 'app' closed cleanly.
-			foreach (var inst in Responder.Instances) {
-				Assert.True (inst.WasDisposed);
-			}
+			//foreach (var inst in Responder.Instances) {
+				//Assert.True (inst.WasDisposed);
+			//}
 #endif
 #endif
 		}
 		}
 
 
@@ -100,7 +98,7 @@ namespace Terminal.Gui.ApplicationTests {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void Init_Unbalanced_Throwss ()
+		public void Init_Unbalanced_Throws ()
 		{
 		{
 			Application.Init (new FakeDriver ());
 			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]
 		[Fact]
 		public void Init_Begin_End_Cleans_Up ()
 		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 ()
 		public void Constructor_Setups_Driver ()
 		{
 		{
 			var ml = new MainLoop (new FakeMainLoop ());
 			var ml = new MainLoop (new FakeMainLoop ());
-			Assert.NotNull (ml.Driver);
+			Assert.NotNull (ml.MainLoopDriver);
 		}
 		}
 
 
 		// Idle Handler tests
 		// Idle Handler tests
@@ -525,6 +525,10 @@ namespace Terminal.Gui.ApplicationTests {
 			{
 			{
 				throw new NotImplementedException ();
 				throw new NotImplementedException ();
 			}
 			}
+			public void TearDown ()
+			{
+				throw new NotImplementedException ();
+			}
 
 
 			public void Setup (MainLoop mainLoop)
 			public void Setup (MainLoop mainLoop)
 			{
 			{
@@ -659,7 +663,9 @@ namespace Terminal.Gui.ApplicationTests {
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.True (btn.ProcessKey (new KeyEvent (Key.Enter, null)));
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (cancel, btn.Text);
 					Assert.Equal (one, total);
 					Assert.Equal (one, total);
-				} else if (taskCompleted) 					Application.RequestStop ();
+				} else if (taskCompleted) {
+					Application.RequestStop ();
+				}
 			};
 			};
 
 
 			Application.Run ();
 			Application.Run ();

+ 0 - 10
UnitTests/AssemblyInfo.cs

@@ -1,11 +1 @@
 global using CM = Terminal.Gui.ConfigurationManager;
 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;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using Terminal.Gui;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
-using static AutoInitShutdownAttribute;
 
 
 namespace Terminal.Gui.ClipboardTests {
 namespace Terminal.Gui.ClipboardTests {
 	public class ClipboardTests {
 	public class ClipboardTests {

+ 24 - 34
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -13,7 +13,7 @@ using Attribute = Terminal.Gui.Attribute;
 namespace Terminal.Gui.ConfigurationTests {
 namespace Terminal.Gui.ConfigurationTests {
 	public class ConfigurationManagerTests {
 	public class ConfigurationManagerTests {
 
 
-		public static readonly JsonSerializerOptions _jsonOptions = new() {
+		public static readonly JsonSerializerOptions _jsonOptions = new () {
 			Converters = {
 			Converters = {
 				new AttributeJsonConverter (),
 				new AttributeJsonConverter (),
 				new ColorJsonConverter (),
 				new ColorJsonConverter (),
@@ -65,44 +65,43 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (boolSrc, boolCopy);
 			Assert.Equal (boolSrc, boolCopy);
 
 
 			// Structs
 			// 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);
 			var attrCopy = DeepMemberwiseCopy (attrSrc, attrDest);
 			Assert.Equal (attrSrc, attrCopy);
 			Assert.Equal (attrSrc, attrCopy);
 
 
 			// Classes
 			// 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);
 			var colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest);
 			Assert.Equal (colorschemeSrc, colorschemeCopy);
 			Assert.Equal (colorschemeSrc, colorschemeCopy);
 
 
 			// Dictionaries
 			// 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);
 			var dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (dictSrc, dictCopy);
 			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);
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (dictSrc, dictCopy);
 			Assert.Equal (dictSrc, dictCopy);
 
 
 			// src adds an item
 			// 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);
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
 			Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
 
 
 			// src updates only one item
 			// 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);
 			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (2, dictCopy.Count);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
 			Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
 			Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
-
 		}
 		}
 
 
 		//[Fact ()]
 		//[Fact ()]
@@ -207,7 +206,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void Reset_Resets()
+		public void Reset_Resets ()
 		{
 		{
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
 			ConfigurationManager.Reset ();
 			ConfigurationManager.Reset ();
@@ -225,7 +224,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 			ConfigurationManager.Settings.Apply ();
 			ConfigurationManager.Settings.Apply ();
 
 
 			// assert apply worked
 			// assert apply worked
@@ -233,7 +231,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.True (Application.IsMouseDisabled);
 			Assert.True (Application.IsMouseDisabled);
-			Assert.True (Application.EnableConsoleScrolling);
 
 
 			//act
 			//act
 			ConfigurationManager.Reset ();
 			ConfigurationManager.Reset ();
@@ -245,14 +242,12 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.False (Application.IsMouseDisabled);
 			Assert.False (Application.IsMouseDisabled);
-			Assert.False (Application.EnableConsoleScrolling);
 
 
 			// arrange
 			// arrange
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 			ConfigurationManager.Settings.Apply ();
 			ConfigurationManager.Settings.Apply ();
 
 
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
 			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
@@ -268,7 +263,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageDown | Key.CtrlMask, Application.AlternateForwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, Application.AlternateBackwardKey);
 			Assert.False (Application.IsMouseDisabled);
 			Assert.False (Application.IsMouseDisabled);
-			Assert.False (Application.EnableConsoleScrolling);
 
 
 		}
 		}
 
 
@@ -312,7 +306,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Reset ();
 			ConfigurationManager.Reset ();
 			ConfigurationManager.GetHardCodedDefaults ();
 			ConfigurationManager.GetHardCodedDefaults ();
 			var stream = ConfigurationManager.ToStream ();
 			var stream = ConfigurationManager.ToStream ();
-			
+
 			ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson");
 			ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson");
 		}
 		}
 
 
@@ -320,7 +314,7 @@ namespace Terminal.Gui.ConfigurationTests {
 		public void TestConfigurationManagerInitDriver_NoLocations ()
 		public void TestConfigurationManagerInitDriver_NoLocations ()
 		{
 		{
 
 
-			
+
 		}
 		}
 
 
 		[Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
 		[Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
@@ -336,7 +330,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 
 			// Change Base
 			// Change Base
 			var json = ConfigurationManager.ToStream ();
 			var json = ConfigurationManager.ToStream ();
-			
+
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver");
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver");
 
 
 			var colorSchemes = ((Dictionary<string, ColorScheme>)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue);
 			var colorSchemes = ((Dictionary<string, ColorScheme>)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue);
@@ -507,7 +501,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 
 			ConfigurationManager.Reset ();
 			ConfigurationManager.Reset ();
 			ConfigurationManager.ThrowOnJsonErrors = true;
 			ConfigurationManager.ThrowOnJsonErrors = true;
-			
+
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
 			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
 
 
 			Assert.Equal (Key.Q | Key.CtrlMask, Application.QuitKey);
 			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.White, Colors.ColorSchemes ["Base"].Normal.Foreground);
 			Assert.Equal (Color.Blue, Colors.ColorSchemes ["Base"].Normal.Background);
 			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.White, colorSchemes ["Base"].Normal.Foreground);
 			Assert.Equal (Color.Blue, colorSchemes ["Base"].Normal.Background);
 			Assert.Equal (Color.Blue, colorSchemes ["Base"].Normal.Background);
 
 
@@ -571,7 +565,7 @@ namespace Terminal.Gui.ConfigurationTests {
 								""UserDefined"": {
 								""UserDefined"": {
 									""AbNormal"": {
 									""AbNormal"": {
 										""foreground"": ""green"",
 										""foreground"": ""green"",
-										""background"": ""1234""
+										""background"": ""black""
 									}
 									}
 								}
 								}
 							}
 							}
@@ -615,7 +609,7 @@ namespace Terminal.Gui.ConfigurationTests {
 
 
 			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
 			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
 			Assert.StartsWith ("Unknown property", jsonException.Message);
 			Assert.StartsWith ("Unknown property", jsonException.Message);
-			
+
 			Assert.Equal (0, ConfigurationManager.jsonErrors.Length);
 			Assert.Equal (0, ConfigurationManager.jsonErrors.Length);
 
 
 			ConfigurationManager.ThrowOnJsonErrors = false;
 			ConfigurationManager.ThrowOnJsonErrors = false;
@@ -661,7 +655,7 @@ namespace Terminal.Gui.ConfigurationTests {
 								""UserDefined"": {
 								""UserDefined"": {
 									""AbNormal"": {
 									""AbNormal"": {
 										""foreground"": ""green"",
 										""foreground"": ""green"",
-										""background"": ""1234""
+										""background"": ""black""
 									}
 									}
 								}
 								}
 							}
 							}
@@ -696,7 +690,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings.Update (json, "test");
 			ConfigurationManager.Settings.Update (json, "test");
 
 
 			ConfigurationManager.Settings.Update ("{}}", "test");
 			ConfigurationManager.Settings.Update ("{}}", "test");
-			
+
 			Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length);
 			Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length);
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -743,12 +737,11 @@ namespace Terminal.Gui.ConfigurationTests {
 		public void Load_FiresUpdated ()
 		public void Load_FiresUpdated ()
 		{
 		{
 			ConfigurationManager.Reset ();
 			ConfigurationManager.Reset ();
-			
+
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 
 			ConfigurationManager.Updated += ConfigurationManager_Updated;
 			ConfigurationManager.Updated += ConfigurationManager_Updated;
 			bool fired = false;
 			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.PageDown | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
 				Assert.Equal (Key.PageUp | Key.CtrlMask, ConfigurationManager.Settings ["Application.AlternateBackwardKey"].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.IsMouseDisabled"].PropertyValue);
-				Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue);
 			}
 			}
 
 
 			ConfigurationManager.Load (true);
 			ConfigurationManager.Load (true);
@@ -785,7 +777,6 @@ namespace Terminal.Gui.ConfigurationTests {
 				Assert.Equal (Key.F, Application.AlternateForwardKey);
 				Assert.Equal (Key.F, Application.AlternateForwardKey);
 				Assert.Equal (Key.B, Application.AlternateBackwardKey);
 				Assert.Equal (Key.B, Application.AlternateBackwardKey);
 				Assert.True (Application.IsMouseDisabled);
 				Assert.True (Application.IsMouseDisabled);
-				Assert.True (Application.EnableConsoleScrolling);
 			}
 			}
 
 
 			// act
 			// act
@@ -793,7 +784,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 
 			ConfigurationManager.Apply ();
 			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 {
 namespace Terminal.Gui.ConfigurationTests {
 	public class ColorJsonConverterTests {
 	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 {
 	public class AttributeJsonConverterTests {
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void TestDeserialize ()
 		public void TestDeserialize ()
@@ -155,7 +198,7 @@ namespace Terminal.Gui.ConfigurationTests {
 			// Test serializing to human-readable color names
 			// Test serializing to human-readable color names
 			var attribute = new Attribute (Color.Blue, Color.Green);
 			var attribute = new Attribute (Color.Blue, Color.Green);
 			var json = JsonSerializer.Serialize<Attribute> (attribute, ConfigurationManagerTests._jsonOptions);
 			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.AlternateForwardKey"].PropertyValue is Key);
 			Assert.True (ConfigurationManager.Settings ["Application.AlternateBackwardKey"].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.IsMouseDisabled"].PropertyValue is bool);
-			Assert.True (ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue is bool);
 
 
 			Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string);
 			Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string);
 			Assert.Equal ("Default", ConfigurationManager.Settings ["Theme"].PropertyValue as 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.PageDown | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue);
 			Assert.Equal (Key.PageUp | Key.CtrlMask, (Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].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.IsMouseDisabled"].PropertyValue);
-			Assert.False ((bool)ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue);
 
 
 			// act
 			// act
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = Key.Q;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings ["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 
 			ConfigurationManager.Settings.Apply ();
 			ConfigurationManager.Settings.Apply ();
 
 
@@ -59,7 +56,6 @@ namespace Terminal.Gui.ConfigurationTests {
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.F, Application.AlternateForwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.Equal (Key.B, Application.AlternateBackwardKey);
 			Assert.True (Application.IsMouseDisabled);
 			Assert.True (Application.IsMouseDisabled);
-			Assert.True (Application.EnableConsoleScrolling);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -73,14 +69,12 @@ namespace Terminal.Gui.ConfigurationTests {
 			updatedSettings["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			updatedSettings["Application.AlternateForwardKey"].PropertyValue = Key.F;
 			updatedSettings["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			updatedSettings["Application.AlternateBackwardKey"].PropertyValue = Key.B;
 			updatedSettings["Application.IsMouseDisabled"].PropertyValue = true;
 			updatedSettings["Application.IsMouseDisabled"].PropertyValue = true;
-			updatedSettings["Application.EnableConsoleScrolling"].PropertyValue = true;
 
 
 			ConfigurationManager.Settings.Update (updatedSettings);
 			ConfigurationManager.Settings.Update (updatedSettings);
 			Assert.Equal (Key.End, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
 			Assert.Equal (Key.End, ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue);
 			Assert.Equal (Key.F, updatedSettings ["Application.AlternateForwardKey"].PropertyValue);
 			Assert.Equal (Key.F, updatedSettings ["Application.AlternateForwardKey"].PropertyValue);
 			Assert.Equal (Key.B, updatedSettings ["Application.AlternateBackwardKey"].PropertyValue);
 			Assert.Equal (Key.B, updatedSettings ["Application.AlternateBackwardKey"].PropertyValue);
 			Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].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.Foreground);
 			Assert.Equal (default (Color), attr.Background);
 			Assert.Equal (default (Color), attr.Background);
 
 
-			// Test value, foreground, background
-			var value = 42;
+			// Test foreground, background
 			var fg = new Color ();
 			var fg = new Color ();
 			fg = Color.Red;
 			fg = Color.Red;
 
 
 			var bg = new Color ();
 			var bg = new Color ();
 			bg = Color.Blue;
 			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);
 			attr = new Attribute (fg, bg);
 
 
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (bg, attr.Background);
 			Assert.Equal (bg, attr.Background);
 
 
 			attr = new Attribute (fg);
 			attr = new Attribute (fg);
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (fg, attr.Foreground);
 			Assert.Equal (fg, attr.Background);
 			Assert.Equal (fg, attr.Background);
 
 
 			attr = new Attribute (bg);
 			attr = new Attribute (bg);
+			Assert.True (attr.Initialized);
+			Assert.True (attr.HasValidColors);
 			Assert.Equal (bg, attr.Foreground);
 			Assert.Equal (bg, attr.Foreground);
 			Assert.Equal (bg, attr.Background);
 			Assert.Equal (bg, attr.Background);
 
 
@@ -73,44 +71,15 @@ namespace Terminal.Gui.DriverTests {
 
 
 			// Test conversion to int
 			// Test conversion to int
 			attr = new Attribute (value, fg, bg);
 			attr = new Attribute (value, fg, bg);
-			int value_implicit = (int)attr.Value;
+			int value_implicit = attr.Value;
 			Assert.Equal (value, value_implicit);
 			Assert.Equal (value, value_implicit);
 
 
-			// Test conversion from int
-			attr = value;
 			Assert.Equal (value, attr.Value);
 			Assert.Equal (value, attr.Value);
 
 
 			driver.End ();
 			driver.End ();
 			Application.Shutdown ();
 			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]
 		[Fact]
 		public void Make_SetsNotInitialized_NoDriver ()
 		public void Make_SetsNotInitialized_NoDriver ()
 		{
 		{
@@ -227,5 +196,47 @@ namespace Terminal.Gui.DriverTests {
 			attr = new Attribute ((Color)(-1), (Color)(-1));
 			attr = new Attribute ((Color)(-1), (Color)(-1));
 			Assert.False (attr.HasValidColors);
 			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;
 			Console.BackgroundColor = ConsoleColor.Green;
 			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
 			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
 			driver.Move (2, 3);
 			driver.Move (2, 3);
-			Assert.Equal (2, Console.CursorLeft);
-			Assert.Equal (3, Console.CursorTop);
 
 
 			driver.End ();
 			driver.End ();
 			Assert.Equal (0, Console.CursorLeft);
 			Assert.Equal (0, Console.CursorLeft);
@@ -225,17 +223,6 @@ namespace Terminal.Gui.DriverTests {
 			Assert.Equal (40, Application.Driver.Rows);
 			Assert.Equal (40, Application.Driver.Rows);
 			Assert.True (wasTerminalResized);
 			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 ();
 			Application.Shutdown ();
 		}
 		}

+ 2 - 120
UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs

@@ -19,12 +19,11 @@ namespace Terminal.Gui.DriverTests {
 
 
 		[Theory]
 		[Theory]
 		[InlineData (typeof (FakeDriver))]
 		[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);
 			var driver = (FakeDriver)Activator.CreateInstance (driverType);
 			Application.Init (driver);
 			Application.Init (driver);
 
 
-			Assert.False (Application.EnableConsoleScrolling);
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowLeft);
 			Assert.Equal (0, Console.WindowTop);
 			Assert.Equal (0, Console.WindowTop);
 
 
@@ -34,123 +33,6 @@ namespace Terminal.Gui.DriverTests {
 
 
 			Application.Shutdown ();
 			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))]
 		[ClassData (typeof (PacketTest))]
 		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 		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) {
 			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.Run ();
 				Application.Shutdown ();
 				Application.Shutdown ();
 			}
 			}

+ 9 - 9
UnitTests/FileServices/FileDialogTests.cs

@@ -217,14 +217,14 @@ namespace Terminal.Gui.FileServicesTests {
 			Assert.Multiple (
 			Assert.Multiple (
 				() => {
 				() => {
 					// Only the subfolder should be selected
 					// Only the subfolder should be selected
-					Assert.Equal (1, dlg.MultiSelected.Count);
+					Assert.Single (dlg.MultiSelected);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 				},
 				},
 				() => {
 				() => {
 					// Event should also agree with the final state
 					// Event should also agree with the final state
 					Assert.NotNull (eventMultiSelected);
 					Assert.NotNull (eventMultiSelected);
-					Assert.Equal (1, eventMultiSelected.Count);
+					Assert.Single (eventMultiSelected);
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 				}
 				}
 			);
 			);
@@ -330,14 +330,14 @@ namespace Terminal.Gui.FileServicesTests {
 			Assert.Multiple (
 			Assert.Multiple (
 				() => {
 				() => {
 					// Only the subfolder should be selected
 					// Only the subfolder should be selected
-					Assert.Equal (1, dlg.MultiSelected.Count);
+					Assert.Single (dlg.MultiSelected);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.Path);
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 					AssertIsTheSubfolder (dlg.MultiSelected.Single ());
 				},
 				},
 				() => {
 				() => {
 					// Event should also agree with the final state
 					// Event should also agree with the final state
 					Assert.NotNull (eventMultiSelected);
 					Assert.NotNull (eventMultiSelected);
-					Assert.Equal (1, eventMultiSelected.Count);
+					Assert.Single (eventMultiSelected);
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 					AssertIsTheSubfolder (eventMultiSelected.Single ());
 				}
 				}
 			);
 			);
@@ -382,7 +382,7 @@ namespace Terminal.Gui.FileServicesTests {
 			fd.Style.Culture = new CultureInfo ("en-US");
 			fd.Style.Culture = new CultureInfo ("en-US");
 
 
 			fd.Draw ();
 			fd.Draw ();
-			
+
 			string expected =
 			string expected =
 			@$"
 			@$"
  ┌──────────────────────────────────────────────────────────────────┐
  ┌──────────────────────────────────────────────────────────────────┐
@@ -467,7 +467,7 @@ namespace Terminal.Gui.FileServicesTests {
 
 
 			fd.Draw ();
 			fd.Draw ();
 
 
-			TestHelpers.AssertDriverUsedColors (other,dir,img,exe);
+			TestHelpers.AssertDriverUsedColors (other, dir, img, exe);
 		}
 		}
 
 
 		private ColorScheme GetColorScheme (Attribute a)
 		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", true)]
 		[InlineData (".csv", "c:\\MyFile.CSV", true)]
 		[InlineData (".csv", "c:\\MyFile.csv.bak", false)]
 		[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));
 			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", "c:\\temp\\Dockerfile", true)]
 		[InlineData ("Dockerfile", "Dockerfile", true)]
 		[InlineData ("Dockerfile", "Dockerfile", true)]
 		[InlineData ("Dockerfile", "someimg.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));
 			Assert.Equal (expected, new AllowedType ("Test", allowed).IsAllowed (candidate));
 		}
 		}
@@ -504,7 +504,7 @@ namespace Terminal.Gui.FileServicesTests {
 		[Theory]
 		[Theory]
 		[InlineData (".Designer.cs", "c:\\MyView.Designer.cs", true)]
 		[InlineData (".Designer.cs", "c:\\MyView.Designer.cs", true)]
 		[InlineData (".Designer.cs", "c:\\temp/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", "c:\\MyView.DESIGNER.CS", true)]
 		[InlineData (".Designer.cs", "MyView.cs", false)]
 		[InlineData (".Designer.cs", "MyView.cs", false)]
 		public void TestAllowedType_DoubleBarreled (string allowed, string candidate, bool expected)
 		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]
 		[Fact]
 		public void Constructor_Defaults ()
 		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]
 		[Fact]
 		public void Add_Tests ()
 		public void Add_Tests ()
 		{
 		{
-			var escSeqReq = new EscSeqReqProc ();
+			var escSeqReq = new EscSeqRequests ();
 			escSeqReq.Add ("t");
 			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);
 			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);
 			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);
 			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]
 		[Fact]
 		public void Remove_Tests ()
 		public void Remove_Tests ()
 		{
 		{
-			var escSeqReq = new EscSeqReqProc ();
+			var escSeqReq = new EscSeqRequests ();
 			escSeqReq.Add ("t");
 			escSeqReq.Add ("t");
 			escSeqReq.Remove ("t");
 			escSeqReq.Remove ("t");
-			Assert.Empty (escSeqReq.EscSeqReqStats);
+			Assert.Empty (escSeqReq.Statuses);
 
 
 			escSeqReq.Add ("t", 2);
 			escSeqReq.Add ("t", 2);
 			escSeqReq.Remove ("t");
 			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");
 			escSeqReq.Remove ("t");
-			Assert.Empty (escSeqReq.EscSeqReqStats);
+			Assert.Empty (escSeqReq.Statuses);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
 		public void Requested_Tests ()
 		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");
 			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 ()
 		public void Defaults_Values ()
 		{
 		{
 			Assert.Equal ('\x1b', EscSeqUtils.KeyEsc);
 			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[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse);
 			Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse);
 			Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse);
 			Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse);
 			Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse);
 			Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse);
 			Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse);
 			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]
 		[Fact]
@@ -26,51 +26,51 @@ namespace Terminal.Gui.InputTests {
 		{
 		{
 			var cki = new ConsoleKeyInfo ('r', 0, false, false, false);
 			var cki = new ConsoleKeyInfo ('r', 0, false, false, false);
 			var expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, true, false, false);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, false, true, false);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, false, false, true);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, true, true, false);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, false, true, true);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('r', 0, true, true, true);
 			expectedCki = 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);
 			cki = new ConsoleKeyInfo ('\u0012', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('R', ConsoleKey.R, false, false, true);
 			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);
 			cki = new ConsoleKeyInfo ('\0', (ConsoleKey)64, false, false, true);
 			expectedCki = new ConsoleKeyInfo (' ', ConsoleKey.Spacebar, 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);
 			cki = new ConsoleKeyInfo ('\r', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('\r', ConsoleKey.Enter, 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);
 			cki = new ConsoleKeyInfo ('\u007f', 0, false, false, false);
 			expectedCki = new ConsoleKeyInfo ('\u007f', ConsoleKey.Backspace, 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);
 			cki = new ConsoleKeyInfo ('R', 0, false, false, false);
 			expectedCki = 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]
 		[Fact]
@@ -83,7 +83,7 @@ namespace Terminal.Gui.InputTests {
 			Assert.Equal (cki, expectedCkInfos [0]);
 			Assert.Equal (cki, expectedCkInfos [0]);
 		}
 		}
 
 
-		private EscSeqReqProc escSeqReqProc;
+		private EscSeqRequests escSeqReqProc;
 		private ConsoleKeyInfo newConsoleKeyInfo;
 		private ConsoleKeyInfo newConsoleKeyInfo;
 		private ConsoleKey key;
 		private ConsoleKey key;
 		private ConsoleKeyInfo [] cki;
 		private ConsoleKeyInfo [] cki;
@@ -617,7 +617,7 @@ namespace Terminal.Gui.InputTests {
 			ClearAll ();
 			ClearAll ();
 
 
 			Assert.Null (escSeqReqProc);
 			Assert.Null (escSeqReqProc);
-			escSeqReqProc = new EscSeqReqProc ();
+			escSeqReqProc = new EscSeqRequests ();
 			escSeqReqProc.Add ("t");
 			escSeqReqProc.Add ("t");
 
 
 			cki = new ConsoleKeyInfo [] {
 			cki = new ConsoleKeyInfo [] {
@@ -633,10 +633,10 @@ namespace Terminal.Gui.InputTests {
 				new ConsoleKeyInfo ('t', 0, false, false, false)
 				new ConsoleKeyInfo ('t', 0, false, false, false)
 			};
 			};
 			expectedCki = default;
 			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);
 			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 (expectedCki, newConsoleKeyInfo);
 			Assert.Equal (0, (int)key);
 			Assert.Equal (0, (int)key);
 			Assert.Equal (0, (int)mod);
 			Assert.Equal (0, (int)mod);

+ 102 - 124
UnitTests/TestHelpers.cs

@@ -4,7 +4,6 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
 using Xunit;
 using Xunit;
-using Terminal.Gui;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 using System.Reflection;
 using System.Reflection;
 using System.Diagnostics;
 using System.Diagnostics;
@@ -12,7 +11,9 @@ using System.Diagnostics;
 using Attribute = Terminal.Gui.Attribute;
 using Attribute = Terminal.Gui.Attribute;
 using Microsoft.VisualStudio.TestPlatform.Utilities;
 using Microsoft.VisualStudio.TestPlatform.Utilities;
 using Xunit.Sdk;
 using Xunit.Sdk;
+using System.Globalization;
 
 
+namespace Terminal.Gui;
 // This class enables test functions annotated with the [AutoInitShutdown] attribute to 
 // 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
 // automatically call Application.Init at start of the test and Application.Shutdown after the
 // test exits. 
 // 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.
 	/// 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>
 	/// Only valid if <paramref name="autoInit"/> is true.</param>
 	/// <param name="useFakeClipboard">If true, will force the use of <see cref="FakeDriver.FakeClipboard"/>. 
 	/// <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.
 	/// <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.
 	/// <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 
 	/// <param name="configLocation">Determines what config file locations <see cref="ConfigurationManager"/> will 
 	/// load from.</param>
 	/// load from.</param>
-	public AutoInitShutdownAttribute (bool autoInit = true, bool autoShutdown = true,
+	public AutoInitShutdownAttribute (bool autoInit = true,
 		Type consoleDriverType = null,
 		Type consoleDriverType = null,
 		bool useFakeClipboard = true,
 		bool useFakeClipboard = true,
 		bool fakeClipboardAlwaysThrowsNotSupportedException = false,
 		bool fakeClipboardAlwaysThrowsNotSupportedException = false,
 		bool fakeClipboardIsSupportedAlwaysTrue = false,
 		bool fakeClipboardIsSupportedAlwaysTrue = false,
 		ConfigurationManager.ConfigLocations configLocation = ConfigurationManager.ConfigLocations.DefaultOnly)
 		ConfigurationManager.ConfigLocations configLocation = ConfigurationManager.ConfigLocations.DefaultOnly)
 	{
 	{
-		//Assert.True (autoInit == false && consoleDriverType == null);
-
 		AutoInit = autoInit;
 		AutoInit = autoInit;
-		AutoShutdown = autoShutdown;
-		DriverType = consoleDriverType ?? typeof (FakeDriver);
+		CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+		_driverType = consoleDriverType ?? typeof (FakeDriver);
 		FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
 		FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
 		FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 		FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
 		FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
 		FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
 		ConfigurationManager.Locations = configLocation;
 		ConfigurationManager.Locations = configLocation;
 	}
 	}
 
 
-	static bool _init = false;
 	bool AutoInit { get; }
 	bool AutoInit { get; }
-	bool AutoShutdown { get; }
-	Type DriverType;
+	Type _driverType;
 
 
 	public override void Before (MethodInfo methodUnderTest)
 	public override void Before (MethodInfo methodUnderTest)
 	{
 	{
 		Debug.WriteLine ($"Before: {methodUnderTest.Name}");
 		Debug.WriteLine ($"Before: {methodUnderTest.Name}");
-		if (AutoShutdown && _init) {
-			throw new InvalidOperationException ("After did not run when AutoShutdown was specified.");
-		}
 		if (AutoInit) {
 		if (AutoInit) {
-			Application.Init ((ConsoleDriver)Activator.CreateInstance (DriverType));
-			_init = true;
+			Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
 		}
 		}
 	}
 	}
-
+	
 	public override void After (MethodInfo methodUnderTest)
 	public override void After (MethodInfo methodUnderTest)
 	{
 	{
 		Debug.WriteLine ($"After: {methodUnderTest.Name}");
 		Debug.WriteLine ($"After: {methodUnderTest.Name}");
-		if (AutoShutdown) {
+		if (AutoInit) {
 			Application.Shutdown ();
 			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
 #pragma warning disable xUnit1013 // Public method should be marked as test
 	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, bool ignoreLeadingWhitespace = false)
 	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output, bool ignoreLeadingWhitespace = false)
 	{
 	{
 #pragma warning restore xUnit1013 // Public method should be marked as test
 #pragma warning restore xUnit1013 // Public method should be marked as test
 
 
 		var sb = new StringBuilder ();
 		var sb = new StringBuilder ();
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = (FakeDriver)Application.Driver;
 
 
 		var contents = driver.Contents;
 		var contents = driver.Contents;
 
 
 		for (int r = 0; r < driver.Rows; r++) {
 		for (int r = 0; r < driver.Rows; r++) {
 			for (int c = 0; c < driver.Cols; c++) {
 			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)) {
 				if (rune.DecodeSurrogatePair (out char [] spair)) {
 					sb.Append (spair);
 					sb.Append (spair);
 				} else {
 				} else {
@@ -135,51 +133,47 @@ class TestHelpers {
 
 
 		var actualLook = sb.ToString ();
 		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)
 	public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
 	{
 	{
 		var lines = new List<List<Rune>> ();
 		var lines = new List<List<Rune>> ();
 		var sb = new StringBuilder ();
 		var sb = new StringBuilder ();
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = Application.Driver;
 		var x = -1;
 		var x = -1;
 		var y = -1;
 		var y = -1;
-		int w = -1;
-		int h = -1;
-
+		var w = -1;
+		var h = -1;
+		
 		var contents = driver.Contents;
 		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> ();
 			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 (rune != (Rune)' ') {
 					if (x == -1) {
 					if (x == -1) {
 						x = c;
 						x = c;
@@ -196,26 +190,19 @@ class TestHelpers {
 					}
 					}
 					h = r - y + 1;
 					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
 		// Remove unnecessary empty lines
 		if (lines.Count > 0) {
 		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
 		// 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];
 				var rune = row [c];
 				if (rune != (Rune)' ' || (row.Sum (x => x.GetColumns ()) == w)) {
 				if (rune != (Rune)' ' || (row.Sum (x => x.GetColumns ()) == w)) {
 					break;
 					break;
@@ -236,25 +223,22 @@ class TestHelpers {
 
 
 		var actualLook = sb.ToString ();
 		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);
 		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
 #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 ();
 		expectedLook = expectedLook.Trim ();
-		var driver = ((FakeDriver)Application.Driver);
-
+		var driver = (FakeDriver)Application.Driver;
+		
 		var contents = driver.Contents;
 		var contents = driver.Contents;
 
 
-		int r = 0;
+		var r = 0;
 		foreach (var line in expectedLook.Split ('\n').Select (l => l.Trim ())) {
 		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");
 					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 				}
 				}
 
 
 				var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0];
 				var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0];
 				var userExpected = line [c];
 				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++;
 			r++;
@@ -312,40 +293,37 @@ class TestHelpers {
 	/// <param name="expectedColors"></param>
 	/// <param name="expectedColors"></param>
 	internal static void AssertDriverUsedColors (params Attribute [] expectedColors)
 	internal static void AssertDriverUsedColors (params Attribute [] expectedColors)
 	{
 	{
-		var driver = ((FakeDriver)Application.Driver);
+		var driver = (FakeDriver)Application.Driver;
 
 
 		var contents = driver.Contents;
 		var contents = driver.Contents;
 
 
 		var toFind = expectedColors.ToList ();
 		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
 #pragma warning disable xUnit1013 // Public method should be marked as test
@@ -376,11 +354,11 @@ class TestHelpers {
 	{
 	{
 		var replaced = toReplace;
 		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;
 		return replaced;
 	}
 	}

+ 119 - 27
UnitTests/Text/RuneTests.cs

@@ -5,6 +5,7 @@ using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using Xunit;
 using Xunit;
+using static Terminal.Gui.SpinnerStyle;
 
 
 namespace Terminal.Gui.TextTests;
 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 (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 (0x00E9, false)] // Latin Small Letter E with Acute, Unicode U+00E9 é 
 	[InlineData (0x0061, false)] // Latin Small Letter A is U+0061.
 	[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);
 		var rune = new Rune (codepoint);
 		Assert.Equal (expected, rune.IsCombiningMark ());
 		Assert.Equal (expected, rune.IsCombiningMark ());
@@ -53,6 +57,8 @@ public class RuneTests {
 	[InlineData (0x0301)] // Combining acute accent (é)
 	[InlineData (0x0301)] // Combining acute accent (é)
 	[InlineData (0x0302)] // Combining Circumflex Accent
 	[InlineData (0x0302)] // Combining Circumflex Accent
 	[InlineData (0x0061)] // Combining ogonek (a small hook or comma shape)
 	[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)
 	public void MakePrintable_Combining_Character_Is_Not_Printable (int code)
 	{
 	{
 		var rune = new Rune (code);
 		var rune = new Rune (code);
@@ -61,30 +67,65 @@ public class RuneTests {
 	}
 	}
 
 
 	[Theory]
 	[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 ('a', "a", 1, 1, 1)]
 	[InlineData ('b', "b", 1, 1, 1)]
 	[InlineData ('b', "b", 1, 1, 1)]
 	[InlineData (123, "{", 1, 1, 1)]        // { Left Curly Bracket
 	[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 ('\u25a0', "■", 1, 1, 3)]   // ■ Black Square
 	[InlineData ('\u25a1', "□", 1, 1, 3)]   // □ White 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 ('\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 ('\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 ('\u2615', "☕", 2, 1, 3)] // ☕ Hot Beverage
 	[InlineData ('\u231a', "⌚", 2, 1, 3)] // ⌚ Watch
 	[InlineData ('\u231a', "⌚", 2, 1, 3)] // ⌚ Watch
 	[InlineData ('\u231b', "⌛", 2, 1, 3)] // ⌛ Hourglass
 	[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);
 		var rune = new Rune (code);
 		Assert.Equal (str, rune.ToString ());
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
 		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, 0x9f, 0xa4, 0x96 }, "🤖", 2, 2)] // 🤖 Robot Face
 	[InlineData (new byte [] { 0xf0, 0x90, 0x90, 0xa1 }, "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData (new byte [] { 0xf0, 0x90, 0x90, 0xa1 }, "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData (new byte [] { 0xf0, 0x9f, 0x8c, 0xb9 }, "🌹", 2, 2)] // 🌹 Rose
 	[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);
 		var operationStatus = Rune.DecodeFromUtf8 (code, out Rune rune, out int bytesConsumed);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (str, rune.ToString ());
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (bytesConsumed, rune.Utf8SequenceLength);
 		Assert.Equal (bytesConsumed, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
 		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 [] { '\ud83e', '\udde0' }, "🧠", 2, 2, 4)] // 🧠 Brain
 	[InlineData (new char [] { '\ud801', '\udc21' }, "𐐡", 1, 2, 4)]  // 𐐡 Deseret Capital Letter Er
 	[InlineData (new char [] { '\ud801', '\udc21' }, "𐐡", 1, 2, 4)]  // 𐐡 Deseret Capital Letter Er
 	[InlineData (new char [] { '\ud83c', '\udf39' }, "🌹", 2, 2, 4)] // 🌹 Rose
 	[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]);
 		var rune = new Rune (code [0], code [1]);
 		Assert.Equal (str, rune.ToString ());
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.Equal (utf8Length, rune.Utf8SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
 		Assert.True (Rune.IsValid (rune.Value));
@@ -134,12 +177,14 @@ public class RuneTests {
 	[InlineData ("\U0001f9e0", "🧠", 2, 2)] // 🧠 Brain
 	[InlineData ("\U0001f9e0", "🧠", 2, 2)] // 🧠 Brain
 	[InlineData ("\U00010421", "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData ("\U00010421", "𐐡", 1, 2)] // 𐐡 Deseret Capital Letter Er
 	[InlineData ("\U0001f339", "🌹", 2, 2)] // 🌹 Rose
 	[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);
 		var operationStatus = Rune.DecodeFromUtf16 (code, out Rune rune, out int charsConsumed);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (str, rune.ToString ());
 		Assert.Equal (str, rune.ToString ());
-		Assert.Equal (runeLength, rune.GetColumns ());
+		Assert.Equal (columns, rune.GetColumns ());
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (stringLength, rune.ToString ().Length);
 		Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
 		Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
 		Assert.True (Rune.IsValid (rune.Value));
 		Assert.True (Rune.IsValid (rune.Value));
@@ -147,7 +192,7 @@ public class RuneTests {
 		// with DecodeRune
 		// with DecodeRune
 		(var nrune, var size) = code.DecodeRune ();
 		(var nrune, var size) = code.DecodeRune ();
 		Assert.Equal (str, nrune.ToString ());
 		Assert.Equal (str, nrune.ToString ());
-		Assert.Equal (runeLength, nrune.GetColumns ());
+		Assert.Equal (columns, nrune.GetColumns ());
 		Assert.Equal (stringLength, nrune.ToString ().Length);
 		Assert.Equal (stringLength, nrune.ToString ().Length);
 		Assert.Equal (size, nrune.Utf8SequenceLength);
 		Assert.Equal (size, nrune.Utf8SequenceLength);
 		for (int x = 0; x < code.Length - 1; x++) {
 		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 ("\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 ("\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
 	[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 (str, code.Normalize ());
 		Assert.Equal (codeLength, code.Length);
 		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);
 		Assert.Equal (stringLength, str.Length);
 	}
 	}
 
 
@@ -539,7 +584,7 @@ public class RuneTests {
 
 
 	[Theory]
 	[Theory]
 	[InlineData ("Hello, 世界", 13, 11, 9)]   // Without Surrogate Pairs
 	[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)
 	public void Test_DecodeRune_Extension (string text, int bytesLength, int colsLength, int textLength)
 	{
 	{
 		List<Rune> runes = new List<Rune> ();
 		List<Rune> runes = new List<Rune> ();
@@ -558,7 +603,7 @@ public class RuneTests {
 
 
 	[Theory]
 	[Theory]
 	[InlineData ("Hello, 世界", 13, 11, 9, "界世 ,olleH")]   // Without Surrogate Pairs
 	[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)
 	public void Test_DecodeLastRune_Extension (string text, int bytesLength, int colsLength, int textLength, string encoded)
 	{
 	{
 		List<Rune> runes = new List<Rune> ();
 		List<Rune> runes = new List<Rune> ();
@@ -641,17 +686,64 @@ public class RuneTests {
 		Assert.False (d.SequenceEqual (b [1]));
 		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]
 	[Fact]
 	public void Rune_GetColumns_Versus_String_GetColumns_With_Non_Printable_Characters ()
 	public void Rune_GetColumns_Versus_String_GetColumns_With_Non_Printable_Characters ()
 	{
 	{
 		int sumRuneWidth = 0;
 		int sumRuneWidth = 0;
 		int sumConsoleWidth = 0;
 		int sumConsoleWidth = 0;
 		for (uint i = 0; i < 32; i++) {
 		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);
 		Assert.Equal (0, sumConsoleWidth);
 	}
 	}
 
 

+ 3 - 1
UnitTests/Text/StringTests.cs

@@ -16,7 +16,7 @@ public class StringTests {
 	public void TestGetColumns_Null ()
 	public void TestGetColumns_Null ()
 	{
 	{
 		string? str = null;
 		string? str = null;
-		Assert.Equal (0, str.GetColumns ());
+		Assert.Equal (0, str!.GetColumns ());
 	}
 	}
 
 
 	[Fact]
 	[Fact]
@@ -51,6 +51,8 @@ public class StringTests {
 	[InlineData ("👨‍👩‍👦‍👦👨‍👩‍👦‍👦", 16)]
 	[InlineData ("👨‍👩‍👦‍👦👨‍👩‍👦‍👦", 16)]
 	[InlineData ("山", 2)] // The character for "mountain" in Chinese/Japanese/Korean (山), Unicode U+5C71
 	[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 ("山🙂", 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)
 	public void TestGetColumns_MultiRune_WideBMP (string str, int expected)
 	{
 	{
 		Assert.Equal (expected, str.GetColumns ());
 		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;
 using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // 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>
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <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="System.Collections" Version="4.3.0" />
     <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="19.2.29" />
     <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>
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
     </PackageReference>
@@ -39,6 +39,11 @@
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
     <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
     <ProjectReference Include="..\UICatalog\UICatalog.csproj" />
   </ItemGroup>
   </ItemGroup>
+  <ItemGroup>
+    <None Update="xunit.runner.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
   <PropertyGroup Label="FineCodeCoverage">
   <PropertyGroup Label="FineCodeCoverage">
     <Enabled>
     <Enabled>
       False
       False

+ 6 - 6
UnitTests/View/DrawTests.cs

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

+ 1 - 1
UnitTests/View/KeyboardTests.cs

@@ -160,7 +160,7 @@ namespace Terminal.Gui.ViewTests {
 
 
 			Application.Top.Add (view);
 			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 ();
 			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
 			// 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
 			// 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.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);
 			Assert.True (lbl.SuperView.LayoutNeeded);
 			lbl.SuperView.Draw ();
 			lbl.SuperView.Draw ();
 			Assert.Equal ("12  ", GetContents ());
 			Assert.Equal ("12  ", GetContents ());
@@ -550,7 +550,7 @@ Y
 			{
 			{
 				var text = "";
 				var text = "";
 				for (int i = 0; i < 4; i++) {
 				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;
 				return text;
 			}
 			}

+ 25 - 20
UnitTests/View/ViewTests.cs

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

+ 1 - 1
UnitTests/Views/ListViewTests.cs

@@ -240,7 +240,7 @@ namespace Terminal.Gui.ViewsTests {
 			{
 			{
 				var item = "";
 				var item = "";
 				for (int i = 0; i < 7; i++) {
 				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;
 				return item;
 			}
 			}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 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)
 		private TabView GetTabView (out Tab tab1, out Tab tab2, bool initFakeDriver = true)
 		{
 		{
-			if (initFakeDriver)
+			if (initFakeDriver) {
 				InitFakeDriver ();
 				InitFakeDriver ();
+			}
 
 
 			var tv = new TabView ();
 			var tv = new TabView ();
+			tv.BeginInit ();
+			tv.EndInit ();
 			tv.ColorScheme = new ColorScheme ();
 			tv.ColorScheme = new ColorScheme ();
 			tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false);
 			tv.AddTab (tab1 = new Tab ("Tab1", new TextField ("hi")), false);
 			tv.AddTab (tab2 = new Tab ("Tab2", new Label ("hi2")), 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
 			// Now test making the width too small for the MinAcceptableWidth
 			// the Column won't fit so should not be rendered
 			// the Column won't fit so should not be rendered
 			var driver = ((FakeDriver)Application.Driver);
 			var driver = ((FakeDriver)Application.Driver);
-			driver.UpdateOffScreen ();
+			driver.ClearContents ();
 
 
 
 
 			tableView.Bounds = new Rect (0, 0, 9, 5);
 			tableView.Bounds = new Rect (0, 0, 9, 5);

+ 1 - 1
UnitTests/Views/TextFieldTests.cs

@@ -1137,7 +1137,7 @@ namespace Terminal.Gui.ViewsTests {
 			{
 			{
 				var item = "";
 				var item = "";
 				for (int i = 0; i < 16; i++) {
 				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;
 				return item;
 			}
 			}

+ 4 - 4
UnitTests/Views/ToplevelTests.cs

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

+ 5 - 4
UnitTests/Views/TreeViewTests.cs

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

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott