浏览代码

Fixes #3192. Improve correctness / clarity of existing `View.AutoSize` functionality/unit tests (#3202)

* Removed resharper settings from editorconfig

* Remove constructors with frame parameters from Button class.

* Remove constructors with frame parameters from CheckBox class.

* Cleanup code.

* Remove constructors with frame parameters from ComboBox class.

* @BDisp
Remove constructors with frame parameters from FrameView class.

* Remove constructors with frame parameters from Label class.

* Remove constructors with frame parameters from ListView class.

* Remove constructors with frame parameters from ScrollBarView class.

* Remove constructors with frame parameters from ScrollView class.

* Remove namespace braces.

* Cleanup code.

* Cleanup code.

* Cleanup code.

* Remove constructors with frame parameters from TextField class.

* Remove constructors with frame parameters from TimeField class.

* Fixes #3182. OnResizeNeeded returns int.MaxValue and int.MaxValue when Application.Top is null, should return Size.Empty.

* Remove constructors with frame parameters from Toplevel class.

* Remove constructors with frame parameters from Window class.

* Fix merge errors.

* Revert "Fixes #3182. OnResizeNeeded returns int.MaxValue and int.MaxValue when Application.Top is null, should return Size.Empty."

This reverts commit cf9c24b846c1831eb340833e5a792c1d6cce31ea.

* Revert unit test.

* Fixes #2882. TabView: 'Frame.DrawFrame(Rect, bool)' is obsolete: 'This method is obsolete in v2. Use use LineCanvas or Frame (#2980)

* Fixes #2882. TabView: 'Frame.DrawFrame(Rect, bool)' is obsolete: 'This method is obsolete in v2. Use use LineCanvas or Frame

* Trying fix this unit test that sometimes fail.

* Fixes #2983. View need a alternative DrawFrame for the v2.

* Use new DrawFrame method.

* Change _lines field to Lines property.

* Add TabWindow unit test.

* Add DrawIncompleteFrame method and unit tests.

* Add more unit tests to LineCanvas.

* Fix newline conflict errors.

* Revert "Change _lines field to Lines property."

This reverts commit ab6c5f3094c78e884a52d26840671f1140a24ab4.

* Add DrawIncompleteFrame method and unit tests.

* Add more unit tests to LineCanvas.

* Fix newline conflict errors.

* Force render immediately instead of join.

* I will never rely on zero-location-based unit test again.

* Fix TestTreeViewColor unit test fail.

* Using location of 3 to avoid be divisible by 2 and so avoiding bugs.

* Revert "Using location of 3 to avoid be divisible by 2 and so avoiding bugs."

This reverts commit dd3df135d8e6f18ff1cc11440823b3f3eafddcac.

* Revert "I will never rely on zero-location-based unit test again."

This reverts commit 62adf6f2850b0fb9e83782cee8c47a1202a3031e.

* Revert "Fix newline conflict errors."

This reverts commit 4acf72612dff0ba907f20c7f8826253d73c102c8.

* Revert "Add more unit tests to LineCanvas."

This reverts commit 66bc6f514e88ae9844c5608f89d7368938ed2486.

* Revert "Add DrawIncompleteFrame method and unit tests."

This reverts commit 680ba264e16b42e2261e697dca9aa54761feae1e.

* Resolving merge conflicts.

* Revert "Use new DrawFrame method."

This reverts commit 69a7f17f19fbb7985a49b039b37403d9735d8f70.

* Revert "Fixes #2983. View need a alternative DrawFrame for the v2."

This reverts commit dade9fd767ce3aacc91fa13d0b4537d233de0a49.

* Reverting this changes to start a new one.

* Add horizontal and vertical support for combining glyphs.

* Fix text and auto size behavior.

* Add TabWidth property.

* Add unit test for WordWrap.

* Fixes #3017. View TextDirection returns incorrect size on a vertical direction instance with AutoSize as false.

* Using Frame to force read from the get method.

* Fix some issues with AutoSize and ForceValidatePosDim.

* Fixing broken unit tests.

* Restoring code I've broken.

* Removing forgotten code.

* Only LayoutStyle.Computed can change the Frame.

* DateField and TimeField depends on LayoutStyle.Computed.

* Fix unit tests related with LayoutStyle.

* Implements tabs, left and right arrows as View.

* Draws a minimum full border.

* Adds missing XML parameter.

* Adds assert tests for Frame.

* Removes duplicates InlineData.

* Adds more unit tests for minimum full border without Left and Right thickness.

* Trying to fix the TestTreeViewColor unit test fail.

* Prevents a user to set TextDirection to -1.

* Prevents any invalid TextDirection value.

* Removes (TextDirection)(-1).

* Removes unnecessary TextDirection initialization.

* Removes LayoutStyle.

* Fixing unit tests with border.

* Trying to fix TestTreeViewColor again.

* Revert "Trying to fix TestTreeViewColor again."

This reverts commit c2efa8e42e7776f23a8f664746d946506351244b.

* Trying to fix TestTreeViewColor again.

* Fix merge errors.

* Fix merge errors.

* Restoring unit test.

* Restores the right XML comment.

* Fix Disposing unit tests that sometimes throws because some instances aren't cleared on others unit tests classes.

* Fix Disposing unit tests that sometimes throws because some instances aren't cleared on others unit tests classes.

* Only call OnResizeNeeded if it's LayoutStyle.Computed.

* Fix merge errors.

* Fix merge errors.

* Fix unit tests fail.

* Reformat.

* Again.

* Rename to OnDrawAdornments.

* Fix failing unit tests.

* Reduces indentation and cleanup code.

* Cleanup code.

* Fix bug done when cleanup.

* Replace FrameHandledMouseEvent to AdornmentHandledMouseEvent.

* Removes Tab constructor parameters.

---------

Co-authored-by: Tig <[email protected]>

* Fix merge errors.

* Remove constructors with parameters from Button.

* Remove parenthesis on objects initializers from Button.

* Remove constructors with parameters from CheckBox.

* Remove parenthesis on objects initializers from CheckBox.

* Remove constructors with parameters from ComboBox.

* Remove constructors with parameters from FrameView.

* Remove parenthesis on objects initializers from FrameView.

* Initial commit

* Renamed Direction enum for clarity in refactoring unit tests

* Moved nav tests to NavigationTests

* Moved view tests around

* Cleaning up TextFormatter and View.AutoSize code

* Fixed latent TextFormatter bug with \n

* removed Application dependency on some autosize unit tests

* Fixed Label tests to deal with auotsize overriding height/width

* Fixed more label tests. WIP

* Fixed all places where AutoSize = happend after setting Dims

* Started adding new primitive View.Text tests

* Code comments

* WIP: Enforce that it makes no sense to set Width/Height if AutoSize = true. Update Unit tests to match.

* WIP: Enforce that it makes no sense to set Width/Height if AutoSize = true. Update Unit tests to match.

* Remove frame set from the View constructor and prevent SetRelativeLayout running if not yet initialized.

* Changes needed for unit tests pass on remove parameters constructors from the Label class.

* Remove constructors with parameters from Label.

* Remove parenthesis on objects initializers from Label.

* Prefix private fields with underscore.

* Renamed to MaxLength.

* Remove constructors with parameters from ListView.

* MakeWrapper not needed anymore.

* Remove parenthesis on objects initializers from ListView.

* WIP: Enforce that it makes no sense to set Width/Height if AutoSize = true. Update Unit tests to match.

* Massive code cleanup - use parameterless constructors and ensure AutoSize is set properly. Code reformat.

* Massive code cleanup - use parameterless constructors and ensure AutoSize is set properly. Code reformat.

* Fixed messagebox

* Remove constructors with parameters from ScrollBarView and ScrollView.

* Remove parenthesis on objects initializers from ScrollBarView and ScrollView.

* Cleanup code.

* Fix merge errors.

* Add empty dotsettings for solution and projects.

* Set ReSharper language analysis level for projects to  C#12

* Make ReSharper consider itself the boss for style

* Add rule to enforce property backing fields above the property

* Disable auto-detection of naming rules so ReSharper doesn't change them by itself

* Don't let someone's VS settings override the indent settings

* Explicitly set tab width to 4 spaces and force spaces.

* Rules to keep various multi-line constructs aligned within themselves

* Curly brace rules (Using K&R style, per current project spec)

* Blank line rules

Mostly to add breaks in various situations, and also to enforce max of 1 blank line.

* Increase auto-wrap to 160 from default of 120

* Line break at end of all files, to make Unixy systems happy

* Keep attributes on their own lines except for methods and records that are themselves single-line

* Increase attribute auto-wrap to 60 from default of 38

* Wrap/chop rules for long method signatures and record declarations

Chop if either already multi-line or if over 8 parameters.
Chop AFTER the left paren and BEFORE the first parameter.

* Chop rules for generics

For generics with multi-line type parameters or multiple type parameter constraints, chop in a way that has consistent alignment.

* Always enforce enum members on their own lines

* One-line functions completely on one line

They're usually expression-bodied anyway, here, so this is almost irrelevant

* Keep control flow statements on their own lines

* Follow same chop rules for method invocation as method declaration

* Chop long or multi-line method chains or patterns

* Wrap rules for binary operators

Operator at beginning of new lines
Auto-chop if long or already multi-line

* Spaces between keywords and their opening parentheses

* Add file layout rules for organization of reorderable items like fields, properties, etc.

Ugly XML, so load it up in the UI to look at it
It's MOSTLY the ReSharper defaults, but with more aggressive sorting, generally by access modifier and name, within each grouping.

* Deconstructors use per-member types

* Use keywords for built-in types and increase severity of inspection for violations

Also apply to IntPtr and similar, which should now be nint and similar

* Apply syntax style on completion.

* Use var when evident for built-in and simple types, but not elsewhere

* Increase severity for some minor redundancy and clarity inspections

* Enforce braces always required for blocks, and treat as error

* Warn if a local function is not statement-bodied

* Increase various inspection severities

IF Condition => Severity:
Attributes not wrapped property => Suggestion
Constructors expression-bodied => Error
default doesn't have type when it isn't clear => suggestion
Namespaces not file-scoped => error
Methods not statement-bodied => hint
Null check pattern not the object property pattern => Error

* Adjust preference order of null check patterns to make object pattern highest

* XmlDoc rules to keep tags and contents aligned and wrapped

* Add a few custom profiles for code cleanup and make the full profile default

* Remove parts for languages not used in this solution

* copied in v2_develop changes

* Merged v2_develop

* Added Begin/Init unit tests. Removed Application dependencey from AutoSizeFalse tests

* TextFormatter.Lines -> GetLines ()

* Let ReSharper know we intend to localize things

* TextFormatter code cleanup

* copied in v2_develop changes

* Merged v2_develop

* Spaces aren't wanted here either.

* Fix merge errors.

* Fixes ContentBottomRightCorner related with https://github.com/gui-cs/Terminal.Gui/issues/3211#issue-2098878820

* Remove constructors with parameters from ContextMenu.

* Remove commented code.

* Remove constructors with parameters from OpenDialog.

* Remove constructors with parameters from SaveDialog.

* Remove constructors with parameters from TextField.

* Remove constructors with parameters from TimeField.

* Fix unit test.

* Remove unnecessary SetInitialProperties method.

* Remove unnecessary SetInitialProperties method.

* Remove parenthesis on objects initializers from Toplevel and Window.

* Remove constructors with parameters from RadioGroup.

* Remove constructors with parameters from TextView.

* Remove constructors with parameters from MenuBar.

* TEMPORARY: Turn everything that was set to error down to warning or lower

* Fixes #3219. MenuBar is opened by call OpenMenu even it's disabled.

* Remove constructors with parameters from Menu.

* Remove constructors with parameters from View.

* Change constructor to internal because is mainly useful for testing.

* ReSharper Cleanup Code.

* Added format only r# config

* Fixes #3224. TextFormatter.Lines should return a single string.Empty list even with Width or Height equal to zero.

* Remove constructors with parameters from Dialog.

* Remove constructors with parameters from TextValidateField.

* Fixes https://github.com/gui-cs/Terminal.Gui/issues/3224#issuecomment-1924096038

* Fixes #3225. Press CursorDown on TabView doesn't move to the next view.

* Fixes #3229. TextFormatter should have a FillRemaining property.

* Testing formatting merge in bdisp

* Testing formatting merge

* Testing formatting merge bdisp

* Testing formatting merge 2

* xmldoc format

* R# Full Code Cleanup

* R# Full Code Cleanup2

* R# Full Code Cleanup2

* Merged! But broke tests

* Refixing...

* Refixed DrawTests

* Refixed ViewTests

* Refixed Text Tests

* Refixed more unit tests

* Refixed scenarios

* Refixed rest of scenarios

* Refixed ViewsTests

* Refixed rest of tests. All unit tests pass again!

* Fixed warnings

* Updated R# version. Added new code cleanup settings

* Applied latest code cleanup to solution

* Another code cleanup pass

* Tweaked r# settings. .editorconfig now matches.

* r# else on separate line

* r# - update

* r# - full solution

* test commit

* test commit

* test commit

* Removed extra profiles

* Full cleanup following cleaning up profiles

* Null checking pattern rules/inspections

* Tabs and extra whitespace are evil

* Attributes on their own lines

* Code layout rules to put fields first and to put backing fields with their properties

* Merged v2_develop

* Full cleanup following dodexahedron's PR

---------

Co-authored-by: BDisp <[email protected]>
Co-authored-by: Brandon Thetford <[email protected]>
Tig 1 年之前
父节点
当前提交
4430fe2cc6
共有 100 个文件被更改,包括 26054 次插入24239 次删除
  1. 155 17
      .editorconfig
  2. 66 58
      Example/Example.cs
  3. 200 180
      ReactiveExample/LoginView.cs
  4. 80 69
      ReactiveExample/LoginViewModel.cs
  5. 13 11
      ReactiveExample/Program.cs
  6. 55 36
      ReactiveExample/TerminalScheduler.cs
  7. 1828 1526
      Terminal.Gui/Application.cs
  8. 201 165
      Terminal.Gui/Clipboard/Clipboard.cs
  9. 119 109
      Terminal.Gui/Clipboard/ClipboardBase.cs
  10. 21 34
      Terminal.Gui/Clipboard/IClipboard.cs
  11. 24 37
      Terminal.Gui/Configuration/AppScope.cs
  12. 106 95
      Terminal.Gui/Configuration/AttributeJsonConverter.cs
  13. 49 47
      Terminal.Gui/Configuration/ColorJsonConverter.cs
  14. 110 99
      Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs
  15. 109 85
      Terminal.Gui/Configuration/ConfigProperty.cs
  16. 592 517
      Terminal.Gui/Configuration/ConfigurationManager.cs
  17. 15 31
      Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs
  18. 58 41
      Terminal.Gui/Configuration/DictionaryJsonConverter.cs
  19. 164 122
      Terminal.Gui/Configuration/KeyCodeJsonConverter.cs
  20. 12 11
      Terminal.Gui/Configuration/KeyJsonConverter.cs
  21. 136 112
      Terminal.Gui/Configuration/RuneJsonConverter.cs
  22. 72 67
      Terminal.Gui/Configuration/Scope.cs
  23. 223 130
      Terminal.Gui/Configuration/ScopeJsonConverter.cs
  24. 16 22
      Terminal.Gui/Configuration/SerializableConfigurationProperty.cs
  25. 109 103
      Terminal.Gui/Configuration/SettingsScope.cs
  26. 130 164
      Terminal.Gui/Configuration/ThemeManager.cs
  27. 11 11
      Terminal.Gui/Configuration/ThemeScope.cs
  28. 920 1107
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  29. 2528 1718
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  30. 256 212
      Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs
  31. 1007 784
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  32. 230 214
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  33. 303 249
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  34. 693 583
      Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs
  35. 165 172
      Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs
  36. 48 139
      Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs
  37. 124 113
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs
  38. 1476 1200
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  39. 1698 1978
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  40. 526 433
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  41. 36 41
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  42. 1755 1377
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  43. 843 702
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  44. 52 83
      Terminal.Gui/Drawing/AnsiColorCode.cs
  45. 138 163
      Terminal.Gui/Drawing/Attribute.cs
  46. 35 38
      Terminal.Gui/Drawing/Cell.cs
  47. 607 415
      Terminal.Gui/Drawing/Color.Formatting.cs
  48. 79 72
      Terminal.Gui/Drawing/Color.Operators.cs
  49. 276 267
      Terminal.Gui/Drawing/Color.cs
  50. 69 63
      Terminal.Gui/Drawing/ColorExtensions.cs
  51. 57 91
      Terminal.Gui/Drawing/ColorName.cs
  52. 77 76
      Terminal.Gui/Drawing/ColorParseException.cs
  53. 215 239
      Terminal.Gui/Drawing/ColorScheme.cs
  54. 439 689
      Terminal.Gui/Drawing/Glyphs.cs
  55. 22 24
      Terminal.Gui/Drawing/ICustomColorFormatter.cs
  56. 957 835
      Terminal.Gui/Drawing/LineCanvas.cs
  57. 58 65
      Terminal.Gui/Drawing/Ruler.cs
  58. 193 196
      Terminal.Gui/Drawing/StraightLine.cs
  59. 235 212
      Terminal.Gui/Drawing/StraightLineExtensions.cs
  60. 298 298
      Terminal.Gui/Drawing/Thickness.cs
  61. 12 24
      Terminal.Gui/Drawing/ThicknessEventArgs.cs
  62. 93 116
      Terminal.Gui/FileServices/AllowedType.cs
  63. 167 137
      Terminal.Gui/FileServices/DefaultFileOperations.cs
  64. 19 24
      Terminal.Gui/FileServices/DefaultSearchMatcher.cs
  65. 97 111
      Terminal.Gui/FileServices/FileDialogHistory.cs
  66. 90 84
      Terminal.Gui/FileServices/FileDialogState.cs
  67. 198 228
      Terminal.Gui/FileServices/FileDialogStyle.cs
  68. 107 103
      Terminal.Gui/FileServices/FileSystemInfoStats.cs
  69. 60 80
      Terminal.Gui/FileServices/FileSystemTreeBuilder.cs
  70. 18 27
      Terminal.Gui/FileServices/FilesSelectedEventArgs.cs
  71. 32 41
      Terminal.Gui/FileServices/IFileOperations.cs
  72. 10 20
      Terminal.Gui/FileServices/ISearchMatcher.cs
  73. 266 414
      Terminal.Gui/Input/Command.cs
  74. 14 25
      Terminal.Gui/Input/GrabMouseEventArgs.cs
  75. 926 1044
      Terminal.Gui/Input/Key.cs
  76. 223 253
      Terminal.Gui/Input/KeyBinding.cs
  77. 17 27
      Terminal.Gui/Input/KeyChangedEventArgs.cs
  78. 9 19
      Terminal.Gui/Input/KeystrokeNavigatorEventArgs.cs
  79. 126 175
      Terminal.Gui/Input/Mouse.cs
  80. 28 29
      Terminal.Gui/Input/MouseEventEventArgs.cs
  81. 16 26
      Terminal.Gui/Input/MouseFlagsChangedEventArgs.cs
  82. 9 20
      Terminal.Gui/Input/PointEventArgs.cs
  83. 130 179
      Terminal.Gui/Input/Responder.cs
  84. 171 149
      Terminal.Gui/Input/ShortcutHelper.cs
  85. 374 372
      Terminal.Gui/MainLoop.cs
  86. 47 72
      Terminal.Gui/RunState.cs
  87. 9 21
      Terminal.Gui/RunStateEventArgs.cs
  88. 242 195
      Terminal.Gui/StackExtensions.cs
  89. 4 0
      Terminal.Gui/Terminal.Gui.csproj.DotSettings
  90. 196 181
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  91. 55 66
      Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs
  92. 20 31
      Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
  93. 87 122
      Terminal.Gui/Text/Autocomplete/IAutocomplete.cs
  94. 14 23
      Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs
  95. 578 518
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  96. 95 94
      Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs
  97. 26 34
      Terminal.Gui/Text/Autocomplete/Suggestion.cs
  98. 17 33
      Terminal.Gui/Text/CollectionNavigator.cs
  99. 219 211
      Terminal.Gui/Text/CollectionNavigatorBase.cs
  100. 144 164
      Terminal.Gui/Text/RuneExtensions.cs

+ 155 - 17
.editorconfig

@@ -1,35 +1,53 @@
 [*.cs]
-indent_style = tab
-indent_size = 8
-tab_width = 8
-csharp_new_line_before_open_brace = methods,local_functions
-csharp_new_line_before_else = false
-csharp_new_line_before_catch = false
-csharp_new_line_before_finally = false
-end_of_line = crlf
-
 csharp_indent_case_contents = true
-csharp_indent_switch_labels = false
 csharp_indent_labels = flush_left
 csharp_space_after_keywords_in_control_flow_statements = true
 csharp_space_between_method_declaration_parameter_list_parentheses = false
 csharp_space_between_method_call_parameter_list_parentheses = false
 csharp_preserve_single_line_blocks = true
-dotnet_style_require_accessibility_modifiers = never
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
 csharp_style_var_when_type_is_apparent = true:none
-csharp_prefer_braces = true:none
-csharp_space_before_open_square_brackets = true
+csharp_prefer_braces = true:warning
 csharp_space_between_method_call_name_and_opening_parenthesis = true
 csharp_space_between_method_declaration_name_and_open_parenthesis = true
 
 # Microsoft .NET properties
+csharp_new_line_before_members_in_object_initializers = false
+csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning
 csharp_style_var_elsewhere = true:none
+dotnet_naming_rule.private_constants_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_constants_rule.severity = warning
+dotnet_naming_rule.private_constants_rule.style = all_upper_style
+dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
+dotnet_naming_rule.private_static_readonly_rule.import_to_resharper = as_predefined
+dotnet_naming_rule.private_static_readonly_rule.severity = warning
+dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style
+dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
+dotnet_naming_style.all_upper_style.capitalization = all_upper
+dotnet_naming_style.all_upper_style.word_separator = _
+dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
+dotnet_naming_style.lower_camel_case_style.required_prefix = _
+dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
+dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion
+dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion
+dotnet_style_predefined_type_for_locals_parameters_members = true:error
+dotnet_style_predefined_type_for_member_access = true:error
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
 
 # ReSharper properties
 csharp_space_around_binary_operators = before_and_after
 csharp_using_directive_placement = outside_namespace:silent
 csharp_prefer_simple_using_statement = true:suggestion
-csharp_style_namespace_declarations = file_scoped:none
+csharp_style_namespace_declarations = file_scoped:error
 csharp_style_prefer_method_group_conversion = true:silent
 csharp_style_prefer_top_level_statements = true:silent
 csharp_style_prefer_primary_constructors = true:suggestion
@@ -70,12 +88,117 @@ csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
 csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
 csharp_style_prefer_not_pattern = true:suggestion
 csharp_style_prefer_extended_property_pattern = true:suggestion
-csharp_style_var_for_built_in_types = true:none
+csharp_style_var_for_built_in_types = false:suggestion
+resharper_align_first_arg_by_paren = true
+resharper_align_linq_query = true
+resharper_align_multiline_argument = true
+resharper_align_multiline_array_and_object_initializer = true
+resharper_align_multiline_binary_patterns = true
+resharper_align_multiline_calls_chain = true
+resharper_align_multiline_expression = true
+resharper_align_multiline_extends_list = true
+resharper_align_multiline_list_pattern = true
+resharper_align_multiline_parameter = true
+resharper_align_multiline_property_pattern = true
+resharper_align_multiline_switch_expression = true
+resharper_align_multiple_declaration = true
+resharper_align_multline_type_parameter_constrains = true
+resharper_align_multline_type_parameter_list = true
+resharper_align_tuple_components = true
+resharper_allow_comment_after_lbrace = true
+resharper_apply_auto_detected_rules = false
+resharper_apply_on_completion = true
+resharper_blank_lines_after_control_transfer_statements = 1
+resharper_blank_lines_around_single_line_local_method = 1
+resharper_blank_lines_before_control_transfer_statements = 1
+resharper_blank_lines_before_multiline_statements = 1
+resharper_blank_lines_before_single_line_comment = 1
+resharper_braces_redundant = true
+resharper_brace_style = next_line
+resharper_builtin_type_apply_to_native_integer = true
+resharper_constructor_or_destructor_body = block_body
+resharper_csharp_insert_final_newline = true
+resharper_csharp_keep_blank_lines_in_code = 1
+resharper_csharp_keep_blank_lines_in_declarations = 0
+resharper_csharp_max_line_length = 160
+resharper_csharp_use_indent_from_vs = false
+resharper_csharp_wrap_arguments_style = chop_if_long
+resharper_csharp_wrap_before_binary_opsign = true
+resharper_csharp_wrap_parameters_style = chop_if_long
+resharper_default_value_when_type_not_evident = default_expression
+resharper_empty_block_style = together
+resharper_for_built_in_types = use_var_when_evident
+resharper_for_other_types = use_explicit_type
+resharper_for_simple_types = use_var_when_evident
+resharper_indent_anonymous_method_block = true
+resharper_keep_existing_declaration_block_arrangement = false
+resharper_keep_existing_embedded_block_arrangement = false
+resharper_keep_existing_enum_arrangement = false
+resharper_max_attribute_length_for_same_line = 60
+resharper_max_enum_members_on_line = 1
+resharper_max_formal_parameters_on_line = 8
+resharper_max_invocation_arguments_on_line = 8
+resharper_max_primary_constructor_parameters_on_line = 8
+resharper_method_or_operator_body = block_body
+resharper_new_line_before_while = true
+resharper_null_checking_pattern_style = empty_recursive_pattern
+resharper_outdent_commas = true
+resharper_place_accessorholder_attribute_on_same_line = false
+resharper_place_accessor_attribute_on_same_line = false
+resharper_place_field_attribute_on_same_line = false
+resharper_place_method_attribute_on_same_line = if_owner_is_single_line
+resharper_place_simple_embedded_statement_on_same_line = false
+resharper_prefer_separate_deconstructed_variables_declaration = true
+resharper_space_before_checked_parentheses = true
+resharper_space_before_default_parentheses = true
+resharper_space_before_nameof_parentheses = true
+resharper_space_before_new_parentheses = true
+resharper_space_before_sizeof_parentheses = true
+resharper_space_before_typeof_parentheses = true
+resharper_use_heuristics_for_body_style = true
+resharper_wrap_after_declaration_lpar = true
+resharper_wrap_after_invocation_lpar = true
+resharper_wrap_before_declaration_rpar = true
+resharper_wrap_before_first_type_parameter_constraint = true
+resharper_wrap_before_type_parameter_langle = true
+resharper_wrap_chained_binary_patterns = chop_if_long
+resharper_wrap_chained_method_calls = chop_if_long
+resharper_wrap_list_pattern = chop_if_long
+resharper_wrap_text = false
+resharper_xmldoc_allow_far_alignment = true
+resharper_xmldoc_attribute_indent = align_by_first_attribute
+resharper_xmldoc_keep_user_linebreaks = false
+resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than = 120
+resharper_xmldoc_space_before_self_closing = false
+resharper_xmldoc_use_indent_from_vs = false
+resharper_xmldoc_wrap_tags_and_pi = false
+
+# ReSharper inspection severities
+resharper_arrange_accessor_owner_body_highlighting = suggestion
+resharper_arrange_attributes_highlighting = suggestion
+resharper_arrange_constructor_or_destructor_body_highlighting = error
+resharper_arrange_default_value_when_type_not_evident_highlighting = suggestion
+resharper_arrange_local_function_body_highlighting = warning
+resharper_arrange_method_or_operator_body_highlighting = hint
+resharper_arrange_null_checking_pattern_highlighting = error
+resharper_arrange_object_creation_when_type_not_evident_highlighting = warning
+resharper_arrange_redundant_parentheses_highlighting = warning
+resharper_arrange_static_member_qualifier_highlighting = warning
+resharper_enforce_do_while_statement_braces_highlighting = error
+resharper_enforce_fixed_statement_braces_highlighting = error
+resharper_enforce_foreach_statement_braces_highlighting = error
+resharper_enforce_for_statement_braces_highlighting = error
+resharper_enforce_if_statement_braces_highlighting = error
+resharper_enforce_lock_statement_braces_highlighting = error
+resharper_enforce_using_statement_braces_highlighting = error
+resharper_enforce_while_statement_braces_highlighting = error
+resharper_suggest_var_or_type_built_in_types_highlighting = hint
+resharper_suggest_var_or_type_elsewhere_highlighting = suggestion
+resharper_suggest_var_or_type_simple_types_highlighting = hint
+csharp_space_before_open_square_brackets = true
 
 [*.{cs,vb}]
 dotnet_style_operator_placement_when_wrapping = beginning_of_line
-tab_width = 8
-indent_size = 8
 end_of_line = crlf
 dotnet_style_coalesce_expression = true:suggestion
 dotnet_style_null_propagation = true:suggestion
@@ -155,3 +278,18 @@ dotnet_style_qualification_for_field = false:silent
 dotnet_style_qualification_for_property = false:silent
 dotnet_style_qualification_for_method = false:silent
 dotnet_style_qualification_for_event = false:silent
+
+[*.{cshtml,htm,html,razor}]
+indent_style = tab
+indent_size = tab
+tab_width = 4
+
+[*.{asax,ascx,aspx,axaml,cs,master,paml,skin,vb,xaml,xamlx,xoml}]
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,jsproj,lsproj,njsproj,nuspec,proj,props,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
+indent_style = space
+indent_size = 2
+tab_width = 2

+ 66 - 58
Example/Example.cs

@@ -3,70 +3,78 @@
 
 // A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
 
+using System;
 using Terminal.Gui;
 
 Application.Run<ExampleWindow> ();
 
-System.Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).usernameText.Text}");
+Console.WriteLine ($"Username: {((ExampleWindow)Application.Top).UserNameText.Text}");
 
 // Before the application exits, reset Terminal.Gui for clean shutdown
 Application.Shutdown ();
 
 // Defines a top-level window with border and title
-public class ExampleWindow : Window {
-	public TextField usernameText;
-	
-	public ExampleWindow ()
-	{
-		Title = $"Example App ({Application.QuitKey} to quit)";
-
-		// Create input components and labels
-		var usernameLabel = new Label () { 
-			Text = "Username:" 
-		};
-
-		usernameText = new TextField ("") {
-			// Position text field adjacent to the label
-			X = Pos.Right (usernameLabel) + 1,
-
-			// Fill remaining horizontal space
-			Width = Dim.Fill (),
-		};
-
-		var passwordLabel = new Label () {
-			Text = "Password:",
-			X = Pos.Left (usernameLabel),
-			Y = Pos.Bottom (usernameLabel) + 1
-		};
-
-		var passwordText = new TextField ("") {
-			Secret = true,
-			// align with the text box above
-			X = Pos.Left (usernameText),
-			Y = Pos.Top (passwordLabel),
-			Width = Dim.Fill (),
-		};
-
-		// Create login button
-		var btnLogin = new Button () {
-			Text = "Login",
-			Y = Pos.Bottom(passwordLabel) + 1,
-			// center the login button horizontally
-			X = Pos.Center (),
-			IsDefault = true,
-		};
-
-		// When login button is clicked display a message popup
-		btnLogin.Clicked += (s,e) => {
-			if (usernameText.Text == "admin" && passwordText.Text == "password") {
-				MessageBox.Query ("Logging In", "Login Successful", "Ok");
-				Application.RequestStop ();
-			} else {
-				MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
-			}
-		};
-
-		// Add the views to the Window
-		Add (usernameLabel, usernameText, passwordLabel, passwordText, btnLogin);
-	}
-}
+public class ExampleWindow : Window
+{
+    public TextField UserNameText;
+
+    public ExampleWindow ()
+    {
+        Title = $"Example App ({Application.QuitKey} to quit)";
+
+        // Create input components and labels
+        var usernameLabel = new Label { Text = "Username:" };
+
+        UserNameText = new TextField
+        {
+            // Position text field adjacent to the label
+            X = Pos.Right (usernameLabel) + 1,
+
+            // Fill remaining horizontal space
+            Width = Dim.Fill ()
+        };
+
+        var passwordLabel = new Label
+        {
+            Text = "Password:", X = Pos.Left (usernameLabel), Y = Pos.Bottom (usernameLabel) + 1
+        };
+
+        var passwordText = new TextField
+        {
+            Secret = true,
+
+            // align with the text box above
+            X = Pos.Left (UserNameText),
+            Y = Pos.Top (passwordLabel),
+            Width = Dim.Fill ()
+        };
+
+        // Create login button
+        var btnLogin = new Button
+        {
+            Text = "Login",
+            Y = Pos.Bottom (passwordLabel) + 1,
+
+            // center the login button horizontally
+            X = Pos.Center (),
+            IsDefault = true
+        };
+
+        // When login button is clicked display a message popup
+        btnLogin.Clicked += (s, e) =>
+                            {
+                                if (UserNameText.Text == "admin" && passwordText.Text == "password")
+                                {
+                                    MessageBox.Query ("Logging In", "Login Successful", "Ok");
+                                    Application.RequestStop ();
+                                }
+                                else
+                                {
+                                    MessageBox.ErrorQuery ("Logging In", "Incorrect username or password", "Ok");
+                                }
+                            };
+
+        // Add the views to the Window
+        Add (usernameLabel, UserNameText, passwordLabel, passwordText, btnLogin);
+    }
+}

+ 200 - 180
ReactiveExample/LoginView.cs

@@ -1,185 +1,205 @@
 using System.Reactive.Disposables;
 using System.Reactive.Linq;
-using System.Text;
+using ReactiveMarbles.ObservableEvents;
 using ReactiveUI;
 using Terminal.Gui;
-using ReactiveMarbles.ObservableEvents;
 
-namespace ReactiveExample {
-	public class LoginView : Window, IViewFor<LoginViewModel> {
-		readonly CompositeDisposable _disposable = new CompositeDisposable();
-		
-		public LoginView (LoginViewModel viewModel) : base() {
-			Title = "Reactive Extensions Example";
-			ViewModel = viewModel;
-			var usernameLengthLabel = UsernameLengthLabel (TitleLabel ());
-			var usernameInput = UsernameInput (usernameLengthLabel);
-			var passwordLengthLabel = PasswordLengthLabel (usernameInput);
-			var passwordInput = PasswordInput (passwordLengthLabel);
-			var validationLabel = ValidationLabel (passwordInput);
-			var loginButton = LoginButton (validationLabel);
-			var clearButton = ClearButton (loginButton);
-			LoginProgressLabel (clearButton);
-		}
-		
-		public LoginViewModel ViewModel { get; set; }
-
-		protected override void Dispose (bool disposing) {
-			_disposable.Dispose ();
-			base.Dispose (disposing);
-		}
-
-		Label TitleLabel () {
-			var label = new Label("Login Form");
-			Add (label);
-			return label;
-		}
-
-		TextField UsernameInput (View previous) {
-			var usernameInput = new TextField (ViewModel.Username) {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyValue (x => x.Username)
-				.BindTo (usernameInput, x => x.Text)
-				.DisposeWith (_disposable);
-			usernameInput
-				.Events ()
-				.TextChanged
-				.Select (old => usernameInput.Text)
-				.DistinctUntilChanged ()
-				.BindTo (ViewModel, x => x.Username)
-				.DisposeWith (_disposable);
-			Add (usernameInput);
-			return usernameInput;
-		}
-
-		Label UsernameLengthLabel (View previous) {
-			var usernameLengthLabel = new Label {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyValue (x => x.UsernameLength)
-				.Select (length => $"Username ({length} characters)")
-				.BindTo (usernameLengthLabel, x => x.Text)
-				.DisposeWith (_disposable);
-			Add (usernameLengthLabel);
-			return usernameLengthLabel;
-		}
-
-		TextField PasswordInput (View previous) {
-			var passwordInput = new TextField (ViewModel.Password) {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyValue (x => x.Password)
-				.BindTo (passwordInput, x => x.Text)
-				.DisposeWith (_disposable);
-			passwordInput
-				.Events ()
-				.TextChanged
-				.Select (old => passwordInput.Text)
-				.DistinctUntilChanged ()
-				.BindTo (ViewModel, x => x.Password)
-				.DisposeWith (_disposable);
-			Add (passwordInput);
-			return passwordInput;
-		}
-
-		Label PasswordLengthLabel (View previous) {
-			var passwordLengthLabel = new Label {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyValue (x => x.PasswordLength)
-				.Select (length => $"Password ({length} characters)")
-				.BindTo (passwordLengthLabel, x => x.Text)
-				.DisposeWith (_disposable);
-			Add (passwordLengthLabel);
-			return passwordLengthLabel;
-		}
-
-		Label ValidationLabel (View previous) {
-			var error = "Please, enter user name and password.";
-			var success = "The input is valid!";
-			var validationLabel = new Label(error) {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyValue (x => x.IsValid)	
-				.Select (valid => valid ? success : error)
-				.BindTo (validationLabel, x => x.Text)
-				.DisposeWith (_disposable);
-			ViewModel
-				.WhenAnyValue (x => x.IsValid)	
-				.Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
-				.BindTo (validationLabel, x => x.ColorScheme)
-				.DisposeWith (_disposable);
-			Add (validationLabel);
-			return validationLabel;
-		}
-
-		Label LoginProgressLabel (View previous) {
-			var progress = "Logging in...";
-			var idle = "Press 'Login' to log in.";
-			var loginProgressLabel = new Label(idle) {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			ViewModel
-				.WhenAnyObservable (x => x.Login.IsExecuting)
-				.Select (executing => executing ? progress : idle)
-				.ObserveOn (RxApp.MainThreadScheduler)
-				.BindTo (loginProgressLabel, x => x.Text)
-				.DisposeWith (_disposable);
-			Add (loginProgressLabel);
-			return loginProgressLabel;
-		}
-
-		Button LoginButton (View previous) {
-			var loginButton = new Button ("Login") {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			loginButton
-				.Events ()
-				.Clicked
-				.InvokeCommand (ViewModel, x => x.Login)
-				.DisposeWith (_disposable);
-			Add (loginButton);
-			return loginButton;
-		}
-
-		Button ClearButton (View previous) {
-			var clearButton = new Button("Clear") {
-				X = Pos.Left(previous),
-				Y = Pos.Top(previous) + 1,
-				Width = 40
-			};
-			clearButton
-				.Events ()
-				.Clicked
-				.InvokeCommand (ViewModel, x => x.Clear)
-				.DisposeWith (_disposable);
-			Add (clearButton);
-			return clearButton;
-		}
-		
-		object IViewFor.ViewModel {
-			get => ViewModel;
-			set => ViewModel = (LoginViewModel) value;
-		}
-	}
-}
+namespace ReactiveExample;
+
+public class LoginView : Window, IViewFor<LoginViewModel>
+{
+    private readonly CompositeDisposable _disposable = new ();
+
+    public LoginView (LoginViewModel viewModel)
+    {
+        Title = "Reactive Extensions Example";
+        ViewModel = viewModel;
+        Label usernameLengthLabel = UsernameLengthLabel (TitleLabel ());
+        TextField usernameInput = UsernameInput (usernameLengthLabel);
+        Label passwordLengthLabel = PasswordLengthLabel (usernameInput);
+        TextField passwordInput = PasswordInput (passwordLengthLabel);
+        Label validationLabel = ValidationLabel (passwordInput);
+        Button loginButton = LoginButton (validationLabel);
+        Button clearButton = ClearButton (loginButton);
+        LoginProgressLabel (clearButton);
+    }
+
+    public LoginViewModel ViewModel { get; set; }
+
+    object IViewFor.ViewModel
+    {
+        get => ViewModel;
+        set => ViewModel = (LoginViewModel)value;
+    }
+
+    protected override void Dispose (bool disposing)
+    {
+        _disposable.Dispose ();
+        base.Dispose (disposing);
+    }
+
+    private Button ClearButton (View previous)
+    {
+        var clearButton = new Button
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = "Clear"
+        };
+
+        clearButton
+            .Events ()
+            .Clicked
+            .InvokeCommand (ViewModel, x => x.Clear)
+            .DisposeWith (_disposable);
+        Add (clearButton);
+
+        return clearButton;
+    }
+
+    private Button LoginButton (View previous)
+    {
+        var loginButton = new Button
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = "Login"
+        };
+
+        loginButton
+            .Events ()
+            .Clicked
+            .InvokeCommand (ViewModel, x => x.Login)
+            .DisposeWith (_disposable);
+        Add (loginButton);
+
+        return loginButton;
+    }
+
+    private Label LoginProgressLabel (View previous)
+    {
+        var progress = "Logging in...";
+        var idle = "Press 'Login' to log in.";
+
+        var loginProgressLabel = new Label
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = idle
+        };
+
+        ViewModel
+            .WhenAnyObservable (x => x.Login.IsExecuting)
+            .Select (executing => executing ? progress : idle)
+            .ObserveOn (RxApp.MainThreadScheduler)
+            .BindTo (loginProgressLabel, x => x.Text)
+            .DisposeWith (_disposable);
+        Add (loginProgressLabel);
+
+        return loginProgressLabel;
+    }
+
+    private TextField PasswordInput (View previous)
+    {
+        var passwordInput = new TextField
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = ViewModel.Password
+        };
+
+        ViewModel
+            .WhenAnyValue (x => x.Password)
+            .BindTo (passwordInput, x => x.Text)
+            .DisposeWith (_disposable);
+
+        passwordInput
+            .Events ()
+            .TextChanged
+            .Select (old => passwordInput.Text)
+            .DistinctUntilChanged ()
+            .BindTo (ViewModel, x => x.Password)
+            .DisposeWith (_disposable);
+        Add (passwordInput);
+
+        return passwordInput;
+    }
+
+    private Label PasswordLengthLabel (View previous)
+    {
+        var passwordLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40 };
+
+        ViewModel
+            .WhenAnyValue (x => x.PasswordLength)
+            .Select (length => $"Password ({length} characters)")
+            .BindTo (passwordLengthLabel, x => x.Text)
+            .DisposeWith (_disposable);
+        Add (passwordLengthLabel);
+
+        return passwordLengthLabel;
+    }
+
+    private Label TitleLabel ()
+    {
+        var label = new Label { Text = "Login Form" };
+        Add (label);
+
+        return label;
+    }
+
+    private TextField UsernameInput (View previous)
+    {
+        var usernameInput = new TextField
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = ViewModel.Username
+        };
+
+        ViewModel
+            .WhenAnyValue (x => x.Username)
+            .BindTo (usernameInput, x => x.Text)
+            .DisposeWith (_disposable);
+
+        usernameInput
+            .Events ()
+            .TextChanged
+            .Select (old => usernameInput.Text)
+            .DistinctUntilChanged ()
+            .BindTo (ViewModel, x => x.Username)
+            .DisposeWith (_disposable);
+        Add (usernameInput);
+
+        return usernameInput;
+    }
+
+    private Label UsernameLengthLabel (View previous)
+    {
+        var usernameLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40 };
+
+        ViewModel
+            .WhenAnyValue (x => x.UsernameLength)
+            .Select (length => $"Username ({length} characters)")
+            .BindTo (usernameLengthLabel, x => x.Text)
+            .DisposeWith (_disposable);
+        Add (usernameLengthLabel);
+
+        return usernameLengthLabel;
+    }
+
+    private Label ValidationLabel (View previous)
+    {
+        var error = "Please, enter user name and password.";
+        var success = "The input is valid!";
+
+        var validationLabel = new Label
+        {
+            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Text = error
+        };
+
+        ViewModel
+            .WhenAnyValue (x => x.IsValid)
+            .Select (valid => valid ? success : error)
+            .BindTo (validationLabel, x => x.Text)
+            .DisposeWith (_disposable);
+
+        ViewModel
+            .WhenAnyValue (x => x.IsValid)
+            .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
+            .BindTo (validationLabel, x => x.ColorScheme)
+            .DisposeWith (_disposable);
+        Add (validationLabel);
+
+        return validationLabel;
+    }
+}

+ 80 - 69
ReactiveExample/LoginViewModel.cs

@@ -6,72 +6,83 @@ using System.Threading.Tasks;
 using ReactiveUI;
 using ReactiveUI.Fody.Helpers;
 
-namespace ReactiveExample {
-	//
-	// This view model can be easily shared across different UI frameworks.
-	// For example, if you have a WPF or XF app with view models written
-	// this way, you can easily port your app to Terminal.Gui by implementing
-	// the views with Terminal.Gui classes and ReactiveUI bindings.
-	//
-	// We mark the view model with the [DataContract] attributes and this
-	// allows you to save the view model class to the disk, and then to read
-	// the view model from the disk, making your app state persistent.
-	// See also: https://www.reactiveui.net../docs/handbook/data-persistence/
-	//
-	[DataContract]
-	public class LoginViewModel : ReactiveObject {
-		readonly ObservableAsPropertyHelper<int> _usernameLength;
-		readonly ObservableAsPropertyHelper<int> _passwordLength;
-		readonly ObservableAsPropertyHelper<bool> _isValid;
-		
-		public LoginViewModel () {
-			var canLogin = this.WhenAnyValue (
-				x => x.Username, 
-				x => x.Password,
-				(username, password) =>
-					!string.IsNullOrEmpty (username) &&
-					!string.IsNullOrEmpty (password));
-			
-			_isValid = canLogin.ToProperty (this, x => x.IsValid);
-			Login = ReactiveCommand.CreateFromTask<EventArgs> (
-				e => Task.Delay (TimeSpan.FromSeconds (1)),
-				canLogin);
-			
-			_usernameLength = this
-				.WhenAnyValue (x => x.Username)
-				.Select (name => name.Length)
-				.ToProperty (this, x => x.UsernameLength);
-			_passwordLength = this
-				.WhenAnyValue (x => x.Password)
-				.Select (password => password.Length)
-				.ToProperty (this, x => x.PasswordLength);
-			
-			Clear = ReactiveCommand.Create<EventArgs> (e => { });
-			Clear.Subscribe (unit => {
-				Username = string.Empty;
-				Password = string.Empty;
-			});
-		}
-		
-		[Reactive, DataMember]
-		public string Username { get; set; } = string.Empty;
-		
-		[Reactive, DataMember]
-		public string Password { get; set; } = string.Empty;
-		
-		[IgnoreDataMember]
-		public int UsernameLength => _usernameLength.Value;
-		
-		[IgnoreDataMember]
-		public int PasswordLength => _passwordLength.Value;
-
-		[IgnoreDataMember]
-		public ReactiveCommand<EventArgs, Unit> Login { get; }
-		
-		[IgnoreDataMember]
-		public ReactiveCommand<EventArgs, Unit> Clear { get; }
-		
-		[IgnoreDataMember]
-		public bool IsValid => _isValid.Value;
-	}
-}
+namespace ReactiveExample;
+
+//
+// This view model can be easily shared across different UI frameworks.
+// For example, if you have a WPF or XF app with view models written
+// this way, you can easily port your app to Terminal.Gui by implementing
+// the views with Terminal.Gui classes and ReactiveUI bindings.
+//
+// We mark the view model with the [DataContract] attributes and this
+// allows you to save the view model class to the disk, and then to read
+// the view model from the disk, making your app state persistent.
+// See also: https://www.reactiveui.net../docs/handbook/data-persistence/
+//
+[DataContract]
+public class LoginViewModel : ReactiveObject
+{
+    private readonly ObservableAsPropertyHelper<bool> _isValid;
+    private readonly ObservableAsPropertyHelper<int> _passwordLength;
+    private readonly ObservableAsPropertyHelper<int> _usernameLength;
+
+    public LoginViewModel ()
+    {
+        IObservable<bool> canLogin = this.WhenAnyValue (
+                                                        x => x.Username,
+                                                        x => x.Password,
+                                                        (username, password) =>
+                                                            !string.IsNullOrEmpty (username) && !string.IsNullOrEmpty (password)
+                                                       );
+
+        _isValid = canLogin.ToProperty (this, x => x.IsValid);
+
+        Login = ReactiveCommand.CreateFromTask<EventArgs> (
+                                                           e => Task.Delay (TimeSpan.FromSeconds (1)),
+                                                           canLogin
+                                                          );
+
+        _usernameLength = this
+                          .WhenAnyValue (x => x.Username)
+                          .Select (name => name.Length)
+                          .ToProperty (this, x => x.UsernameLength);
+
+        _passwordLength = this
+                          .WhenAnyValue (x => x.Password)
+                          .Select (password => password.Length)
+                          .ToProperty (this, x => x.PasswordLength);
+
+        Clear = ReactiveCommand.Create<EventArgs> (e => { });
+
+        Clear.Subscribe (
+                         unit =>
+                         {
+                             Username = string.Empty;
+                             Password = string.Empty;
+                         }
+                        );
+    }
+
+    [IgnoreDataMember]
+    public ReactiveCommand<EventArgs, Unit> Clear { get; }
+
+    [IgnoreDataMember]
+    public bool IsValid => _isValid.Value;
+
+    [IgnoreDataMember]
+    public ReactiveCommand<EventArgs, Unit> Login { get; }
+
+    [Reactive]
+    [DataMember]
+    public string Password { get; set; } = string.Empty;
+
+    [IgnoreDataMember]
+    public int PasswordLength => _passwordLength.Value;
+
+    [Reactive]
+    [DataMember]
+    public string Username { get; set; } = string.Empty;
+
+    [IgnoreDataMember]
+    public int UsernameLength => _usernameLength.Value;
+}

+ 13 - 11
ReactiveExample/Program.cs

@@ -2,14 +2,16 @@
 using ReactiveUI;
 using Terminal.Gui;
 
-namespace ReactiveExample {
-	public static class Program {
-		static void Main (string [] args) {
-			Application.Init ();
-			RxApp.MainThreadScheduler = TerminalScheduler.Default;
-			RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
-			Application.Run (new LoginView (new LoginViewModel ()));
-			Application.Shutdown ();
-		}
-	}
-}
+namespace ReactiveExample;
+
+public static class Program
+{
+    private static void Main (string [] args)
+    {
+        Application.Init ();
+        RxApp.MainThreadScheduler = TerminalScheduler.Default;
+        RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
+        Application.Run (new LoginView (new LoginViewModel ()));
+        Application.Shutdown ();
+    }
+}

+ 55 - 36
ReactiveExample/TerminalScheduler.cs

@@ -3,39 +3,58 @@ using System.Reactive.Concurrency;
 using System.Reactive.Disposables;
 using Terminal.Gui;
 
-namespace ReactiveExample {
-	public class TerminalScheduler : LocalScheduler {
-		public static readonly TerminalScheduler Default = new TerminalScheduler();
-		TerminalScheduler () { }
-
-		public override IDisposable Schedule<TState> (
-			TState state, TimeSpan dueTime,
-			Func<IScheduler, TState, IDisposable> action) {
-			
-			IDisposable PostOnMainLoop() {
-				var composite = new CompositeDisposable(2);
-				var cancellation = new CancellationDisposable();
-				Application.Invoke (() => {
-					if (!cancellation.Token.IsCancellationRequested)
-						composite.Add(action(this, state));
-				});
-				composite.Add(cancellation);
-				return composite;
-			}
-
-			IDisposable PostOnMainLoopAsTimeout () {
-				var composite = new CompositeDisposable (2);
-				var timeout = Application.AddTimeout (dueTime, () => {
-					composite.Add(action (this, state));
-					return false;
-				});
-				composite.Add (Disposable.Create (() => Application.RemoveTimeout (timeout)));
-				return composite;
-			}
-
-			return dueTime == TimeSpan.Zero 
-				? PostOnMainLoop ()
-				: PostOnMainLoopAsTimeout ();
-		}
-	}
-}
+namespace ReactiveExample;
+
+public class TerminalScheduler : LocalScheduler
+{
+    public static readonly TerminalScheduler Default = new ();
+    private TerminalScheduler () { }
+
+    public override IDisposable Schedule<TState> (
+        TState state,
+        TimeSpan dueTime,
+        Func<IScheduler, TState, IDisposable> action
+    )
+    {
+        IDisposable PostOnMainLoop ()
+        {
+            var composite = new CompositeDisposable (2);
+            var cancellation = new CancellationDisposable ();
+
+            Application.Invoke (
+                                () =>
+                                {
+                                    if (!cancellation.Token.IsCancellationRequested)
+                                    {
+                                        composite.Add (action (this, state));
+                                    }
+                                }
+                               );
+            composite.Add (cancellation);
+
+            return composite;
+        }
+
+        IDisposable PostOnMainLoopAsTimeout ()
+        {
+            var composite = new CompositeDisposable (2);
+
+            object timeout = Application.AddTimeout (
+                                                     dueTime,
+                                                     () =>
+                                                     {
+                                                         composite.Add (action (this, state));
+
+                                                         return false;
+                                                     }
+                                                    );
+            composite.Add (Disposable.Create (() => Application.RemoveTimeout (timeout)));
+
+            return composite;
+        }
+
+        return dueTime == TimeSpan.Zero
+                   ? PostOnMainLoop ()
+                   : PostOnMainLoopAsTimeout ();
+    }
+}

+ 1828 - 1526
Terminal.Gui/Application.cs

@@ -1,19 +1,12 @@
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Linq;
 using System.Globalization;
 using System.Reflection;
-using System.IO;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// A static, singleton class representing the application. This class is the entry point for the application.
-/// </summary>
+/// <summary>A static, singleton class representing the application. This class is the entry point for the application.</summary>
 /// <example>
-/// <code>
+///     <code>
 /// // A simple Terminal.Gui app that creates a window with a frame and title with 
 /// // 5 rows/columns of padding.
 /// Application.Init();
@@ -28,1525 +21,1834 @@ namespace Terminal.Gui;
 /// Application.Shutdown();
 /// </code>
 /// </example>
-/// <remarks>
-/// TODO: Flush this out.
-/// </remarks>
-public static partial class Application {
-
-	// IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
-	// Encapsulate all setting of initial state for Application; Having
-	// this in a function like this ensures we don't make mistakes in
-	// guaranteeing that the state of this singleton is deterministic when Init
-	// starts running and after Shutdown returns.
-	internal static void ResetState ()
-	{
-		// Shutdown is the bookend for Init. As such it needs to clean up all resources
-		// Init created. Apps that do any threading will need to code defensively for this.
-		// e.g. see Issue #537
-		foreach (var t in _topLevels) {
-			t.Running = false;
-			t.Dispose ();
-		}
-		_topLevels.Clear ();
-		Current = null;
-		Top?.Dispose ();
-		Top = null;
-
-		// MainLoop stuff
-		MainLoop?.Dispose ();
-		MainLoop = null;
-		_mainThreadId = -1;
-		Iteration = null;
-		EndAfterFirstIteration = false;
-		
-		// Driver stuff
-		if (Driver != null) {
-			Driver.SizeChanged -= Driver_SizeChanged;
-			Driver.KeyDown -= Driver_KeyDown;
-			Driver.KeyUp -= Driver_KeyUp;
-			Driver.MouseEvent -= Driver_MouseEvent;
-			Driver?.End ();
-			Driver = null;
-		}
-		// Don't reset ForceDriver; it needs to be set before Init is called.
-		//ForceDriver = string.Empty;
-		Force16Colors = false;
-		_forceFakeConsole = false;
-		
-		// Run State stuff
-		NotifyNewRunState = null;
-		NotifyStopRunState = null;
-		MouseGrabView = null;
-		_initialized = false;
-
-		// Mouse
-		_mouseEnteredView = null;
-		WantContinuousButtonPressedView = null;
-		MouseEvent = null;
-		GrabbedMouse = null;
-		UnGrabbingMouse = null;
-		GrabbedMouse = null;
-		UnGrabbedMouse = null;
-
-		// Keyboard
-		AlternateBackwardKey = Key.Empty;
-		AlternateForwardKey = Key.Empty;
-		QuitKey = Key.Empty;
-		KeyDown = null;
-		KeyUp = null;
-		SizeChanging = null;
-
-		Colors.Reset ();
-
-		// Reset synchronization context to allow the user to run async/await,
-		// as the main loop has been ended, the synchronization context from 
-		// gui.cs does no longer process any callbacks. See #1084 for more details:
-		// (https://github.com/gui-cs/Terminal.Gui/issues/1084).
-		SynchronizationContext.SetSynchronizationContext (syncContext: null);
-	}
-
-	/// <summary>
-	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.
-	/// </summary>
-	public static ConsoleDriver Driver { get; internal set; }
-
-	/// <summary>
-	/// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If
-	/// not specified, the driver is selected based on the platform.
-	/// </summary>
-	/// <remarks>
-	/// Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if
-	/// called with either `driver` or `driverName` specified.
-	/// </remarks>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static string ForceDriver { get; set; } = string.Empty;
-
-	/// <summary>
-	/// Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in <see cref="ColorName"/>.
-	/// The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output as long as the selected <see cref="ConsoleDriver"/>
-	/// supports TrueColor.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static bool Force16Colors { get; set; } = false;
-
-	// For Unit testing - ignores UseSystemConsole
-	internal static bool _forceFakeConsole;
-
-	static List<CultureInfo> _cachedSupportedCultures;
-
-	/// <summary>
-	/// Gets all cultures supported by the application without the invariant language.
-	/// </summary>
-	public static List<CultureInfo> SupportedCultures => _cachedSupportedCultures;
-
-	internal static List<CultureInfo> GetSupportedCultures ()
-	{
-		var culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
-
-		// Get the assembly
-		var assembly = Assembly.GetExecutingAssembly ();
-
-		//Find the location of the assembly
-		string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
-
-		// Find the resource file name of the assembly
-		string resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll";
-
-		// Return all culture for which satellite folder found with culture code.
-		return culture.Where (cultureInfo =>
-			Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name)) &&
-			File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
-		).ToList ();
-	}
-
-	#region Initialization (Init/Shutdown)
-	/// <summary>
-	/// Initializes a new instance of <see cref="Terminal.Gui"/> Application. 
-	/// </summary>
-	/// <para>
-	/// Call this method once per instance (or after <see cref="Shutdown"/> has been called).
-	/// </para>
-	/// <para>
-	/// This function loads the right <see cref="ConsoleDriver"/> for the platform, 
-	/// Creates a <see cref="Toplevel"/>. and assigns it to <see cref="Top"/>
-	/// </para>
-	/// <para>
-	/// <see cref="Shutdown"/> must be called when the application is closing (typically after <see cref="Run(Func{Exception, bool})"/> has 
-	/// returned) to ensure resources are cleaned up and terminal settings restored.
-	/// </para>
-	/// <para>
-	/// The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> function 
-	/// combines <see cref="Init(ConsoleDriver, string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
-	/// into a single call. An application cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> 
-	/// without explicitly calling <see cref="Init(ConsoleDriver, string)"/>.
-	/// </para>
-	/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
-	/// <param name="driverName">The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
-	public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (() => new Toplevel (), driver, driverName);
-
-	internal static bool _initialized = false;
-	internal static int _mainThreadId = -1;
-
-	// INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
-	//
-	// Called from:
-	// 
-	// Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
-	// Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
-	// Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
-	// 
-	// calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
-	internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, string driverName = null, bool calledViaRunT = false)
-	{
-		if (_initialized && driver == null) {
-			return;
-		}
-
-		if (_initialized) {
-			throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
-		}
-
-		if (!calledViaRunT) {
-			// Reset all class variables (Application is a singleton).
-			ResetState ();
-		}
-
-		// For UnitTests
-		if (driver != null) {
-			Driver = driver;
-		}
-
-		// Start the process of configuration management.
-		// Note that we end up calling LoadConfigurationFromAllSources
-		// multiple times. We need to do this because some settings are only
-		// valid after a Driver is loaded. In this cases we need just 
-		// `Settings` so we can determine which driver to use.
-		Load (true);
-		Apply ();
-
-		// Ignore Configuration for ForceDriver if driverName is specified
-		if (!string.IsNullOrEmpty (driverName)) {
-			ForceDriver = driverName;
-		}
-
-		if (Driver == null) {
-			var p = Environment.OSVersion.Platform;
-			if (string.IsNullOrEmpty (ForceDriver)) {
-				if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
-					Driver = new WindowsDriver ();
-				} else {
-					Driver = new CursesDriver ();
-				}
-			} else {
-				var drivers = GetDriverTypes ();
-				var driverType = drivers.FirstOrDefault (t => t.Name.ToLower () == ForceDriver.ToLower ());
-				if (driverType != null) {
-					Driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-				} else {
-					throw new ArgumentException ($"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}");
-				}
-			}
-		}
-
-		try {
-			MainLoop = Driver.Init ();
-		} catch (InvalidOperationException ex) {
-			// This is a case where the driver is unable to initialize the console.
-			// This can happen if the console is already in use by another process or
-			// if running in unit tests.
-			// In this case, we want to throw a more specific exception.
-			throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
-		}
-
-		Driver.SizeChanged += (s, args) => OnSizeChanging (args);
-		Driver.KeyDown += (s, args) => OnKeyDown (args);
-		Driver.KeyUp += (s, args) => OnKeyUp (args);
-		Driver.MouseEvent += (s, args) => OnMouseEvent (args);
-
-		SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
-
-		Top = topLevelFactory ();
-		Current = Top;
-
-		// Ensure Top's layout is up to date.
-		Current.SetRelativeLayout (Driver.Bounds);
-
-		_cachedSupportedCultures = GetSupportedCultures ();
-		_mainThreadId = Thread.CurrentThread.ManagedThreadId;
-		_initialized = true;
-	}
-
-	static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) => OnSizeChanging (e);
-
-	static void Driver_KeyDown (object sender, Key e) => OnKeyDown (e);
-
-	static void Driver_KeyUp (object sender, Key e) => OnKeyUp (e);
-
-	static void Driver_MouseEvent (object sender, MouseEventEventArgs e) => OnMouseEvent (e);
-
-	/// <summary>
-	/// Gets of list of <see cref="ConsoleDriver"/> types that are available.
-	/// </summary>
-	/// <returns></returns>
-	public static List<Type> GetDriverTypes ()
-	{
-		// use reflection to get the list of drivers
-		var driverTypes = new List<Type> ();
-		foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) {
-			foreach (var type in asm.GetTypes ()) {
-				if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) {
-					driverTypes.Add (type);
-				}
-			}
-		}
-		return driverTypes;
-	}
-
-	/// <summary>
-	/// Shutdown an application initialized with <see cref="Init"/>.
-	/// </summary>
-	/// <remarks>
-	/// Shutdown must be called for every call to <see cref="Init"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
-	/// to ensure all resources are cleaned up (Disposed) and terminal settings are restored.
-	/// </remarks>
-	public static void Shutdown ()
-	{
-		ResetState ();
-		PrintJsonErrors ();
-	}
-	#endregion Initialization (Init/Shutdown)
-
-	#region Run (Begin, Run, End, Stop)
-	/// <summary>
-	/// Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is created in 
-	/// <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
-	/// </summary>
-	/// <remarks>
-	///	If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to
-	///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
-	///	and manually dispose of the <see cref="RunState"/> token when the application is done.
-	/// </remarks>
-	public static event EventHandler<RunStateEventArgs> NotifyNewRunState;
-
-	/// <summary>
-	/// Notify that a existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).
-	/// </summary>
-	/// <remarks>
-	///	If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to
-	///	<see cref="Begin(Toplevel)"/> must also subscribe to <see cref="NotifyStopRunState"/>
-	///	and manually dispose of the <see cref="RunState"/> token when the application is done.
-	/// </remarks>
-	public static event EventHandler<ToplevelEventArgs> NotifyStopRunState;
-
-	/// <summary>
-	/// Building block API: Prepares the provided <see cref="Toplevel"/> for execution.
-	/// </summary>
-	/// <returns>The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon completion.</returns>
-	/// <param name="Toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
-	/// <remarks>
-	/// This method prepares the provided <see cref="Toplevel"/> for running with the focus,
-	/// it adds this to the list of <see cref="Toplevel"/>s, lays out the Subviews, focuses the first element, and draws the
-	/// <see cref="Toplevel"/> in the screen. This is usually followed by executing
-	/// the <see cref="RunLoop"/> method, and then the <see cref="End(RunState)"/> method upon termination which will
-	///  undo these changes.
-	/// </remarks>
-	public static RunState Begin (Toplevel Toplevel)
-	{
-		if (Toplevel == null) {
-			throw new ArgumentNullException (nameof (Toplevel));
-		} else if (Toplevel.IsOverlappedContainer && OverlappedTop != Toplevel && OverlappedTop != null) {
-			throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
-		}
-
-		// Ensure the mouse is ungrabed.
-		MouseGrabView = null;
-
-		var rs = new RunState (Toplevel);
-
-		// View implements ISupportInitializeNotification which is derived from ISupportInitialize
-		if (!Toplevel.IsInitialized) {
-			Toplevel.BeginInit ();
-			Toplevel.EndInit ();
-		}
-
-		lock (_topLevels) {
-			// If Top was already initialized with Init, and Begin has never been called
-			// Top was not added to the Toplevels Stack. It will thus never get disposed.
-			// Clean it up here:
-			if (Top != null && Toplevel != Top && !_topLevels.Contains (Top)) {
-				Top.Dispose ();
-				Top = null;
-			} else if (Top != null && Toplevel != Top && _topLevels.Contains (Top)) {
-				Top.OnLeave (Toplevel);
-			}
-			// BUGBUG: We should not depend on `Id` internally. 
-			// BUGBUG: It is super unclear what this code does anyway.
-			if (string.IsNullOrEmpty (Toplevel.Id)) {
-				int count = 1;
-				string id = (_topLevels.Count + count).ToString ();
-				while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null) {
-					count++;
-					id = (_topLevels.Count + count).ToString ();
-				}
-				Toplevel.Id = (_topLevels.Count + count).ToString ();
-
-				_topLevels.Push (Toplevel);
-			} else {
-				var dup = _topLevels.FirstOrDefault (x => x.Id == Toplevel.Id);
-				if (dup == null) {
-					_topLevels.Push (Toplevel);
-				}
-			}
-
-			if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0) {
-				throw new ArgumentException ("There are duplicates Toplevels Id's");
-			}
-		}
-		if (Top == null || Toplevel.IsOverlappedContainer) {
-			Top = Toplevel;
-		}
-
-		bool refreshDriver = true;
-		if (OverlappedTop == null || Toplevel.IsOverlappedContainer || Current?.Modal == false && Toplevel.Modal
-		|| Current?.Modal == false && !Toplevel.Modal || Current?.Modal == true && Toplevel.Modal) {
-
-			if (Toplevel.Visible) {
-				Current = Toplevel;
-				SetCurrentOverlappedAsTop ();
-			} else {
-				refreshDriver = false;
-			}
-		} else if (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Modal == true && !_topLevels.Peek ().Modal
-			|| OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false) {
-			refreshDriver = false;
-			MoveCurrent (Toplevel);
-		} else {
-			refreshDriver = false;
-			MoveCurrent (Current);
-		}
-
-		//if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
-		Toplevel.SetRelativeLayout (Driver.Bounds);
-		//}
-		Toplevel.LayoutSubviews ();
-		Toplevel.PositionToplevels ();
-		Toplevel.FocusFirst ();
-		if (refreshDriver) {
-			OverlappedTop?.OnChildLoaded (Toplevel);
-			Toplevel.OnLoaded ();
-			Toplevel.SetNeedsDisplay ();
-			Toplevel.Draw ();
-			Toplevel.PositionCursor ();
-			Driver.Refresh ();
-		}
-
-		NotifyNewRunState?.Invoke (Toplevel, new RunStateEventArgs (rs));
-		return rs;
-	}
-
-	/// <summary>
-	/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of <see cref="Top"/>.
-	/// </summary>
-	/// <remarks>
-	/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
-	/// </remarks>
-	public static void Run (Func<Exception, bool> errorHandler = null) => Run (Top, errorHandler);
-
-	/// <summary>
-	/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> 
-	/// with a new instance of the specified <see cref="Toplevel"/>-derived class.
-	/// <para>
-	/// Calling <see cref="Init"/> first is not needed as this function will initialize the application.
-	/// </para>
-	/// <para>
-	/// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has 
-	/// returned) to ensure resources are cleaned up and terminal settings restored.
-	/// </para>
-	/// </summary>
-	/// <remarks>
-	/// See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.
-	/// </remarks>
-	/// <param name="errorHandler"></param>
-	/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
-	/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
-	/// Must be <see langword="null"/> if <see cref="Init"/> has already been called. 
-	/// </param>
-	public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null) where T : Toplevel, new()
-	{
-		if (_initialized) {
-			if (Driver != null) {
-				// Init() has been called and we have a driver, so just run the app.
-				var top = new T ();
-				var type = top.GetType ().BaseType;
-				while (type != typeof (Toplevel) && type != typeof (object)) {
-					type = type.BaseType;
-				}
-				if (type != typeof (Toplevel)) {
-					throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
-				}
-				Run (top, errorHandler);
-			} else {
-				// This code path should be impossible because Init(null, null) will select the platform default driver
-				throw new InvalidOperationException ("Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called.");
-			}
-		} else {
-			// Init() has NOT been called.
-			InternalInit (() => new T (), driver, null, true);
-			Run (Top, errorHandler);
-		}
-	}
-
-	/// <summary>
-	///  Runs the main loop on the given <see cref="Toplevel"/> container.
-	/// </summary>
-	/// <remarks>
-	///  <para>
-	///   This method is used to start processing events
-	///   for the main application, but it is also used to
-	///   run other modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
-	///  </para>
-	///  <para>
-	///   To make a <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call <see cref="Application.RequestStop"/>.
-	///  </para>
-	///  <para>
-	///   Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling <see cref="Begin(Toplevel)"/>,
-	///   followed by <see cref="RunLoop(RunState)"/>, and then calling <see cref="End(RunState)"/>.
-	///  </para>
-	///  <para>
-	///   Alternatively, to have a program control the main loop and 
-	///   process events manually, call <see cref="Begin(Toplevel)"/> to set things up manually and then
-	///   repeatedly call <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this
-	///   the <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and
-	///   then return control immediately.
-	///  </para>
-	///  <para>
-	///   RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be rethrown. 
-	///   Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/> 
-	///   returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise 
-	///   this method will exit.
-	///  </para>
-	/// </remarks>
-	/// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
-	/// <param name="errorHandler">RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true, rethrows when null).</param>
-	public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
-	{
-		bool resume = true;
-		while (resume) {
+/// <remarks>TODO: Flush this out.</remarks>
+public static partial class Application
+{
+    // For Unit testing - ignores UseSystemConsole
+    internal static bool _forceFakeConsole;
+
+    /// <summary>Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
+    public static ConsoleDriver Driver { get; internal set; }
+
+    /// <summary>
+    ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
+    ///     <see cref="ColorName"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
+    ///     as long as the selected <see cref="ConsoleDriver"/> supports TrueColor.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If not
+    ///     specified, the driver is selected based on the platform.
+    /// </summary>
+    /// <remarks>
+    ///     Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if called
+    ///     with either `driver` or `driverName` specified.
+    /// </remarks>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static string ForceDriver { get; set; } = string.Empty;
+
+    /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
+    public static List<CultureInfo> SupportedCultures { get; private set; }
+
+    internal static List<CultureInfo> GetSupportedCultures ()
+    {
+        CultureInfo [] culture = CultureInfo.GetCultures (CultureTypes.AllCultures);
+
+        // Get the assembly
+        var assembly = Assembly.GetExecutingAssembly ();
+
+        //Find the location of the assembly
+        string assemblyLocation = AppDomain.CurrentDomain.BaseDirectory;
+
+        // Find the resource file name of the assembly
+        var resourceFilename = $"{Path.GetFileNameWithoutExtension (assembly.Location)}.resources.dll";
+
+        // Return all culture for which satellite folder found with culture code.
+        return culture.Where (
+                              cultureInfo =>
+                                  Directory.Exists (Path.Combine (assemblyLocation, cultureInfo.Name))
+                                  && File.Exists (Path.Combine (assemblyLocation, cultureInfo.Name, resourceFilename))
+                             )
+                      .ToList ();
+    }
+
+    // IMPORTANT: Ensure all property/fields are reset here. See Init_ResetState_Resets_Properties unit test.
+    // Encapsulate all setting of initial state for Application; Having
+    // this in a function like this ensures we don't make mistakes in
+    // guaranteeing that the state of this singleton is deterministic when Init
+    // starts running and after Shutdown returns.
+    internal static void ResetState ()
+    {
+        // Shutdown is the bookend for Init. As such it needs to clean up all resources
+        // Init created. Apps that do any threading will need to code defensively for this.
+        // e.g. see Issue #537
+        foreach (Toplevel t in _topLevels)
+        {
+            t.Running = false;
+            t.Dispose ();
+        }
+
+        _topLevels.Clear ();
+        Current = null;
+        Top?.Dispose ();
+        Top = null;
+
+        // MainLoop stuff
+        MainLoop?.Dispose ();
+        MainLoop = null;
+        _mainThreadId = -1;
+        Iteration = null;
+        EndAfterFirstIteration = false;
+
+        // Driver stuff
+        if (Driver != null)
+        {
+            Driver.SizeChanged -= Driver_SizeChanged;
+            Driver.KeyDown -= Driver_KeyDown;
+            Driver.KeyUp -= Driver_KeyUp;
+            Driver.MouseEvent -= Driver_MouseEvent;
+            Driver?.End ();
+            Driver = null;
+        }
+
+        // Don't reset ForceDriver; it needs to be set before Init is called.
+        //ForceDriver = string.Empty;
+        Force16Colors = false;
+        _forceFakeConsole = false;
+
+        // Run State stuff
+        NotifyNewRunState = null;
+        NotifyStopRunState = null;
+        MouseGrabView = null;
+        _initialized = false;
+
+        // Mouse
+        _mouseEnteredView = null;
+        WantContinuousButtonPressedView = null;
+        MouseEvent = null;
+        GrabbedMouse = null;
+        UnGrabbingMouse = null;
+        GrabbedMouse = null;
+        UnGrabbedMouse = null;
+
+        // Keyboard
+        AlternateBackwardKey = Key.Empty;
+        AlternateForwardKey = Key.Empty;
+        QuitKey = Key.Empty;
+        KeyDown = null;
+        KeyUp = null;
+        SizeChanging = null;
+
+        Colors.Reset ();
+
+        // Reset synchronization context to allow the user to run async/await,
+        // as the main loop has been ended, the synchronization context from 
+        // gui.cs does no longer process any callbacks. See #1084 for more details:
+        // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
+        SynchronizationContext.SetSynchronizationContext (null);
+    }
+
+    #region Initialization (Init/Shutdown)
+
+    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
+    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    /// <para>
+    ///     This function loads the right <see cref="ConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     assigns it to <see cref="Top"/>
+    /// </para>
+    /// <para>
+    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///     <see cref="Run(Func{Exception, bool})"/> has returned) to ensure resources are cleaned up and terminal settings
+    ///     restored.
+    /// </para>
+    /// <para>
+    ///     The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> function combines
+    ///     <see cref="Init(ConsoleDriver, string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/> into a single
+    ///     call. An application cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> without explicitly calling
+    ///     <see cref="Init(ConsoleDriver, string)"/>.
+    /// </para>
+    /// <param name="driver">
+    ///     The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
+    /// </param>
+    /// <param name="driverName">
+    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
+    ///     <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     specified the default driver for the platform will be used.
+    /// </param>
+    public static void Init (ConsoleDriver driver = null, string driverName = null) { InternalInit (() => new Toplevel (), driver, driverName); }
+
+    internal static bool _initialized;
+    internal static int _mainThreadId = -1;
+
+    // INTERNAL function for initializing an app with a Toplevel factory object, driver, and mainloop.
+    //
+    // Called from:
+    // 
+    // Init() - When the user wants to use the default Toplevel. calledViaRunT will be false, causing all state to be reset.
+    // Run<T>() - When the user wants to use a custom Toplevel. calledViaRunT will be true, enabling Run<T>() to be called without calling Init first.
+    // Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
+    // 
+    // calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
+    internal static void InternalInit (
+        Func<Toplevel> topLevelFactory,
+        ConsoleDriver driver = null,
+        string driverName = null,
+        bool calledViaRunT = false
+    )
+    {
+        if (_initialized && driver == null)
+        {
+            return;
+        }
+
+        if (_initialized)
+        {
+            throw new InvalidOperationException ("Init has already been called and must be bracketed by Shutdown.");
+        }
+
+        if (!calledViaRunT)
+        {
+            // Reset all class variables (Application is a singleton).
+            ResetState ();
+        }
+
+        // For UnitTests
+        if (driver != null)
+        {
+            Driver = driver;
+        }
+
+        // Start the process of configuration management.
+        // Note that we end up calling LoadConfigurationFromAllSources
+        // multiple times. We need to do this because some settings are only
+        // valid after a Driver is loaded. In this cases we need just 
+        // `Settings` so we can determine which driver to use.
+        Load (true);
+        Apply ();
+
+        // Ignore Configuration for ForceDriver if driverName is specified
+        if (!string.IsNullOrEmpty (driverName))
+        {
+            ForceDriver = driverName;
+        }
+
+        if (Driver == null)
+        {
+            PlatformID p = Environment.OSVersion.Platform;
+
+            if (string.IsNullOrEmpty (ForceDriver))
+            {
+                if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+                {
+                    Driver = new WindowsDriver ();
+                }
+                else
+                {
+                    Driver = new CursesDriver ();
+                }
+            }
+            else
+            {
+                List<Type> drivers = GetDriverTypes ();
+                Type driverType = drivers.FirstOrDefault (t => t.Name.ToLower () == ForceDriver.ToLower ());
+
+                if (driverType != null)
+                {
+                    Driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+                }
+                else
+                {
+                    throw new ArgumentException (
+                                                 $"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}"
+                                                );
+                }
+            }
+        }
+
+        try
+        {
+            MainLoop = Driver.Init ();
+        }
+        catch (InvalidOperationException ex)
+        {
+            // This is a case where the driver is unable to initialize the console.
+            // This can happen if the console is already in use by another process or
+            // if running in unit tests.
+            // In this case, we want to throw a more specific exception.
+            throw new InvalidOperationException (
+                                                 "Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.",
+                                                 ex
+                                                );
+        }
+
+        Driver.SizeChanged += (s, args) => OnSizeChanging (args);
+        Driver.KeyDown += (s, args) => OnKeyDown (args);
+        Driver.KeyUp += (s, args) => OnKeyUp (args);
+        Driver.MouseEvent += (s, args) => OnMouseEvent (args);
+
+        SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
+
+        Top = topLevelFactory ();
+        Current = Top;
+
+        // Ensure Top's layout is up to date.
+        Current.SetRelativeLayout (Driver.Bounds);
+
+        SupportedCultures = GetSupportedCultures ();
+        _mainThreadId = Thread.CurrentThread.ManagedThreadId;
+        _initialized = true;
+    }
+
+    private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
+    private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); }
+    private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); }
+    private static void Driver_MouseEvent (object sender, MouseEventEventArgs e) { OnMouseEvent (e); }
+
+    /// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
+    /// <returns></returns>
+    public static List<Type> GetDriverTypes ()
+    {
+        // use reflection to get the list of drivers
+        List<Type> driverTypes = new ();
+
+        foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies ())
+        {
+            foreach (Type type in asm.GetTypes ())
+            {
+                if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract)
+                {
+                    driverTypes.Add (type);
+                }
+            }
+        }
+
+        return driverTypes;
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public static void Shutdown ()
+    {
+        ResetState ();
+        PrintJsonErrors ();
+    }
+
+    #endregion Initialization (Init/Shutdown)
+
+    #region Run (Begin, Run, End, Stop)
+
+    /// <summary>
+    ///     Notify that a new <see cref="RunState"/> was created (<see cref="Begin(Toplevel)"/> was called). The token is
+    ///     created in <see cref="Begin(Toplevel)"/> and this event will be fired before that function exits.
+    /// </summary>
+    /// <remarks>
+    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public static event EventHandler<RunStateEventArgs> NotifyNewRunState;
+
+    /// <summary>Notify that a existent <see cref="RunState"/> is stopping (<see cref="End(RunState)"/> was called).</summary>
+    /// <remarks>
+    ///     If <see cref="EndAfterFirstIteration"/> is <see langword="true"/> callers to <see cref="Begin(Toplevel)"/>
+    ///     must also subscribe to <see cref="NotifyStopRunState"/> and manually dispose of the <see cref="RunState"/> token
+    ///     when the application is done.
+    /// </remarks>
+    public static event EventHandler<ToplevelEventArgs> NotifyStopRunState;
+
+    /// <summary>Building block API: Prepares the provided <see cref="Toplevel"/> for execution.</summary>
+    /// <returns>
+    ///     The <see cref="RunState"/> handle that needs to be passed to the <see cref="End(RunState)"/> method upon
+    ///     completion.
+    /// </returns>
+    /// <param name="Toplevel">The <see cref="Toplevel"/> to prepare execution for.</param>
+    /// <remarks>
+    ///     This method prepares the provided <see cref="Toplevel"/> for running with the focus, it adds this to the list
+    ///     of <see cref="Toplevel"/>s, lays out the Subviews, focuses the first element, and draws the <see cref="Toplevel"/>
+    ///     in the screen. This is usually followed by executing the <see cref="RunLoop"/> method, and then the
+    ///     <see cref="End(RunState)"/> method upon termination which will undo these changes.
+    /// </remarks>
+    public static RunState Begin (Toplevel Toplevel)
+    {
+        if (Toplevel == null)
+        {
+            throw new ArgumentNullException (nameof (Toplevel));
+        }
+
+        if (Toplevel.IsOverlappedContainer && OverlappedTop != Toplevel && OverlappedTop != null)
+        {
+            throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
+        }
+
+        // Ensure the mouse is ungrabed.
+        MouseGrabView = null;
+
+        var rs = new RunState (Toplevel);
+
+        // View implements ISupportInitializeNotification which is derived from ISupportInitialize
+        if (!Toplevel.IsInitialized)
+        {
+            Toplevel.BeginInit ();
+            Toplevel.EndInit ();
+        }
+
+        lock (_topLevels)
+        {
+            // If Top was already initialized with Init, and Begin has never been called
+            // Top was not added to the Toplevels Stack. It will thus never get disposed.
+            // Clean it up here:
+            if (Top != null && Toplevel != Top && !_topLevels.Contains (Top))
+            {
+                Top.Dispose ();
+                Top = null;
+            }
+            else if (Top != null && Toplevel != Top && _topLevels.Contains (Top))
+            {
+                Top.OnLeave (Toplevel);
+            }
+
+            // BUGBUG: We should not depend on `Id` internally. 
+            // BUGBUG: It is super unclear what this code does anyway.
+            if (string.IsNullOrEmpty (Toplevel.Id))
+            {
+                var count = 1;
+                var id = (_topLevels.Count + count).ToString ();
+
+                while (_topLevels.Count > 0 && _topLevels.FirstOrDefault (x => x.Id == id) != null)
+                {
+                    count++;
+                    id = (_topLevels.Count + count).ToString ();
+                }
+
+                Toplevel.Id = (_topLevels.Count + count).ToString ();
+
+                _topLevels.Push (Toplevel);
+            }
+            else
+            {
+                Toplevel dup = _topLevels.FirstOrDefault (x => x.Id == Toplevel.Id);
+
+                if (dup == null)
+                {
+                    _topLevels.Push (Toplevel);
+                }
+            }
+
+            if (_topLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
+            {
+                throw new ArgumentException ("There are duplicates Toplevels Id's");
+            }
+        }
+
+        if (Top == null || Toplevel.IsOverlappedContainer)
+        {
+            Top = Toplevel;
+        }
+
+        var refreshDriver = true;
+
+        if (OverlappedTop == null
+            || Toplevel.IsOverlappedContainer
+            || (Current?.Modal == false && Toplevel.Modal)
+            || (Current?.Modal == false && !Toplevel.Modal)
+            || (Current?.Modal == true && Toplevel.Modal))
+        {
+            if (Toplevel.Visible)
+            {
+                Current = Toplevel;
+                SetCurrentOverlappedAsTop ();
+            }
+            else
+            {
+                refreshDriver = false;
+            }
+        }
+        else if ((OverlappedTop != null
+                  && Toplevel != OverlappedTop
+                  && Current?.Modal == true
+                  && !_topLevels.Peek ().Modal)
+                 || (OverlappedTop != null && Toplevel != OverlappedTop && Current?.Running == false))
+        {
+            refreshDriver = false;
+            MoveCurrent (Toplevel);
+        }
+        else
+        {
+            refreshDriver = false;
+            MoveCurrent (Current);
+        }
+
+        //if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
+        Toplevel.SetRelativeLayout (Driver.Bounds);
+
+        //}
+        Toplevel.LayoutSubviews ();
+        Toplevel.PositionToplevels ();
+        Toplevel.FocusFirst ();
+
+        if (refreshDriver)
+        {
+            OverlappedTop?.OnChildLoaded (Toplevel);
+            Toplevel.OnLoaded ();
+            Toplevel.SetNeedsDisplay ();
+            Toplevel.Draw ();
+            Toplevel.PositionCursor ();
+            Driver.Refresh ();
+        }
+
+        NotifyNewRunState?.Invoke (Toplevel, new RunStateEventArgs (rs));
+
+        return rs;
+    }
+
+    /// <summary>
+    ///     Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with the value of
+    ///     <see cref="Top"/>.
+    /// </summary>
+    /// <remarks>See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.</remarks>
+    public static void Run (Func<Exception, bool> errorHandler = null) { Run (Top, errorHandler); }
+
+    /// <summary>
+    ///     Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> with a new instance of the
+    ///     specified <see cref="Toplevel"/>-derived class.
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    /// </summary>
+    /// <remarks>See <see cref="Run(Toplevel, Func{Exception, bool})"/> for more details.</remarks>
+    /// <param name="errorHandler"></param>
+    /// <param name="driver">
+    ///     The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the platform will
+    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    public static void Run<T> (Func<Exception, bool> errorHandler = null, ConsoleDriver driver = null)
+        where T : Toplevel, new ()
+    {
+        if (_initialized)
+        {
+            if (Driver != null)
+            {
+                // Init() has been called and we have a driver, so just run the app.
+                var top = new T ();
+                Type type = top.GetType ().BaseType;
+
+                while (type != typeof (Toplevel) && type != typeof (object))
+                {
+                    type = type.BaseType;
+                }
+
+                if (type != typeof (Toplevel))
+                {
+                    throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
+                }
+
+                Run (top, errorHandler);
+            }
+            else
+            {
+                // This code path should be impossible because Init(null, null) will select the platform default driver
+                throw new InvalidOperationException (
+                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
+                                                    );
+            }
+        }
+        else
+        {
+            // Init() has NOT been called.
+            InternalInit (() => new T (), driver, null, true);
+            Run (Top, errorHandler);
+        }
+    }
+
+    /// <summary>Runs the main loop on the given <see cref="Toplevel"/> container.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make a <see cref="Run(Toplevel, Func{Exception, bool})"/> stop execution, call
+    ///         <see cref="Application.RequestStop"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Toplevel, Func{Exception, bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
+    ///         <see cref="End(RunState)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Alternatively, to have a program control the main loop and process events manually, call
+    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         return control immediately.
+    ///     </para>
+    ///     <para>
+    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         exit.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">
+    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
+    ///     rethrows when null).
+    /// </param>
+    public static void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
+    {
+        var resume = true;
+
+        while (resume)
+        {
 #if !DEBUG
-				try {
+            try
+            {
 #endif
-			resume = false;
-			var runState = Begin (view);
-			// If EndAfterFirstIteration is true then the user must dispose of the runToken
-			// by using NotifyStopRunState event.
-			RunLoop (runState);
-			if (!EndAfterFirstIteration) {
-				End (runState);
-			}
+            resume = false;
+            RunState runState = Begin (view);
+
+            // If EndAfterFirstIteration is true then the user must dispose of the runToken
+            // by using NotifyStopRunState event.
+            RunLoop (runState);
+
+            if (!EndAfterFirstIteration)
+            {
+                End (runState);
+            }
 #if !DEBUG
-				}
-				catch (Exception error)
-				{
-					if (errorHandler == null)
-					{
-						throw;
-					}
-					resume = errorHandler(error);
-				}
+            }
+            catch (Exception error)
+            {
+                if (errorHandler == null)
+                {
+                    throw;
+                }
+
+                resume = errorHandler (error);
+            }
 #endif
-		}
-	}
-
-	/// <summary>
-	///   Adds a timeout to the application.
-	/// </summary>
-	/// <remarks>
-	///   When time specified passes, the callback will be invoked.
-	///   If the callback returns true, the timeout will be reset, repeating
-	///   the invocation. If it returns false, the timeout will stop and be removed.
-	///
-	///   The returned value is a token that can be used to stop the timeout
-	///   by calling <see cref="RemoveTimeout(object)"/>.
-	/// </remarks>
-	public static object AddTimeout (TimeSpan time, Func<bool> callback) => MainLoop?.AddTimeout (time, callback);
-
-	/// <summary>
-	///   Removes a previously scheduled timeout
-	/// </summary>
-	/// <remarks>
-	///   The token parameter is the value returned by <see cref="AddTimeout"/>.
-	/// </remarks>
-	/// Returns <c>true</c>if the timeout is successfully removed; otherwise, <c>false</c>.
-	/// This method also returns <c>false</c> if the timeout is not found.
-	public static bool RemoveTimeout (object token) => MainLoop?.RemoveTimeout (token) ?? false;
-
-
-	/// <summary>
-	///   Runs <paramref name="action"/> on the thread that is processing events
-	/// </summary>
-	/// <param name="action">the action to be invoked on the main processing thread.</param>
-	public static void Invoke (Action action) => MainLoop?.AddIdle (() => {
-		action ();
-		return false;
-	});
-
-	// TODO: Determine if this is really needed. The only code that calls WakeUp I can find
-	// is ProgressBarStyles and it's not clear it needs to.
-	/// <summary>
-	/// Wakes up the running application that might be waiting on input.
-	/// </summary>
-	public static void Wakeup () => MainLoop?.Wakeup ();
-
-	/// <summary>
-	/// Triggers a refresh of the entire display.
-	/// </summary>
-	public static void Refresh ()
-	{
-		// TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
-		Driver.ClearContents ();
-		View last = null;
-		foreach (var v in _topLevels.Reverse ()) {
-			if (v.Visible) {
-				v.SetNeedsDisplay ();
-				v.SetSubViewNeedsDisplay ();
-				v.Draw ();
-			}
-			last = v;
-		}
-		last?.PositionCursor ();
-		Driver.Refresh ();
-	}
-
-	/// <summary>
-	///  This event is raised on each iteration of the main loop.
-	/// </summary>
-	/// <remarks>
-	///  See also <see cref="Timeout"/>
-	/// </remarks>
-	public static event EventHandler<IterationEventArgs> Iteration;
-
-	/// <summary>
-	/// The <see cref="MainLoop"/> driver for the application
-	/// </summary>
-	/// <value>The main loop.</value>
-	internal static MainLoop MainLoop { get; private set; }
-
-	/// <summary>
-	/// Set to true to cause <see cref="End"/> to be called after the first iteration.
-	/// Set to false (the default) to cause the application to continue running until Application.RequestStop () is called.
-	/// </summary>
-	public static bool EndAfterFirstIteration { get; set; } = false;
-
-	//
-	// provides the sync context set while executing code in Terminal.Gui, to let
-	// users use async/await on their code
-	//
-	class MainLoopSyncContext : SynchronizationContext {
-		public override SynchronizationContext CreateCopy () => new MainLoopSyncContext ();
-
-		public override void Post (SendOrPostCallback d, object state) => MainLoop.AddIdle (() => {
-			d (state);
-			return false;
-		});
-
-		//_mainLoop.Driver.Wakeup ();
-		public override void Send (SendOrPostCallback d, object state)
-		{
-			if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) {
-				d (state);
-			} else {
-				bool wasExecuted = false;
-				Invoke (() => {
-					d (state);
-					wasExecuted = true;
-				});
-				while (!wasExecuted) {
-					Thread.Sleep (15);
-				}
-			}
-		}
-	}
-
-	/// <summary>
-	///  Building block API: Runs the main loop for the created <see cref="Toplevel"/>.
-	/// </summary>
-	/// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
-	public static void RunLoop (RunState state)
-	{
-		if (state == null) {
-			throw new ArgumentNullException (nameof (state));
-		}
-		if (state.Toplevel == null) {
-			throw new ObjectDisposedException ("state");
-		}
-
-		bool firstIteration = true;
-		for (state.Toplevel.Running = true; state.Toplevel.Running;) {
-			MainLoop.Running = true;
-			if (EndAfterFirstIteration && !firstIteration) {
-				return;
-			}
-			RunIteration (ref state, ref firstIteration);
-		}
-		MainLoop.Running = false;
-		// Run one last iteration to consume any outstanding input events from Driver
-		// This is important for remaining OnKeyUp events.
-		RunIteration (ref state, ref firstIteration);
-	}
-
-	/// <summary>
-	/// Run one application iteration.
-	/// </summary>
-	/// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
-	/// <param name="firstIteration">Set to <see langword="true"/> if this is the first run loop iteration. Upon return,
-	/// it will be set to <see langword="false"/> if at least one iteration happened.</param>
-	public static void RunIteration (ref RunState state, ref bool firstIteration)
-	{
-		if (MainLoop.Running && MainLoop.EventsPending ()) {
-			// Notify Toplevel it's ready
-			if (firstIteration) {
-				state.Toplevel.OnReady ();
-			}
-
-			MainLoop.RunIteration ();
-			Iteration?.Invoke (null, new IterationEventArgs ());
-			EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-			if (state.Toplevel != Current) {
-				OverlappedTop?.OnDeactivate (state.Toplevel);
-				state.Toplevel = Current;
-				OverlappedTop?.OnActivate (state.Toplevel);
-				Top.SetSubViewNeedsDisplay ();
-				Refresh ();
-			}
-		}
-
-		firstIteration = false;
-
-		if (state.Toplevel != Top &&
-		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
-			state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
-			Top.Draw ();
-			foreach (var top in _topLevels.Reverse ()) {
-				if (top != Top && top != state.Toplevel) {
-					top.SetNeedsDisplay ();
-					top.SetSubViewNeedsDisplay ();
-					top.Draw ();
-				}
-			}
-		}
-		if (_topLevels.Count == 1 && state.Toplevel == Top
-					&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
-					&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
-
-			state.Toplevel.Clear (Driver.Bounds);
-		}
-
-		if (state.Toplevel.NeedsDisplay ||
-		state.Toplevel.SubViewNeedsDisplay ||
-		state.Toplevel.LayoutNeeded ||
-		OverlappedChildNeedsDisplay ()) {
-			state.Toplevel.Draw ();
-			state.Toplevel.PositionCursor ();
-			Driver.Refresh ();
-		} else {
-			Driver.UpdateCursor ();
-		}
-		if (state.Toplevel != Top &&
-		!state.Toplevel.Modal &&
-		(Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) {
-			Top.Draw ();
-		}
-	}
-
-	/// <summary>
-	/// Stops running the most recent <see cref="Toplevel"/> or the <paramref name="top"/> if provided.
-	/// </summary>
-	/// <param name="top">The <see cref="Toplevel"/> to stop.</param>
-	/// <remarks>
-	///  <para>
-	///  This will cause <see cref="Application.Run(Func{Exception, bool})"/> to return.
-	///  </para>
-	///  <para>
-	///   Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/> property 
-	///   on the currently running <see cref="Toplevel"/> to false.
-	///  </para>
-	/// </remarks>
-	public static void RequestStop (Toplevel top = null)
-	{
-		if (OverlappedTop == null || top == null || OverlappedTop == null && top != null) {
-			top = Current;
-		}
-
-		if (OverlappedTop != null && top.IsOverlappedContainer && top?.Running == true
-		&& (Current?.Modal == false || Current?.Modal == true && Current?.Running == false)) {
-
-			OverlappedTop.RequestStop ();
-		} else if (OverlappedTop != null && top != Current && Current?.Running == true && Current?.Modal == true
-			&& top.Modal && top.Running) {
-
-			var ev = new ToplevelClosingEventArgs (Current);
-			Current.OnClosing (ev);
-			if (ev.Cancel) {
-				return;
-			}
-			ev = new ToplevelClosingEventArgs (top);
-			top.OnClosing (ev);
-			if (ev.Cancel) {
-				return;
-			}
-			Current.Running = false;
-			OnNotifyStopRunState (Current);
-			top.Running = false;
-			OnNotifyStopRunState (top);
-		} else if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
-			&& Current?.Running == true && !top.Running
-			|| OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == false
-			&& Current?.Running == false && !top.Running && _topLevels.ToArray () [1].Running) {
-
-			MoveCurrent (top);
-		} else if (OverlappedTop != null && Current != top && Current?.Running == true && !top.Running
-			&& Current?.Modal == true && top.Modal) {
-			// The Current and the top are both modal so needed to set the Current.Running to false too.
-			Current.Running = false;
-			OnNotifyStopRunState (Current);
-		} else if (OverlappedTop != null && Current == top && OverlappedTop?.Running == true && Current?.Running == true && top.Running
-			&& Current?.Modal == true && top.Modal) {
-			// The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
-			// both are the same, so needed to set the Current.Running to false too.
-			Current.Running = false;
-			OnNotifyStopRunState (Current);
-		} else {
-			Toplevel currentTop;
-			if (top == Current || Current?.Modal == true && !top.Modal) {
-				currentTop = Current;
-			} else {
-				currentTop = top;
-			}
-			if (!currentTop.Running) {
-				return;
-			}
-			var ev = new ToplevelClosingEventArgs (currentTop);
-			currentTop.OnClosing (ev);
-			if (ev.Cancel) {
-				return;
-			}
-			currentTop.Running = false;
-			OnNotifyStopRunState (currentTop);
-		}
-	}
-
-	static void OnNotifyStopRunState (Toplevel top)
-	{
-		if (EndAfterFirstIteration) {
-			NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top));
-		}
-	}
-
-	/// <summary>
-	/// Building block API: completes the execution of a <see cref="Toplevel"/> that was started with <see cref="Begin(Toplevel)"/> .
-	/// </summary>
-	/// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
-	public static void End (RunState runState)
-	{
-		if (runState == null) {
-			throw new ArgumentNullException (nameof (runState));
-		}
-
-		if (OverlappedTop != null) {
-			OverlappedTop.OnChildUnloaded (runState.Toplevel);
-		} else {
-			runState.Toplevel.OnUnloaded ();
-		}
-
-		// End the RunState.Toplevel 
-		// First, take it off the Toplevel Stack
-		if (_topLevels.Count > 0) {
-			if (_topLevels.Peek () != runState.Toplevel) {
-				// If there the top of the stack is not the RunState.Toplevel then
-				// this call to End is not balanced with the call to Begin that started the RunState
-				throw new ArgumentException ("End must be balanced with calls to Begin");
-			}
-			_topLevels.Pop ();
-		}
-
-		// Notify that it is closing
-		runState.Toplevel?.OnClosed (runState.Toplevel);
-
-		// If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel 
-		// is a child of MidTop and we should notify the OverlappedTop that it is closing
-		if (OverlappedTop != null && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop) {
-			OverlappedTop.OnChildClosed (runState.Toplevel);
-		}
-
-		// Set Current and Top to the next TopLevel on the stack
-		if (_topLevels.Count == 0) {
-			Current = null;
-		} else {
-			Current = _topLevels.Peek ();
-			if (_topLevels.Count == 1 && Current == OverlappedTop) {
-				OverlappedTop.OnAllChildClosed ();
-			} else {
-				SetCurrentOverlappedAsTop ();
-				runState.Toplevel.OnLeave (Current);
-				Current.OnEnter (runState.Toplevel);
-			}
-			Refresh ();
-		}
-
-		runState.Toplevel?.Dispose ();
-		runState.Toplevel = null;
-		runState.Dispose ();
-	}
-	#endregion Run (Begin, Run, End)
-
-	#region Toplevel handling
-	/// <summary>
-	/// Holds the stack of TopLevel views.
-	/// </summary>
-	// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
-	// about TopLevels that are just a SubView of another View?
-	internal static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
-
-	/// <summary>
-	/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
-	/// </summary>
-	/// <value>The top.</value>
-	public static Toplevel Top { get; private set; }
-
-	/// <summary>
-	/// The current <see cref="Toplevel"/> object. This is updated when <see cref="Application.Run(Func{Exception, bool})"/> 
-	/// enters and leaves to point to the current <see cref="Toplevel"/> .
-	/// </summary>
-	/// <value>The current.</value>
-	public static Toplevel Current { get; private set; }
-
-	static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel)
-	{
-		if (!Toplevel.Running || Toplevel == Current && Toplevel.Visible || OverlappedTop == null || _topLevels.Peek ().Modal) {
-			return;
-		}
-
-		foreach (var top in _topLevels.Reverse ()) {
-			if (top.Modal && top != Current) {
-				MoveCurrent (top);
-				return;
-			}
-		}
-		if (!Toplevel.Visible && Toplevel == Current) {
-			OverlappedMoveNext ();
-		}
-	}
-
-	static View FindDeepestTop (Toplevel start, int x, int y, out int resx, out int resy)
-	{
-		var startFrame = start.Frame;
-
-		if (!startFrame.Contains (x, y)) {
-			resx = 0;
-			resy = 0;
-			return null;
-		}
-
-		if (_topLevels != null) {
-			int count = _topLevels.Count;
-			if (count > 0) {
-				int rx = x - startFrame.X;
-				int ry = y - startFrame.Y;
-				foreach (var t in _topLevels) {
-					if (t != Current) {
-						if (t != start && t.Visible && t.Frame.Contains (rx, ry)) {
-							start = t;
-							break;
-						}
-					}
-				}
-			}
-		}
-		resx = x - startFrame.X;
-		resy = y - startFrame.Y;
-		return start;
-	}
-
-	static View FindTopFromView (View view)
-	{
-		var top = view?.SuperView != null && view?.SuperView != Top
-			? view.SuperView : view;
-
-		while (top?.SuperView != null && top?.SuperView != Top) {
-			top = top.SuperView;
-		}
-		return top;
-	}
-
-	// Only return true if the Current has changed.
-	static bool MoveCurrent (Toplevel top)
-	{
-		// The Current is modal and the top is not modal Toplevel then
-		// the Current must be moved above the first not modal Toplevel.
-		if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Modal == true && !_topLevels.Peek ().Modal) {
-			lock (_topLevels) {
-				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
-			}
-			int index = 0;
-			var savedToplevels = _topLevels.ToArray ();
-			foreach (var t in savedToplevels) {
-				if (!t.Modal && t != Current && t != top && t != savedToplevels [index]) {
-					lock (_topLevels) {
-						_topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
-					}
-				}
-				index++;
-			}
-			return false;
-		}
-		// The Current and the top are both not running Toplevel then
-		// the top must be moved above the first not running Toplevel.
-		if (OverlappedTop != null && top != OverlappedTop && top != Current && Current?.Running == false && !top.Running) {
-			lock (_topLevels) {
-				_topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
-			}
-			int index = 0;
-			foreach (var t in _topLevels.ToArray ()) {
-				if (!t.Running && t != Current && index > 0) {
-					lock (_topLevels) {
-						_topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
-					}
-				}
-				index++;
-			}
-			return false;
-		}
-		if (OverlappedTop != null && top?.Modal == true && _topLevels.Peek () != top
-		|| OverlappedTop != null && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop
-		|| OverlappedTop != null && Current?.Modal == false && top != Current
-		|| OverlappedTop != null && Current?.Modal == true && top == OverlappedTop) {
-			lock (_topLevels) {
-				_topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
-				Current = top;
-			}
-		}
-		return true;
-	}
-
-	/// <summary>
-	/// Invoked when the terminal's size changed. The new size of the terminal is provided.
-	/// </summary>
-	/// <remarks>
-	/// Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/>
-	/// to prevent <see cref="Application"/> from changing it's size to match the new terminal size.
-	/// </remarks>
-	public static event EventHandler<SizeChangedEventArgs> SizeChanging;
-
-	/// <summary>
-	/// Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and
-	/// fires the <see cref="SizeChanging"/> event.
-	/// </summary>
-	/// <param name="args">The new size.</param>
-	/// <returns><see lanword="true"/>if the size was changed.</returns>
-	public static bool OnSizeChanging (SizeChangedEventArgs args)
-	{
-		SizeChanging?.Invoke (null, args);
-		if (args.Cancel) {
-			return false;
-		}
-
-		foreach (var t in _topLevels) {
-			t.SetRelativeLayout (new Rect (0, 0, args.Size.Width, args.Size.Height));
-			t.LayoutSubviews ();
-			t.PositionToplevels ();
-			t.OnSizeChanging (new SizeChangedEventArgs (args.Size));
-		}
-		Refresh ();
-		return true;
-	}
-	#endregion Toplevel handling
-
-	#region Mouse handling
-	/// <summary>
-	/// Disable or enable the mouse. The mouse is enabled by default.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static bool IsMouseDisabled { get; set; }
-
-	/// <summary>
-	/// The current <see cref="View"/> object that wants continuous mouse button pressed events.
-	/// </summary>
-	public static View WantContinuousButtonPressedView { get; private set; }
-
-	/// <summary>
-	/// Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be
-	/// routed to this view until the view calls <see cref="UngrabMouse"/> or the mouse is released.
-	/// </summary>
-	public static View MouseGrabView { get; private set; }
-
-	/// <summary>
-	/// Invoked when a view wants to grab the mouse; can be canceled.
-	/// </summary>
-	public static event EventHandler<GrabMouseEventArgs> GrabbingMouse;
-
-	/// <summary>
-	/// Invoked when a view wants un-grab the mouse; can be canceled.
-	/// </summary>
-	public static event EventHandler<GrabMouseEventArgs> UnGrabbingMouse;
-
-	/// <summary>
-	/// Invoked after a view has grabbed the mouse.
-	/// </summary>
-	public static event EventHandler<ViewEventArgs> GrabbedMouse;
-
-	/// <summary>
-	/// Invoked after a view has un-grabbed the mouse.
-	/// </summary>
-	public static event EventHandler<ViewEventArgs> UnGrabbedMouse;
-
-	/// <summary>
-	/// Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/> is called.
-	/// </summary>
-	/// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
-	public static void GrabMouse (View view)
-	{
-		if (view == null) {
-			return;
-		}
-		if (!OnGrabbingMouse (view)) {
-			OnGrabbedMouse (view);
-			MouseGrabView = view;
-		}
-	}
-
-	/// <summary>
-	/// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
-	/// </summary>
-	public static void UngrabMouse ()
-	{
-		if (MouseGrabView == null) {
-			return;
-		}
-		if (!OnUnGrabbingMouse (MouseGrabView)) {
-			OnUnGrabbedMouse (MouseGrabView);
-			MouseGrabView = null;
-		}
-	}
-
-	static bool OnGrabbingMouse (View view)
-	{
-		if (view == null) {
-			return false;
-		}
-		var evArgs = new GrabMouseEventArgs (view);
-		GrabbingMouse?.Invoke (view, evArgs);
-		return evArgs.Cancel;
-	}
-
-	static bool OnUnGrabbingMouse (View view)
-	{
-		if (view == null) {
-			return false;
-		}
-		var evArgs = new GrabMouseEventArgs (view);
-		UnGrabbingMouse?.Invoke (view, evArgs);
-		return evArgs.Cancel;
-	}
-
-	static void OnGrabbedMouse (View view)
-	{
-		if (view == null) {
-			return;
-		}
-		GrabbedMouse?.Invoke (view, new ViewEventArgs (view));
-	}
-
-	static void OnUnGrabbedMouse (View view)
-	{
-		if (view == null) {
-			return;
-		}
-		UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view));
-	}
-
-	// Used by OnMouseEvent to track the last view that was clicked on.
-	internal static View _mouseEnteredView;
-
-	/// <summary>
-	/// Event fired when a mouse move or click occurs. Coordinates are screen relative.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Use this event to receive mouse events in screen coordinates. Use <see cref="Responder.MouseEvent"/> to receive
-	/// mouse events relative to a <see cref="View"/>'s bounds.
-	/// </para>
-	/// <para>
-	/// The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.
-	/// </para>
-	/// </remarks>
-	public static event EventHandler<MouseEventEventArgs> MouseEvent;
-
-	/// <summary>
-	/// Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.
-	/// </summary>
-	/// <remarks>
-	/// This method can be used to simulate a mouse event, e.g. in unit tests.
-	/// </remarks>
-	/// <param name="a">The mouse event with coordinates relative to the screen.</param>
-	public static void OnMouseEvent (MouseEventEventArgs a)
-	{
-		static bool OutsideRect (Point p, Rect r) => p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom;
-
-		if (IsMouseDisabled) {
-			return;
-		}
-
-		var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int screenX, out int screenY);
-
-		if (view != null && view.WantContinuousButtonPressed) {
-			WantContinuousButtonPressedView = view;
-		} else {
-			WantContinuousButtonPressedView = null;
-		}
-		if (view != null) {
-			a.MouseEvent.View = view;
-		}
-		MouseEvent?.Invoke (null, new MouseEventEventArgs (a.MouseEvent));
-
-		if (a.MouseEvent.Handled) {
-			return;
-		}
-
-		if (MouseGrabView != null) {
-			// If the mouse is grabbed, send the event to the view that grabbed it.
-			// The coordinates are relative to the Bounds of the view that grabbed the mouse.
-			var newxy = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y);
-			var nme = new MouseEvent () {
-				X = newxy.X,
-				Y = newxy.Y,
-				Flags = a.MouseEvent.Flags,
-				OfX = a.MouseEvent.X - newxy.X,
-				OfY = a.MouseEvent.Y - newxy.Y,
-				View = view
-			};
-			if (OutsideRect (new Point (nme.X, nme.Y), MouseGrabView.Bounds)) {
-				// The mouse has moved outside the bounds of the the view that
-				// grabbed the mouse, so we tell the view that last got 
-				// OnMouseEnter the mouse is leaving
-				// BUGBUG: That sentence makes no sense. Either I'm missing something
-				// or this logic is flawed.
-				_mouseEnteredView?.OnMouseLeave (a.MouseEvent);
-			}
-			//System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-			if (MouseGrabView?.OnMouseEvent (nme) == true) {
-				return;
-			}
-		}
-
-		if ((view == null || view == OverlappedTop) &&
-		Current is { Modal: false } && OverlappedTop != null &&
-		a.MouseEvent.Flags != MouseFlags.ReportMousePosition &&
-		a.MouseEvent.Flags != 0) {
-
-			var top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _);
-			view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY);
-
-			if (view != null && view != OverlappedTop && top != Current) {
-				MoveCurrent ((Toplevel)top);
-			}
-		}
-
-		bool AdornmentHandledMouseEvent(Adornment frame)
-		{
-			if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false) {
-				var boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
-				var me = new MouseEvent () {
-					X = boundsPoint.X,
-					Y = boundsPoint.Y,
-					Flags = a.MouseEvent.Flags,
-					OfX = boundsPoint.X,
-					OfY = boundsPoint.Y,
-					View = frame
-				};
-				frame.OnMouseEvent (me);
-				return true;
-			}
-			return false;
-		}
-
-		if (view != null) {
-			// Work inside-out (Padding, Border, Margin)
-			// TODO: Debate whether inside-out or outside-in is the right strategy
-			if (AdornmentHandledMouseEvent(view?.Padding)) {
-				return;
-			}
-			if (AdornmentHandledMouseEvent(view?.Border)) {
-				if (view is Toplevel) {
-					// TODO: This is a temporary hack to work around the fact that 
-					// drag handling is handled in Toplevel (See Issue #2537)
-
-					var me = new MouseEvent () {
-						X = screenX,
-						Y = screenY,
-						Flags = a.MouseEvent.Flags,
-						OfX = screenX,
-						OfY = screenY,
-						View = view
-					};
-
-					if (_mouseEnteredView == null) {
-						_mouseEnteredView = view;
-						view.OnMouseEnter (me);
-					} else if (_mouseEnteredView != view) {
-						_mouseEnteredView.OnMouseLeave (me);
-						view.OnMouseEnter (me);
-						_mouseEnteredView = view;
-					}
-
-					if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition) {
-						return;
-					}
-
-					WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
-
-					if (view.OnMouseEvent (me)) {
-						// Should we bubble up the event, if it is not handled?
-						//return;
-					}
-
-					BringOverlappedTopToFront ();
-				}
-				return;
-			}
-
-			if (AdornmentHandledMouseEvent(view?.Margin)) {
-				return;
-			}
-
-			var bounds = view.BoundsToScreen (view.Bounds);
-			if (bounds.Contains (a.MouseEvent.X, a.MouseEvent.Y)) {
-				var boundsPoint = view.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
-				var me = new MouseEvent () {
-					X = boundsPoint.X,
-					Y = boundsPoint.Y,
-					Flags = a.MouseEvent.Flags,
-					OfX = boundsPoint.X,
-					OfY = boundsPoint.Y,
-					View = view
-				};
-
-				if (_mouseEnteredView == null) {
-					_mouseEnteredView = view;
-					view.OnMouseEnter (me);
-				} else if (_mouseEnteredView != view) {
-					_mouseEnteredView.OnMouseLeave (me);
-					view.OnMouseEnter (me);
-					_mouseEnteredView = view;
-				}
-
-				if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition) {
-					return;
-				}
-
-				WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
-
-				if (view.OnMouseEvent (me)) {
-					// Should we bubble up the event, if it is not handled?
-					//return;
-				}
-
-				BringOverlappedTopToFront ();
-			}
-		}
-	}
-	#endregion Mouse handling
-
-	#region Keyboard handling
-	static Key _alternateForwardKey = Key.Empty; // Defined in config.json
-
-	/// <summary>
-	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	[JsonConverter (typeof (KeyJsonConverter))]
-	public static Key AlternateForwardKey {
-		get => _alternateForwardKey;
-		set {
-			if (_alternateForwardKey != value) {
-				var oldKey = _alternateForwardKey;
-				_alternateForwardKey = value;
-				OnAlternateForwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
-			}
-		}
-	}
-
-	static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
-	{
-		foreach (var top in _topLevels.ToArray ()) {
-			top.OnAlternateForwardKeyChanged (e);
-		}
-	}
-
-	static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
-
-	/// <summary>
-	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	[JsonConverter (typeof (KeyJsonConverter))]
-	public static Key AlternateBackwardKey {
-		get => _alternateBackwardKey;
-		set {
-			if (_alternateBackwardKey != value) {
-				var oldKey = _alternateBackwardKey;
-				_alternateBackwardKey = value;
-				OnAlternateBackwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
-			}
-		}
-	}
-
-	static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
-	{
-		foreach (var top in _topLevels.ToArray ()) {
-			top.OnAlternateBackwardKeyChanged (oldKey);
-		}
-	}
-
-	static Key _quitKey = Key.Empty; // Defined in config.json
-
-	/// <summary>
-	/// Gets or sets the key to quit the application.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	[JsonConverter (typeof (KeyJsonConverter))]
-	public static Key QuitKey {
-		get => _quitKey;
-		set {
-			if (_quitKey != value) {
-				var oldKey = _quitKey;
-				_quitKey = value;
-				OnQuitKeyChanged (new KeyChangedEventArgs (oldKey, value));
-			}
-		}
-	}
-
-	static void OnQuitKeyChanged (KeyChangedEventArgs e)
-	{
-		// Duplicate the list so if it changes during enumeration we're safe
-		foreach (var top in _topLevels.ToArray ()) {
-			top.OnQuitKeyChanged (e);
-		}
-	}
-
-	/// <summary>
-	/// Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>. 
-	/// <para>
-	/// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and
-	/// to prevent additional processing.
-	/// </para>
-	/// </summary>
-	/// <remarks>
-	/// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses)
-	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
-	/// <para>
-	/// Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.
-	/// </para>
-	/// </remarks>
-	public static event EventHandler<Key> KeyDown;
-
-	/// <summary>
-	/// Called by the <see cref="ConsoleDriver"/> when the user presses a key.
-	/// Fires the <see cref="KeyDown"/> event
-	/// then calls <see cref="View.NewKeyDownEvent"/> on all top level views.
-	/// Called after <see cref="OnKeyDown"/> and before <see cref="OnKeyUp"/>.
-	/// </summary>
-	/// <remarks>
-	/// Can be used to simulate key press events.
-	/// </remarks>
-	/// <param name="keyEvent"></param>
-	/// <returns><see langword="true"/> if the key was handled.</returns>
-	public static bool OnKeyDown (Key keyEvent)
-	{
-		if (!_initialized) {
-			return true;
-		}
-
-		KeyDown?.Invoke (null, keyEvent);
-		if (keyEvent.Handled) {
-			return true;
-		}
-
-		foreach (var topLevel in _topLevels.ToList ()) {
-			if (topLevel.NewKeyDownEvent (keyEvent)) {
-				return true;
-			}
-			if (topLevel.Modal) {
-				break;
-			}
-		}
-
-		// Invoke any Global KeyBindings
-		foreach (var topLevel in _topLevels.ToList ()) {
-			foreach (var view in topLevel.Subviews.Where (v => v.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out var _))) {
-				if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out var _)) {
-					keyEvent.Scope = KeyBindingScope.Application;
-					bool? handled = view.OnInvokingKeyBindings (keyEvent);
-					if (handled != null && (bool)handled) {
-						return true;
-					}
-				}
-			}
-		}
-
-		return false;
-	}
-
-	/// <summary>
-	/// Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
-	/// <para>
-	/// Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and
-	/// to prevent additional processing.
-	/// </para>
-	/// </summary>
-	/// <remarks>
-	/// All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses)
-	/// do not support firing the <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
-	/// <para>
-	/// Fired after <see cref="KeyDown"/>.
-	/// </para>
-	/// </remarks>
-	public static event EventHandler<Key> KeyUp;
-
-	/// <summary>
-	/// Called by the <see cref="ConsoleDriver"/> when the user releases a key.
-	/// Fires the <see cref="KeyUp"/> event
-	/// then calls <see cref="View.NewKeyUpEvent"/> on all top level views.
-	/// Called after <see cref="OnKeyDown"/>.
-	/// </summary>
-	/// <remarks>
-	/// Can be used to simulate key press events.
-	/// </remarks>
-	/// <param name="a"></param>
-	/// <returns><see langword="true"/> if the key was handled.</returns>
-	public static bool OnKeyUp (Key a)
-	{
-		if (!_initialized) {
-			return true;
-		}
-
-		KeyUp?.Invoke (null, a);
-		if (a.Handled) {
-			return true;
-		}
-		foreach (var topLevel in _topLevels.ToList ()) {
-			if (topLevel.NewKeyUpEvent (a)) {
-				return true;
-			}
-			if (topLevel.Modal) {
-				break;
-			}
-		}
-		return false;
-	}
-	#endregion Keyboard handling
+        }
+    }
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    public static object AddTimeout (TimeSpan time, Func<bool> callback) { return MainLoop?.AddTimeout (time, callback); }
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
+    /// Returns
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.
+    public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; }
+
+    /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
+    /// <param name="action">the action to be invoked on the main processing thread.</param>
+    public static void Invoke (Action action)
+    {
+        MainLoop?.AddIdle (
+                           () =>
+                           {
+                               action ();
+
+                               return false;
+                           }
+                          );
+    }
+
+    // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
+    // is ProgressBarStyles and it's not clear it needs to.
+    /// <summary>Wakes up the running application that might be waiting on input.</summary>
+    public static void Wakeup () { MainLoop?.Wakeup (); }
+
+    /// <summary>Triggers a refresh of the entire display.</summary>
+    public static void Refresh ()
+    {
+        // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
+        Driver.ClearContents ();
+        View last = null;
+
+        foreach (Toplevel v in _topLevels.Reverse ())
+        {
+            if (v.Visible)
+            {
+                v.SetNeedsDisplay ();
+                v.SetSubViewNeedsDisplay ();
+                v.Draw ();
+            }
+
+            last = v;
+        }
+
+        last?.PositionCursor ();
+        Driver.Refresh ();
+    }
+
+    /// <summary>This event is raised on each iteration of the main loop.</summary>
+    /// <remarks>See also <see cref="Timeout"/></remarks>
+    public static event EventHandler<IterationEventArgs> Iteration;
+
+    /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
+    /// <value>The main loop.</value>
+    internal static MainLoop MainLoop { get; private set; }
+
+    /// <summary>
+    ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
+    ///     cause the application to continue running until Application.RequestStop () is called.
+    /// </summary>
+    public static bool EndAfterFirstIteration { get; set; }
+
+    //
+    // provides the sync context set while executing code in Terminal.Gui, to let
+    // users use async/await on their code
+    //
+    private class MainLoopSyncContext : SynchronizationContext
+    {
+        public override SynchronizationContext CreateCopy () { return new MainLoopSyncContext (); }
+
+        public override void Post (SendOrPostCallback d, object state)
+        {
+            MainLoop.AddIdle (
+                              () =>
+                              {
+                                  d (state);
+
+                                  return false;
+                              }
+                             );
+        }
+
+        //_mainLoop.Driver.Wakeup ();
+        public override void Send (SendOrPostCallback d, object state)
+        {
+            if (Thread.CurrentThread.ManagedThreadId == _mainThreadId)
+            {
+                d (state);
+            }
+            else
+            {
+                var wasExecuted = false;
+
+                Invoke (
+                        () =>
+                        {
+                            d (state);
+                            wasExecuted = true;
+                        }
+                       );
+
+                while (!wasExecuted)
+                {
+                    Thread.Sleep (15);
+                }
+            }
+        }
+    }
+
+    /// <summary>Building block API: Runs the main loop for the created <see cref="Toplevel"/>.</summary>
+    /// <param name="state">The state returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    public static void RunLoop (RunState state)
+    {
+        if (state == null)
+        {
+            throw new ArgumentNullException (nameof (state));
+        }
+
+        if (state.Toplevel == null)
+        {
+            throw new ObjectDisposedException ("state");
+        }
+
+        var firstIteration = true;
+
+        for (state.Toplevel.Running = true; state.Toplevel.Running;)
+        {
+            MainLoop.Running = true;
+
+            if (EndAfterFirstIteration && !firstIteration)
+            {
+                return;
+            }
+
+            RunIteration (ref state, ref firstIteration);
+        }
+
+        MainLoop.Running = false;
+
+        // Run one last iteration to consume any outstanding input events from Driver
+        // This is important for remaining OnKeyUp events.
+        RunIteration (ref state, ref firstIteration);
+    }
+
+    /// <summary>Run one application iteration.</summary>
+    /// <param name="state">The state returned by <see cref="Begin(Toplevel)"/>.</param>
+    /// <param name="firstIteration">
+    ///     Set to <see langword="true"/> if this is the first run loop iteration. Upon return, it
+    ///     will be set to <see langword="false"/> if at least one iteration happened.
+    /// </param>
+    public static void RunIteration (ref RunState state, ref bool firstIteration)
+    {
+        if (MainLoop.Running && MainLoop.EventsPending ())
+        {
+            // Notify Toplevel it's ready
+            if (firstIteration)
+            {
+                state.Toplevel.OnReady ();
+            }
+
+            MainLoop.RunIteration ();
+            Iteration?.Invoke (null, new IterationEventArgs ());
+            EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
+
+            if (state.Toplevel != Current)
+            {
+                OverlappedTop?.OnDeactivate (state.Toplevel);
+                state.Toplevel = Current;
+                OverlappedTop?.OnActivate (state.Toplevel);
+                Top.SetSubViewNeedsDisplay ();
+                Refresh ();
+            }
+        }
+
+        firstIteration = false;
+
+        if (state.Toplevel != Top && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
+        {
+            state.Toplevel.SetNeedsDisplay (state.Toplevel.Frame);
+            Top.Draw ();
+
+            foreach (Toplevel top in _topLevels.Reverse ())
+            {
+                if (top != Top && top != state.Toplevel)
+                {
+                    top.SetNeedsDisplay ();
+                    top.SetSubViewNeedsDisplay ();
+                    top.Draw ();
+                }
+            }
+        }
+
+        if (_topLevels.Count == 1
+            && state.Toplevel == Top
+            && (Driver.Cols != state.Toplevel.Frame.Width
+                || Driver.Rows != state.Toplevel.Frame.Height)
+            && (state.Toplevel.NeedsDisplay
+                || state.Toplevel.SubViewNeedsDisplay
+                || state.Toplevel.LayoutNeeded))
+        {
+            state.Toplevel.Clear (Driver.Bounds);
+        }
+
+        if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ())
+        {
+            state.Toplevel.Draw ();
+            state.Toplevel.PositionCursor ();
+            Driver.Refresh ();
+        }
+        else
+        {
+            Driver.UpdateCursor ();
+        }
+
+        if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
+        {
+            Top.Draw ();
+        }
+    }
+
+    /// <summary>Stops running the most recent <see cref="Toplevel"/> or the <paramref name="top"/> if provided.</summary>
+    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Application.Run(Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         Calling <see cref="Application.RequestStop"/> is equivalent to setting the <see cref="Toplevel.Running"/>
+    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///     </para>
+    /// </remarks>
+    public static void RequestStop (Toplevel top = null)
+    {
+        if (OverlappedTop == null || top == null || (OverlappedTop == null && top != null))
+        {
+            top = Current;
+        }
+
+        if (OverlappedTop != null
+            && top.IsOverlappedContainer
+            && top?.Running == true
+            && (Current?.Modal == false || (Current?.Modal == true && Current?.Running == false)))
+        {
+            OverlappedTop.RequestStop ();
+        }
+        else if (OverlappedTop != null
+                 && top != Current
+                 && Current?.Running == true
+                 && Current?.Modal == true
+                 && top.Modal
+                 && top.Running)
+        {
+            var ev = new ToplevelClosingEventArgs (Current);
+            Current.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            ev = new ToplevelClosingEventArgs (top);
+            top.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+            top.Running = false;
+            OnNotifyStopRunState (top);
+        }
+        else if ((OverlappedTop != null
+                  && top != OverlappedTop
+                  && top != Current
+                  && Current?.Modal == false
+                  && Current?.Running == true
+                  && !top.Running)
+                 || (OverlappedTop != null
+                     && top != OverlappedTop
+                     && top != Current
+                     && Current?.Modal == false
+                     && Current?.Running == false
+                     && !top.Running
+                     && _topLevels.ToArray () [1].Running))
+        {
+            MoveCurrent (top);
+        }
+        else if (OverlappedTop != null
+                 && Current != top
+                 && Current?.Running == true
+                 && !top.Running
+                 && Current?.Modal == true
+                 && top.Modal)
+        {
+            // The Current and the top are both modal so needed to set the Current.Running to false too.
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+        }
+        else if (OverlappedTop != null
+                 && Current == top
+                 && OverlappedTop?.Running == true
+                 && Current?.Running == true
+                 && top.Running
+                 && Current?.Modal == true
+                 && top.Modal)
+        {
+            // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
+            // both are the same, so needed to set the Current.Running to false too.
+            Current.Running = false;
+            OnNotifyStopRunState (Current);
+        }
+        else
+        {
+            Toplevel currentTop;
+
+            if (top == Current || (Current?.Modal == true && !top.Modal))
+            {
+                currentTop = Current;
+            }
+            else
+            {
+                currentTop = top;
+            }
+
+            if (!currentTop.Running)
+            {
+                return;
+            }
+
+            var ev = new ToplevelClosingEventArgs (currentTop);
+            currentTop.OnClosing (ev);
+
+            if (ev.Cancel)
+            {
+                return;
+            }
+
+            currentTop.Running = false;
+            OnNotifyStopRunState (currentTop);
+        }
+    }
+
+    private static void OnNotifyStopRunState (Toplevel top)
+    {
+        if (EndAfterFirstIteration)
+        {
+            NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top));
+        }
+    }
+
+    /// <summary>
+    ///     Building block API: completes the execution of a <see cref="Toplevel"/> that was started with
+    ///     <see cref="Begin(Toplevel)"/> .
+    /// </summary>
+    /// <param name="runState">The <see cref="RunState"/> returned by the <see cref="Begin(Toplevel)"/> method.</param>
+    public static void End (RunState runState)
+    {
+        if (runState == null)
+        {
+            throw new ArgumentNullException (nameof (runState));
+        }
+
+        if (OverlappedTop != null)
+        {
+            OverlappedTop.OnChildUnloaded (runState.Toplevel);
+        }
+        else
+        {
+            runState.Toplevel.OnUnloaded ();
+        }
+
+        // End the RunState.Toplevel 
+        // First, take it off the Toplevel Stack
+        if (_topLevels.Count > 0)
+        {
+            if (_topLevels.Peek () != runState.Toplevel)
+            {
+                // If there the top of the stack is not the RunState.Toplevel then
+                // this call to End is not balanced with the call to Begin that started the RunState
+                throw new ArgumentException ("End must be balanced with calls to Begin");
+            }
+
+            _topLevels.Pop ();
+        }
+
+        // Notify that it is closing
+        runState.Toplevel?.OnClosed (runState.Toplevel);
+
+        // If there is a OverlappedTop that is not the RunState.Toplevel then runstate.TopLevel 
+        // is a child of MidTop and we should notify the OverlappedTop that it is closing
+        if (OverlappedTop != null && !runState.Toplevel.Modal && runState.Toplevel != OverlappedTop)
+        {
+            OverlappedTop.OnChildClosed (runState.Toplevel);
+        }
+
+        // Set Current and Top to the next TopLevel on the stack
+        if (_topLevels.Count == 0)
+        {
+            Current = null;
+        }
+        else
+        {
+            Current = _topLevels.Peek ();
+
+            if (_topLevels.Count == 1 && Current == OverlappedTop)
+            {
+                OverlappedTop.OnAllChildClosed ();
+            }
+            else
+            {
+                SetCurrentOverlappedAsTop ();
+                runState.Toplevel.OnLeave (Current);
+                Current.OnEnter (runState.Toplevel);
+            }
+
+            Refresh ();
+        }
+
+        runState.Toplevel?.Dispose ();
+        runState.Toplevel = null;
+        runState.Dispose ();
+    }
+
+    #endregion Run (Begin, Run, End)
+
+    #region Toplevel handling
+
+    /// <summary>Holds the stack of TopLevel views.</summary>
+
+    // BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
+    // about TopLevels that are just a SubView of another View?
+    internal static readonly Stack<Toplevel> _topLevels = new ();
+
+    /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)</summary>
+    /// <value>The top.</value>
+    public static Toplevel Top { get; private set; }
+
+    /// <summary>
+    ///     The current <see cref="Toplevel"/> object. This is updated when
+    ///     <see cref="Application.Run(Func{Exception, bool})"/> enters and leaves to point to the current
+    ///     <see cref="Toplevel"/> .
+    /// </summary>
+    /// <value>The current.</value>
+    public static Toplevel Current { get; private set; }
+
+    private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel Toplevel)
+    {
+        if (!Toplevel.Running
+            || (Toplevel == Current && Toplevel.Visible)
+            || OverlappedTop == null
+            || _topLevels.Peek ().Modal)
+        {
+            return;
+        }
+
+        foreach (Toplevel top in _topLevels.Reverse ())
+        {
+            if (top.Modal && top != Current)
+            {
+                MoveCurrent (top);
+
+                return;
+            }
+        }
+
+        if (!Toplevel.Visible && Toplevel == Current)
+        {
+            OverlappedMoveNext ();
+        }
+    }
+
+    private static View FindDeepestTop (Toplevel start, int x, int y, out int resx, out int resy)
+    {
+        Rect startFrame = start.Frame;
+
+        if (!startFrame.Contains (x, y))
+        {
+            resx = 0;
+            resy = 0;
+
+            return null;
+        }
+
+        if (_topLevels != null)
+        {
+            int count = _topLevels.Count;
+
+            if (count > 0)
+            {
+                int rx = x - startFrame.X;
+                int ry = y - startFrame.Y;
+
+                foreach (Toplevel t in _topLevels)
+                {
+                    if (t != Current)
+                    {
+                        if (t != start && t.Visible && t.Frame.Contains (rx, ry))
+                        {
+                            start = t;
+
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        resx = x - startFrame.X;
+        resy = y - startFrame.Y;
+
+        return start;
+    }
+
+    private static View FindTopFromView (View view)
+    {
+        View top = view?.SuperView != null && view?.SuperView != Top
+                       ? view.SuperView
+                       : view;
+
+        while (top?.SuperView != null && top?.SuperView != Top)
+        {
+            top = top.SuperView;
+        }
+
+        return top;
+    }
+
+    // Only return true if the Current has changed.
+    private static bool MoveCurrent (Toplevel top)
+    {
+        // The Current is modal and the top is not modal Toplevel then
+        // the Current must be moved above the first not modal Toplevel.
+        if (OverlappedTop != null
+            && top != OverlappedTop
+            && top != Current
+            && Current?.Modal == true
+            && !_topLevels.Peek ().Modal)
+        {
+            lock (_topLevels)
+            {
+                _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
+            }
+
+            var index = 0;
+            Toplevel [] savedToplevels = _topLevels.ToArray ();
+
+            foreach (Toplevel t in savedToplevels)
+            {
+                if (!t.Modal && t != Current && t != top && t != savedToplevels [index])
+                {
+                    lock (_topLevels)
+                    {
+                        _topLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
+                    }
+                }
+
+                index++;
+            }
+
+            return false;
+        }
+
+        // The Current and the top are both not running Toplevel then
+        // the top must be moved above the first not running Toplevel.
+        if (OverlappedTop != null
+            && top != OverlappedTop
+            && top != Current
+            && Current?.Running == false
+            && !top.Running)
+        {
+            lock (_topLevels)
+            {
+                _topLevels.MoveTo (Current, 0, new ToplevelEqualityComparer ());
+            }
+
+            var index = 0;
+
+            foreach (Toplevel t in _topLevels.ToArray ())
+            {
+                if (!t.Running && t != Current && index > 0)
+                {
+                    lock (_topLevels)
+                    {
+                        _topLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
+                    }
+                }
+
+                index++;
+            }
+
+            return false;
+        }
+
+        if ((OverlappedTop != null && top?.Modal == true && _topLevels.Peek () != top)
+            || (OverlappedTop != null && Current != OverlappedTop && Current?.Modal == false && top == OverlappedTop)
+            || (OverlappedTop != null && Current?.Modal == false && top != Current)
+            || (OverlappedTop != null && Current?.Modal == true && top == OverlappedTop))
+        {
+            lock (_topLevels)
+            {
+                _topLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
+                Current = top;
+            }
+        }
+
+        return true;
+    }
+
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    /// <remarks>
+    ///     Event handlers can set <see cref="SizeChangedEventArgs.Cancel"/> to <see langword="true"/> to prevent
+    ///     <see cref="Application"/> from changing it's size to match the new terminal size.
+    /// </remarks>
+    public static event EventHandler<SizeChangedEventArgs> SizeChanging;
+
+    /// <summary>
+    ///     Called when the application's size changes. Sets the size of all <see cref="Toplevel"/>s and fires the
+    ///     <see cref="SizeChanging"/> event.
+    /// </summary>
+    /// <param name="args">The new size.</param>
+    /// <returns><see lanword="true"/>if the size was changed.</returns>
+    public static bool OnSizeChanging (SizeChangedEventArgs args)
+    {
+        SizeChanging?.Invoke (null, args);
+
+        if (args.Cancel)
+        {
+            return false;
+        }
+
+        foreach (Toplevel t in _topLevels)
+        {
+            t.SetRelativeLayout (new Rect (0, 0, args.Size.Width, args.Size.Height));
+            t.LayoutSubviews ();
+            t.PositionToplevels ();
+            t.OnSizeChanging (new SizeChangedEventArgs (args.Size));
+        }
+
+        Refresh ();
+
+        return true;
+    }
+
+    #endregion Toplevel handling
+
+    #region Mouse handling
+
+    /// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool IsMouseDisabled { get; set; }
+
+    /// <summary>The current <see cref="View"/> object that wants continuous mouse button pressed events.</summary>
+    public static View WantContinuousButtonPressedView { get; private set; }
+
+    /// <summary>
+    ///     Gets the view that grabbed the mouse (e.g. for dragging). When this is set, all mouse events will be routed to
+    ///     this view until the view calls <see cref="UngrabMouse"/> or the mouse is released.
+    /// </summary>
+    public static View MouseGrabView { get; private set; }
+
+    /// <summary>Invoked when a view wants to grab the mouse; can be canceled.</summary>
+    public static event EventHandler<GrabMouseEventArgs> GrabbingMouse;
+
+    /// <summary>Invoked when a view wants un-grab the mouse; can be canceled.</summary>
+    public static event EventHandler<GrabMouseEventArgs> UnGrabbingMouse;
+
+    /// <summary>Invoked after a view has grabbed the mouse.</summary>
+    public static event EventHandler<ViewEventArgs> GrabbedMouse;
+
+    /// <summary>Invoked after a view has un-grabbed the mouse.</summary>
+    public static event EventHandler<ViewEventArgs> UnGrabbedMouse;
+
+    /// <summary>
+    ///     Grabs the mouse, forcing all mouse events to be routed to the specified view until <see cref="UngrabMouse"/>
+    ///     is called.
+    /// </summary>
+    /// <param name="view">View that will receive all mouse events until <see cref="UngrabMouse"/> is invoked.</param>
+    public static void GrabMouse (View view)
+    {
+        if (view == null)
+        {
+            return;
+        }
+
+        if (!OnGrabbingMouse (view))
+        {
+            OnGrabbedMouse (view);
+            MouseGrabView = view;
+        }
+    }
+
+    /// <summary>Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.</summary>
+    public static void UngrabMouse ()
+    {
+        if (MouseGrabView == null)
+        {
+            return;
+        }
+
+        if (!OnUnGrabbingMouse (MouseGrabView))
+        {
+            OnUnGrabbedMouse (MouseGrabView);
+            MouseGrabView = null;
+        }
+    }
+
+    private static bool OnGrabbingMouse (View view)
+    {
+        if (view == null)
+        {
+            return false;
+        }
+
+        var evArgs = new GrabMouseEventArgs (view);
+        GrabbingMouse?.Invoke (view, evArgs);
+
+        return evArgs.Cancel;
+    }
+
+    private static bool OnUnGrabbingMouse (View view)
+    {
+        if (view == null)
+        {
+            return false;
+        }
+
+        var evArgs = new GrabMouseEventArgs (view);
+        UnGrabbingMouse?.Invoke (view, evArgs);
+
+        return evArgs.Cancel;
+    }
+
+    private static void OnGrabbedMouse (View view)
+    {
+        if (view == null)
+        {
+            return;
+        }
+
+        GrabbedMouse?.Invoke (view, new ViewEventArgs (view));
+    }
+
+    private static void OnUnGrabbedMouse (View view)
+    {
+        if (view == null)
+        {
+            return;
+        }
+
+        UnGrabbedMouse?.Invoke (view, new ViewEventArgs (view));
+    }
+
+    // Used by OnMouseEvent to track the last view that was clicked on.
+    internal static View _mouseEnteredView;
+
+    /// <summary>Event fired when a mouse move or click occurs. Coordinates are screen relative.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         Use this event to receive mouse events in screen coordinates. Use <see cref="Responder.MouseEvent"/> to
+    ///         receive mouse events relative to a <see cref="View"/>'s bounds.
+    ///     </para>
+    ///     <para>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
+    /// </remarks>
+    public static event EventHandler<MouseEventEventArgs> MouseEvent;
+
+    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
+    /// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
+    /// <param name="a">The mouse event with coordinates relative to the screen.</param>
+    public static void OnMouseEvent (MouseEventEventArgs a)
+    {
+        static bool OutsideRect (Point p, Rect r) { return p.X < 0 || p.X > r.Right || p.Y < 0 || p.Y > r.Bottom; }
+
+        if (IsMouseDisabled)
+        {
+            return;
+        }
+
+        var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y, out int screenX, out int screenY);
+
+        if (view != null && view.WantContinuousButtonPressed)
+        {
+            WantContinuousButtonPressedView = view;
+        }
+        else
+        {
+            WantContinuousButtonPressedView = null;
+        }
+
+        if (view != null)
+        {
+            a.MouseEvent.View = view;
+        }
+
+        MouseEvent?.Invoke (null, new MouseEventEventArgs (a.MouseEvent));
+
+        if (a.MouseEvent.Handled)
+        {
+            return;
+        }
+
+        if (MouseGrabView != null)
+        {
+            // If the mouse is grabbed, send the event to the view that grabbed it.
+            // The coordinates are relative to the Bounds of the view that grabbed the mouse.
+            Point newxy = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y);
+
+            var nme = new MouseEvent
+            {
+                X = newxy.X,
+                Y = newxy.Y,
+                Flags = a.MouseEvent.Flags,
+                OfX = a.MouseEvent.X - newxy.X,
+                OfY = a.MouseEvent.Y - newxy.Y,
+                View = view
+            };
+
+            if (OutsideRect (new Point (nme.X, nme.Y), MouseGrabView.Bounds))
+            {
+                // The mouse has moved outside the bounds of the the view that
+                // grabbed the mouse, so we tell the view that last got 
+                // OnMouseEnter the mouse is leaving
+                // BUGBUG: That sentence makes no sense. Either I'm missing something
+                // or this logic is flawed.
+                _mouseEnteredView?.OnMouseLeave (a.MouseEvent);
+            }
+
+            //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+            if (MouseGrabView?.OnMouseEvent (nme) == true)
+            {
+                return;
+            }
+        }
+
+        if ((view == null || view == OverlappedTop)
+            && Current is { Modal: false }
+            && OverlappedTop != null
+            && a.MouseEvent.Flags != MouseFlags.ReportMousePosition
+            && a.MouseEvent.Flags != 0)
+        {
+            View top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y, out _, out _);
+            view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y, out screenX, out screenY);
+
+            if (view != null && view != OverlappedTop && top != Current)
+            {
+                MoveCurrent ((Toplevel)top);
+            }
+        }
+
+        bool AdornmentHandledMouseEvent (Adornment frame)
+        {
+            if (frame?.Thickness.Contains (frame.FrameToScreen (), a.MouseEvent.X, a.MouseEvent.Y) ?? false)
+            {
+                Point boundsPoint = frame.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
+
+                var me = new MouseEvent
+                {
+                    X = boundsPoint.X,
+                    Y = boundsPoint.Y,
+                    Flags = a.MouseEvent.Flags,
+                    OfX = boundsPoint.X,
+                    OfY = boundsPoint.Y,
+                    View = frame
+                };
+                frame.OnMouseEvent (me);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        if (view != null)
+        {
+            // Work inside-out (Padding, Border, Margin)
+            // TODO: Debate whether inside-out or outside-in is the right strategy
+            if (AdornmentHandledMouseEvent (view?.Padding))
+            {
+                return;
+            }
+
+            if (AdornmentHandledMouseEvent (view?.Border))
+            {
+                if (view is Toplevel)
+                {
+                    // TODO: This is a temporary hack to work around the fact that 
+                    // drag handling is handled in Toplevel (See Issue #2537)
+
+                    var me = new MouseEvent
+                    {
+                        X = screenX,
+                        Y = screenY,
+                        Flags = a.MouseEvent.Flags,
+                        OfX = screenX,
+                        OfY = screenY,
+                        View = view
+                    };
+
+                    if (_mouseEnteredView == null)
+                    {
+                        _mouseEnteredView = view;
+                        view.OnMouseEnter (me);
+                    }
+                    else if (_mouseEnteredView != view)
+                    {
+                        _mouseEnteredView.OnMouseLeave (me);
+                        view.OnMouseEnter (me);
+                        _mouseEnteredView = view;
+                    }
+
+                    if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition)
+                    {
+                        return;
+                    }
+
+                    WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+
+                    if (view.OnMouseEvent (me))
+                    {
+                        // Should we bubble up the event, if it is not handled?
+                        //return;
+                    }
+
+                    BringOverlappedTopToFront ();
+                }
+
+                return;
+            }
+
+            if (AdornmentHandledMouseEvent (view?.Margin))
+            {
+                return;
+            }
+
+            Rect bounds = view.BoundsToScreen (view.Bounds);
+
+            if (bounds.Contains (a.MouseEvent.X, a.MouseEvent.Y))
+            {
+                Point boundsPoint = view.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
+
+                var me = new MouseEvent
+                {
+                    X = boundsPoint.X,
+                    Y = boundsPoint.Y,
+                    Flags = a.MouseEvent.Flags,
+                    OfX = boundsPoint.X,
+                    OfY = boundsPoint.Y,
+                    View = view
+                };
+
+                if (_mouseEnteredView == null)
+                {
+                    _mouseEnteredView = view;
+                    view.OnMouseEnter (me);
+                }
+                else if (_mouseEnteredView != view)
+                {
+                    _mouseEnteredView.OnMouseLeave (me);
+                    view.OnMouseEnter (me);
+                    _mouseEnteredView = view;
+                }
+
+                if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition)
+                {
+                    return;
+                }
+
+                WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+
+                if (view.OnMouseEvent (me))
+                {
+                    // Should we bubble up the event, if it is not handled?
+                    //return;
+                }
+
+                BringOverlappedTopToFront ();
+            }
+        }
+    }
+
+    #endregion Mouse handling
+
+    #region Keyboard handling
+
+    private static Key _alternateForwardKey = Key.Empty; // Defined in config.json
+
+    /// <summary>Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key AlternateForwardKey
+    {
+        get => _alternateForwardKey;
+        set
+        {
+            if (_alternateForwardKey != value)
+            {
+                Key oldKey = _alternateForwardKey;
+                _alternateForwardKey = value;
+                OnAlternateForwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
+            }
+        }
+    }
+
+    private static void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
+    {
+        foreach (Toplevel top in _topLevels.ToArray ())
+        {
+            top.OnAlternateForwardKeyChanged (e);
+        }
+    }
+
+    private static Key _alternateBackwardKey = Key.Empty; // Defined in config.json
+
+    /// <summary>Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key AlternateBackwardKey
+    {
+        get => _alternateBackwardKey;
+        set
+        {
+            if (_alternateBackwardKey != value)
+            {
+                Key oldKey = _alternateBackwardKey;
+                _alternateBackwardKey = value;
+                OnAlternateBackwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
+            }
+        }
+    }
+
+    private static void OnAlternateBackwardKeyChanged (KeyChangedEventArgs oldKey)
+    {
+        foreach (Toplevel top in _topLevels.ToArray ())
+        {
+            top.OnAlternateBackwardKeyChanged (oldKey);
+        }
+    }
+
+    private static Key _quitKey = Key.Empty; // Defined in config.json
+
+    /// <summary>Gets or sets the key to quit the application.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    [JsonConverter (typeof (KeyJsonConverter))]
+    public static Key QuitKey
+    {
+        get => _quitKey;
+        set
+        {
+            if (_quitKey != value)
+            {
+                Key oldKey = _quitKey;
+                _quitKey = value;
+                OnQuitKeyChanged (new KeyChangedEventArgs (oldKey, value));
+            }
+        }
+    }
+
+    private static void OnQuitKeyChanged (KeyChangedEventArgs e)
+    {
+        // Duplicate the list so if it changes during enumeration we're safe
+        foreach (Toplevel top in _topLevels.ToArray ())
+        {
+            top.OnQuitKeyChanged (e);
+        }
+    }
+
+    /// <summary>
+    ///     Event fired when the user presses a key. Fired by <see cref="OnKeyDown"/>.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/> and before <see cref="KeyUp"/>.</para>
+    /// </remarks>
+    public static event EventHandler<Key> KeyDown;
+
+    /// <summary>
+    ///     Called by the <see cref="ConsoleDriver"/> when the user presses a key. Fires the <see cref="KeyDown"/> event
+    ///     then calls <see cref="View.NewKeyDownEvent"/> on all top level views. Called after <see cref="OnKeyDown"/> and
+    ///     before <see cref="OnKeyUp"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key press events.</remarks>
+    /// <param name="keyEvent"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    public static bool OnKeyDown (Key keyEvent)
+    {
+        if (!_initialized)
+        {
+            return true;
+        }
+
+        KeyDown?.Invoke (null, keyEvent);
+
+        if (keyEvent.Handled)
+        {
+            return true;
+        }
+
+        foreach (Toplevel topLevel in _topLevels.ToList ())
+        {
+            if (topLevel.NewKeyDownEvent (keyEvent))
+            {
+                return true;
+            }
+
+            if (topLevel.Modal)
+            {
+                break;
+            }
+        }
+
+        // Invoke any Global KeyBindings
+        foreach (Toplevel topLevel in _topLevels.ToList ())
+        {
+            foreach (View view in topLevel.Subviews.Where (
+                                                           v => v.KeyBindings.TryGet (
+                                                                                      keyEvent.KeyCode,
+                                                                                      KeyBindingScope.Application,
+                                                                                      out KeyBinding _
+                                                                                     )
+                                                          ))
+            {
+                if (view.KeyBindings.TryGet (keyEvent.KeyCode, KeyBindingScope.Application, out KeyBinding _))
+                {
+                    keyEvent.Scope = KeyBindingScope.Application;
+                    bool? handled = view.OnInvokingKeyBindings (keyEvent);
+
+                    if (handled != null && (bool)handled)
+                    {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Event fired when the user releases a key. Fired by <see cref="OnKeyUp"/>.
+    ///     <para>
+    ///         Set <see cref="Key.Handled"/> to <see langword="true"/> to indicate the key was handled and to prevent
+    ///         additional processing.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     All drivers support firing the <see cref="KeyDown"/> event. Some drivers (Curses) do not support firing the
+    ///     <see cref="KeyDown"/> and <see cref="KeyUp"/> events.
+    ///     <para>Fired after <see cref="KeyDown"/>.</para>
+    /// </remarks>
+    public static event EventHandler<Key> KeyUp;
+
+    /// <summary>
+    ///     Called by the <see cref="ConsoleDriver"/> when the user releases a key. Fires the <see cref="KeyUp"/> event
+    ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="OnKeyDown"/>.
+    /// </summary>
+    /// <remarks>Can be used to simulate key press events.</remarks>
+    /// <param name="a"></param>
+    /// <returns><see langword="true"/> if the key was handled.</returns>
+    public static bool OnKeyUp (Key a)
+    {
+        if (!_initialized)
+        {
+            return true;
+        }
+
+        KeyUp?.Invoke (null, a);
+
+        if (a.Handled)
+        {
+            return true;
+        }
+
+        foreach (Toplevel topLevel in _topLevels.ToList ())
+        {
+            if (topLevel.NewKeyUpEvent (a))
+            {
+                return true;
+            }
+
+            if (topLevel.Modal)
+            {
+                break;
+            }
+        }
+
+        return false;
+    }
+
+    #endregion Keyboard handling
 }
-/// <summary>
-/// Event arguments for the <see cref="Application.Iteration"/> event.
-/// </summary>
-public class IterationEventArgs {
-}
+
+/// <summary>Event arguments for the <see cref="Application.Iteration"/> event.</summary>
+public class IterationEventArgs
+{ }

+ 201 - 165
Terminal.Gui/Clipboard/Clipboard.cs

@@ -1,174 +1,210 @@
-using System.Text;
-using System;
-using System.Diagnostics;
-using System.Threading.Tasks;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Provides cut, copy, and paste support for the OS clipboard.
-/// </summary>
+/// <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>
+///     <para>On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.</para>
+///     <para>
+///         On Linux, when not running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class uses
+///         the xclip command line tool. If xclip is not installed, the clipboard will not work.
+///     </para>
+///     <para>
+///         On Linux, when running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class launches
+///         Windows' powershell.exe via WSL interop and uses the "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets.
+///     </para>
+///     <para>
+///         On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools and
+///         the Mac clipboard APIs vai P/Invoke.
+///     </para>
 /// </remarks>
-public static class Clipboard {
-	static string _contents = string.Empty;
-
-		/// <summary>
-		/// Gets (copies from) or sets (pastes to) the contents of the OS clipboard.
-		/// </summary>
-		public static string Contents {
-			get {
-				try {
-					if (IsSupported) {
-						var clipData = Application.Driver.Clipboard.GetClipboardData ();
-						if (clipData == null) {
-							// throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
-							clipData = string.Empty;
-						}
-						_contents = clipData;
-					}
-				} catch (Exception) {
-					_contents = string.Empty;
-				}
-				return _contents;
-			}
-			set {
-				try {
-					if (IsSupported) {
-						if (value == null) {
-							value = string.Empty;
-						}
-						Application.Driver.Clipboard.SetClipboardData (value);
-					}
-					_contents = value;
-				} catch (Exception) {
-					_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>
-	/// 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;
-		}
-		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;
-	}
+public static class Clipboard
+{
+    private static string _contents = string.Empty;
+
+    /// <summary>Gets (copies from) or sets (pastes to) the contents of the OS clipboard.</summary>
+    public static string Contents
+    {
+        get
+        {
+            try
+            {
+                if (IsSupported)
+                {
+                    string clipData = Application.Driver.Clipboard.GetClipboardData ();
+
+                    if (clipData == null)
+                    {
+                        // throw new InvalidOperationException ($"{Application.Driver.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
+                        clipData = string.Empty;
+                    }
+
+                    _contents = clipData;
+                }
+            }
+            catch (Exception)
+            {
+                _contents = string.Empty;
+            }
+
+            return _contents;
+        }
+        set
+        {
+            try
+            {
+                if (IsSupported)
+                {
+                    if (value == null)
+                    {
+                        value = string.Empty;
+                    }
+
+                    Application.Driver.Clipboard.SetClipboardData (value);
+                }
+
+                _contents = value;
+            }
+            catch (Exception)
+            {
+                _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 => 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;
+        }
+
+        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>
-/// 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.
+///     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");
-	}
-}
+internal static class ClipboardProcessRunner
+{
+    public static (int exitCode, string result) Bash (
+        string commandLine,
+        string inputText = "",
+        bool waitForOutput = false
+    )
+    {
+        var arguments = $"-c \"{commandLine}\"";
+        (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
+
+        return (exitCode, result.TrimEnd ());
+    }
+
+    public static bool DoubleWaitForExit (this Process process)
+    {
+        bool 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"); }
+
+    public static (int exitCode, string result) Process (
+        string cmd,
+        string arguments,
+        string input = null,
+        bool waitForOutput = true
+    )
+    {
+        var output = string.Empty;
+
+        using (var process = new Process
+               {
+                   StartInfo = new ProcessStartInfo
+                   {
+                       FileName = cmd,
+                       Arguments = arguments,
+                       RedirectStandardOutput = true,
+                       RedirectStandardError = true,
+                       RedirectStandardInput = true,
+                       UseShellExecute = false,
+                       CreateNoWindow = true
+                   }
+               })
+        {
+            TaskCompletionSource<bool> eventHandled = new ();
+            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);
+        }
+    }
+}

+ 119 - 109
Terminal.Gui/Clipboard/ClipboardBase.cs

@@ -1,110 +1,120 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Shared abstract class to enforce rules from the implementation of the <see cref="IClipboard"/> interface.
-	/// </summary>
-	public abstract class ClipboardBase : IClipboard {
-		/// <summary>
-		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard
-		/// </summary>
-		public abstract bool IsSupported { get; }
-
-		/// <summary>
-		/// Returns the contents of the OS clipboard if possible.
-		/// </summary>
-		/// <returns>The contents of the OS clipboard if successful.</returns>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
-		public string GetClipboardData ()
-		{
-			try {
-				var result = GetClipboardDataImpl ();
-				if (result == null) {
-					return string.Empty;
-				}
-				return GetClipboardDataImpl ();
-			} catch (NotSupportedException ex) {
-				throw new NotSupportedException ("Failed to copy from the OS clipboard.", ex);
-			}
-		}
-
-		/// <summary>
-		/// Returns the contents of the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
-		/// </summary>
-		/// <returns>The contents of the OS clipboard if successful.</returns>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
-		protected abstract string GetClipboardDataImpl ();
-
-		/// <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>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
-		public void SetClipboardData (string text)
-		{
-			if (text == null) {
-				throw new ArgumentNullException (nameof (text));
-			}
-
-			try {
-				SetClipboardDataImpl (text);
-			} catch (NotSupportedException ex) {
-				throw new NotSupportedException ("Failed to paste to the OS clipboard.", ex);
-			}
-		}
-
-		/// <summary>
-		/// Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific subclasses.
-		/// </summary>
-		/// <param name="text">The text to paste to the OS clipboard.</param>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
-		protected abstract void SetClipboardDataImpl (string text);
-
-		/// <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.</param>
-		/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
-		public bool TryGetClipboardData (out string result)
-		{
-			result = string.Empty;
-			// Don't even try to read because environment is not set up.
-			if (!IsSupported) {
-				return false;
-			}
-
-			try {
-				result = GetClipboardDataImpl ();
-				return true;
-			} catch (NotSupportedException ex) {
-				System.Diagnostics.Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
-				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 bool TrySetClipboardData (string text)
-		{
-			// Don't even try to set because environment is not set up
-			if (!IsSupported) {
-				return false;
-			}
-
-			try {
-				SetClipboardDataImpl (text);
-				return true;
-			} catch (NotSupportedException ex) {
-				System.Diagnostics.Debug.WriteLine ($"TrySetClipboardData: {ex.Message}");
-				return false;
-			}
-		}
-	}
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>Shared abstract class to enforce rules from the implementation of the <see cref="IClipboard"/> interface.</summary>
+public abstract class ClipboardBase : IClipboard
+{
+    /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard</summary>
+    public abstract bool IsSupported { get; }
+
+    /// <summary>Returns the contents of the OS clipboard if possible.</summary>
+    /// <returns>The contents of the OS clipboard if successful.</returns>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
+    public string GetClipboardData ()
+    {
+        try
+        {
+            string result = GetClipboardDataImpl ();
+
+            if (result == null)
+            {
+                return string.Empty;
+            }
+
+            return GetClipboardDataImpl ();
+        }
+        catch (NotSupportedException ex)
+        {
+            throw new NotSupportedException ("Failed to copy from the OS clipboard.", ex);
+        }
+    }
+
+    /// <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>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
+    public void SetClipboardData (string text)
+    {
+        if (text == null)
+        {
+            throw new ArgumentNullException (nameof (text));
+        }
+
+        try
+        {
+            SetClipboardDataImpl (text);
+        }
+        catch (NotSupportedException ex)
+        {
+            throw new NotSupportedException ("Failed to paste to the OS clipboard.", ex);
+        }
+    }
+
+    /// <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.</param>
+    /// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
+    public bool TryGetClipboardData (out string result)
+    {
+        result = string.Empty;
+
+        // Don't even try to read because environment is not set up.
+        if (!IsSupported)
+        {
+            return false;
+        }
+
+        try
+        {
+            result = GetClipboardDataImpl ();
+
+            return true;
+        }
+        catch (NotSupportedException ex)
+        {
+            Debug.WriteLine ($"TryGetClipboardData: {ex.Message}");
+
+            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 bool TrySetClipboardData (string text)
+    {
+        // Don't even try to set because environment is not set up
+        if (!IsSupported)
+        {
+            return false;
+        }
+
+        try
+        {
+            SetClipboardDataImpl (text);
+
+            return true;
+        }
+        catch (NotSupportedException ex)
+        {
+            Debug.WriteLine ($"TrySetClipboardData: {ex.Message}");
+
+            return false;
+        }
+    }
+
+    /// <summary>
+    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific
+    ///     subclasses.
+    /// </summary>
+    /// <returns>The contents of the OS clipboard if successful.</returns>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to copy from the OS clipboard.</exception>
+    protected abstract string GetClipboardDataImpl ();
+
+    /// <summary>
+    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>
+    ///     -specific subclasses.
+    /// </summary>
+    /// <param name="text">The text to paste to the OS clipboard.</param>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to paste to the OS clipboard.</exception>
+    protected abstract void SetClipboardDataImpl (string text);
 }

+ 21 - 34
Terminal.Gui/Clipboard/IClipboard.cs

@@ -1,40 +1,27 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Definition to interact with the OS clipboard.
-	/// </summary>
-	public interface IClipboard {
-		/// <summary>
-		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
-		/// </summary>
-		bool IsSupported { get; }
+/// <summary>Definition to interact with the OS clipboard.</summary>
+public interface IClipboard
+{
+    /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
+    bool IsSupported { get; }
 
-		/// <summary>
-		/// Get the operation system clipboard.
-		/// </summary>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents.</exception>
-		string GetClipboardData ();
+    /// <summary>Get the operation system clipboard.</summary>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents.</exception>
+    string GetClipboardData ();
 
-		/// <summary>
-		/// Gets the operation system clipboard if possible.
-		/// </summary>
-		/// <param name="result">Clipboard contents read</param>
-		/// <returns>true if it was possible to read the OS clipboard.</returns>
-		bool TryGetClipboardData (out string result);
+    /// <summary>Sets the operation system clipboard.</summary>
+    /// <param name="text"></param>
+    /// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents.</exception>
+    void SetClipboardData (string text);
 
-		/// <summary>
-		/// Sets the operation system clipboard.
-		/// </summary>
-		/// <param name="text"></param>
-		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents.</exception>
-		void SetClipboardData (string text);
+    /// <summary>Gets the operation system clipboard if possible.</summary>
+    /// <param name="result">Clipboard contents read</param>
+    /// <returns>true if it was possible to read the OS clipboard.</returns>
+    bool TryGetClipboardData (out string result);
 
-		/// <summary>
-		/// Sets the operation system clipboard if possible.
-		/// </summary>
-		/// <param name="text"></param>
-		/// <returns>True if the clipboard content was set successfully.</returns>
-		bool TrySetClipboardData (string text);
-	}
+    /// <summary>Sets the operation system clipboard if possible.</summary>
+    /// <param name="text"></param>
+    /// <returns>True if the clipboard content was set successfully.</returns>
+    bool TrySetClipboardData (string text);
 }

+ 24 - 37
Terminal.Gui/Configuration/AppScope.cs

@@ -1,42 +1,29 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
+#nullable enable
 using System.Text.Json.Serialization;
-using static Terminal.Gui.ConfigurationManager;
 
-#nullable enable
+namespace Terminal.Gui;
 
-namespace Terminal.Gui; 
-
-/// <summary>
-/// The <see cref="Scope{T}"/> class for application-defined configuration settings.
-/// </summary>
-/// <remarks>
-/// </remarks>
+/// <summary>The <see cref="Scope{T}"/> class for application-defined configuration settings.</summary>
+/// <remarks></remarks>
 /// <example>
-/// <para>
-/// Use the <see cref="SerializableConfigurationProperty"/> attribute to mark properties that should be serialized as part
-/// of application-defined configuration settings.
-/// </para>
-/// <code>
-/// public class MyAppSettings {
-///	[SerializableConfigurationProperty (Scope = typeof (AppScope))]
-///	public static bool? MyProperty { get; set; } = true;
-/// }
-/// </code>
-/// <para>
-/// THe resultant Json will look like this:
-/// </para>
-/// <code>
-///   "AppSettings": {
-///     "MyAppSettings.MyProperty": true,
-///     "UICatalog.ShowStatusBar": true
-///   },
-/// </code>
-/// </example> 
+///     <para>
+///         Use the <see cref="SerializableConfigurationProperty"/> attribute to mark properties that should be
+///         serialized as part of application-defined configuration settings.
+///     </para>
+///     <code>
+///  public class MyAppSettings {
+/// 	[SerializableConfigurationProperty (Scope = typeof (AppScope))]
+/// 	public static bool? MyProperty { get; set; } = true;
+///  }
+///  </code>
+///     <para>THe resultant Json will look like this:</para>
+///     <code>
+///    "AppSettings": {
+///      "MyAppSettings.MyProperty": true,
+///      "UICatalog.ShowStatusBar": true
+///    },
+///  </code>
+/// </example>
 [JsonConverter (typeof (ScopeJsonConverter<AppScope>))]
-public class AppScope : Scope<AppScope> {
-}
+public class AppScope : Scope<AppScope>
+{ }

+ 106 - 95
Terminal.Gui/Configuration/AttributeJsonConverter.cs

@@ -1,97 +1,108 @@
-using System;
-using System.Text.Json;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace Terminal.Gui; 
-
-/// <summary>
-/// Json converter fro the <see cref="Attribute"/> class.
-/// </summary>
-class AttributeJsonConverter : JsonConverter<Attribute> {
-	static AttributeJsonConverter _instance;
-
-	/// <summary>
-	/// 
-	/// </summary>
-	public static AttributeJsonConverter Instance {
-		get {
-			if (_instance == null) {
-				_instance = new AttributeJsonConverter ();
-			}
-
-			return _instance;
-		}
-	}
-
-	public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-	{
-		if (reader.TokenType != JsonTokenType.StartObject) {
-			throw new JsonException ($"Unexpected StartObject token when parsing Attribute: {reader.TokenType}.");
-		}
-
-		var attribute = new Attribute ();
-		Color? foreground = null;
-		Color? background = null;
-		while (reader.Read ()) {
-			if (reader.TokenType == JsonTokenType.EndObject) {
-				if (foreground == null || background == null) {
-					throw new JsonException ("Both Foreground and Background colors must be provided.");
-				}
-				return new Attribute (foreground.Value, background.Value);
-			}
-
-			if (reader.TokenType != JsonTokenType.PropertyName) {
-				throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
-			}
-
-			var propertyName = reader.GetString ();
-			reader.Read ();
-			var color = $"\"{reader.GetString ()}\"";
-
-			switch (propertyName?.ToLower ()) {
-			case "foreground":
-				foreground = JsonSerializer.Deserialize<Color> (color, options);
-				break;
-			case "background":
-				background = JsonSerializer.Deserialize<Color> (color, options);
-				break;
-			//case "bright":
-			//case "bold":
-			//	attribute.Bright = reader.GetBoolean ();
-			//	break;
-			//case "dim":
-			//	attribute.Dim = reader.GetBoolean ();
-			//	break;
-			//case "underline":
-			//	attribute.Underline = reader.GetBoolean ();
-			//	break;
-			//case "blink":
-			//	attribute.Blink = reader.GetBoolean ();
-			//	break;
-			//case "reverse":
-			//	attribute.Reverse = reader.GetBoolean ();
-			//	break;
-			//case "hidden":
-			//	attribute.Hidden = reader.GetBoolean ();
-			//	break;
-			//case "strike-through":
-			//	attribute.StrikeThrough = reader.GetBoolean ();
-			//	break;				
-			default:
-				throw new JsonException ($"Unknown Attribute property {propertyName}.");
-			}
-		}
-		throw new JsonException ();
-	}
-
-	public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
-	{
-		writer.WriteStartObject ();
-		writer.WritePropertyName (nameof (Attribute.Foreground));
-		ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
-		writer.WritePropertyName (nameof (Attribute.Background));
-		ColorJsonConverter.Instance.Write (writer, value.Background, options);
-
-		writer.WriteEndObject ();
-	}
-}
+namespace Terminal.Gui;
+
+/// <summary>Json converter fro the <see cref="Attribute"/> class.</summary>
+internal class AttributeJsonConverter : JsonConverter<Attribute>
+{
+    private static AttributeJsonConverter _instance;
+
+    /// <summary></summary>
+    public static AttributeJsonConverter Instance
+    {
+        get
+        {
+            if (_instance == null)
+            {
+                _instance = new AttributeJsonConverter ();
+            }
+
+            return _instance;
+        }
+    }
+
+    public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        if (reader.TokenType != JsonTokenType.StartObject)
+        {
+            throw new JsonException ($"Unexpected StartObject token when parsing Attribute: {reader.TokenType}.");
+        }
+
+        var attribute = new Attribute ();
+        Color? foreground = null;
+        Color? background = null;
+
+        while (reader.Read ())
+        {
+            if (reader.TokenType == JsonTokenType.EndObject)
+            {
+                if (foreground == null || background == null)
+                {
+                    throw new JsonException ("Both Foreground and Background colors must be provided.");
+                }
+
+                return new Attribute (foreground.Value, background.Value);
+            }
+
+            if (reader.TokenType != JsonTokenType.PropertyName)
+            {
+                throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
+            }
+
+            string propertyName = reader.GetString ();
+            reader.Read ();
+            var color = $"\"{reader.GetString ()}\"";
+
+            switch (propertyName?.ToLower ())
+            {
+                case "foreground":
+                    foreground = JsonSerializer.Deserialize<Color> (color, options);
+
+                    break;
+                case "background":
+                    background = JsonSerializer.Deserialize<Color> (color, options);
+
+                    break;
+
+                //case "bright":
+                //case "bold":
+                //	attribute.Bright = reader.GetBoolean ();
+                //	break;
+                //case "dim":
+                //	attribute.Dim = reader.GetBoolean ();
+                //	break;
+                //case "underline":
+                //	attribute.Underline = reader.GetBoolean ();
+                //	break;
+                //case "blink":
+                //	attribute.Blink = reader.GetBoolean ();
+                //	break;
+                //case "reverse":
+                //	attribute.Reverse = reader.GetBoolean ();
+                //	break;
+                //case "hidden":
+                //	attribute.Hidden = reader.GetBoolean ();
+                //	break;
+                //case "strike-through":
+                //	attribute.StrikeThrough = reader.GetBoolean ();
+                //	break;				
+                default:
+                    throw new JsonException ($"Unknown Attribute property {propertyName}.");
+            }
+        }
+
+        throw new JsonException ();
+    }
+
+    public override void Write (Utf8JsonWriter writer, Attribute value, JsonSerializerOptions options)
+    {
+        writer.WriteStartObject ();
+        writer.WritePropertyName (nameof (Attribute.Foreground));
+        ColorJsonConverter.Instance.Write (writer, value.Foreground, options);
+        writer.WritePropertyName (nameof (Attribute.Background));
+        ColorJsonConverter.Instance.Write (writer, value.Background, options);
+
+        writer.WriteEndObject ();
+    }
+}

+ 49 - 47
Terminal.Gui/Configuration/ColorJsonConverter.cs

@@ -1,50 +1,52 @@
-using System.Text.Json.Serialization;
 using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+/// <summary>Json converter for the <see cref="Color"/> class.</summary>
+internal class ColorJsonConverter : JsonConverter<Color>
+{
+    private static ColorJsonConverter _instance;
+
+    /// <summary>Singleton</summary>
+    public static ColorJsonConverter Instance
+    {
+        get
+        {
+            if (_instance == null)
+            {
+                _instance = new ColorJsonConverter ();
+            }
+
+            return _instance;
+        }
+    }
+
+    public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        // Check if the value is a string
+        if (reader.TokenType == JsonTokenType.String)
+        {
+            // Get the color string
+            ReadOnlySpan<char> colorString = reader.GetString ();
+
+            // Check if the color string is a color name
+            if (Enum.TryParse (colorString, true, out ColorName color))
+            {
+                // Return the parsed color
+                return new Color (in color);
+            }
+
+            if (Color.TryParse (colorString, null, out Color parsedColor))
+            {
+                return parsedColor;
+            }
+
+            throw new JsonException ($"Unexpected color name: {colorString}.");
+        }
+
+        throw new JsonException ($"Unexpected token when parsing Color: {reader.TokenType}");
+    }
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Json converter for the <see cref="Color"/> class.
-	/// </summary>
-	internal class ColorJsonConverter : JsonConverter<Color> {
-		private static ColorJsonConverter _instance;
-
-		/// <summary>
-		/// Singleton
-		/// </summary>
-		public static ColorJsonConverter Instance {
-			get {
-				if (_instance == null) {
-					_instance = new ColorJsonConverter ();
-				}
-
-				return _instance;
-			}
-		}
-
-		public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-		{
-			// Check if the value is a string
-			if (reader.TokenType == JsonTokenType.String) {
-				// Get the color string
-				ReadOnlySpan<char> colorString = reader.GetString ();
-
-				// Check if the color string is a color name
-				if (Enum.TryParse (colorString, ignoreCase: true, out ColorName color)) {
-					// Return the parsed color
-					return new Color(in color);
-				}
-				if (Color.TryParse(colorString,null, out Color parsedColor)) {
-					return parsedColor;
-				}
-				throw new JsonException ($"Unexpected color name: {colorString}.");
-			} else {
-				throw new JsonException ($"Unexpected token when parsing Color: {reader.TokenType}");
-			}
-		}
-
-		public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
-		{
-			writer.WriteStringValue (value.ToString());
-		}
-	}
+    public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { writer.WriteStringValue (value.ToString ()); }
 }

+ 110 - 99
Terminal.Gui/Configuration/ColorSchemeJsonConverter.cs

@@ -1,101 +1,112 @@
-using System;
-using System.Text.Json;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Implements a JSON converter for <see cref="ColorScheme"/>. 
-	/// </summary>
-	class ColorSchemeJsonConverter : JsonConverter<ColorScheme> {
-		private static ColorSchemeJsonConverter instance;
-
-		/// <summary>
-		/// Singleton
-		/// </summary>
-		public static ColorSchemeJsonConverter Instance {
-			get {
-				if (instance == null) {
-					instance = new ColorSchemeJsonConverter ();
-				}
-				return instance;
-			}
-		}
-		
-		/// <inheritdoc/>
-		public override ColorScheme Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-		{
-			if (reader.TokenType != JsonTokenType.StartObject) {
-				throw new JsonException ($"Unexpected StartObject token when parsing ColorScheme: {reader.TokenType}.");
-			}
-
-			Attribute normal = Attribute.Default;
-			Attribute focus = Attribute.Default;
-			Attribute hotNormal = Attribute.Default;
-			Attribute hotFocus = Attribute.Default;
-			Attribute disabled = Attribute.Default;
-
-			while (reader.Read ()) {
-				if (reader.TokenType == JsonTokenType.EndObject) {
-					var colorScheme = new ColorScheme () {
-						Normal = normal,
-						Focus = focus,
-						HotNormal = hotNormal,
-						HotFocus = hotFocus,
-						Disabled = disabled
-					};
-
-					return colorScheme;
-				}
-
-				if (reader.TokenType != JsonTokenType.PropertyName) {
-					throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
-				}
-
-				var propertyName = reader.GetString ();
-				reader.Read ();
-				var attribute = JsonSerializer.Deserialize<Attribute> (ref reader, options);
-
-				switch (propertyName.ToLower()) {
-				case "normal":
-					normal = attribute;
-					break;
-				case "focus":
-					focus = attribute;
-					break;
-				case "hotnormal":
-					hotNormal = attribute;
-					break;
-				case "hotfocus":
-					hotFocus = attribute;
-					break;
-				case "disabled":
-					disabled = attribute;
-					break;
-				default:
-					throw new JsonException ($"Unrecognized ColorScheme Attribute name: {propertyName}.");
-				}
-			}
-
-			throw new JsonException ();
-		}
-
-		/// <inheritdoc/>
-		public override void Write (Utf8JsonWriter writer, ColorScheme value, JsonSerializerOptions options)
-		{
-			writer.WriteStartObject ();
-
-			writer.WritePropertyName ("Normal");
-			AttributeJsonConverter.Instance.Write (writer, value.Normal, options);
-			writer.WritePropertyName ("Focus");
-			AttributeJsonConverter.Instance.Write (writer, value.Focus, options);
-			writer.WritePropertyName ("HotNormal");
-			AttributeJsonConverter.Instance.Write (writer, value.HotNormal, options);
-			writer.WritePropertyName ("HotFocus");
-			AttributeJsonConverter.Instance.Write (writer, value.HotFocus, options);
-			writer.WritePropertyName ("Disabled");
-			AttributeJsonConverter.Instance.Write (writer, value.Disabled, options);
-
-			writer.WriteEndObject ();
-		}
-	}
-}
+namespace Terminal.Gui;
+
+/// <summary>Implements a JSON converter for <see cref="ColorScheme"/>.</summary>
+internal class ColorSchemeJsonConverter : JsonConverter<ColorScheme>
+{
+    private static ColorSchemeJsonConverter instance;
+
+    /// <summary>Singleton</summary>
+    public static ColorSchemeJsonConverter Instance
+    {
+        get
+        {
+            if (instance == null)
+            {
+                instance = new ColorSchemeJsonConverter ();
+            }
+
+            return instance;
+        }
+    }
+
+    /// <inheritdoc/>
+    public override ColorScheme Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        if (reader.TokenType != JsonTokenType.StartObject)
+        {
+            throw new JsonException ($"Unexpected StartObject token when parsing ColorScheme: {reader.TokenType}.");
+        }
+
+        var normal = Attribute.Default;
+        var focus = Attribute.Default;
+        var hotNormal = Attribute.Default;
+        var hotFocus = Attribute.Default;
+        var disabled = Attribute.Default;
+
+        while (reader.Read ())
+        {
+            if (reader.TokenType == JsonTokenType.EndObject)
+            {
+                var colorScheme = new ColorScheme
+                {
+                    Normal = normal,
+                    Focus = focus,
+                    HotNormal = hotNormal,
+                    HotFocus = hotFocus,
+                    Disabled = disabled
+                };
+
+                return colorScheme;
+            }
+
+            if (reader.TokenType != JsonTokenType.PropertyName)
+            {
+                throw new JsonException ($"Unexpected token when parsing Attribute: {reader.TokenType}.");
+            }
+
+            string propertyName = reader.GetString ();
+            reader.Read ();
+            var attribute = JsonSerializer.Deserialize<Attribute> (ref reader, options);
+
+            switch (propertyName.ToLower ())
+            {
+                case "normal":
+                    normal = attribute;
+
+                    break;
+                case "focus":
+                    focus = attribute;
+
+                    break;
+                case "hotnormal":
+                    hotNormal = attribute;
+
+                    break;
+                case "hotfocus":
+                    hotFocus = attribute;
+
+                    break;
+                case "disabled":
+                    disabled = attribute;
+
+                    break;
+                default:
+                    throw new JsonException ($"Unrecognized ColorScheme Attribute name: {propertyName}.");
+            }
+        }
+
+        throw new JsonException ();
+    }
+
+    /// <inheritdoc/>
+    public override void Write (Utf8JsonWriter writer, ColorScheme value, JsonSerializerOptions options)
+    {
+        writer.WriteStartObject ();
+
+        writer.WritePropertyName ("Normal");
+        AttributeJsonConverter.Instance.Write (writer, value.Normal, options);
+        writer.WritePropertyName ("Focus");
+        AttributeJsonConverter.Instance.Write (writer, value.Focus, options);
+        writer.WritePropertyName ("HotNormal");
+        AttributeJsonConverter.Instance.Write (writer, value.HotNormal, options);
+        writer.WritePropertyName ("HotFocus");
+        AttributeJsonConverter.Instance.Write (writer, value.HotFocus, options);
+        writer.WritePropertyName ("Disabled");
+        AttributeJsonConverter.Instance.Write (writer, value.Disabled, options);
+
+        writer.WriteEndObject ();
+    }
+}

+ 109 - 85
Terminal.Gui/Configuration/ConfigProperty.cs

@@ -1,6 +1,4 @@
 #nullable enable
-
-using System;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
@@ -8,99 +6,125 @@ using System.Text.Json.Serialization;
 namespace Terminal.Gui;
 
 /// <summary>
-/// Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/>
-/// to get and set the property's value.
+///     Holds a property's value and the <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to
+///     get and set the property's value.
 /// </summary>
 /// <remarks>
-/// Configuration properties must be <see langword="public"/> and <see langword="static"/>
-/// and have the <see cref="SerializableConfigurationProperty"/>
-/// attribute. If the type of the property requires specialized JSON serialization,
-/// a <see cref="JsonConverter"/> must be provided using
-/// the <see cref="JsonConverterAttribute"/> attribute.
+///     Configuration properties must be <see langword="public"/> and <see langword="static"/> and have the
+///     <see cref="SerializableConfigurationProperty"/> attribute. If the type of the property requires specialized JSON
+///     serialization, a <see cref="JsonConverter"/> must be provided using the <see cref="JsonConverterAttribute"/>
+///     attribute.
 /// </remarks>
-public class ConfigProperty {
+public class ConfigProperty
+{
+    /// <summary>Describes the property.</summary>
+    public PropertyInfo? PropertyInfo { get; set; }
+
+    /// <summary>
+    ///     Holds the property's value as it was either read from the class's implementation or from a config file. If the
+    ///     property has not been set (e.g. because no configuration file specified a value), this will be
+    ///     <see langword="null"/>.
+    /// </summary>
+    /// <remarks>
+    ///     On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements
+    ///     of the object that are non-null).
+    /// </remarks>
+    public object? PropertyValue { get; set; }
+
+    /// <summary>Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.</summary>
+    /// <returns></returns>
+    public bool Apply ()
+    {
+        if (PropertyValue != null)
+        {
+            try
+            {
+                if (PropertyInfo?.GetValue (null) != null)
+                {
+                    PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
+                }
+            }
+            catch (TargetInvocationException tie)
+            {
+                // Check if there is an inner exception
+                if (tie.InnerException != null)
+                {
+                    // Handle the inner exception separately without catching the outer exception
+                    Exception? innerException = tie.InnerException;
+
+                    // Handle the inner exception here
+                    throw new JsonException (
+                                             $"Error Applying Configuration Change: {innerException.Message}",
+                                             innerException
+                                            );
+                }
 
-	/// <summary>
-	/// Describes the property.
-	/// </summary>
-	public PropertyInfo? PropertyInfo { get; set; }
+                // Handle the outer exception or rethrow it if needed
+                throw new JsonException ($"Error Applying Configuration Change: {tie.Message}", tie);
+            }
+            catch (ArgumentException ae)
+            {
+                throw new JsonException (
+                                         $"Error Applying Configuration Change ({PropertyInfo?.Name}): {ae.Message}",
+                                         ae
+                                        );
+            }
+        }
 
-	/// <summary>
-	/// Holds the property's value as it was either read from the class's implementation or from a config file.
-	/// If the property has not been set (e.g. because no configuration file specified a value),
-	/// this will be <see langword="null"/>.
-	/// </summary>
-	/// <remarks>
-	/// On <see langword="set"/>, performs a sparse-copy of the new value to the existing value (only copies elements of
-	/// the object that are non-null).
-	/// </remarks>
-	public object? PropertyValue { get; set; }
+        return PropertyValue != null;
+    }
 
-	/// <summary>
-	/// Helper to get either the Json property named (specified by [JsonPropertyName(name)]
-	/// or the actual property name.
-	/// </summary>
-	/// <param name="pi"></param>
-	/// <returns></returns>
-	public static string GetJsonPropertyName (PropertyInfo pi)
-	{
-		var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
-		return jpna?.Name ?? pi.Name;
-	}
+    /// <summary>
+    ///     Helper to get either the Json property named (specified by [JsonPropertyName(name)] or the actual property
+    ///     name.
+    /// </summary>
+    /// <param name="pi"></param>
+    /// <returns></returns>
+    public static string GetJsonPropertyName (PropertyInfo pi)
+    {
+        var jpna = pi.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
 
-	internal object? UpdateValueFrom (object source)
-	{
-		if (source == null) {
-			return PropertyValue;
-		}
+        return jpna?.Name ?? pi.Name;
+    }
 
-		var ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
-		if (source.GetType () != PropertyInfo!.PropertyType && ut != null && source.GetType () != ut) {
-			throw new ArgumentException ($"The source object ({PropertyInfo!.DeclaringType}.{PropertyInfo!.Name}) is not of type {PropertyInfo!.PropertyType}.");
-		}
-		if (PropertyValue != null) {
-			PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
-		} else {
-			PropertyValue = source;
-		}
+    /// <summary>
+    ///     Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/> into
+    ///     <see cref="PropertyValue"/>.
+    /// </summary>
+    /// <returns></returns>
+    public object? RetrieveValue () { return PropertyValue = PropertyInfo!.GetValue (null); }
 
-		return PropertyValue;
-	}
+    internal object? UpdateValueFrom (object source)
+    {
+        if (source == null)
+        {
+            return PropertyValue;
+        }
 
-	/// <summary>
-	/// Retrieves (using reflection) the value of the static property described in <see cref="PropertyInfo"/>
-	/// into <see cref="PropertyValue"/>.
-	/// </summary>
-	/// <returns></returns>
-	public object? RetrieveValue () => PropertyValue = PropertyInfo!.GetValue (null);
+        Type? ut = Nullable.GetUnderlyingType (PropertyInfo!.PropertyType);
 
-	/// <summary>
-	/// Applies the <see cref="PropertyValue"/> to the property described by <see cref="PropertyInfo"/>.
-	/// </summary>
-	/// <returns></returns>
-	public bool Apply ()
-	{
-		if (PropertyValue != null) {
-			try {
-				if (PropertyInfo?.GetValue (null) != null) {
-					PropertyInfo?.SetValue (null, DeepMemberwiseCopy (PropertyValue, PropertyInfo?.GetValue (null)));
-				}
-			} catch (TargetInvocationException tie) {
-				// Check if there is an inner exception
-				if (tie.InnerException != null) {
-					// Handle the inner exception separately without catching the outer exception
-					var innerException = tie.InnerException;
+        if (source.GetType () != PropertyInfo!.PropertyType && ut != null && source.GetType () != ut)
+        {
+            throw new ArgumentException (
+                                         $"The source object ({
+                                             PropertyInfo!.DeclaringType
+                                         }.{
+                                             PropertyInfo!.Name
+                                         }) is not of type {
+                                             PropertyInfo!.PropertyType
+                                         }."
+                                        );
+        }
 
-					// Handle the inner exception here
-					throw new JsonException ($"Error Applying Configuration Change: {innerException.Message}", innerException);
-				}
+        if (PropertyValue != null)
+        {
+            PropertyValue = DeepMemberwiseCopy (source, PropertyValue);
+        }
+        else
+        {
+            PropertyValue = source;
+        }
 
-				// Handle the outer exception or rethrow it if needed
-				throw new JsonException ($"Error Applying Configuration Change: {tie.Message}", tie);
-			} catch (ArgumentException ae) {
-				throw new JsonException ($"Error Applying Configuration Change ({PropertyInfo?.Name}): {ae.Message}", ae);
-			}
-		}
-		return PropertyValue != null;
-	}
-}
+        return PropertyValue;
+    }
+}

+ 592 - 517
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -1,532 +1,607 @@
 global using static Terminal.Gui.ConfigurationManager;
 global using CM = Terminal.Gui.ConfigurationManager;
-using System;
 using System.Collections;
-using System.Collections.Generic;
 using System.Diagnostics;
-using System.IO;
-using System.Linq;
 using System.Reflection;
-using System.Text;
 using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-
 #nullable enable
 
 namespace Terminal.Gui;
 
 /// <summary>
-/// Provides settings and configuration management for Terminal.Gui applications.
-/// <para>
-/// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration
-/// files.
-/// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g.
-/// <c>C:/Users/username/.tui</c>,
-/// or <c>/usr/username/.tui</c>),
-/// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
-/// within the Terminal.Gui application's main assembly.
-/// </para>
-/// <para>
-/// Settings are defined in JSON format, according to this schema:
-/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
-/// </para>
-/// <para>
-/// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings
-/// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
-/// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
-/// </para>
-/// Settings are applied using the following precedence (higher precedence settings
-/// overwrite lower precedence settings):
-/// <para>
-/// 1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest
-/// precedence
-/// </para>
-/// <para>
-/// 2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
-/// </para>
-/// <para>
-/// 3. Application configuration found in the applications's resources (<c>Resources/config.json</c>).
-/// </para>
-/// <para>
-/// 4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
-/// </para>
-/// <para>
-/// 5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
-/// </para>
-/// <para>
-/// 6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest
-/// Precidence.
-/// </para>
+///     Provides settings and configuration management for Terminal.Gui applications.
+///     <para>
+///         Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted
+///         configuration files. The configuration files can be placed in at <c>.tui</c> folder in the user's home
+///         directory (e.g. <c>C:/Users/username/.tui</c>, or <c>/usr/username/.tui</c>), the folder where the Terminal.Gui
+///         application was launched from (e.g. <c>./.tui</c> ), or as a resource within the Terminal.Gui application's
+///         main assembly.
+///     </para>
+///     <para>
+///         Settings are defined in JSON format, according to this schema:
+///         https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+///     </para>
+///     <para>
+///         Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>.
+///         Settings that will apply to a specific Terminal.Gui application reside in files named
+///         <c>appname.config.json</c>, where <c>appname</c> is the assembly name of the application (e.g.
+///         <c>UICatalog.config.json</c>).
+///     </para>
+///     Settings are applied using the following precedence (higher precedence settings overwrite lower precedence
+///     settings):
+///     <para>
+///         1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) --
+///         Highest precedence
+///     </para>
+///     <para>
+///         2. Application configuration found in the directory the app was launched from (
+///         <c>./.tui/appname.config.json</c>).
+///     </para>
+///     <para>3. Application configuration found in the applications's resources (<c>Resources/config.json</c>).</para>
+///     <para>4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).</para>
+///     <para>5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).</para>
+///     <para>
+///         6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) --
+///         Lowest Precidence.
+///     </para>
 /// </summary>
-public static class ConfigurationManager {
-
-	/// <summary>
-	/// Describes the location of the configuration files. The constants can be
-	/// combined (bitwise) to specify multiple locations.
-	/// </summary>
-	[Flags]
-	public enum ConfigLocations {
-		/// <summary>
-		/// No configuration will be loaded.
-		/// </summary>
-		/// <remarks>
-		/// Used for development and testing only. For Terminal,Gui to function properly, at least
-		/// <see cref="DefaultOnly"/> should be set.
-		/// </remarks>
-		None = 0,
-
-		/// <summary>
-		/// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest
-		/// Precedence.
-		/// </summary>
-		DefaultOnly,
-
-		/// <summary>
-		/// This constant is a combination of all locations
-		/// </summary>
-		All = -1
-
-	}
-
-	static readonly string _configFilename = "config.json";
-
-	internal static readonly JsonSerializerOptions _serializerOptions = new() {
-		ReadCommentHandling = JsonCommentHandling.Skip,
-		PropertyNameCaseInsensitive = true,
-		DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
-		WriteIndented = true,
-		Converters = {
-			// We override the standard Rune converter to support specifying Glyphs in
-			// a flexible way
-			new RuneJsonConverter (),
-			// Override Key to support "Ctrl+Q" format.
-			new KeyJsonConverter ()
-		},
-		// Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
-		Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
-
-	};
-
-	/// <summary>
-	/// A dictionary of all properties in the Terminal.Gui project that are decorated with the
-	/// <see cref="SerializableConfigurationProperty"/> attribute.
-	/// The keys are the property names pre-pended with the class that implements the property (e.g.
-	/// <c>Application.UseSystemConsole</c>).
-	/// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
-	/// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
-	/// </summary>
-	/// <remarks>
-	/// Is <see langword="null"/> until <see cref="Initialize"/> is called.
-	/// </remarks>
-	internal static Dictionary<string, ConfigProperty>? _allConfigProperties;
-
-	/// <summary>
-	/// The backing property for <see cref="Settings"/>.
-	/// </summary>
-	/// <remarks>
-	/// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
-	/// deserialization (see <see cref="Load"/>).
-	/// </remarks>
-	static SettingsScope? _settings;
-
-	internal static StringBuilder jsonErrors = new ();
-
-	/// <summary>
-	/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the
-	/// <see cref="SettingsScope"/>
-	/// attribute value.
-	/// </summary>
-	public static SettingsScope? Settings {
-		get {
-			if (_settings == null) {
-				throw new InvalidOperationException ("ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property.");
-			}
-			return _settings;
-		}
-		set => _settings = value!;
-	}
-
-	/// <summary>
-	/// The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
-	/// attribute value.
-	/// </summary>
-	public static ThemeManager? Themes => ThemeManager.Instance;
-
-	/// <summary>
-	/// Application-specific configuration settings scope.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("AppSettings")]
-	public static AppScope? AppSettings { get; set; }
-
-	/// <summary>
-	/// The set of glyphs used to draw checkboxes, lines, borders, etc...See also
-	/// <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)] [JsonPropertyName ("Glyphs")]
-	public static GlyphDefinitions Glyphs { get; set; } = new ();
-
-	/// <summary>
-	/// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters
-	/// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the
-	/// console when <see cref="Application.Shutdown"/> is called.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static bool? ThrowOnJsonErrors { get; set; } = false;
-
-	/// <summary>
-	/// Name of the running application. By default this property is set to the application's assembly name.
-	/// </summary>
-	public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
-
-	/// <summary>
-	/// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
-	/// The value is <see cref="ConfigLocations.All"/>.
-	/// </summary>
-	public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
-
-	/// <summary>
-	/// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
-	/// startup to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
-	/// </summary>
-	internal static void Initialize ()
-	{
-		_allConfigProperties = new Dictionary<string, ConfigProperty> ();
-		_settings = null;
-
-		var classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
-		// Get Terminal.Gui.dll classes
-
-		var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
-			from type in assembly.GetTypes ()
-			where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
-			select type;
-
-		foreach (var classWithConfig in types) {
-			classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
-		}
-
-		Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
-		classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
-
-		foreach (var p in from c in classesWithConfigProps
-			let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
-				prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
-			let enumerable = props
-			from p in enumerable
-			select p) {
-			if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
-				if (p.GetGetMethod (true)!.IsStatic) {
-					// If the class name is omitted, JsonPropertyName is allowed. 
-					_allConfigProperties!.Add (scp.OmitClassName ? ConfigProperty.GetJsonPropertyName (p) : $"{p.DeclaringType?.Name}.{p.Name}", new ConfigProperty {
-						PropertyInfo = p,
-						PropertyValue = null
-					});
-				} else {
-					throw new Exception ($"Property {p.Name} in class {p.DeclaringType?.Name} is not static. All SerializableConfigurationProperty properties must be static.");
-				}
-			}
-		}
-
-		_allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key).ToDictionary (x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);
-
-		Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
-		//_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($"  Property: {x.Key}"));
-
-		AppSettings = new AppScope ();
-	}
-
-	/// <summary>
-	/// Creates a JSON document with the configuration specified.
-	/// </summary>
-	/// <returns></returns>
-	internal static string ToJson ()
-	{
-		Debug.WriteLine ("ConfigurationManager.ToJson()");
-		return JsonSerializer.Serialize (Settings!, _serializerOptions);
-	}
-
-	internal static Stream ToStream ()
-	{
-		var json = JsonSerializer.Serialize (Settings!, _serializerOptions);
-		// turn it into a stream
-		var stream = new MemoryStream ();
-		var writer = new StreamWriter (stream);
-		writer.Write (json);
-		writer.Flush ();
-		stream.Position = 0;
-		return stream;
-	}
-
-	internal static void AddJsonError (string error)
-	{
-		Debug.WriteLine ($"ConfigurationManager: {error}");
-		jsonErrors.AppendLine (error);
-	}
-
-	/// <summary>
-	/// Prints any Json deserialization errors that occurred during deserialization to the console.
-	/// </summary>
-	public static void PrintJsonErrors ()
-	{
-		if (jsonErrors.Length > 0) {
-			Console.WriteLine (@"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
-			Console.WriteLine (jsonErrors.ToString ());
-		}
-	}
-
-	static void ClearJsonErrors () => jsonErrors.Clear ();
-
-	/// <summary>
-	/// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
-	/// event.
-	/// </summary>
-	public static void OnUpdated ()
-	{
-		Debug.WriteLine (@"ConfigurationManager.OnApplied()");
-		Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
-	}
-
-	/// <summary>
-	/// Event fired when the configuration has been updated from a configuration source.
-	/// application.
-	/// </summary>
-	public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
-
-	/// <summary>
-	/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
-	/// (e.g. in <see cref="Application.Init"/> starts. Called by <see cref="Load"/>
-	/// if the <c>reset</c> parameter is <see langword="true"/>.
-	/// </summary>
-	/// <remarks>
-	/// 
-	/// </remarks>
-	public static void Reset ()
-	{
-		Debug.WriteLine (@"ConfigurationManager.Reset()");
-		if (_allConfigProperties == null) {
-			Initialize ();
-		}
-
-		ClearJsonErrors ();
-
-		Settings = new SettingsScope ();
-		ThemeManager.Reset ();
-		AppSettings = new AppScope ();
-
-		// To enable some unit tests, we only load from resources if the flag is set
-		if (Locations.HasFlag (ConfigLocations.DefaultOnly)) {
-			Settings.UpdateFromResource (typeof (ConfigurationManager).Assembly, $"Terminal.Gui.Resources.{_configFilename}");
-		}
-		Apply ();
-		ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
-		AppSettings?.Apply ();
-	}
-
-	/// <summary>
-	/// Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
-	/// the library to generate the default configuration file. Before calling Application.Init, make sure
-	/// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
-	/// </summary>
-	/// <remarks>
-	///         <para>
-	///         This method is only really useful when using ConfigurationManagerTests
-	///         to generate the JSON doc that is embedded into Terminal.Gui (during development).
-	///         </para>
-	///         <para>
-	///         WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
-	///         that are NOT generated by this function. If you use this function to regenerate
-	///         <c>Terminal.Gui.Resources.config.json</c>,
-	///         make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
-	///         </para>
-	/// </remarks>
-	internal static void GetHardCodedDefaults ()
-	{
-		if (_allConfigProperties == null) {
-			throw new InvalidOperationException ("Initialize must be called first.");
-		}
-		Settings = new SettingsScope ();
-		ThemeManager.GetHardCodedDefaults ();
-		AppSettings?.RetrieveValues ();
-		foreach (var p in Settings!.Where (cp => cp.Value.PropertyInfo != null)) {
-			Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
-		}
-	}
-
-	/// <summary>
-	/// Applies the configuration settings to the running <see cref="Application"/> instance.
-	/// </summary>
-	public static void Apply ()
-	{
-		var settings = false;
-		var themes = false;
-		var appSettings = false;
-		try {
-			settings = Settings?.Apply () ?? false;
-			themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme) && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
-			appSettings = AppSettings?.Apply () ?? false;
-
-		} catch (JsonException e) {
-			if (ThrowOnJsonErrors ?? false) {
-				throw;
-			} else {
-				AddJsonError ($"Error applying Configuration Change: {e.Message}");
-			}
-		} finally {
-			if (settings || themes || appSettings) {
-				OnApplied ();
-			}
-		}
-	}
-
-	/// <summary>
-	/// Called when an updated configuration has been applied to the
-	/// application. Fires the <see cref="Applied"/> event.
-	/// </summary>
-	public static void OnApplied ()
-	{
-		Debug.WriteLine ("ConfigurationManager.OnApplied()");
-		Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
-
-		// TODO: Refactor ConfigurationManager to not use an event handler for this.
-		// Instead, have it call a method on any class appropriately attributed
-		// to update the cached values. See Issue #2871
-	}
-
-	/// <summary>
-	/// Event fired when an updated configuration has been applied to the
-	/// application.
-	/// </summary>
-	public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
-
-	/// <summary>
-	/// Loads all settings found in the various configuration storage locations to
-	/// the <see cref="ConfigurationManager"/>. Optionally,
-	/// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults.
-	/// </summary>
-	/// <remarks>
-	/// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
-	/// </remarks>
-	/// <param name="reset">
-	/// If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
-	/// be reset to the defaults.
-	/// </param>
-	public static void Load (bool reset = false)
-	{
-		Debug.WriteLine ("ConfigurationManager.Load()");
-
-		if (reset) {
-			Reset ();
-		}
-
-		// LibraryResources is always loaded by Reset
-		if (Locations == ConfigLocations.All) {
-			var embeddedStylesResourceName = Assembly.GetEntryAssembly ()?
-				.GetManifestResourceNames ().FirstOrDefault (x => x.EndsWith (_configFilename));
-			if (string.IsNullOrEmpty (embeddedStylesResourceName)) {
-				embeddedStylesResourceName = _configFilename;
-			}
-
-			Settings = Settings?
-				// Global current directory
-				.Update ($"./.tui/{_configFilename}")?
-				// Global home directory
-				.Update ($"~/.tui/{_configFilename}")?
-				// App resources
-				.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)?
-				// App current directory
-				.Update ($"./.tui/{AppName}.{_configFilename}")?
-				// App home directory
-				.Update ($"~/.tui/{AppName}.{_configFilename}");
-		}
-	}
-
-	/// <summary>
-	/// Returns an empty Json document with just the $schema tag.
-	/// </summary>
-	/// <returns></returns>
-	public static string GetEmptyJson ()
-	{
-		var emptyScope = new SettingsScope ();
-		emptyScope.Clear ();
-		return JsonSerializer.Serialize (emptyScope, _serializerOptions);
-	}
-
-	/// <summary>
-	/// System.Text.Json does not support copying a deserialized object to an existing instance.
-	/// To work around this, we implement a 'deep, memberwise copy' method.
-	/// </summary>
-	/// <remarks>
-	/// TOOD: When System.Text.Json implements `PopulateObject` revisit
-	/// https://github.com/dotnet/corefx/issues/37627
-	/// </remarks>
-	/// <param name="source"></param>
-	/// <param name="destination"></param>
-	/// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
-	internal static object? DeepMemberwiseCopy (object? source, object? destination)
-	{
-		if (destination == null) {
-			throw new ArgumentNullException (nameof (destination));
-		}
-
-		if (source == null) {
-			return null!;
-		}
-
-		if (source.GetType () == typeof (SettingsScope)) {
-			return ((SettingsScope)destination).Update ((SettingsScope)source);
-		}
-		if (source.GetType () == typeof (ThemeScope)) {
-			return ((ThemeScope)destination).Update ((ThemeScope)source);
-		}
-		if (source.GetType () == typeof (AppScope)) {
-			return ((AppScope)destination).Update ((AppScope)source);
-		}
-
-		// If value type, just use copy constructor.
-		if (source.GetType ().IsValueType || source.GetType () == typeof (string)) {
-			return source;
-		}
-
-		// Dictionary
-		if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
-			foreach (var srcKey in ((IDictionary)source).Keys) {
-				if (srcKey is string) { }
-				if (((IDictionary)destination).Contains (srcKey)) {
-					((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
-				} else {
-					((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
-				}
-			}
-			return destination;
-		}
-
-		// ALl other object types
-		var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
-		var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
-		foreach ((var sourceProp, var destProp) in
-			from sourceProp in sourceProps
-			where destProps.Any (x => x.Name == sourceProp.Name)
-			let destProp = destProps.First (x => x.Name == sourceProp.Name)
-			where destProp.CanWrite
-			select (sourceProp, destProp)) {
-
-			var sourceVal = sourceProp.GetValue (source);
-			var destVal = destProp.GetValue (destination);
-			if (sourceVal != null) {
-				try {
-					if (destVal != null) {
-						// Recurse
-						destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
-					} else {
-						destProp.SetValue (destination, sourceVal);
-					}
-				} catch (ArgumentException e) {
-					throw new JsonException ($"Error Applying Configuration Change: {e.Message}", e);
-				}
-			}
-		}
-		return destination!;
-	}
-}
+public static class ConfigurationManager
+{
+    /// <summary>
+    ///     Describes the location of the configuration files. The constants can be combined (bitwise) to specify multiple
+    ///     locations.
+    /// </summary>
+    [Flags]
+    public enum ConfigLocations
+    {
+        /// <summary>No configuration will be loaded.</summary>
+        /// <remarks>
+        ///     Used for development and testing only. For Terminal,Gui to function properly, at least
+        ///     <see cref="DefaultOnly"/> should be set.
+        /// </remarks>
+        None = 0,
+
+        /// <summary>
+        ///     Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) --
+        ///     Lowest Precedence.
+        /// </summary>
+        DefaultOnly,
+
+        /// <summary>This constant is a combination of all locations</summary>
+        All = -1
+    }
+
+    /// <summary>
+    ///     A dictionary of all properties in the Terminal.Gui project that are decorated with the
+    ///     <see cref="SerializableConfigurationProperty"/> attribute. The keys are the property names pre-pended with the
+    ///     class that implements the property (e.g. <c>Application.UseSystemConsole</c>). The values are instances of
+    ///     <see cref="ConfigProperty"/> which hold the property's value and the <see cref="PropertyInfo"/> that allows
+    ///     <see cref="ConfigurationManager"/> to get and set the property's value.
+    /// </summary>
+    /// <remarks>Is <see langword="null"/> until <see cref="Initialize"/> is called.</remarks>
+    internal static Dictionary<string, ConfigProperty>? _allConfigProperties;
+
+    internal static readonly JsonSerializerOptions _serializerOptions = new ()
+    {
+        ReadCommentHandling = JsonCommentHandling.Skip,
+        PropertyNameCaseInsensitive = true,
+        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+        WriteIndented = true,
+        Converters =
+        {
+            // We override the standard Rune converter to support specifying Glyphs in
+            // a flexible way
+            new RuneJsonConverter (),
+
+            // Override Key to support "Ctrl+Q" format.
+            new KeyJsonConverter ()
+        },
+
+        // Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
+        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+    };
+
+    internal static StringBuilder jsonErrors = new ();
+
+    private static readonly string _configFilename = "config.json";
+
+    /// <summary>The backing property for <see cref="Settings"/>.</summary>
+    /// <remarks>
+    ///     Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by deserialization
+    ///     (see <see cref="Load"/>).
+    /// </remarks>
+    private static SettingsScope? _settings;
+
+    /// <summary>Name of the running application. By default this property is set to the application's assembly name.</summary>
+    public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
+
+    /// <summary>Application-specific configuration settings scope.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+    [JsonPropertyName ("AppSettings")]
+    public static AppScope? AppSettings { get; set; }
+
+    /// <summary>
+    ///     The set of glyphs used to draw checkboxes, lines, borders, etc...See also
+    ///     <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+    [JsonPropertyName ("Glyphs")]
+    public static GlyphDefinitions Glyphs { get; set; } = new ();
+
+    /// <summary>
+    ///     Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files. The value is
+    ///     <see cref="ConfigLocations.All"/>.
+    /// </summary>
+    public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
+
+    /// <summary>
+    ///     The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the
+    ///     <see cref="SettingsScope"/> attribute value.
+    /// </summary>
+    public static SettingsScope? Settings
+    {
+        get
+        {
+            if (_settings == null)
+            {
+                throw new InvalidOperationException (
+                                                     "ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property."
+                                                    );
+            }
+
+            return _settings;
+        }
+        set => _settings = value!;
+    }
+
+    /// <summary>
+    ///     The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
+    ///     attribute value.
+    /// </summary>
+    public static ThemeManager? Themes => ThemeManager.Instance;
+
+    /// <summary>
+    ///     Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters an
+    ///     error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the console
+    ///     when <see cref="Application.Shutdown"/> is called.
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool? ThrowOnJsonErrors { get; set; } = false;
+
+    /// <summary>Event fired when an updated configuration has been applied to the application.</summary>
+    public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
+
+    /// <summary>Applies the configuration settings to the running <see cref="Application"/> instance.</summary>
+    public static void Apply ()
+    {
+        var settings = false;
+        var themes = false;
+        var appSettings = false;
+
+        try
+        {
+            settings = Settings?.Apply () ?? false;
+
+            themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme)
+                     && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+            appSettings = AppSettings?.Apply () ?? false;
+        }
+        catch (JsonException e)
+        {
+            if (ThrowOnJsonErrors ?? false)
+            {
+                throw;
+            }
+            else
+            {
+                AddJsonError ($"Error applying Configuration Change: {e.Message}");
+            }
+        }
+        finally
+        {
+            if (settings || themes || appSettings)
+            {
+                OnApplied ();
+            }
+        }
+    }
+
+    /// <summary>Returns an empty Json document with just the $schema tag.</summary>
+    /// <returns></returns>
+    public static string GetEmptyJson ()
+    {
+        var emptyScope = new SettingsScope ();
+        emptyScope.Clear ();
+
+        return JsonSerializer.Serialize (emptyScope, _serializerOptions);
+    }
+
+    /// <summary>
+    ///     Loads all settings found in the various configuration storage locations to the
+    ///     <see cref="ConfigurationManager"/>. Optionally, resets all settings attributed with
+    ///     <see cref="SerializableConfigurationProperty"/> to the defaults.
+    /// </summary>
+    /// <remarks>Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.</remarks>
+    /// <param name="reset">
+    ///     If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will be reset to the
+    ///     defaults.
+    /// </param>
+    public static void Load (bool reset = false)
+    {
+        Debug.WriteLine ("ConfigurationManager.Load()");
+
+        if (reset)
+        {
+            Reset ();
+        }
+
+        // LibraryResources is always loaded by Reset
+        if (Locations == ConfigLocations.All)
+        {
+            string? embeddedStylesResourceName = Assembly.GetEntryAssembly ()
+                                                         ?
+                                                         .GetManifestResourceNames ()
+                                                         .FirstOrDefault (x => x.EndsWith (_configFilename));
+
+            if (string.IsNullOrEmpty (embeddedStylesResourceName))
+            {
+                embeddedStylesResourceName = _configFilename;
+            }
+
+            Settings = Settings?
+
+                       // Global current directory
+                       .Update ($"./.tui/{_configFilename}")
+                       ?
+
+                       // Global home directory
+                       .Update ($"~/.tui/{_configFilename}")
+                       ?
+
+                       // App resources
+                       .UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)
+                       ?
+
+                       // App current directory
+                       .Update ($"./.tui/{AppName}.{_configFilename}")
+                       ?
+
+                       // App home directory
+                       .Update ($"~/.tui/{AppName}.{_configFilename}");
+        }
+    }
+
+    /// <summary>
+    ///     Called when an updated configuration has been applied to the application. Fires the <see cref="Applied"/>
+    ///     event.
+    /// </summary>
+    public static void OnApplied ()
+    {
+        Debug.WriteLine ("ConfigurationManager.OnApplied()");
+        Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
+
+        // TODO: Refactor ConfigurationManager to not use an event handler for this.
+        // Instead, have it call a method on any class appropriately attributed
+        // to update the cached values. See Issue #2871
+    }
+
+    /// <summary>
+    ///     Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
+    ///     event.
+    /// </summary>
+    public static void OnUpdated ()
+    {
+        Debug.WriteLine (@"ConfigurationManager.OnApplied()");
+        Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
+    }
+
+    /// <summary>Prints any Json deserialization errors that occurred during deserialization to the console.</summary>
+    public static void PrintJsonErrors ()
+    {
+        if (jsonErrors.Length > 0)
+        {
+            Console.WriteLine (
+                               @"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:"
+                              );
+            Console.WriteLine (jsonErrors.ToString ());
+        }
+    }
+
+    /// <summary>
+    ///     Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session (e.g. in
+    ///     <see cref="Application.Init"/> starts. Called by <see cref="Load"/> if the <c>reset</c> parameter is
+    ///     <see langword="true"/>.
+    /// </summary>
+    /// <remarks></remarks>
+    public static void Reset ()
+    {
+        Debug.WriteLine (@"ConfigurationManager.Reset()");
+
+        if (_allConfigProperties == null)
+        {
+            Initialize ();
+        }
+
+        ClearJsonErrors ();
+
+        Settings = new SettingsScope ();
+        ThemeManager.Reset ();
+        AppSettings = new AppScope ();
+
+        // To enable some unit tests, we only load from resources if the flag is set
+        if (Locations.HasFlag (ConfigLocations.DefaultOnly))
+        {
+            Settings.UpdateFromResource (
+                                         typeof (ConfigurationManager).Assembly,
+                                         $"Terminal.Gui.Resources.{_configFilename}"
+                                        );
+        }
+
+        Apply ();
+        ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
+        AppSettings?.Apply ();
+    }
+
+    /// <summary>Event fired when the configuration has been updated from a configuration source. application.</summary>
+    public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
+
+    internal static void AddJsonError (string error)
+    {
+        Debug.WriteLine ($"ConfigurationManager: {error}");
+        jsonErrors.AppendLine (error);
+    }
+
+    /// <summary>
+    ///     System.Text.Json does not support copying a deserialized object to an existing instance. To work around this,
+    ///     we implement a 'deep, memberwise copy' method.
+    /// </summary>
+    /// <remarks>TOOD: When System.Text.Json implements `PopulateObject` revisit https://github.com/dotnet/corefx/issues/37627</remarks>
+    /// <param name="source"></param>
+    /// <param name="destination"></param>
+    /// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
+    internal static object? DeepMemberwiseCopy (object? source, object? destination)
+    {
+        if (destination == null)
+        {
+            throw new ArgumentNullException (nameof (destination));
+        }
+
+        if (source == null)
+        {
+            return null!;
+        }
+
+        if (source.GetType () == typeof (SettingsScope))
+        {
+            return ((SettingsScope)destination).Update ((SettingsScope)source);
+        }
+
+        if (source.GetType () == typeof (ThemeScope))
+        {
+            return ((ThemeScope)destination).Update ((ThemeScope)source);
+        }
+
+        if (source.GetType () == typeof (AppScope))
+        {
+            return ((AppScope)destination).Update ((AppScope)source);
+        }
+
+        // If value type, just use copy constructor.
+        if (source.GetType ().IsValueType || source.GetType () == typeof (string))
+        {
+            return source;
+        }
+
+        // Dictionary
+        if (source.GetType ().IsGenericType
+            && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>)))
+        {
+            foreach (object? srcKey in ((IDictionary)source).Keys)
+            {
+                if (srcKey is string)
+                { }
+
+                if (((IDictionary)destination).Contains (srcKey))
+                {
+                    ((IDictionary)destination) [srcKey] =
+                        DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
+                }
+                else
+                {
+                    ((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
+                }
+            }
+
+            return destination;
+        }
+
+        // ALl other object types
+        List<PropertyInfo>? sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
+        List<PropertyInfo>? destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
+
+        foreach ((PropertyInfo? sourceProp, PropertyInfo? destProp) in
+                 from sourceProp in sourceProps
+                 where destProps.Any (x => x.Name == sourceProp.Name)
+                 let destProp = destProps.First (x => x.Name == sourceProp.Name)
+                 where destProp.CanWrite
+                 select (sourceProp, destProp))
+        {
+            object? sourceVal = sourceProp.GetValue (source);
+            object? destVal = destProp.GetValue (destination);
+
+            if (sourceVal != null)
+            {
+                try
+                {
+                    if (destVal != null)
+                    {
+                        // Recurse
+                        destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
+                    }
+                    else
+                    {
+                        destProp.SetValue (destination, sourceVal);
+                    }
+                }
+                catch (ArgumentException e)
+                {
+                    throw new JsonException ($"Error Applying Configuration Change: {e.Message}", e);
+                }
+            }
+        }
+
+        return destination!;
+    }
+
+    /// <summary>
+    ///     Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
+    ///     the library to generate the default configuration file. Before calling Application.Init, make sure
+    ///     <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method is only really useful when using ConfigurationManagerTests to generate the JSON doc that is
+    ///         embedded into Terminal.Gui (during development).
+    ///     </para>
+    ///     <para>
+    ///         WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes) that are NOT
+    ///         generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
+    ///         make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
+    ///     </para>
+    /// </remarks>
+    internal static void GetHardCodedDefaults ()
+    {
+        if (_allConfigProperties == null)
+        {
+            throw new InvalidOperationException ("Initialize must be called first.");
+        }
+
+        Settings = new SettingsScope ();
+        ThemeManager.GetHardCodedDefaults ();
+        AppSettings?.RetrieveValues ();
+
+        foreach (KeyValuePair<string, ConfigProperty> p in Settings!.Where (cp => cp.Value.PropertyInfo != null))
+        {
+            Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
+        }
+    }
+
+    /// <summary>
+    ///     Initializes the internal state of ConfigurationManager. Nominally called once as part of application startup
+    ///     to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
+    /// </summary>
+    internal static void Initialize ()
+    {
+        _allConfigProperties = new Dictionary<string, ConfigProperty> ();
+        _settings = null;
+
+        Dictionary<string, Type> classesWithConfigProps = new (StringComparer.InvariantCultureIgnoreCase);
+
+        // Get Terminal.Gui.dll classes
+
+        IEnumerable<Type> types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
+                                  from type in assembly.GetTypes ()
+                                  where type.GetProperties ()
+                                            .Any (
+                                                  prop => prop.GetCustomAttribute (
+                                                                                   typeof (SerializableConfigurationProperty)
+                                                                                  )
+                                                          != null
+                                                 )
+                                  select type;
+
+        foreach (Type? classWithConfig in types)
+        {
+            classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
+        }
+
+        Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
+        classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($"  Class: {x.Key}"));
+
+        foreach (PropertyInfo? p in from c in classesWithConfigProps
+                                    let props = c.Value
+                                                 .GetProperties (
+                                                                 BindingFlags.Instance
+                                                                 | BindingFlags.Static
+                                                                 | BindingFlags.NonPublic
+                                                                 | BindingFlags.Public
+                                                                )
+                                                 .Where (
+                                                         prop =>
+                                                             prop.GetCustomAttribute (
+                                                                                      typeof (SerializableConfigurationProperty)
+                                                                                     ) is
+                                                                 SerializableConfigurationProperty
+                                                        )
+                                    let enumerable = props
+                                    from p in enumerable
+                                    select p)
+        {
+            if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty
+                scp)
+            {
+                if (p.GetGetMethod (true)!.IsStatic)
+                {
+                    // If the class name is omitted, JsonPropertyName is allowed. 
+                    _allConfigProperties!.Add (
+                                               scp.OmitClassName
+                                                   ? ConfigProperty.GetJsonPropertyName (p)
+                                                   : $"{p.DeclaringType?.Name}.{p.Name}",
+                                               new ConfigProperty { PropertyInfo = p, PropertyValue = null }
+                                              );
+                }
+                else
+                {
+                    throw new Exception (
+                                         $"Property {
+                                             p.Name
+                                         } in class {
+                                             p.DeclaringType?.Name
+                                         } is not static. All SerializableConfigurationProperty properties must be static."
+                                        );
+                }
+            }
+        }
+
+        _allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key)
+                                                    .ToDictionary (
+                                                                   x => x.Key,
+                                                                   x => x.Value,
+                                                                   StringComparer.InvariantCultureIgnoreCase
+                                                                  );
+
+        Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
+
+        //_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($"  Property: {x.Key}"));
+
+        AppSettings = new AppScope ();
+    }
+
+    /// <summary>Creates a JSON document with the configuration specified.</summary>
+    /// <returns></returns>
+    internal static string ToJson ()
+    {
+        Debug.WriteLine ("ConfigurationManager.ToJson()");
+
+        return JsonSerializer.Serialize (Settings!, _serializerOptions);
+    }
+
+    internal static Stream ToStream ()
+    {
+        string json = JsonSerializer.Serialize (Settings!, _serializerOptions);
+
+        // turn it into a stream
+        var stream = new MemoryStream ();
+        var writer = new StreamWriter (stream);
+        writer.Write (json);
+        writer.Flush ();
+        stream.Position = 0;
+
+        return stream;
+    }
+
+    private static void ClearJsonErrors () { jsonErrors.Clear (); }
+}

+ 15 - 31
Terminal.Gui/Configuration/ConfigurationManagerEventArgs.cs

@@ -1,36 +1,20 @@
-using System;
+#nullable enable
 
-#nullable enable
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event arguments for the <see cref="ConfigurationManager"/> events.
-	/// </summary>
-	public class ConfigurationManagerEventArgs : EventArgs {
-
-		/// <summary>
-		/// Initializes a new instance of <see cref="ConfigurationManagerEventArgs"/>
-		/// </summary>
-		public ConfigurationManagerEventArgs ()
-		{
-		}
-	}
+/// <summary>Event arguments for the <see cref="ConfigurationManager"/> events.</summary>
+public class ConfigurationManagerEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="ConfigurationManagerEventArgs"/></summary>
+    public ConfigurationManagerEventArgs () { }
+}
 
-	/// <summary>
-	/// Event arguments for the <see cref="ThemeManager"/> events.
-	/// </summary>
-	public class ThemeManagerEventArgs : EventArgs {
-		/// <summary>
-		/// The name of the new active theme..
-		/// </summary>
-		public string NewTheme { get; set; } = string.Empty;
+/// <summary>Event arguments for the <see cref="ThemeManager"/> events.</summary>
+public class ThemeManagerEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="ThemeManagerEventArgs"/></summary>
+    public ThemeManagerEventArgs (string newTheme) { NewTheme = newTheme; }
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ThemeManagerEventArgs"/>
-		/// </summary>
-		public ThemeManagerEventArgs (string newTheme)
-		{
-			NewTheme = newTheme;
-		}
-	}
+    /// <summary>The name of the new active theme..</summary>
+    public string NewTheme { get; set; } = string.Empty;
 }

+ 58 - 41
Terminal.Gui/Configuration/DictionaryJsonConverter.cs

@@ -1,43 +1,60 @@
-using System;
-using System.Collections.Generic;
+using System.Text.Json;
 using System.Text.Json.Serialization;
-using System.Text.Json;
-
-namespace Terminal.Gui {
-	class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>> {
-		public override Dictionary<string, T> Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-		{
-			if (reader.TokenType != JsonTokenType.StartArray) {
-				throw new JsonException ($"Expected a JSON array (\"[ {{ ... }} ]\"), but got \"{reader.TokenType}\".");
-			}
-
-			var dictionary = new Dictionary<string, T> ();
-			while (reader.Read ()) {
-				if (reader.TokenType == JsonTokenType.StartObject) {
-					reader.Read ();
-					if (reader.TokenType == JsonTokenType.PropertyName) {
-						string key = reader.GetString ();
-						reader.Read ();
-						T value = JsonSerializer.Deserialize<T> (ref reader, options);
-						dictionary.Add (key, value);
-					}
-				} else if (reader.TokenType == JsonTokenType.EndArray)
-					break;
-			}
-			return dictionary;
-		}
-
-		public override void Write (Utf8JsonWriter writer, Dictionary<string, T> value, JsonSerializerOptions options)
-		{
-			writer.WriteStartArray ();
-			foreach (var item in value) {
-				writer.WriteStartObject ();
-				//writer.WriteString (item.Key, item.Key);
-				writer.WritePropertyName (item.Key);
-				JsonSerializer.Serialize (writer, item.Value, options);
-				writer.WriteEndObject ();
-			}
-			writer.WriteEndArray ();
-		}
-	}
+
+namespace Terminal.Gui;
+
+internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
+{
+    public override Dictionary<string, T> Read (
+        ref Utf8JsonReader reader,
+        Type typeToConvert,
+        JsonSerializerOptions options
+    )
+    {
+        if (reader.TokenType != JsonTokenType.StartArray)
+        {
+            throw new JsonException ($"Expected a JSON array (\"[ {{ ... }} ]\"), but got \"{reader.TokenType}\".");
+        }
+
+        Dictionary<string, T> dictionary = new ();
+
+        while (reader.Read ())
+        {
+            if (reader.TokenType == JsonTokenType.StartObject)
+            {
+                reader.Read ();
+
+                if (reader.TokenType == JsonTokenType.PropertyName)
+                {
+                    string key = reader.GetString ();
+                    reader.Read ();
+                    var value = JsonSerializer.Deserialize<T> (ref reader, options);
+                    dictionary.Add (key, value);
+                }
+            }
+            else if (reader.TokenType == JsonTokenType.EndArray)
+            {
+                break;
+            }
+        }
+
+        return dictionary;
+    }
+
+    public override void Write (Utf8JsonWriter writer, Dictionary<string, T> value, JsonSerializerOptions options)
+    {
+        writer.WriteStartArray ();
+
+        foreach (KeyValuePair<string, T> item in value)
+        {
+            writer.WriteStartObject ();
+
+            //writer.WriteString (item.Key, item.Key);
+            writer.WritePropertyName (item.Key);
+            JsonSerializer.Serialize (writer, item.Value, options);
+            writer.WriteEndObject ();
+        }
+
+        writer.WriteEndArray ();
+    }
 }

+ 164 - 122
Terminal.Gui/Configuration/KeyCodeJsonConverter.cs

@@ -1,126 +1,168 @@
-using System;
-using System.Collections.Generic;
-using System.Text.Json;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-class KeyCodeJsonConverter : JsonConverter<KeyCode> {
-	public override KeyCode Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-	{
-		if (reader.TokenType == JsonTokenType.StartObject) {
-			KeyCode key = KeyCode.Null;
-			var modifierDict = new Dictionary<string, KeyCode> (comparer: StringComparer.InvariantCultureIgnoreCase) {
-				{ "Shift", KeyCode.ShiftMask },
-				{ "Ctrl", KeyCode.CtrlMask },
-				{ "Alt", KeyCode.AltMask }
-			};
-
-			var modifiers = new List<KeyCode> ();
-
-			while (reader.Read ()) {
-				if (reader.TokenType == JsonTokenType.EndObject) {
-					break;
-				}
-
-				if (reader.TokenType == JsonTokenType.PropertyName) {
-					string propertyName = reader.GetString ();
-					reader.Read ();
-
-					switch (propertyName.ToLowerInvariant ()) {
-					case "key":
-						if (reader.TokenType == JsonTokenType.String) {
-							if (Enum.TryParse (reader.GetString (), false, out key)) {
-								break;
-							}
-
-							// The enum uses "D0..D9" for the number keys
-							if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key)) {
-								break;
-							}
-
-							if (key == KeyCode.Null) {
-								throw new JsonException ($"The value \"{reader.GetString ()}\" is not a valid Key.");
-							}
-
-						} else if (reader.TokenType == JsonTokenType.Number) {
-							try {
-								key = (KeyCode)reader.GetInt32 ();
-							} catch (InvalidOperationException ioe) {
-								throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
-							} catch (FormatException ioe) {
-								throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
-							}
-							break;
-						}
-						break;
-
-					case "modifiers":
-						if (reader.TokenType == JsonTokenType.StartArray) {
-							while (reader.Read ()) {
-								if (reader.TokenType == JsonTokenType.EndArray) {
-									break;
-								}
-								string mod = reader.GetString ();
-								try {
-									modifiers.Add (modifierDict [mod]);
-								} catch (KeyNotFoundException e) {
-									throw new JsonException ($"The value \"{mod}\" is not a valid modifier.", e);
-								}
-							}
-						} else {
-							throw new JsonException ($"Expected an array of modifiers, but got \"{reader.TokenType}\".");
-						}
-						break;
-
-					default:
-						throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
-					}
-				}
-			}
-
-			foreach (var modifier in modifiers) {
-				key |= modifier;
-			}
-
-			return key;
-		}
-		throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
-	}
-
-	public override void Write (Utf8JsonWriter writer, KeyCode value, JsonSerializerOptions options)
-	{
-		writer.WriteStartObject ();
-
-		string keyName = (value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask).ToString ();
-		if (keyName != null) {
-			writer.WriteString ("Key", keyName);
-		} else {
-			writer.WriteNumber ("Key", (uint)(value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask));
-		}
-
-		var modifierDict = new Dictionary<string, KeyCode> {
-			{ "Shift", KeyCode.ShiftMask },
-			{ "Ctrl", KeyCode.CtrlMask },
-			{ "Alt", KeyCode.AltMask }
-		};
-
-		var modifiers = new List<string> ();
-		foreach (var pair in modifierDict) {
-			if ((value & pair.Value) == pair.Value) {
-				modifiers.Add (pair.Key);
-			}
-		}
-
-		if (modifiers.Count > 0) {
-			writer.WritePropertyName ("Modifiers");
-			writer.WriteStartArray ();
-			foreach (string modifier in modifiers) {
-				writer.WriteStringValue (modifier);
-			}
-			writer.WriteEndArray ();
-		}
-
-		writer.WriteEndObject ();
-	}
-}
+internal class KeyCodeJsonConverter : JsonConverter<KeyCode>
+{
+    public override KeyCode Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        if (reader.TokenType == JsonTokenType.StartObject)
+        {
+            var key = KeyCode.Null;
+
+            Dictionary<string, KeyCode> modifierDict =
+                new (StringComparer.InvariantCultureIgnoreCase)
+                {
+                    { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask }
+                };
+
+            List<KeyCode> modifiers = new ();
+
+            while (reader.Read ())
+            {
+                if (reader.TokenType == JsonTokenType.EndObject)
+                {
+                    break;
+                }
+
+                if (reader.TokenType == JsonTokenType.PropertyName)
+                {
+                    string propertyName = reader.GetString ();
+                    reader.Read ();
+
+                    switch (propertyName.ToLowerInvariant ())
+                    {
+                        case "key":
+                            if (reader.TokenType == JsonTokenType.String)
+                            {
+                                if (Enum.TryParse (reader.GetString (), false, out key))
+                                {
+                                    break;
+                                }
+
+                                // The enum uses "D0..D9" for the number keys
+                                if (Enum.TryParse (reader.GetString ().TrimStart ('D', 'd'), false, out key))
+                                {
+                                    break;
+                                }
+
+                                if (key == KeyCode.Null)
+                                {
+                                    throw new JsonException (
+                                                             $"The value \"{reader.GetString ()}\" is not a valid Key."
+                                                            );
+                                }
+                            }
+                            else if (reader.TokenType == JsonTokenType.Number)
+                            {
+                                try
+                                {
+                                    key = (KeyCode)reader.GetInt32 ();
+                                }
+                                catch (InvalidOperationException ioe)
+                                {
+                                    throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
+                                }
+                                catch (FormatException ioe)
+                                {
+                                    throw new JsonException ($"Error parsing Key value: {ioe.Message}", ioe);
+                                }
+                            }
+
+                            break;
+
+                        case "modifiers":
+                            if (reader.TokenType == JsonTokenType.StartArray)
+                            {
+                                while (reader.Read ())
+                                {
+                                    if (reader.TokenType == JsonTokenType.EndArray)
+                                    {
+                                        break;
+                                    }
+
+                                    string mod = reader.GetString ();
+
+                                    try
+                                    {
+                                        modifiers.Add (modifierDict [mod]);
+                                    }
+                                    catch (KeyNotFoundException e)
+                                    {
+                                        throw new JsonException ($"The value \"{mod}\" is not a valid modifier.", e);
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                throw new JsonException (
+                                                         $"Expected an array of modifiers, but got \"{reader.TokenType}\"."
+                                                        );
+                            }
+
+                            break;
+
+                        default:
+                            throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
+                    }
+                }
+            }
+
+            foreach (KeyCode modifier in modifiers)
+            {
+                key |= modifier;
+            }
+
+            return key;
+        }
+
+        throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
+    }
+
+    public override void Write (Utf8JsonWriter writer, KeyCode value, JsonSerializerOptions options)
+    {
+        writer.WriteStartObject ();
+
+        var keyName = (value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask).ToString ();
+
+        if (keyName != null)
+        {
+            writer.WriteString ("Key", keyName);
+        }
+        else
+        {
+            writer.WriteNumber ("Key", (uint)(value & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask));
+        }
+
+        Dictionary<string, KeyCode> modifierDict = new ()
+        {
+            { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask }
+        };
+
+        List<string> modifiers = new ();
+
+        foreach (KeyValuePair<string, KeyCode> pair in modifierDict)
+        {
+            if ((value & pair.Value) == pair.Value)
+            {
+                modifiers.Add (pair.Key);
+            }
+        }
+
+        if (modifiers.Count > 0)
+        {
+            writer.WritePropertyName ("Modifiers");
+            writer.WriteStartArray ();
+
+            foreach (string modifier in modifiers)
+            {
+                writer.WriteStringValue (modifier);
+            }
+
+            writer.WriteEndArray ();
+        }
+
+        writer.WriteEndObject ();
+    }
+}

+ 12 - 11
Terminal.Gui/Configuration/KeyJsonConverter.cs

@@ -1,16 +1,17 @@
-using System;
-using System.Text.Json;
+using System.Text.Json;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Support for <see cref="Key"/> in JSON in the form of "Ctrl-X" or "Alt-Shift-F1".
-/// </summary>
-public class KeyJsonConverter : JsonConverter<Key> {
-	/// <inheritdoc />
-	public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Key.TryParse (reader.GetString (), out var key) ? key : Key.Empty;
+/// <summary>Support for <see cref="Key"/> in JSON in the form of "Ctrl-X" or "Alt-Shift-F1".</summary>
+public class KeyJsonConverter : JsonConverter<Key>
+{
+    /// <inheritdoc/>
+    public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        return Key.TryParse (reader.GetString (), out Key key) ? key : Key.Empty;
+    }
 
-	/// <inheritdoc />
-	public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options) => writer.WriteStringValue (value.ToString ());
-}
+    /// <inheritdoc/>
+    public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options) { writer.WriteStringValue (value.ToString ()); }
+}

+ 136 - 112
Terminal.Gui/Configuration/RuneJsonConverter.cs

@@ -1,121 +1,145 @@
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Text;
+using System.Globalization;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
+
 /// <summary>
-/// Json converter for <see cref="Rune"/>. Supports
-/// Json converter for <see cref="Rune"/>. Supports
-/// A string as one of:
-/// - unicode char (e.g. "☑")
-/// - U+hex format (e.g. "U+2611")
-/// - \u format (e.g. "\\u2611")
-/// A number
-/// - The unicode code in decimal
+///     Json converter for <see cref="Rune"/>. Supports Json converter for <see cref="Rune"/>. Supports A string as
+///     one of: - unicode char (e.g. "☑") - U+hex format (e.g. "U+2611") - \u format (e.g. "\\u2611") A number - The
+///     unicode code in decimal
 /// </summary>
-internal class RuneJsonConverter : JsonConverter<Rune> {
-	public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-	{
-		switch (reader.TokenType) {
-		case JsonTokenType.String: {
-				var value = reader.GetString ();
-				int first = RuneExtensions.MaxUnicodeCodePoint + 1;
-				int second = RuneExtensions.MaxUnicodeCodePoint + 1;
-
-				if (value.StartsWith ("U+", StringComparison.OrdinalIgnoreCase) || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase)) {
-					// Handle encoded single char, surrogate pair, or combining mark + char
-					var codePoints = Regex.Matches (value, @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
-						.Cast<Match> ()
-						.Select (match => uint.Parse (match.Groups [1].Value, NumberStyles.HexNumber))
-						.ToArray ();
-
-					if (codePoints.Length == 0 || codePoints.Length > 2) {
-						throw new JsonException ($"Invalid Rune: {value}.");
-					}
-
-					if (codePoints.Length > 0) {
-						first = (int)codePoints [0];
-					}
-
-					if (codePoints.Length == 2) {
-						second = (int)codePoints [1];
-					}
-				} else {
-					// Handle single character, surrogate pair, or combining mark + char
-					if (value.Length == 0 || value.Length > 2) {
-						throw new JsonException ($"Invalid Rune: {value}.");
-					}
-
-					if (value.Length > 0) {
-						first = value [0];
-					}
-					if (value.Length == 2) {
-						second = value [1];
-					}
-				}
-
-				Rune result;
-				if (second == RuneExtensions.MaxUnicodeCodePoint + 1) {
-					// Single codepoint
-					if (!Rune.TryCreate (first, out result)) {
-						throw new JsonException ($"Invalid Rune: {value}.");
-					}
-					return result;
-				}
-
-				// Surrogate pair?
-				if (Rune.TryCreate ((char)first, (char)second, out result)) {
-					return result;
-				}
-
-				if (!Rune.IsValid (second)) {
-					throw new JsonException ($"The second codepoint is not valid: {second} in ({value})");
-				}
-
-				var cm = new Rune (second);
-				if (!cm.IsCombiningMark ()) {
-					throw new JsonException ($"The second codepoint is not a combining mark: {cm} in ({value})");
-				}
-
-				// not a surrogate pair, so a combining mark + char?
-				var combined = string.Concat ((char)first, (char)second).Normalize ();
-
-				if (!Rune.IsValid (combined [0])) {
-					throw new JsonException ($"Invalid combined Rune ({value})");
-				}
-
-				return new Rune (combined [0]);
-			}
-		case JsonTokenType.Number: {
-				uint num = reader.GetUInt32 ();
-				if (Rune.IsValid (num)) {
-					return new Rune (num);
-				}
-				throw new JsonException ($"Invalid Rune (not a scalar Unicode value): {num}.");
-			}
-		default:
-			throw new JsonException ($"Unexpected token when parsing Rune: {reader.TokenType}.");
-		}
-	}
-
-	public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOptions options)
-	{
-		// HACK: Writes a JSON comment in addition to the glyph to ease debugging.
-		// Technically, JSON comments are not valid, but we use relaxed decoding
-		// (ReadCommentHandling = JsonCommentHandling.Skip)
-		//writer.WriteCommentValue ($"(U+{value.Value:X8})");
-		//var printable = value.MakePrintable ();
-		//if (printable == Rune.ReplacementChar) {
-		//	writer.WriteStringValue (value.ToString ());
-		//} else {
-		//	//writer.WriteRawValue ($"\"{value}\"");
-		//}
-
-		writer.WriteNumberValue (value.Value);
-	}
+internal class RuneJsonConverter : JsonConverter<Rune>
+{
+    public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        switch (reader.TokenType)
+        {
+            case JsonTokenType.String:
+            {
+                string value = reader.GetString ();
+                int first = RuneExtensions.MaxUnicodeCodePoint + 1;
+                int second = RuneExtensions.MaxUnicodeCodePoint + 1;
+
+                if (value.StartsWith ("U+", StringComparison.OrdinalIgnoreCase)
+                    || value.StartsWith ("\\U", StringComparison.OrdinalIgnoreCase))
+                {
+                    // Handle encoded single char, surrogate pair, or combining mark + char
+                    uint [] codePoints = Regex.Matches (value, @"(?:\\[uU]\+?|U\+)([0-9A-Fa-f]{1,8})")
+                                              .Select (
+                                                       match => uint.Parse (
+                                                                            match.Groups [1].Value,
+                                                                            NumberStyles.HexNumber
+                                                                           )
+                                                      )
+                                              .ToArray ();
+
+                    if (codePoints.Length == 0 || codePoints.Length > 2)
+                    {
+                        throw new JsonException ($"Invalid Rune: {value}.");
+                    }
+
+                    if (codePoints.Length > 0)
+                    {
+                        first = (int)codePoints [0];
+                    }
+
+                    if (codePoints.Length == 2)
+                    {
+                        second = (int)codePoints [1];
+                    }
+                }
+                else
+                {
+                    // Handle single character, surrogate pair, or combining mark + char
+                    if (value.Length == 0 || value.Length > 2)
+                    {
+                        throw new JsonException ($"Invalid Rune: {value}.");
+                    }
+
+                    if (value.Length > 0)
+                    {
+                        first = value [0];
+                    }
+
+                    if (value.Length == 2)
+                    {
+                        second = value [1];
+                    }
+                }
+
+                Rune result;
+
+                if (second == RuneExtensions.MaxUnicodeCodePoint + 1)
+                {
+                    // Single codepoint
+                    if (!Rune.TryCreate (first, out result))
+                    {
+                        throw new JsonException ($"Invalid Rune: {value}.");
+                    }
+
+                    return result;
+                }
+
+                // Surrogate pair?
+                if (Rune.TryCreate ((char)first, (char)second, out result))
+                {
+                    return result;
+                }
+
+                if (!Rune.IsValid (second))
+                {
+                    throw new JsonException ($"The second codepoint is not valid: {second} in ({value})");
+                }
+
+                var cm = new Rune (second);
+
+                if (!cm.IsCombiningMark ())
+                {
+                    throw new JsonException ($"The second codepoint is not a combining mark: {cm} in ({value})");
+                }
+
+                // not a surrogate pair, so a combining mark + char?
+                string combined = string.Concat ((char)first, (char)second).Normalize ();
+
+                if (!Rune.IsValid (combined [0]))
+                {
+                    throw new JsonException ($"Invalid combined Rune ({value})");
+                }
+
+                return new Rune (combined [0]);
+            }
+            case JsonTokenType.Number:
+            {
+                uint num = reader.GetUInt32 ();
+
+                if (Rune.IsValid (num))
+                {
+                    return new Rune (num);
+                }
+
+                throw new JsonException ($"Invalid Rune (not a scalar Unicode value): {num}.");
+            }
+            default:
+                throw new JsonException ($"Unexpected token when parsing Rune: {reader.TokenType}.");
+        }
+    }
+
+    public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOptions options)
+    {
+        // HACK: Writes a JSON comment in addition to the glyph to ease debugging.
+        // Technically, JSON comments are not valid, but we use relaxed decoding
+        // (ReadCommentHandling = JsonCommentHandling.Skip)
+        //writer.WriteCommentValue ($"(U+{value.Value:X8})");
+        //var printable = value.MakePrintable ();
+        //if (printable == Rune.ReplacementChar) {
+        //	writer.WriteStringValue (value.ToString ());
+        //} else {
+        //	//writer.WriteRawValue ($"\"{value}\"");
+        //}
+
+        writer.WriteNumberValue (value.Value);
+    }
 }
 #pragma warning restore 1591

+ 72 - 67
Terminal.Gui/Configuration/Scope.cs

@@ -1,76 +1,81 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
+#nullable enable
 using System.Reflection;
-using static Terminal.Gui.ConfigurationManager;
 
-#nullable enable
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
+/// <summary>
+///     Defines a configuration settings scope. Classes that inherit from this abstract class can be used to define
+///     scopes for configuration settings. Each scope is a JSON object that contains a set of configuration settings.
+/// </summary>
+public class Scope<T> : Dictionary<string, ConfigProperty>
+{ //, IScope<Scope<T>> {
+    /// <summary>Crates a new instance.</summary>
+    public Scope () : base (StringComparer.InvariantCultureIgnoreCase)
+    {
+        foreach (KeyValuePair<string, ConfigProperty> p in GetScopeProperties ())
+        {
+            Add (p.Key, new ConfigProperty { PropertyInfo = p.Value.PropertyInfo, PropertyValue = null });
+        }
+    }
 
-	/// <summary>
-	/// Defines a configuration settings scope. Classes that inherit from this abstract class can be used to define
-	/// scopes for configuration settings. Each scope is a JSON object that contains a set of configuration settings.
-	/// </summary>
-	public class Scope<T> : Dictionary<string, ConfigProperty> { //, IScope<Scope<T>> {
-		/// <summary>
-		/// Crates a new instance.
-		/// </summary>
-		public Scope () : base (StringComparer.InvariantCultureIgnoreCase)
-		{
-			foreach (var p in GetScopeProperties ()) {
-				Add (p.Key, new ConfigProperty () { PropertyInfo = p.Value.PropertyInfo, PropertyValue = null });
-			}
-		}
+    /// <summary>Retrieves the values of the properties of this scope from their corresponding static properties.</summary>
+    public void RetrieveValues ()
+    {
+        foreach (KeyValuePair<string, ConfigProperty> p in this.Where (cp => cp.Value.PropertyInfo != null))
+        {
+            p.Value.RetrieveValue ();
+        }
+    }
 
-		private IEnumerable<KeyValuePair<string, ConfigProperty>> GetScopeProperties ()
-		{
-			return ConfigurationManager._allConfigProperties!.Where (cp =>
-				(cp.Value.PropertyInfo?.GetCustomAttribute (typeof (SerializableConfigurationProperty))
-				as SerializableConfigurationProperty)?.Scope == GetType ());
-		}
+    /// <summary>Updates this instance from the specified source scope.</summary>
+    /// <param name="source"></param>
+    /// <returns>The updated scope (this).</returns>
+    public Scope<T>? Update (Scope<T> source)
+    {
+        foreach (KeyValuePair<string, ConfigProperty> prop in source)
+        {
+            if (ContainsKey (prop.Key))
+            {
+                this [prop.Key].PropertyValue = this [prop.Key].UpdateValueFrom (prop.Value.PropertyValue!);
+            }
+            else
+            {
+                this [prop.Key].PropertyValue = prop.Value.PropertyValue;
+            }
+        }
 
-		/// <summary>
-		/// Updates this instance from the specified source scope.
-		/// </summary>
-		/// <param name="source"></param>
-		/// <returns>The updated scope (this).</returns>
-		public Scope<T>? Update (Scope<T> source)
-		{
-			foreach (var prop in source) {
-				if (ContainsKey (prop.Key))
-					this [prop.Key].PropertyValue = this [prop.Key].UpdateValueFrom (prop.Value.PropertyValue!);
-				else {
-					this [prop.Key].PropertyValue = prop.Value.PropertyValue;
-				}
-			}
-			return this;
-		}
+        return this;
+    }
 
-		/// <summary>
-		/// Retrieves the values of the properties of this scope from their corresponding static properties.
-		/// </summary>
-		public void RetrieveValues ()
-		{
-			foreach (var p in this.Where (cp => cp.Value.PropertyInfo != null)) {
-				p.Value.RetrieveValue ();
-			}
-		}
+    /// <summary>Applies the values of the properties of this scope to their corresponding static properties.</summary>
+    /// <returns></returns>
+    internal virtual bool Apply ()
+    {
+        var set = false;
 
-		/// <summary>
-		/// Applies the values of the properties of this scope to their corresponding static properties.
-		/// </summary>
-		/// <returns></returns>
-		internal virtual bool Apply ()
-		{
-			bool set = false;
-			foreach (var p in this.Where (t => t.Value != null && t.Value.PropertyValue != null)) {
-				if (p.Value.Apply ()) {
-					set = true;
-				}
-			}
-			return set;
-		}
-	}
+        foreach (KeyValuePair<string, ConfigProperty> p in this.Where (
+                                                                       t => t.Value != null
+                                                                            && t.Value.PropertyValue != null
+                                                                      ))
+        {
+            if (p.Value.Apply ())
+            {
+                set = true;
+            }
+        }
+
+        return set;
+    }
+
+    private IEnumerable<KeyValuePair<string, ConfigProperty>> GetScopeProperties ()
+    {
+        return _allConfigProperties!.Where (
+                                            cp =>
+                                                (cp.Value.PropertyInfo?.GetCustomAttribute (
+                                                                                            typeof (SerializableConfigurationProperty)
+                                                                                           )
+                                                     as SerializableConfigurationProperty)?.Scope
+                                                == GetType ()
+                                           );
+    }
 }

+ 223 - 130
Terminal.Gui/Configuration/ScopeJsonConverter.cs

@@ -1,140 +1,233 @@
-using System;
-using System.Linq;
+#nullable enable
+using System.Diagnostics;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-#nullable enable
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Converts <see cref="Scope{T}"/> instances to/from JSON. Does all the heavy lifting of reading/writing
-/// config data to/from <see cref="ConfigurationManager"/> JSON documents.
+///     Converts <see cref="Scope{T}"/> instances to/from JSON. Does all the heavy lifting of reading/writing config
+///     data to/from <see cref="ConfigurationManager"/> JSON documents.
 /// </summary>
 /// <typeparam name="scopeT"></typeparam>
-internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT : Scope<scopeT> {
-	// See: https://stackoverflow.com/questions/60830084/how-to-pass-an-argument-by-reference-using-reflection
-	internal abstract class ReadHelper {
-		public abstract object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
-	}
-
-	internal class ReadHelper<converterT> : ReadHelper {
-		private readonly ReadDelegate _readDelegate;
-		private delegate converterT ReadDelegate (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
-		public ReadHelper (object converter)
-			=> _readDelegate = (ReadDelegate)Delegate.CreateDelegate (typeof (ReadDelegate), converter, "Read");
-		public override object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
-			=> _readDelegate.Invoke (ref reader, type, options);
-	}
-
-	public override scopeT Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-	{
-		if (reader.TokenType != JsonTokenType.StartObject) {
-			throw new JsonException ($"Expected a JSON object (\"{{ \"propName\" : ... }}\"), but got \"{reader.TokenType}\".");
-		}
-
-		var scope = (scopeT)Activator.CreateInstance (typeof (scopeT))!;
-		while (reader.Read ()) {
-			if (reader.TokenType == JsonTokenType.EndObject) {
-				return scope!;
-			}
-			if (reader.TokenType != JsonTokenType.PropertyName) {
-				throw new JsonException ($"Expected a JSON property name, but got \"{reader.TokenType}\".");
-			}
-			var propertyName = reader.GetString ();
-			reader.Read ();
-
-			if (propertyName != null && scope!.TryGetValue (propertyName, out var configProp)) {
-				// This property name was found in the Scope's ScopeProperties dictionary
-				// Figure out if it needs a JsonConverter and if so, create one
-				var propertyType = configProp?.PropertyInfo?.PropertyType!;
-				if (configProp?.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is JsonConverterAttribute jca) {
-					var converter = Activator.CreateInstance (jca.ConverterType!)!;
-					if (converter.GetType ().BaseType == typeof (JsonConverterFactory)) {
-						var factory = (JsonConverterFactory)converter;
-						if (propertyType != null && factory.CanConvert (propertyType)) {
-							converter = factory.CreateConverter (propertyType, options);
-						}
-					}
-					var readHelper = Activator.CreateInstance ((Type?)typeof (ReadHelper<>).MakeGenericType (typeof (scopeT), propertyType!)!, converter) as ReadHelper;
-					try {
-						scope! [propertyName].PropertyValue = readHelper?.Read (ref reader, propertyType!, options);
-					} catch (NotSupportedException e) {
-						throw new JsonException ($"Error reading property \"{propertyName}\" of type \"{propertyType?.Name}\".", e);
-					}
-				} else {
-					try {
-						scope! [propertyName].PropertyValue = JsonSerializer.Deserialize (ref reader, propertyType!, options);
-					} catch (Exception ex) {
-						System.Diagnostics.Debug.WriteLine ($"scopeT Read: {ex}");
-					}
-				}
-			} else {
-				// It is not a config property. Maybe it's just a property on the Scope with [JsonInclude]
-				// like ScopeSettings.$schema...
-				var property = scope!.GetType ().GetProperties ().Where (p => {
-					var jia = p.GetCustomAttribute (typeof (JsonIncludeAttribute)) as JsonIncludeAttribute;
-					if (jia != null) {
-						var jpna = p.GetCustomAttribute (typeof (JsonPropertyNameAttribute)) as JsonPropertyNameAttribute;
-						if (jpna?.Name == propertyName) {
-							// Bit of a hack, modifying propertyName in an enumerator...
-							propertyName = p.Name;
-							return true;
-						}
-
-						return p.Name == propertyName;
-					}
-					return false;
-				}).FirstOrDefault ();
-
-				if (property != null) {
-					var prop = scope.GetType ().GetProperty (propertyName!)!;
-					prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, options));
-				} else {
-					// Unknown property
-					throw new JsonException ($"Unknown property name \"{propertyName}\".");
-				}
-			}
-		}
-		throw new JsonException ();
-	}
-
-	public override void Write (Utf8JsonWriter writer, scopeT scope, JsonSerializerOptions options)
-	{
-		writer.WriteStartObject ();
-
-		var properties = scope!.GetType ().GetProperties ().Where (p => p.GetCustomAttribute (typeof (JsonIncludeAttribute)) != null);
-		foreach (var p in properties) {
-			writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
-			JsonSerializer.Serialize (writer, scope.GetType ().GetProperty (p.Name)?.GetValue (scope), options);
-		}
-
-		foreach (var p in from p in scope
-					.Where (cp =>
-					cp.Value.PropertyInfo?.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is
-					SerializableConfigurationProperty scp && scp?.Scope == typeof (scopeT))
-					where p.Value.PropertyValue != null
-					select p) {
-
-			writer.WritePropertyName (p.Key);
-			var propertyType = p.Value.PropertyInfo?.PropertyType;
-
-			if (propertyType != null && p.Value.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is JsonConverterAttribute jca) {
-				var converter = Activator.CreateInstance (jca.ConverterType!)!;
-				if (converter.GetType ().BaseType == typeof (JsonConverterFactory)) {
-					var factory = (JsonConverterFactory)converter;
-					if (factory.CanConvert (propertyType)) {
-						converter = factory.CreateConverter (propertyType, options)!;
-					}
-				}
-				if (p.Value.PropertyValue != null) {
-					converter.GetType ().GetMethod ("Write")?.Invoke (converter, new object [] { writer, p.Value.PropertyValue, options });
-				}
-			} else {
-				JsonSerializer.Serialize (writer, p.Value.PropertyValue, options);
-			}
-		}
-		writer.WriteEndObject ();
-	}
+internal class ScopeJsonConverter<scopeT> : JsonConverter<scopeT> where scopeT : Scope<scopeT>
+{
+    public override scopeT Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+    {
+        if (reader.TokenType != JsonTokenType.StartObject)
+        {
+            throw new JsonException (
+                                     $"Expected a JSON object (\"{{ \"propName\" : ... }}\"), but got \"{reader.TokenType}\"."
+                                    );
+        }
+
+        var scope = (scopeT)Activator.CreateInstance (typeof (scopeT))!;
+
+        while (reader.Read ())
+        {
+            if (reader.TokenType == JsonTokenType.EndObject)
+            {
+                return scope!;
+            }
+
+            if (reader.TokenType != JsonTokenType.PropertyName)
+            {
+                throw new JsonException ($"Expected a JSON property name, but got \"{reader.TokenType}\".");
+            }
+
+            string? propertyName = reader.GetString ();
+            reader.Read ();
+
+            if (propertyName != null && scope!.TryGetValue (propertyName, out ConfigProperty? configProp))
+            {
+                // This property name was found in the Scope's ScopeProperties dictionary
+                // Figure out if it needs a JsonConverter and if so, create one
+                Type? propertyType = configProp?.PropertyInfo?.PropertyType!;
+
+                if (configProp?.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is
+                    JsonConverterAttribute jca)
+                {
+                    object? converter = Activator.CreateInstance (jca.ConverterType!)!;
+
+                    if (converter.GetType ().BaseType == typeof (JsonConverterFactory))
+                    {
+                        var factory = (JsonConverterFactory)converter;
+
+                        if (propertyType != null && factory.CanConvert (propertyType))
+                        {
+                            converter = factory.CreateConverter (propertyType, options);
+                        }
+                    }
+
+                    var readHelper = Activator.CreateInstance (
+                                                               (Type?)typeof (ReadHelper<>).MakeGenericType (
+                                                                    typeof (scopeT),
+                                                                    propertyType!
+                                                                   )!,
+                                                               converter
+                                                              ) as ReadHelper;
+
+                    try
+                    {
+                        scope! [propertyName].PropertyValue = readHelper?.Read (ref reader, propertyType!, options);
+                    }
+                    catch (NotSupportedException e)
+                    {
+                        throw new JsonException (
+                                                 $"Error reading property \"{propertyName}\" of type \"{propertyType?.Name}\".",
+                                                 e
+                                                );
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        scope! [propertyName].PropertyValue =
+                            JsonSerializer.Deserialize (ref reader, propertyType!, options);
+                    }
+                    catch (Exception ex)
+                    {
+                        Debug.WriteLine ($"scopeT Read: {ex}");
+                    }
+                }
+            }
+            else
+            {
+                // It is not a config property. Maybe it's just a property on the Scope with [JsonInclude]
+                // like ScopeSettings.$schema...
+                PropertyInfo? property = scope!.GetType ()
+                                               .GetProperties ()
+                                               .Where (
+                                                       p =>
+                                                       {
+                                                           var jia =
+                                                               p.GetCustomAttribute (typeof (JsonIncludeAttribute)) as
+                                                                   JsonIncludeAttribute;
+
+                                                           if (jia != null)
+                                                           {
+                                                               var jpna =
+                                                                   p.GetCustomAttribute (
+                                                                                         typeof (JsonPropertyNameAttribute)
+                                                                                        ) as
+                                                                       JsonPropertyNameAttribute;
+
+                                                               if (jpna?.Name == propertyName)
+                                                               {
+                                                                   // Bit of a hack, modifying propertyName in an enumerator...
+                                                                   propertyName = p.Name;
+
+                                                                   return true;
+                                                               }
+
+                                                               return p.Name == propertyName;
+                                                           }
+
+                                                           return false;
+                                                       }
+                                                      )
+                                               .FirstOrDefault ();
+
+                if (property != null)
+                {
+                    PropertyInfo prop = scope.GetType ().GetProperty (propertyName!)!;
+                    prop.SetValue (scope, JsonSerializer.Deserialize (ref reader, prop.PropertyType, options));
+                }
+                else
+                {
+                    // Unknown property
+                    throw new JsonException ($"Unknown property name \"{propertyName}\".");
+                }
+            }
+        }
+
+        throw new JsonException ();
+    }
+
+    public override void Write (Utf8JsonWriter writer, scopeT scope, JsonSerializerOptions options)
+    {
+        writer.WriteStartObject ();
+
+        IEnumerable<PropertyInfo> properties = scope!.GetType ()
+                                                     .GetProperties ()
+                                                     .Where (
+                                                             p => p.GetCustomAttribute (typeof (JsonIncludeAttribute))
+                                                                  != null
+                                                            );
+
+        foreach (PropertyInfo p in properties)
+        {
+            writer.WritePropertyName (ConfigProperty.GetJsonPropertyName (p));
+            JsonSerializer.Serialize (writer, scope.GetType ().GetProperty (p.Name)?.GetValue (scope), options);
+        }
+
+        foreach (KeyValuePair<string, ConfigProperty> p in from p in scope
+                                                               .Where (
+                                                                       cp =>
+                                                                           cp.Value.PropertyInfo?.GetCustomAttribute (
+                                                                                    typeof (
+                                                                                        SerializableConfigurationProperty)
+                                                                                   )
+                                                                               is
+                                                                               SerializableConfigurationProperty scp
+                                                                           && scp?.Scope == typeof (scopeT)
+                                                                      )
+                                                           where p.Value.PropertyValue != null
+                                                           select p)
+        {
+            writer.WritePropertyName (p.Key);
+            Type? propertyType = p.Value.PropertyInfo?.PropertyType;
+
+            if (propertyType != null
+                && p.Value.PropertyInfo?.GetCustomAttribute (typeof (JsonConverterAttribute)) is JsonConverterAttribute
+                    jca)
+            {
+                object converter = Activator.CreateInstance (jca.ConverterType!)!;
+
+                if (converter.GetType ().BaseType == typeof (JsonConverterFactory))
+                {
+                    var factory = (JsonConverterFactory)converter;
+
+                    if (factory.CanConvert (propertyType))
+                    {
+                        converter = factory.CreateConverter (propertyType, options)!;
+                    }
+                }
+
+                if (p.Value.PropertyValue != null)
+                {
+                    converter.GetType ()
+                             .GetMethod ("Write")
+                             ?.Invoke (converter, new [] { writer, p.Value.PropertyValue, options });
+                }
+            }
+            else
+            {
+                JsonSerializer.Serialize (writer, p.Value.PropertyValue, options);
+            }
+        }
+
+        writer.WriteEndObject ();
+    }
+
+    // See: https://stackoverflow.com/questions/60830084/how-to-pass-an-argument-by-reference-using-reflection
+    internal abstract class ReadHelper
+    {
+        public abstract object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
+    }
+
+    internal class ReadHelper<converterT> : ReadHelper
+    {
+        private readonly ReadDelegate _readDelegate;
+        public ReadHelper (object converter) { _readDelegate = (ReadDelegate)Delegate.CreateDelegate (typeof (ReadDelegate), converter, "Read"); }
+
+        public override object? Read (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
+        {
+            return _readDelegate.Invoke (ref reader, type, options);
+        }
+
+        private delegate converterT ReadDelegate (ref Utf8JsonReader reader, Type type, JsonSerializerOptions options);
+    }
 }

+ 16 - 22
Terminal.Gui/Configuration/SerializableConfigurationProperty.cs

@@ -1,28 +1,22 @@
-using System;
-
-#nullable enable
+#nullable enable
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// An attribute that can be applied to a property to indicate that it should included in the configuration file.
-/// </summary>
+/// <summary>An attribute that can be applied to a property to indicate that it should included in the configuration file.</summary>
 /// <example>
-/// 	[SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
-///	public static LineStyle DefaultBorderStyle {
-///	...
+///     [SerializableConfigurationProperty(Scope = typeof(Configuration.ThemeManager.ThemeScope)), JsonConverter
+///     (typeof (JsonStringEnumConverter))] public static LineStyle DefaultBorderStyle { ...
 /// </example>
-[AttributeUsage (AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
-public class SerializableConfigurationProperty : System.Attribute {
-	/// <summary>
-	/// Specifies the scope of the property. 
-	/// </summary>
-	public Type? Scope { get; set; }
+[AttributeUsage (AttributeTargets.Property)]
+public class SerializableConfigurationProperty : System.Attribute
+{
+    /// <summary>
+    ///     If <see langword="true"/>, the property will be serialized to the configuration file using only the property
+    ///     name as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
+    ///     property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
+    /// </summary>
+    public bool OmitClassName { get; set; }
 
-	/// <summary>
-	/// If <see langword="true"/>, the property will be serialized to the configuration file using only the property name
-	/// as the key. If <see langword="false"/>, the property will be serialized to the configuration file using the
-	/// property name pre-pended with the classname (e.g. <c>Application.UseSystemConsole</c>).
-	/// </summary>
-	public bool OmitClassName { get; set; }
-}
+    /// <summary>Specifies the scope of the property.</summary>
+    public Type? Scope { get; set; }
+}

+ 109 - 103
Terminal.Gui/Configuration/SettingsScope.cs

@@ -1,20 +1,17 @@
-using System;
-using System.Collections.Generic;
+#nullable enable
 using System.Diagnostics;
-using System.IO;
 using System.Reflection;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 
-#nullable enable
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties 
-/// attributed with  <see cref="SettingsScope"/>.
+///     The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties attributed with
+///     <see cref="SettingsScope"/>.
 /// </summary>
-/// <example><code>
+/// <example>
+///     <code>
 ///  {
 ///    "$schema" : "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
 ///    "Application.UseSystemConsole" : true,
@@ -22,100 +19,109 @@ namespace Terminal.Gui;
 ///    "Themes": {
 ///    },
 ///  },
-/// </code></example>
-/// <remarks>
-/// </remarks>
+/// </code>
+/// </example>
+/// <remarks></remarks>
 [JsonConverter (typeof (ScopeJsonConverter<SettingsScope>))]
-public class SettingsScope : Scope<SettingsScope> {
-	/// <summary>
-	/// Points to our JSON schema.
-	/// </summary>
-	[JsonInclude, JsonPropertyName ("$schema")]
-	public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json";
-
-	/// <summary>
-	/// The list of paths to the configuration files.
-	/// </summary>
-	public List<string> Sources = new List<string> ();
-
-	/// <summary>
-	/// Updates the <see cref="SettingsScope"/> with the settings in a JSON string.
-	/// </summary>
-	/// <param name="stream">Json document to update the settings with.</param>
-	/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
-	public SettingsScope? Update (Stream stream, string source)
-	{
-		// Update the existing settings with the new settings.
-		try {
-			Update (JsonSerializer.Deserialize<SettingsScope> (stream, ConfigurationManager._serializerOptions)!);
-			ConfigurationManager.OnUpdated ();
-			Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
-			Sources.Add (source);
-			return this;
-		} catch (JsonException e) {
-			if (ConfigurationManager.ThrowOnJsonErrors ?? false) {
-				throw;
-			} else {
-				ConfigurationManager.AddJsonError ($"Error deserializing {source}: {e.Message}");
-			}
-		}
-		return this;
-	}
-
-	/// <summary>
-	/// Updates the <see cref="SettingsScope"/> with the settings in a JSON file.
-	/// </summary>
-	/// <param name="filePath"></param>
-	public SettingsScope? Update (string filePath)
-	{
-		var realPath = filePath.Replace ("~", Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
-		if (!File.Exists (realPath)) {
-			Debug.WriteLine ($"ConfigurationManager: Configuration file \"{realPath}\" does not exist.");
-			Sources.Add (filePath);
-			return this;
-		}
-
-		var stream = File.OpenRead (realPath);
-		var s = Update (stream, filePath);
-		stream.Close ();
-		stream.Dispose ();
-		return s;
-	}
-
-	/// <summary>
-	/// Updates the <see cref="SettingsScope"/> with the settings from a Json resource.
-	/// </summary>
-	/// <param name="assembly"></param>
-	/// <param name="resourceName"></param>
-	public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName)
-	{
-		if (resourceName == null || string.IsNullOrEmpty (resourceName)) {
-			Debug.WriteLine ($"ConfigurationManager: Resource \"{resourceName}\" does not exist in \"{assembly.GetName ().Name}\".");
-			return this;
-		}
-
-		using Stream? stream = assembly.GetManifestResourceStream (resourceName)!;
-		if (stream == null) {
-			Debug.WriteLine ($"ConfigurationManager: Failed to read resource \"{resourceName}\" from \"{assembly.GetName ().Name}\".");
-			return this;
-		}
-
-		return Update (stream, $"resource://[{assembly.GetName ().Name}]/{resourceName}");
-	}
-
-	/// <summary>
-	/// Updates the <see cref="SettingsScope"/> with the settings in a JSON string.
-	/// </summary>
-	/// <param name="json">Json document to update the settings with.</param>
-	/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
-	public SettingsScope? Update (string json, string source)
-	{
-		var stream = new MemoryStream ();
-		var writer = new StreamWriter (stream);
-		writer.Write (json);
-		writer.Flush ();
-		stream.Position = 0;
-
-		return Update (stream, source);
-	}
+public class SettingsScope : Scope<SettingsScope>
+{
+    /// <summary>The list of paths to the configuration files.</summary>
+    public List<string> Sources = new ();
+
+    /// <summary>Points to our JSON schema.</summary>
+    [JsonInclude]
+    [JsonPropertyName ("$schema")]
+    public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json";
+
+    /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
+    /// <param name="stream">Json document to update the settings with.</param>
+    /// <param name="source">The source (filename/resource name) the Json document was read from.</param>
+    public SettingsScope? Update (Stream stream, string source)
+    {
+        // Update the existing settings with the new settings.
+        try
+        {
+            Update (JsonSerializer.Deserialize<SettingsScope> (stream, _serializerOptions)!);
+            OnUpdated ();
+            Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
+            Sources.Add (source);
+
+            return this;
+        }
+        catch (JsonException e)
+        {
+            if (ThrowOnJsonErrors ?? false)
+            {
+                throw;
+            }
+
+            AddJsonError ($"Error deserializing {source}: {e.Message}");
+        }
+
+        return this;
+    }
+
+    /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON file.</summary>
+    /// <param name="filePath"></param>
+    public SettingsScope? Update (string filePath)
+    {
+        string realPath = filePath.Replace ("~", Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
+
+        if (!File.Exists (realPath))
+        {
+            Debug.WriteLine ($"ConfigurationManager: Configuration file \"{realPath}\" does not exist.");
+            Sources.Add (filePath);
+
+            return this;
+        }
+
+        FileStream stream = File.OpenRead (realPath);
+        SettingsScope? s = Update (stream, filePath);
+        stream.Close ();
+        stream.Dispose ();
+
+        return s;
+    }
+
+    /// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
+    /// <param name="json">Json document to update the settings with.</param>
+    /// <param name="source">The source (filename/resource name) the Json document was read from.</param>
+    public SettingsScope? Update (string json, string source)
+    {
+        var stream = new MemoryStream ();
+        var writer = new StreamWriter (stream);
+        writer.Write (json);
+        writer.Flush ();
+        stream.Position = 0;
+
+        return Update (stream, source);
+    }
+
+    /// <summary>Updates the <see cref="SettingsScope"/> with the settings from a Json resource.</summary>
+    /// <param name="assembly"></param>
+    /// <param name="resourceName"></param>
+    public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName)
+    {
+        if (resourceName == null || string.IsNullOrEmpty (resourceName))
+        {
+            Debug.WriteLine (
+                             $"ConfigurationManager: Resource \"{resourceName}\" does not exist in \"{assembly.GetName ().Name}\"."
+                            );
+
+            return this;
+        }
+
+        using Stream? stream = assembly.GetManifestResourceStream (resourceName)!;
+
+        if (stream == null)
+        {
+            Debug.WriteLine (
+                             $"ConfigurationManager: Failed to read resource \"{resourceName}\" from \"{assembly.GetName ().Name}\"."
+                            );
+
+            return this;
+        }
+
+        return Update (stream, $"resource://[{assembly.GetName ().Name}]/{resourceName}");
+    }
 }

+ 130 - 164
Terminal.Gui/Configuration/ThemeManager.cs

@@ -1,29 +1,22 @@
-using System;
+#nullable enable
 using System.Collections;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.Text.Json.Serialization;
 
-#nullable enable
+namespace Terminal.Gui;
 
-namespace Terminal.Gui; 
-
-/// <summary>
-/// Contains a dictionary of the <see cref="ThemeManager.Theme"/>s for a Terminal.Gui application.
-/// </summary>
+/// <summary>Contains a dictionary of the <see cref="ThemeManager.Theme"/>s for a Terminal.Gui application.</summary>
 /// <remarks>
-/// <para>
-/// A Theme is a collection of settings that are named. The default theme is named "Default".
-/// </para>
-/// <para>
-/// The <see cref="ThemeManager.Theme"/> property is used to detemrine the currently active theme. 
-/// </para>
+///     <para>A Theme is a collection of settings that are named. The default theme is named "Default".</para>
+///     <para>The <see cref="ThemeManager.Theme"/> property is used to detemrine the currently active theme.</para>
 /// </remarks>
 /// <para>
-/// <see cref="ThemeManager"/> is a singleton class. It is created when the first <see cref="ThemeManager"/> property is accessed.
-/// Accessing <see cref="ThemeManager.Instance"/> is the same as accessing <see cref="ConfigurationManager.Themes"/>.
+///     <see cref="ThemeManager"/> is a singleton class. It is created when the first <see cref="ThemeManager"/> property
+///     is accessed. Accessing <see cref="ThemeManager.Instance"/> is the same as accessing
+///     <see cref="ConfigurationManager.Themes"/>.
 /// </para>
-/// <example><code>
+/// <example>
+///     <code>
 /// 	"Themes": [
 /// 	{
 /// 		"Default": {
@@ -55,153 +48,126 @@ namespace Terminal.Gui;
 /// 			}
 /// 		}
 /// 	}
-/// </code></example> 
-public class ThemeManager : IDictionary<string, ThemeScope> {
-	private static readonly ThemeManager _instance = new ThemeManager ();
-	static ThemeManager () { } // Make sure it's truly lazy
-	private ThemeManager () { } // Prevent instantiation outside
-
-	/// <summary>
-	/// Class is a singleton...
-	/// </summary>
-	public static ThemeManager Instance { get { return _instance; } }
-
-	private static string _theme = string.Empty;
-
-	/// <summary>
-	/// The currently selected theme. This is the internal version; see <see cref="Theme"/>.
-	/// </summary>
-	[JsonInclude, SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("Theme")]
-	internal static string SelectedTheme {
-		get => _theme;
-		set {
-			var oldTheme = _theme;
-			_theme = value;
-			if (oldTheme != _theme &&
-				ConfigurationManager.Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes &&
-				themes.ContainsKey (_theme)) {
-				ConfigurationManager.Settings! ["Theme"].PropertyValue = _theme;
-				Instance.OnThemeChanged (oldTheme);
-			}
-		}
-	}
-
-	/// <summary>
-	/// Gets or sets the currently selected theme. The value is persisted to the "Theme"
-	/// property.
-	/// </summary>
-	[JsonIgnore]
-	public string Theme {
-		get => ThemeManager.SelectedTheme;
-		set {
-			ThemeManager.SelectedTheme = value;
-		}
-	}
-
-	/// <summary>
-	/// Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.
-	/// </summary>
-	internal void OnThemeChanged (string theme)
-	{
-		Debug.WriteLine ($"Themes.OnThemeChanged({theme}) -> {Theme}");
-		ThemeChanged?.Invoke (this, new ThemeManagerEventArgs (theme));
-	}
-
-	/// <summary>
-	/// Event fired he selected theme has changed.
-	/// application.
-	/// </summary>
-	public event EventHandler<ThemeManagerEventArgs>? ThemeChanged;
-
-	/// <summary>
-	/// Holds the <see cref="ThemeScope"/> definitions. 
-	/// </summary>
-	[JsonInclude, JsonConverter (typeof (DictionaryJsonConverter<ThemeScope>))]
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
-	public static Dictionary<string, ThemeScope>? Themes {
-		get => ConfigurationManager.Settings? ["Themes"]?.PropertyValue as Dictionary<string, ThemeScope>; // themes ?? new Dictionary<string, ThemeScope> ();
-		set {
-			//if (themes == null || value == null) {
-			//	themes = value;
-			//} else {
-			//	themes = (Dictionary<string, ThemeScope>)DeepMemberwiseCopy (value!, themes!)!;
-			//}
-			ConfigurationManager.Settings! ["Themes"].PropertyValue = value;
-		}
-	}
-
-	internal static void Reset ()
-	{
-		Debug.WriteLine ($"Themes.Reset()");
-		Colors.Reset ();
-		Themes?.Clear ();
-		SelectedTheme = string.Empty;
-	}
-
-	internal static void GetHardCodedDefaults ()
-	{
-		Debug.WriteLine ($"Themes.GetHardCodedDefaults()");
-		var theme = new ThemeScope ();
-		theme.RetrieveValues ();
-
-		Themes = new Dictionary<string, ThemeScope> (StringComparer.InvariantCultureIgnoreCase) { { "Default", theme } };
-		SelectedTheme = "Default";
-	}
-
-	#region IDictionary
-#pragma warning disable 1591
+/// </code>
+/// </example>
+public class ThemeManager : IDictionary<string, ThemeScope>
+{
+    private static string _theme = string.Empty;
+    static ThemeManager () { } // Make sure it's truly lazy
+    private ThemeManager () { } // Prevent instantiation outside
+
+    /// <summary>Class is a singleton...</summary>
+    public static ThemeManager Instance { get; } = new ();
+
+    /// <summary>Gets or sets the currently selected theme. The value is persisted to the "Theme" property.</summary>
+    [JsonIgnore]
+    public string Theme
+    {
+        get => SelectedTheme;
+        set => SelectedTheme = value;
+    }
+
+    /// <summary>Holds the <see cref="ThemeScope"/> definitions.</summary>
+    [JsonInclude]
+    [JsonConverter (typeof (DictionaryJsonConverter<ThemeScope>))]
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+    public static Dictionary<string, ThemeScope>? Themes
+    {
+        get => Settings? ["Themes"]
+                       ?.PropertyValue as
+                   Dictionary<string, ThemeScope>; // themes ?? new Dictionary<string, ThemeScope> ();
+        set =>
+
+            //if (themes == null || value == null) {
+            //	themes = value;
+            //} else {
+            //	themes = (Dictionary<string, ThemeScope>)DeepMemberwiseCopy (value!, themes!)!;
+            //}
+            Settings! ["Themes"].PropertyValue = value;
+    }
+
+    /// <summary>The currently selected theme. This is the internal version; see <see cref="Theme"/>.</summary>
+    [JsonInclude]
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+    [JsonPropertyName ("Theme")]
+    internal static string SelectedTheme
+    {
+        get => _theme;
+        set
+        {
+            string oldTheme = _theme;
+            _theme = value;
+
+            if (oldTheme != _theme && Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes && themes.ContainsKey (_theme))
+            {
+                Settings! ["Theme"].PropertyValue = _theme;
+                Instance.OnThemeChanged (oldTheme);
+            }
+        }
+    }
+
+    /// <summary>Event fired he selected theme has changed. application.</summary>
+    public event EventHandler<ThemeManagerEventArgs>? ThemeChanged;
+
+    internal static void GetHardCodedDefaults ()
+    {
+        Debug.WriteLine ("Themes.GetHardCodedDefaults()");
+        var theme = new ThemeScope ();
+        theme.RetrieveValues ();
+
+        Themes = new Dictionary<string, ThemeScope> (StringComparer.InvariantCultureIgnoreCase)
+        {
+            { "Default", theme }
+        };
+        SelectedTheme = "Default";
+    }
+
+    /// <summary>Called when the selected theme has changed. Fires the <see cref="ThemeChanged"/> event.</summary>
+    internal void OnThemeChanged (string theme)
+    {
+        Debug.WriteLine ($"Themes.OnThemeChanged({theme}) -> {Theme}");
+        ThemeChanged?.Invoke (this, new ThemeManagerEventArgs (theme));
+    }
+
+    internal static void Reset ()
+    {
+        Debug.WriteLine ("Themes.Reset()");
+        Colors.Reset ();
+        Themes?.Clear ();
+        SelectedTheme = string.Empty;
+    }
+
+    #region IDictionary
 
-	public ICollection<string> Keys => ((IDictionary<string, ThemeScope>)Themes!).Keys;
-	public ICollection<ThemeScope> Values => ((IDictionary<string, ThemeScope>)Themes!).Values;
-	public int Count => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Count;
-	public bool IsReadOnly => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).IsReadOnly;
-	public ThemeScope this [string key] { get => ((IDictionary<string, ThemeScope>)Themes!) [key]; set => ((IDictionary<string, ThemeScope>)Themes!) [key] = value; }
-	public void Add (string key, ThemeScope value)
-	{
-		((IDictionary<string, ThemeScope>)Themes!).Add (key, value);
-	}
-	public bool ContainsKey (string key)
-	{
-		return ((IDictionary<string, ThemeScope>)Themes!).ContainsKey (key);
-	}
-	public bool Remove (string key)
-	{
-		return ((IDictionary<string, ThemeScope>)Themes!).Remove (key);
-	}
-	public bool TryGetValue (string key, out ThemeScope value)
-	{
-		return ((IDictionary<string, ThemeScope>)Themes!).TryGetValue (key, out value!);
-	}
-	public void Add (KeyValuePair<string, ThemeScope> item)
-	{
-		((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Add (item);
-	}
-	public void Clear ()
-	{
-		((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Clear ();
-	}
-	public bool Contains (KeyValuePair<string, ThemeScope> item)
-	{
-		return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Contains (item);
-	}
-	public void CopyTo (KeyValuePair<string, ThemeScope> [] array, int arrayIndex)
-	{
-		((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).CopyTo (array, arrayIndex);
-	}
-	public bool Remove (KeyValuePair<string, ThemeScope> item)
-	{
-		return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Remove (item);
-	}
-	public IEnumerator<KeyValuePair<string, ThemeScope>> GetEnumerator ()
-	{
-		return ((IEnumerable<KeyValuePair<string, ThemeScope>>)Themes!).GetEnumerator ();
-	}
-
-	IEnumerator IEnumerable.GetEnumerator ()
-	{
-		return ((IEnumerable)Themes!).GetEnumerator ();
-	}
+#pragma warning disable 1591
+    public ICollection<string> Keys => ((IDictionary<string, ThemeScope>)Themes!).Keys;
+    public ICollection<ThemeScope> Values => ((IDictionary<string, ThemeScope>)Themes!).Values;
+    public int Count => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Count;
+    public bool IsReadOnly => ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).IsReadOnly;
+
+    public ThemeScope this [string key]
+    {
+        get => ((IDictionary<string, ThemeScope>)Themes!) [key];
+        set => ((IDictionary<string, ThemeScope>)Themes!) [key] = value;
+    }
+
+    public void Add (string key, ThemeScope value) { ((IDictionary<string, ThemeScope>)Themes!).Add (key, value); }
+    public bool ContainsKey (string key) { return ((IDictionary<string, ThemeScope>)Themes!).ContainsKey (key); }
+    public bool Remove (string key) { return ((IDictionary<string, ThemeScope>)Themes!).Remove (key); }
+    public bool TryGetValue (string key, out ThemeScope value) { return ((IDictionary<string, ThemeScope>)Themes!).TryGetValue (key, out value!); }
+    public void Add (KeyValuePair<string, ThemeScope> item) { ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Add (item); }
+    public void Clear () { ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Clear (); }
+    public bool Contains (KeyValuePair<string, ThemeScope> item) { return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Contains (item); }
+
+    public void CopyTo (KeyValuePair<string, ThemeScope> [] array, int arrayIndex)
+    {
+        ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).CopyTo (array, arrayIndex);
+    }
+
+    public bool Remove (KeyValuePair<string, ThemeScope> item) { return ((ICollection<KeyValuePair<string, ThemeScope>>)Themes!).Remove (item); }
+    public IEnumerator<KeyValuePair<string, ThemeScope>> GetEnumerator () { return ((IEnumerable<KeyValuePair<string, ThemeScope>>)Themes!).GetEnumerator (); }
+    IEnumerator IEnumerable.GetEnumerator () { return ((IEnumerable)Themes!).GetEnumerator (); }
 #pragma warning restore 1591
 
-	#endregion
-}
+    #endregion
+}

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

@@ -1,18 +1,17 @@
-using System.Text.Json.Serialization;
-
-#nullable enable
+#nullable enable
+using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
 /// <summary>
-/// The root object for a Theme. A Theme is a set of settings that are applied to the running <see cref="Application"/>
-/// as a group.
+///     The root object for a Theme. A Theme is a set of settings that are applied to the running
+///     <see cref="Application"/> as a group.
 /// </summary>
 /// <remarks>
-/// <para>
-/// </para>
+///     <para></para>
 /// </remarks>
-/// <example><code>
+/// <example>
+///     <code>
 /// 	"Default": {
 /// 		"ColorSchemes": [
 /// 		{
@@ -41,7 +40,8 @@ namespace Terminal.Gui;
 /// 
 /// 		}
 /// 	}
-/// </code></example> 
+/// </code>
+/// </example>
 [JsonConverter (typeof (ScopeJsonConverter<ThemeScope>))]
-public class ThemeScope : Scope<ThemeScope> {
-}
+public class ThemeScope : Scope<ThemeScope>
+{ }

+ 920 - 1107
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -1,1126 +1,939 @@
 //
 // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
 //
-using System.Text;
-using System;
+
 using System.Diagnostics;
-using System.Linq;
-using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Base class for Terminal.Gui ConsoleDriver implementations.
-/// </summary>
+/// <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.
+///     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>
-	/// Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
-	/// <code>
-	///  public ColorTests ()
-	///  {
-	///    ConsoleDriver.RunningUnitTests = true;
-	///  }
-	/// </code>
-	/// </summary>
-	internal static bool RunningUnitTests { get; set; }
-
-	#region Setup & Teardown
-	/// <summary>
-	/// Initializes the driver
-	/// </summary>
-	/// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-	internal abstract MainLoop Init ();
-
-	/// <summary>
-	/// Ends the execution of the console driver.
-	/// </summary>
-	internal abstract void End ();
-	#endregion
-
-	/// <summary>
-	/// The event fired when the terminal is resized.
-	/// </summary>
-	public event EventHandler<SizeChangedEventArgs> SizeChanged;
-
-	/// <summary>
-	/// Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.
-	/// </summary>
-	/// <param name="args"></param>
-	public void OnSizeChanged (SizeChangedEventArgs args) => SizeChanged?.Invoke (this, args);
-
-	/// <summary>
-	/// The number of columns visible in the terminal.
-	/// </summary>
-	public virtual int Cols {
-		get => _cols;
-		internal set {
-			_cols = value;
-			ClearContents ();
-		}
-	}
-
-	/// <summary>
-	/// The number of rows visible in the terminal.
-	/// </summary>
-	public virtual int Rows {
-		get => _rows;
-		internal set {
-			_rows = value;
-			ClearContents ();
-		}
-	}
-
-	/// <summary>
-	/// The leftmost column in the terminal.
-	/// </summary>
-	public virtual int Left { get; internal set; } = 0;
-
-	/// <summary>
-	/// The topmost row in the terminal.
-	/// </summary>
-	public virtual int Top { get; internal set; } = 0;
-
-	/// <summary>
-	/// Get the operating system clipboard.
-	/// </summary>
-	public IClipboard Clipboard { get; internal set; }
-
-	/// <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>
-	/// 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 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>
-	/// 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>
-	/// 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) => Rune.IsValid (rune.Value);
-
-	/// <summary>
-	/// Adds the specified rune to the display at the current cursor position. 
-	/// </summary>
-	/// <remarks>
-	/// <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>
-	/// <param name="rune">Rune to add.</param>
-	public void AddRune (Rune rune)
-	{
-		int runeWidth = -1;
-		bool validLocation = IsValidLocation (Col, Row);
-		if (validLocation) {
-			rune = rune.MakePrintable ();
-			runeWidth = rune.GetColumns ();
-			if (runeWidth == 0 && rune.IsCombiningMark ()) {
-				// AtlasEngine does not support NON-NORMALIZED combining marks in a way
-				// compatible with the driver architecture. Any CMs (except in the first col)
-				// are correctly combined with the base char, but are ALSO treated as 1 column
-				// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-				// 
-				// Until this is addressed (see Issue #), we do our best by 
-				// a) Attempting to normalize any CM with the base char to it's left
-				// b) Ignoring any CMs that don't normalize
-				if (Col > 0) {
-					if (Contents [Row, Col - 1].CombiningMarks.Count > 0) {
-						// Just add this mark to the list
-						Contents [Row, Col - 1].CombiningMarks.Add (rune);
-						// Ignore. Don't move to next column (let the driver figure out what to do).
-					} else {
-						// Attempt to normalize the cell to our left combined with this mark
-						string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
-
-						// Normalize to Form C (Canonical Composition)
-						string normalized = combined.Normalize (NormalizationForm.FormC);
-						if (normalized.Length == 1) {
-							// It normalized! We can just set the Cell to the left with the
-							// normalized codepoint 
-							Contents [Row, Col - 1].Rune = (Rune)normalized [0];
-							// Ignore. Don't move to next column because we're already there
-						} else {
-							// It didn't normalize. Add it to the Cell to left's CM list
-							Contents [Row, Col - 1].CombiningMarks.Add (rune);
-							// Ignore. Don't move to next column (let the driver figure out what to do).
-						}
-					}
-					Contents [Row, Col - 1].Attribute = CurrentAttribute;
-					Contents [Row, Col - 1].IsDirty = true;
-				} else {
-					// Most drivers will render a combining mark at col 0 as the mark
-					Contents [Row, Col].Rune = rune;
-					Contents [Row, Col].Attribute = CurrentAttribute;
-					Contents [Row, Col].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].Rune.GetColumns () > 1) {
-						// Invalidate cell to left
-						Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
-						Contents [Row, Col - 1].IsDirty = true;
-					}
-				}
-
-
-				if (runeWidth < 1) {
-					Contents [Row, Col].Rune = Rune.ReplacementChar;
-
-				} else if (runeWidth == 1) {
-					Contents [Row, Col].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].Rune = Rune.ReplacementChar;
-					} else {
-						Contents [Row, Col].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].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].Rune = (Rune)' ';
-					Contents [Row, Col].IsDirty = false;
-				}
-				_dirtyLines [Row] = true;
-			}
-		}
-
-		if (runeWidth is < 0 or > 0) {
-			Col++;
-		}
-
-		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;
-
-				// TODO: Determine if we should wipe this out (for now now)
-				//Contents [Row, Col].Rune = (Rune)' ';
-			}
-			Col++;
-		}
-	}
-
-	/// <summary>
-	/// Adds the specified <see langword="char"/> to the display at the current cursor position. This method
-	/// is a convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
-	/// </summary>
-	/// <param name="c">Character to add.</param>
-	public void AddRune (char c) => AddRune (new Rune (c));
-
-	/// <summary>
-	/// Adds the <paramref name="str"/> to the display at the cursor position.
-	/// </summary>
-	/// <remarks>
-	/// <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>
-	/// <param name="str">String.</param>
-	public void AddStr (string str)
-	{
-		var runes = str.EnumerateRunes ().ToList ();
-		for (int i = 0; i < runes.Count; i++) {
-			//if (runes [i].IsCombiningMark()) {
-
-			//	// Attempt to normalize
-			//	string combined = runes [i-1] + runes [i].ToString();
-
-			//	// Normalize to Form C (Canonical Composition)
-			//	string normalized = combined.Normalize (NormalizationForm.FormC);
-
-			//	runes [i-]
-			//}
-			AddRune (runes [i]);
-		}
-	}
-
-	Rect _clip;
-
-	/// <summary>
-	/// Tests whether the specified coordinate are valid for drawing. 
-	/// </summary>
-	/// <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>
-	/// 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>
-	/// Updates the screen to reflect all the changes that have been done to the display buffer
-	/// </summary>
-	public abstract void Refresh ();
-
-	/// <summary>
-	/// Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.
-	/// </summary>
-	public abstract void UpdateCursor ();
-
-	/// <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>
-	/// 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>
-	/// 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 ();
-
-	// 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>
-	/// 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 (int row = 0; row < Rows; row++) {
-					for (int c = 0; c < Cols; c++) {
-						Contents [row, c] = new Cell () {
-							Rune = (Rune)' ',
-							Attribute = new Attribute (Color.White, Color.Black),
-							IsDirty = true
-						};
-						_dirtyLines [row] = true;
-					}
-				}
-			} catch (IndexOutOfRangeException) { }
-		}
-	}
-
-	/// <summary>
-	/// Redraws the physical screen with the contents that have been queued up via any of the printing commands.
-	/// </summary>
-	public abstract void UpdateScreen ();
-
-	#region Color Handling
-	/// <summary>
-	/// Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.
-	/// </summary>
-	public virtual bool SupportsTrueColor => true;
-
-	/// <summary>
-	/// Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors. See <see cref="Application.Force16Colors"/>
-	/// to change this setting via <see cref="ConfigurationManager"/>.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is  <see langword="false"/>, indicating
-	/// that the <see cref="ConsoleDriver"/> cannot support TrueColor.
-	/// </para>
-	/// </remarks>
-	internal virtual bool Force16Colors {
-		get => Application.Force16Colors || !SupportsTrueColor;
-		set => Application.Force16Colors = value || !SupportsTrueColor;
-	}
-
-	Attribute _currentAttribute;
-	int _cols;
-	int _rows;
-
-	/// <summary>
-	/// The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/> call.
-	/// </summary>
-	public Attribute CurrentAttribute {
-		get => _currentAttribute;
-		set {
-			if (Application.Driver != null) {
-				_currentAttribute = new Attribute (value.Foreground, value.Background);
-				return;
-			}
-
-			_currentAttribute = 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>
-	/// Gets the current <see cref="Attribute"/>.
-	/// </summary>
-	/// <returns>The current attribute.</returns>
-	public Attribute GetAttribute () => CurrentAttribute;
-
-	// TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be
-	// removed (and Attribute can lose the platformColor property).
-	/// <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 virtual Attribute MakeColor (Color foreground, Color background) =>
-		// Encode the colors into the int value.
-		new (
-			-1, // only used by cursesdriver!
-			foreground,
-			background
-		);
-	#endregion
-
-	#region Mouse and Keyboard
-	/// <summary>
-	/// Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.
-	/// </summary>
-	public event EventHandler<Key> KeyDown;
-
-	/// <summary>
-	/// Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to <see cref="OnKeyUp"/>.
-	/// </summary>
-	/// <param name="a"></param>
-	public void OnKeyDown (Key a) => KeyDown?.Invoke (this, a);
-
-	/// <summary>
-	/// Event fired when a key is released. 
-	/// </summary>
-	/// <remarks>
-	/// Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is complete.
-	/// </remarks>
-	public event EventHandler<Key> KeyUp;
-
-	/// <summary>
-	/// Called when a key is released. Fires the <see cref="KeyUp"/> event.
-	/// </summary>
-	/// <remarks>
-	/// Drivers that do not support key release events will calls this method after <see cref="OnKeyDown"/> processing is complete.
-	/// </remarks>
-	/// <param name="a"></param>
-	public void OnKeyUp (Key a) => KeyUp?.Invoke (this, a);
-
-	/// <summary>
-	/// Event fired when a mouse event occurs.
-	/// </summary>
-	public event EventHandler<MouseEventEventArgs> MouseEvent;
-
-	/// <summary>
-	/// Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.
-	/// </summary>
-	/// <param name="a"></param>
-	public void OnMouseEvent (MouseEventEventArgs a) => MouseEvent?.Invoke (this, a);
-
-	/// <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);
-	#endregion
-
-	/// <summary>
-	/// Enables diagnostic functions
-	/// </summary>
-	[Flags]
-	public enum DiagnosticFlags : uint {
-		/// <summary>
-		/// All diagnostics off
-		/// </summary>
-		Off = 0b_0000_0000,
-
-		/// <summary>
-		/// When enabled, <see cref="View.OnDrawAdornments"/> 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="View.OnDrawAdornments"/> will draw a 
-		/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
-		/// </summary>
-		FramePadding = 0b_0000_0010
-	}
-
-	/// <summary>
-	/// Set flags to enable/disable <see cref="ConsoleDriver"/> diagnostics.
-	/// </summary>
-	public static DiagnosticFlags Diagnostics { get; set; }
-
-	/// <summary>
-	/// Gets the dimensions of the terminal.
-	/// </summary>
-	public Rect Bounds => new Rect (0, 0, Cols, Rows);
-
-	/// <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 ();
-
-	// 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 (int r = rect.Y; r < rect.Y + rect.Height; r++) {
-			for (int c = rect.X; c < rect.X + rect.Width; c++) {
-				Application.Driver.Move (c, r);
-				Application.Driver.AddRune (rune == default ? new Rune (' ') : rune);
-			}
-		}
-	}
-
-	/// <summary>
-	/// Fills the specified rectangle with the specified <see langword="char"/>. This method
-	/// is a convenience method that calls <see cref="FillRect(Rect, Rune)"/>.
-	/// </summary>
-	/// <param name="rect"></param>
-	/// <param name="c"></param>
-	public void FillRect (Rect rect, char c) => FillRect (rect, new Rune (c));
-
-	/// <summary>
-	/// Returns the name of the driver and relevant library version information.
-	/// </summary>
-	/// <returns></returns>
-	public virtual string GetVersionInfo () => GetType ().Name;
+public abstract class ConsoleDriver
+{
+    /// <summary>Enables diagnostic functions</summary>
+    [Flags]
+    public enum DiagnosticFlags : uint
+    {
+        /// <summary>All diagnostics off</summary>
+        Off = 0b_0000_0000,
+
+        /// <summary>
+        ///     When enabled, <see cref="View.OnDrawAdornments"/> 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="View.OnDrawAdornments"/> will draw a 'L', 'R', 'T', and 'B' when clearing
+        ///     <see cref="Thickness"/>'s instead of ' '.
+        /// </summary>
+        FramePadding = 0b_0000_0010
+    }
+
+    // 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>Gets the dimensions of the terminal.</summary>
+    public Rect Bounds => new (0, 0, Cols, Rows);
+
+    /// <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; set; }
+
+    /// <summary>Get the operating system clipboard.</summary>
+    public IClipboard Clipboard { get; internal set; }
+
+    /// <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>The number of columns visible in the terminal.</summary>
+    public virtual int Cols
+    {
+        get => _cols;
+        internal set
+        {
+            _cols = value;
+            ClearContents ();
+        }
+    }
+
+    /// <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>Set flags to enable/disable <see cref="ConsoleDriver"/> diagnostics.</summary>
+    public static DiagnosticFlags Diagnostics { get; set; }
+
+    /// <summary>The leftmost column in the terminal.</summary>
+    public virtual int Left { get; internal set; } = 0;
+
+    /// <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>The number of rows visible in the terminal.</summary>
+    public virtual int Rows
+    {
+        get => _rows;
+        internal set
+        {
+            _rows = value;
+            ClearContents ();
+        }
+    }
+
+    /// <summary>The topmost row in the terminal.</summary>
+    public virtual int Top { get; internal set; } = 0;
+
+    /// <summary>
+    ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
+    ///     <code>
+    ///  public ColorTests ()
+    ///  {
+    ///    ConsoleDriver.RunningUnitTests = true;
+    ///  }
+    /// </code>
+    /// </summary>
+    internal static bool RunningUnitTests { get; set; }
+
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <remarks>
+    ///     <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>
+    /// <param name="rune">Rune to add.</param>
+    public void AddRune (Rune rune)
+    {
+        int runeWidth = -1;
+        bool validLocation = IsValidLocation (Col, Row);
+
+        if (validLocation)
+        {
+            rune = rune.MakePrintable ();
+            runeWidth = rune.GetColumns ();
+
+            if (runeWidth == 0 && rune.IsCombiningMark ())
+            {
+                // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                // compatible with the driver architecture. Any CMs (except in the first col)
+                // are correctly combined with the base char, but are ALSO treated as 1 column
+                // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                // 
+                // Until this is addressed (see Issue #), we do our best by 
+                // a) Attempting to normalize any CM with the base char to it's left
+                // b) Ignoring any CMs that don't normalize
+                if (Col > 0)
+                {
+                    if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
+                    {
+                        // Just add this mark to the list
+                        Contents [Row, Col - 1].CombiningMarks.Add (rune);
+
+                        // Ignore. Don't move to next column (let the driver figure out what to do).
+                    }
+                    else
+                    {
+                        // Attempt to normalize the cell to our left combined with this mark
+                        string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
+
+                        // Normalize to Form C (Canonical Composition)
+                        string normalized = combined.Normalize (NormalizationForm.FormC);
+
+                        if (normalized.Length == 1)
+                        {
+                            // It normalized! We can just set the Cell to the left with the
+                            // normalized codepoint 
+                            Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+
+                            // Ignore. Don't move to next column because we're already there
+                        }
+                        else
+                        {
+                            // It didn't normalize. Add it to the Cell to left's CM list
+                            Contents [Row, Col - 1].CombiningMarks.Add (rune);
+
+                            // Ignore. Don't move to next column (let the driver figure out what to do).
+                        }
+                    }
+
+                    Contents [Row, Col - 1].Attribute = CurrentAttribute;
+                    Contents [Row, Col - 1].IsDirty = true;
+                }
+                else
+                {
+                    // Most drivers will render a combining mark at col 0 as the mark
+                    Contents [Row, Col].Rune = rune;
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+                    Contents [Row, Col].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].Rune.GetColumns () > 1)
+                    {
+                        // Invalidate cell to left
+                        Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
+                        Contents [Row, Col - 1].IsDirty = true;
+                    }
+                }
+
+                if (runeWidth < 1)
+                {
+                    Contents [Row, Col].Rune = Rune.ReplacementChar;
+                }
+                else if (runeWidth == 1)
+                {
+                    Contents [Row, Col].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].Rune = Rune.ReplacementChar;
+                    }
+                    else
+                    {
+                        Contents [Row, Col].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].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].Rune = (Rune)' ';
+                    Contents [Row, Col].IsDirty = false;
+                }
+
+                _dirtyLines [Row] = true;
+            }
+        }
+
+        if (runeWidth is < 0 or > 0)
+        {
+            Col++;
+        }
+
+        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;
+
+                // TODO: Determine if we should wipe this out (for now now)
+                //Contents [Row, Col].Rune = (Rune)' ';
+            }
+
+            Col++;
+        }
+    }
+
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    public void AddRune (char c) { AddRune (new Rune (c)); }
+
+    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
+    /// <remarks>
+    ///     <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>
+    /// <param name="str">String.</param>
+    public void AddStr (string str)
+    {
+        List<Rune> runes = str.EnumerateRunes ().ToList ();
+
+        for (var i = 0; i < runes.Count; i++)
+        {
+            //if (runes [i].IsCombiningMark()) {
+
+            //	// Attempt to normalize
+            //	string combined = runes [i-1] + runes [i].ToString();
+
+            //	// Normalize to Form C (Canonical Composition)
+            //	string normalized = combined.Normalize (NormalizationForm.FormC);
+
+            //	runes [i-]
+            //}
+            AddRune (runes [i]);
+        }
+    }
+
+    /// <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
+                        {
+                            Rune = (Rune)' ', Attribute = new Attribute (Color.White, Color.Black), IsDirty = true
+                        };
+                        _dirtyLines [row] = true;
+                    }
+                }
+            }
+            catch (IndexOutOfRangeException)
+            { }
+        }
+    }
+
+    /// <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 ();
+
+    // 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 (int r = rect.Y; r < rect.Y + rect.Height; r++)
+        {
+            for (int c = rect.X; c < rect.X + rect.Width; c++)
+            {
+                Application.Driver.Move (c, r);
+                Application.Driver.AddRune (rune == default (Rune) ? new Rune (' ') : rune);
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
+    ///     that calls <see cref="FillRect(Rect, Rune)"/>.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="c"></param>
+    public void FillRect (Rect rect, char c) { FillRect (rect, new Rune (c)); }
+
+    /// <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>Returns the name of the driver and relevant library version information.</summary>
+    /// <returns></returns>
+    public virtual string GetVersionInfo () { return GetType ().Name; }
+
+    /// <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>Tests whether the specified coordinate are valid for drawing.</summary>
+    /// <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) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); }
+
+    /// <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>Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.</summary>
+    /// <param name="args"></param>
+    public void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
+
+    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
+    public abstract void Refresh ();
+
+    /// <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 event fired when the terminal is resized.</summary>
+    public event EventHandler<SizeChangedEventArgs> SizeChanged;
+
+    /// <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 ();
+
+    /// <summary>Sets the position of the terminal cursor to <see cref="Col"/> and <see cref="Row"/>.</summary>
+    public abstract void UpdateCursor ();
+
+    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
+    public abstract void UpdateScreen ();
+
+    #region Setup & Teardown
+
+    /// <summary>Initializes the driver</summary>
+    /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
+    internal abstract MainLoop Init ();
+
+    /// <summary>Ends the execution of the console driver.</summary>
+    internal abstract void End ();
+
+    #endregion
+
+    #region Color Handling
+
+    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    public virtual bool SupportsTrueColor => true;
+
+    /// <summary>
+    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///     </para>
+    /// </remarks>
+    internal virtual bool Force16Colors
+    {
+        get => Application.Force16Colors || !SupportsTrueColor;
+        set => Application.Force16Colors = value || !SupportsTrueColor;
+    }
+
+    private Attribute _currentAttribute;
+    private int _cols;
+    private int _rows;
+
+    /// <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 (Application.Driver != null)
+            {
+                _currentAttribute = new Attribute (value.Foreground, value.Background);
+
+                return;
+            }
+
+            _currentAttribute = 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)
+    {
+        Attribute prevAttribute = CurrentAttribute;
+        CurrentAttribute = c;
+
+        return prevAttribute;
+    }
+
+    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
+    /// <returns>The current attribute.</returns>
+    public Attribute GetAttribute () { return CurrentAttribute; }
+
+    // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be
+    // removed (and Attribute can lose the platformColor property).
+    /// <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 virtual Attribute MakeColor (Color foreground, Color background)
+    {
+        // Encode the colors into the int value.
+        return new Attribute (
+                              -1, // only used by cursesdriver!
+                              foreground,
+                              background
+                             );
+    }
+
+    #endregion
+
+    #region Mouse and Keyboard
+
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
+    public event EventHandler<Key> KeyDown;
+
+    /// <summary>
+    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
+    ///     <see cref="OnKeyUp"/>.
+    /// </summary>
+    /// <param name="a"></param>
+    public void OnKeyDown (Key a) { KeyDown?.Invoke (this, a); }
+
+    /// <summary>Event fired when a key is released.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
+    ///     complete.
+    /// </remarks>
+    public event EventHandler<Key> KeyUp;
+
+    /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will calls this method after <see cref="OnKeyDown"/> processing
+    ///     is complete.
+    /// </remarks>
+    /// <param name="a"></param>
+    public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    public event EventHandler<MouseEventEventArgs> MouseEvent;
+
+    /// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
+    /// <param name="a"></param>
+    public void OnMouseEvent (MouseEventEventArgs a) { MouseEvent?.Invoke (this, a); }
+
+    /// <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);
+
+    #endregion
 }
 
-/// <summary>
-/// Terminal Cursor Visibility settings.
-/// </summary>
+/// <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,
-
-	/// <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,
-
-	/// <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
+///     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,
+
+    /// <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,
+
+    /// <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>
-/// The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a consistent
-/// way for application code to specify keys and receive key events. 
-/// <para>
-/// The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for common
-/// operations. For example, <see cref="Key.IsAlt"/> and <see cref="Key.IsCtrl"/> provide a convenient way
-/// to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed.
-/// </para>
+///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
+///     consistent way for application code to specify keys and receive key events.
+///     <para>
+///         The <see cref="Key"/> class provides a higher-level abstraction, with helper methods and properties for
+///         common operations. For example, <see cref="Key.IsAlt"/> and <see cref="Key.IsCtrl"/> provide a convenient way
+///         to check whether the Alt or Ctrl modifier keys were pressed when a key was pressed.
+///     </para>
 /// </summary>
 /// <remarks>
-/// <para>
-/// Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
-/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
-/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-/// </para>
-/// <para>
-/// Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>, <see cref="KeyCode.D1"/>, etc.).
-/// </para>
-/// <para>
-/// The shift modifiers (<see cref="KeyCode.ShiftMask"/>, <see cref="KeyCode.CtrlMask"/>, and <see cref="KeyCode.AltMask"/>) can be combined (with logical or)
-/// with the other key codes to represent shifted keys. For example, the <see cref="KeyCode.A"/> enum value represents the un-shifted 'a' key, while
-/// <see cref="KeyCode.ShiftMask"/> | <see cref="KeyCode.A"/> represents the 'A' key (shifted 'a' key). Likewise, <see cref="KeyCode.AltMask"/> | <see cref="KeyCode.A"/>
-/// represents the 'Alt+A' key combination.
-/// </para>
-/// <para>
-/// All other keys that produce a printable character are encoded as the Unicode value of the character. For example, the <see cref="KeyCode"/>
-/// for the '!' character is 33, which is the Unicode value for '!'. Likewise, `â` is 226, `Â` is 194, etc.
-/// </para>
-/// <para>
-/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
-/// otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>).
-/// </para>
+///     <para>
+///         Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a
+///         keyboard. Enum values are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.).
+///         Even though the values are the same as the ASCII values for uppercase characters, these enum values represent
+///         *lowercase*, un-shifted characters.
+///     </para>
+///     <para>
+///         Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>,
+///         <see cref="KeyCode.D1"/>, etc.).
+///     </para>
+///     <para>
+///         The shift modifiers (<see cref="KeyCode.ShiftMask"/>, <see cref="KeyCode.CtrlMask"/>, and
+///         <see cref="KeyCode.AltMask"/>) can be combined (with logical or) with the other key codes to represent shifted
+///         keys. For example, the <see cref="KeyCode.A"/> enum value represents the un-shifted 'a' key, while
+///         <see cref="KeyCode.ShiftMask"/> | <see cref="KeyCode.A"/> represents the 'A' key (shifted 'a' key). Likewise,
+///         <see cref="KeyCode.AltMask"/> | <see cref="KeyCode.A"/> represents the 'Alt+A' key combination.
+///     </para>
+///     <para>
+///         All other keys that produce a printable character are encoded as the Unicode value of the character. For
+///         example, the <see cref="KeyCode"/> for the '!' character is 33, which is the Unicode value for '!'. Likewise,
+///         `â` is 226, `Â` is 194, etc.
+///     </para>
+///     <para>
+///         If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
+///         the one of the lower bits (as extracted by <see cref="CharMask"/>).
+///     </para>
 /// </remarks>
 [Flags]
-public enum KeyCode : uint {
-	/// <summary>
-	/// Mask that indicates that the key is a unicode codepoint. Values outside this range
-	/// indicate the key has shift modifiers or is a special key like function keys, arrows keys and so on.
-	/// </summary>
-	CharMask = 0x_f_ffff,
-
-	/// <summary>
-	/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
-	/// otherwise, the value is in the the lower bits (as extracted by <see cref="CharMask"/>).
-	/// </summary>
-	SpecialMask = 0x_fff0_0000,
-
-	/// <summary>
-	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
-	/// The actual value must be extracted by removing the ShiftMask.
-	/// </summary>
-	ShiftMask = 0x_1000_0000,
-
-	/// <summary>
-	/// When this value is set, the Key encodes the sequence Alt-KeyValue.
-	/// The actual value must be extracted by removing the AltMask.
-	/// </summary>
-	AltMask = 0x_8000_0000,
-
-	/// <summary>
-	/// When this value is set, the Key encodes the sequence Ctrl-KeyValue.
-	/// The actual value must be extracted by removing the CtrlMask.
-	/// </summary>
-	CtrlMask = 0x_4000_0000,
-
-	/// <summary>
-	/// The key code representing an invalid or empty key.
-	/// </summary>
-	Null = 0,
-
-	/// <summary>
-	/// Backspace key.
-	/// </summary>
-	Backspace = 8,
-
-	/// <summary>
-	/// The key code for the tab key (forwards tab key).
-	/// </summary>
-	Tab = 9,
-
-	/// <summary>
-	/// The key code for the return key.
-	/// </summary>
-	Enter = ConsoleKey.Enter,
-
-	/// <summary>
-	/// The key code for the clear key.
-	/// </summary>
-	Clear = 12,
-
-	/// <summary>
-	/// The key code for the escape key.
-	/// </summary>
-	Esc = 27,
-
-	/// <summary>
-	/// The key code for the space bar key.
-	/// </summary>
-	Space = 32,
-
-	/// <summary>
-	/// Digit 0.
-	/// </summary>
-	D0 = 48,
-
-	/// <summary>
-	/// Digit 1.
-	/// </summary>
-	D1,
-
-	/// <summary>
-	/// Digit 2.
-	/// </summary>
-	D2,
-
-	/// <summary>
-	/// Digit 3.
-	/// </summary>
-	D3,
-
-	/// <summary>
-	/// Digit 4.
-	/// </summary>
-	D4,
-
-	/// <summary>
-	/// Digit 5.
-	/// </summary>
-	D5,
-
-	/// <summary>
-	/// Digit 6.
-	/// </summary>
-	D6,
-
-	/// <summary>
-	/// Digit 7.
-	/// </summary>
-	D7,
-
-	/// <summary>
-	/// Digit 8.
-	/// </summary>
-	D8,
-
-	/// <summary>
-	/// Digit 9.
-	/// </summary>
-	D9,
-
-	/// <summary>
-	/// The key code for the A key
-	/// </summary>
-	A = 65,
-
-	/// <summary>
-	/// The key code for the B key
-	/// </summary>
-	B,
-
-	/// <summary>
-	/// The key code for the C key
-	/// </summary>
-	C,
-
-	/// <summary>
-	/// The key code for the D key
-	/// </summary>
-	D,
-
-	/// <summary>
-	/// The key code for the E key
-	/// </summary>
-	E,
-
-	/// <summary>
-	/// The key code for the F key
-	/// </summary>
-	F,
-
-	/// <summary>
-	/// The key code for the G key
-	/// </summary>
-	G,
-
-	/// <summary>
-	/// The key code for the H key
-	/// </summary>
-	H,
-
-	/// <summary>
-	/// The key code for the I key
-	/// </summary>
-	I,
-
-	/// <summary>
-	/// The key code for the J key
-	/// </summary>
-	J,
-
-	/// <summary>
-	/// The key code for the K key
-	/// </summary>
-	K,
-
-	/// <summary>
-	/// The key code for the L key
-	/// </summary>
-	L,
-
-	/// <summary>
-	/// The key code for the M key
-	/// </summary>
-	M,
-
-	/// <summary>
-	/// The key code for the N key
-	/// </summary>
-	N,
-
-	/// <summary>
-	/// The key code for the O key
-	/// </summary>
-	O,
-
-	/// <summary>
-	/// The key code for the P key
-	/// </summary>
-	P,
-
-	/// <summary>
-	/// The key code for the Q key
-	/// </summary>
-	Q,
-
-	/// <summary>
-	/// The key code for the R key
-	/// </summary>
-	R,
-
-	/// <summary>
-	/// The key code for the S key
-	/// </summary>
-	S,
-
-	/// <summary>
-	/// The key code for the T key
-	/// </summary>
-	T,
-
-	/// <summary>
-	/// The key code for the U key
-	/// </summary>
-	U,
-
-	/// <summary>
-	/// The key code for the V key
-	/// </summary>
-	V,
-
-	/// <summary>
-	/// The key code for the W key
-	/// </summary>
-	W,
-
-	/// <summary>
-	/// The key code for the X key
-	/// </summary>
-	X,
-
-	/// <summary>
-	/// The key code for the Y key
-	/// </summary>
-	Y,
-
-	/// <summary>
-	/// The key code for the Z key
-	/// </summary>
-	Z,
-
-	///// <summary>
-	///// The key code for the Delete key.
-	///// </summary>
-	//Delete = 127,
-
-	// --- Special keys ---
-	// The values below are common non-alphanum keys. Their values are 
-	// based on the .NET ConsoleKey values, which, in-turn are based on the
-	// VK_ values from the Windows API. 
-	// We add MaxCodePoint to avoid conflicts with the Unicode values.
-
-	/// <summary>
-	/// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control
-	/// keys. 
-	/// </summary>
-	MaxCodePoint = 0x10FFFF,
-
-	/// <summary>
-	/// Cursor up key
-	/// </summary>
-	CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
-
-	/// <summary>
-	/// Cursor down key.
-	/// </summary>
-	CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
-
-	/// <summary>
-	/// Cursor left key.
-	/// </summary>
-	CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
-
-	/// <summary>
-	/// Cursor right key.
-	/// </summary>
-	CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
-
-	/// <summary>
-	/// Page Up key.
-	/// </summary>
-	PageUp = MaxCodePoint + ConsoleKey.PageUp,
-
-	/// <summary>
-	/// Page Down key.
-	/// </summary>
-	PageDown = MaxCodePoint + ConsoleKey.PageDown,
-
-	/// <summary>
-	/// Home key.
-	/// </summary>
-	Home = MaxCodePoint + ConsoleKey.Home,
-
-	/// <summary>
-	/// End key.
-	/// </summary>
-	End = MaxCodePoint + ConsoleKey.End,
-
-	/// <summary>
-	/// Insert (INS) key.
-	/// </summary>
-	Insert = MaxCodePoint + ConsoleKey.Insert,
-
-	/// <summary>
-	/// Delete (DEL) key.
-	/// </summary>
-	Delete = MaxCodePoint + ConsoleKey.Delete,
-
-	/// <summary>
-	/// Print screen character key.
-	/// </summary>
-	PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
-
-	/// <summary>
-	/// F1 key.
-	/// </summary>
-	F1 = MaxCodePoint + ConsoleKey.F1,
-
-	/// <summary>
-	/// F2 key.
-	/// </summary>
-	F2 = MaxCodePoint + ConsoleKey.F2,
-
-	/// <summary>
-	/// F3 key.
-	/// </summary>
-	F3 = MaxCodePoint + ConsoleKey.F3,
-
-	/// <summary>
-	/// F4 key.
-	/// </summary>
-	F4 = MaxCodePoint + ConsoleKey.F4,
-
-	/// <summary>
-	/// F5 key.
-	/// </summary>
-	F5 = MaxCodePoint + ConsoleKey.F5,
-
-	/// <summary>
-	/// F6 key.
-	/// </summary>
-	F6 = MaxCodePoint + ConsoleKey.F6,
-
-	/// <summary>
-	/// F7 key.
-	/// </summary>
-	F7 = MaxCodePoint + ConsoleKey.F7,
-
-	/// <summary>
-	/// F8 key.
-	/// </summary>
-	F8 = MaxCodePoint + ConsoleKey.F8,
-
-	/// <summary>
-	/// F9 key.
-	/// </summary>
-	F9 = MaxCodePoint + ConsoleKey.F9,
-
-	/// <summary>
-	/// F10 key.
-	/// </summary>
-	F10 = MaxCodePoint + ConsoleKey.F10,
-
-	/// <summary>
-	/// F11 key.
-	/// </summary>
-	F11 = MaxCodePoint + ConsoleKey.F11,
-
-	/// <summary>
-	/// F12 key.
-	/// </summary>
-	F12 = MaxCodePoint + ConsoleKey.F12,
-
-	/// <summary>
-	/// F13 key.
-	/// </summary>
-	F13 = MaxCodePoint + ConsoleKey.F13,
-
-	/// <summary>
-	/// F14 key.
-	/// </summary>
-	F14 = MaxCodePoint + ConsoleKey.F14,
-
-	/// <summary>
-	/// F15 key.
-	/// </summary>
-	F15 = MaxCodePoint + ConsoleKey.F15,
-
-	/// <summary>
-	/// F16 key.
-	/// </summary>
-	F16 = MaxCodePoint + ConsoleKey.F16,
-
-	/// <summary>
-	/// F17 key.
-	/// </summary>
-	F17 = MaxCodePoint + ConsoleKey.F17,
-
-	/// <summary>
-	/// F18 key.
-	/// </summary>
-	F18 = MaxCodePoint + ConsoleKey.F18,
-
-	/// <summary>
-	/// F19 key.
-	/// </summary>
-	F19 = MaxCodePoint + ConsoleKey.F19,
-
-	/// <summary>
-	/// F20 key.
-	/// </summary>
-	F20 = MaxCodePoint + ConsoleKey.F20,
-
-	/// <summary>
-	/// F21 key.
-	/// </summary>
-	F21 = MaxCodePoint + ConsoleKey.F21,
-
-	/// <summary>
-	/// F22 key.
-	/// </summary>
-	F22 = MaxCodePoint + ConsoleKey.F22,
-
-	/// <summary>
-	/// F23 key.
-	/// </summary>
-	F23 = MaxCodePoint + ConsoleKey.F23,
-
-	/// <summary>
-	/// F24 key.
-	/// </summary>
-	F24 = MaxCodePoint + ConsoleKey.F24,
-}
+public enum KeyCode : uint
+{
+    /// <summary>
+    ///     Mask that indicates that the key is a unicode codepoint. Values outside this range indicate the key has shift
+    ///     modifiers or is a special key like function keys, arrows keys and so on.
+    /// </summary>
+    CharMask = 0x_f_ffff,
+
+    /// <summary>
+    ///     If the <see cref="SpecialMask"/> is set, then the value is that of the special mask, otherwise, the value is
+    ///     in the the lower bits (as extracted by <see cref="CharMask"/>).
+    /// </summary>
+    SpecialMask = 0x_fff0_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Shift-KeyValue. The actual value must be extracted by
+    ///     removing the ShiftMask.
+    /// </summary>
+    ShiftMask = 0x_1000_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Alt-KeyValue. The actual value must be extracted by
+    ///     removing the AltMask.
+    /// </summary>
+    AltMask = 0x_8000_0000,
+
+    /// <summary>
+    ///     When this value is set, the Key encodes the sequence Ctrl-KeyValue. The actual value must be extracted by
+    ///     removing the CtrlMask.
+    /// </summary>
+    CtrlMask = 0x_4000_0000,
+
+    /// <summary>The key code representing an invalid or empty key.</summary>
+    Null = 0,
+
+    /// <summary>Backspace key.</summary>
+    Backspace = 8,
+
+    /// <summary>The key code for the tab key (forwards tab key).</summary>
+    Tab = 9,
+
+    /// <summary>The key code for the return key.</summary>
+    Enter = ConsoleKey.Enter,
+
+    /// <summary>The key code for the clear key.</summary>
+    Clear = 12,
+
+    /// <summary>The key code for the escape key.</summary>
+    Esc = 27,
+
+    /// <summary>The key code for the space bar key.</summary>
+    Space = 32,
+
+    /// <summary>Digit 0.</summary>
+    D0 = 48,
+
+    /// <summary>Digit 1.</summary>
+    D1,
+
+    /// <summary>Digit 2.</summary>
+    D2,
+
+    /// <summary>Digit 3.</summary>
+    D3,
+
+    /// <summary>Digit 4.</summary>
+    D4,
+
+    /// <summary>Digit 5.</summary>
+    D5,
+
+    /// <summary>Digit 6.</summary>
+    D6,
+
+    /// <summary>Digit 7.</summary>
+    D7,
+
+    /// <summary>Digit 8.</summary>
+    D8,
+
+    /// <summary>Digit 9.</summary>
+    D9,
+
+    /// <summary>The key code for the A key</summary>
+    A = 65,
+
+    /// <summary>The key code for the B key</summary>
+    B,
+
+    /// <summary>The key code for the C key</summary>
+    C,
+
+    /// <summary>The key code for the D key</summary>
+    D,
+
+    /// <summary>The key code for the E key</summary>
+    E,
+
+    /// <summary>The key code for the F key</summary>
+    F,
+
+    /// <summary>The key code for the G key</summary>
+    G,
+
+    /// <summary>The key code for the H key</summary>
+    H,
+
+    /// <summary>The key code for the I key</summary>
+    I,
+
+    /// <summary>The key code for the J key</summary>
+    J,
+
+    /// <summary>The key code for the K key</summary>
+    K,
+
+    /// <summary>The key code for the L key</summary>
+    L,
+
+    /// <summary>The key code for the M key</summary>
+    M,
+
+    /// <summary>The key code for the N key</summary>
+    N,
+
+    /// <summary>The key code for the O key</summary>
+    O,
+
+    /// <summary>The key code for the P key</summary>
+    P,
+
+    /// <summary>The key code for the Q key</summary>
+    Q,
+
+    /// <summary>The key code for the R key</summary>
+    R,
+
+    /// <summary>The key code for the S key</summary>
+    S,
+
+    /// <summary>The key code for the T key</summary>
+    T,
+
+    /// <summary>The key code for the U key</summary>
+    U,
+
+    /// <summary>The key code for the V key</summary>
+    V,
+
+    /// <summary>The key code for the W key</summary>
+    W,
+
+    /// <summary>The key code for the X key</summary>
+    X,
+
+    /// <summary>The key code for the Y key</summary>
+    Y,
+
+    /// <summary>The key code for the Z key</summary>
+    Z,
+
+    ///// <summary>
+    ///// The key code for the Delete key.
+    ///// </summary>
+    //Delete = 127,
+
+    // --- Special keys ---
+    // The values below are common non-alphanum keys. Their values are 
+    // based on the .NET ConsoleKey values, which, in-turn are based on the
+    // VK_ values from the Windows API. 
+    // We add MaxCodePoint to avoid conflicts with the Unicode values.
+
+    /// <summary>The maximum Unicode codepoint value. Used to encode the non-alphanumeric control keys.</summary>
+    MaxCodePoint = 0x10FFFF,
+
+    /// <summary>Cursor up key</summary>
+    CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
+
+    /// <summary>Cursor down key.</summary>
+    CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
+
+    /// <summary>Cursor left key.</summary>
+    CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
+
+    /// <summary>Cursor right key.</summary>
+    CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
+
+    /// <summary>Page Up key.</summary>
+    PageUp = MaxCodePoint + ConsoleKey.PageUp,
+
+    /// <summary>Page Down key.</summary>
+    PageDown = MaxCodePoint + ConsoleKey.PageDown,
+
+    /// <summary>Home key.</summary>
+    Home = MaxCodePoint + ConsoleKey.Home,
+
+    /// <summary>End key.</summary>
+    End = MaxCodePoint + ConsoleKey.End,
+
+    /// <summary>Insert (INS) key.</summary>
+    Insert = MaxCodePoint + ConsoleKey.Insert,
+
+    /// <summary>Delete (DEL) key.</summary>
+    Delete = MaxCodePoint + ConsoleKey.Delete,
+
+    /// <summary>Print screen character key.</summary>
+    PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
+
+    /// <summary>F1 key.</summary>
+    F1 = MaxCodePoint + ConsoleKey.F1,
+
+    /// <summary>F2 key.</summary>
+    F2 = MaxCodePoint + ConsoleKey.F2,
+
+    /// <summary>F3 key.</summary>
+    F3 = MaxCodePoint + ConsoleKey.F3,
+
+    /// <summary>F4 key.</summary>
+    F4 = MaxCodePoint + ConsoleKey.F4,
+
+    /// <summary>F5 key.</summary>
+    F5 = MaxCodePoint + ConsoleKey.F5,
+
+    /// <summary>F6 key.</summary>
+    F6 = MaxCodePoint + ConsoleKey.F6,
+
+    /// <summary>F7 key.</summary>
+    F7 = MaxCodePoint + ConsoleKey.F7,
+
+    /// <summary>F8 key.</summary>
+    F8 = MaxCodePoint + ConsoleKey.F8,
+
+    /// <summary>F9 key.</summary>
+    F9 = MaxCodePoint + ConsoleKey.F9,
+
+    /// <summary>F10 key.</summary>
+    F10 = MaxCodePoint + ConsoleKey.F10,
+
+    /// <summary>F11 key.</summary>
+    F11 = MaxCodePoint + ConsoleKey.F11,
+
+    /// <summary>F12 key.</summary>
+    F12 = MaxCodePoint + ConsoleKey.F12,
+
+    /// <summary>F13 key.</summary>
+    F13 = MaxCodePoint + ConsoleKey.F13,
+
+    /// <summary>F14 key.</summary>
+    F14 = MaxCodePoint + ConsoleKey.F14,
+
+    /// <summary>F15 key.</summary>
+    F15 = MaxCodePoint + ConsoleKey.F15,
+
+    /// <summary>F16 key.</summary>
+    F16 = MaxCodePoint + ConsoleKey.F16,
+
+    /// <summary>F17 key.</summary>
+    F17 = MaxCodePoint + ConsoleKey.F17,
+
+    /// <summary>F18 key.</summary>
+    F18 = MaxCodePoint + ConsoleKey.F18,
+
+    /// <summary>F19 key.</summary>
+    F19 = MaxCodePoint + ConsoleKey.F19,
+
+    /// <summary>F20 key.</summary>
+    F20 = MaxCodePoint + ConsoleKey.F20,
+
+    /// <summary>F21 key.</summary>
+    F21 = MaxCodePoint + ConsoleKey.F21,
+
+    /// <summary>F22 key.</summary>
+    F22 = MaxCodePoint + ConsoleKey.F22,
+
+    /// <summary>F23 key.</summary>
+    F23 = MaxCodePoint + ConsoleKey.F23,
+
+    /// <summary>F24 key.</summary>
+    F24 = MaxCodePoint + ConsoleKey.F24
+}

+ 2528 - 1718
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -1,1726 +1,2536 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
+using System.Globalization;
 using System.Runtime.InteropServices;
-using System.Text;
 
 namespace Terminal.Gui.ConsoleDrivers;
 
-/// <summary>
-/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
-/// </summary>
-public static class ConsoleKeyMapping {
-
+/// <summary>Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.</summary>
+public static class ConsoleKeyMapping
+{
 #if !WT_ISSUE_8871_FIXED // https://github.com/microsoft/terminal/issues/8871
-	/// <summary>
-	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
-	/// </summary>
-	/// <param name="vk"></param>
-	/// <param name="uMapType">
-	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an un-shifted
-	/// character value in the low order word of the return value. 
-	/// </param>
-	/// <param name="dwhkl"></param>
-	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
-	/// are indicated by setting the top bit of the return value. If there is no translation,
-	/// the function returns 0. See Remarks.</returns>
-	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)]
-	extern static uint MapVirtualKeyEx (VK vk, uint uMapType, IntPtr dwhkl);
-
-	/// <summary>
-	/// Retrieves the active input locale identifier (formerly called the keyboard layout).
-	/// </summary>
-	/// <param name="idThread">0 for current thread</param>
-	/// <returns>The return value is the input locale identifier for the thread.
-	/// The low word contains a Language Identifier for the input language
-	/// and the high word contains a device handle to the physical layout of the keyboard.
-	/// </returns>
-	[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)]
-	extern static IntPtr GetKeyboardLayout (IntPtr idThread);
-
-	//[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)]
-	//extern static uint GetKeyboardLayoutName (uint idThread);
-
-	[DllImport ("user32.dll")]
-	extern static IntPtr GetForegroundWindow ();
-
-	[DllImport ("user32.dll")]
-	extern static IntPtr GetWindowThreadProcessId (IntPtr hWnd, IntPtr ProcessId);
-
-	/// <summary>
-	/// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters using
-	/// the Win32 API MapVirtualKey.
-	/// </summary>
-	/// <param name="vk"></param>
-	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
-	/// are indicated by setting the top bit of the return value. If there is no translation,
-	/// the function returns 0.</returns>
-	public static uint MapVKtoChar (VK vk)
-	{
-		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
-			return 0;
-		}
-		var tid = GetWindowThreadProcessId (GetForegroundWindow (), 0);
-		var hkl = GetKeyboardLayout (tid);
-		return MapVirtualKeyEx (vk, 2, hkl);
-	}
+    /// <summary>
+    ///     Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a
+    ///     virtual-key code.
+    /// </summary>
+    /// <param name="vk"></param>
+    /// <param name="uMapType">
+    ///     If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an
+    ///     un-shifted character value in the low order word of the return value.
+    /// </param>
+    /// <param name="dwhkl"></param>
+    /// <returns>
+    ///     An un-shifted character value in the low order word of the return value. Dead keys (diacritics) are indicated
+    ///     by setting the top bit of the return value. If there is no translation, the function returns 0. See Remarks.
+    /// </returns>
+    [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)]
+    private static extern uint MapVirtualKeyEx (VK vk, uint uMapType, nint dwhkl);
+
+    /// <summary>Retrieves the active input locale identifier (formerly called the keyboard layout).</summary>
+    /// <param name="idThread">0 for current thread</param>
+    /// <returns>
+    ///     The return value is the input locale identifier for the thread. The low word contains a Language Identifier
+    ///     for the input language and the high word contains a device handle to the physical layout of the keyboard.
+    /// </returns>
+    [DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)]
+    private static extern nint GetKeyboardLayout (nint idThread);
+
+    //[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)]
+    //extern static uint GetKeyboardLayoutName (uint idThread);
+    [DllImport ("user32.dll")]
+    private static extern nint GetForegroundWindow ();
+
+    [DllImport ("user32.dll")]
+    private static extern nint GetWindowThreadProcessId (nint hWnd, nint ProcessId);
+
+    /// <summary>
+    ///     Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or
+    ///     characters using the Win32 API MapVirtualKey.
+    /// </summary>
+    /// <param name="vk"></param>
+    /// <returns>
+    ///     An un-shifted character value in the low order word of the return value. Dead keys (diacritics) are indicated
+    ///     by setting the top bit of the return value. If there is no translation, the function returns 0.
+    /// </returns>
+    public static uint MapVKtoChar (VK vk)
+    {
+        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+        {
+            return 0;
+        }
+
+        nint tid = GetWindowThreadProcessId (GetForegroundWindow (), 0);
+        nint hkl = GetKeyboardLayout (tid);
+
+        return MapVirtualKeyEx (vk, 2, hkl);
+    }
 #else
-	/// <summary>
-	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
-	/// </summary>
-	/// <param name="vk"></param>
-	/// <param name="uMapType">
-	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted
-	/// character value in the low order word of the return value. 
-	/// </param>
-	/// <returns>An unshifted character value in the low order word of the return value. Dead keys (diacritics)
-	/// are indicated by setting the top bit of the return value. If there is no translation,
-	/// the function returns 0. See Remarks.</returns>
-	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)]
-	extern static uint MapVirtualKey (VK vk, uint uMapType = 2);
-
-	uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk);
+    /// <summary>
+    /// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
+    /// </summary>
+    /// <param name="vk"></param>
+    /// <param name="uMapType">
+    /// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted
+    /// character value in the low order word of the return value. 
+    /// </param>
+    /// <returns>An unshifted character value in the low order word of the return value. Dead keys (diacritics)
+    /// are indicated by setting the top bit of the return value. If there is no translation,
+    /// the function returns 0. See Remarks.</returns>
+    [DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)]
+    extern static uint MapVirtualKey (VK vk, uint uMapType = 2);
+
+    uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk);
 #endif
-	/// <summary>
-	/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
-	/// </summary>
-	/// <param name="pwszKLID"></param>
-	/// <returns></returns>
-	[DllImport ("user32.dll")]
-	extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);
-
-	/// <summary>
-	/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
-	/// </summary>
-	/// <returns></returns>
-	public static string GetKeyboardLayoutName ()
-	{
-		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
-			return "none";
-		}
-
-		StringBuilder klidSB = new StringBuilder ();
-		GetKeyboardLayoutName (klidSB);
-		return klidSB.ToString ();
-	}
-
-	class ScanCodeMapping : IEquatable<ScanCodeMapping> {
-		public uint ScanCode;
-		public VK VirtualKey;
-		public ConsoleModifiers Modifiers;
-		public uint UnicodeChar;
-
-		public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
-		{
-			ScanCode = scanCode;
-			VirtualKey = virtualKey;
-			Modifiers = modifiers;
-			UnicodeChar = unicodeChar;
-		}
-
-		public bool Equals (ScanCodeMapping other)
-		{
-			return ScanCode.Equals (other.ScanCode) &&
-				VirtualKey.Equals (other.VirtualKey) &&
-				Modifiers.Equals (other.Modifiers) &&
-				UnicodeChar.Equals (other.UnicodeChar);
-		}
-	}
-
-	static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
-	{
-		if (modifiers.HasFlag (ConsoleModifiers.Shift)
-		&& !modifiers.HasFlag (ConsoleModifiers.Alt)
-		&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
-			return ConsoleModifiers.Shift;
-		} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-			return modifiers;
-		}
-
-		return 0;
-	}
-
-	static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
-	{
-		switch (propName) {
-		case "UnicodeChar":
-			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
-			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-				return _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
-			}
-			return sCode;
-		case "VirtualKey":
-			sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers);
-			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-				return _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == 0);
-			}
-			return sCode;
-		}
-
-		return null;
-	}
-
-	// BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios
-	/// <summary>
-	/// Get the scan code from a <see cref="ConsoleKeyInfo"/>.
-	/// </summary>
-	/// <param name="consoleKeyInfo">The console key info.</param>
-	/// <returns>The value if apply.</returns>
-	public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-	{
-		var mod = GetModifiers (consoleKeyInfo.Modifiers);
-		ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod);
-		if (scode != null) {
-			return scode.ScanCode;
-		}
-
-		return 0;
-	}
-
-	// BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator
-	/// <summary>
-	/// Gets the <see cref="ConsoleKeyInfo"/> from the provided <see cref="KeyCode"/>.
-	/// </summary>
-	/// <param name="key">The key code.</param>
-	/// <returns>The console key info.</returns>
-	public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
-	{
-		var modifiers = MapToConsoleModifiers (key);
-		var keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey);
-		if (isConsoleKey) {
-			var mod = GetModifiers (modifiers);
-			var scode = GetScanCode ("VirtualKey", (uint)keyValue, mod);
-			if (scode != null) {
-				return new ConsoleKeyInfo ((char)scode.UnicodeChar, (ConsoleKey)scode.VirtualKey, modifiers.HasFlag (ConsoleModifiers.Shift),
-					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
-			}
-		} else {
-			var keyChar = GetKeyCharFromUnicodeChar ((uint)keyValue, modifiers, out uint consoleKey, out _, isConsoleKey);
-			if (consoleKey != 0) {
-				return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
-					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
-			}
-		}
-
-		return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
-			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
-	}
-
-	/// <summary>
-	/// Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.
-	/// </summary>
-	/// <param name="key">The key code.</param>
-	/// <returns>The console modifiers.</returns>
-	public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
-	{
-		var modifiers = new ConsoleModifiers ();
-		if (key.HasFlag (KeyCode.ShiftMask)) {
-			modifiers |= ConsoleModifiers.Shift;
-		}
-		if (key.HasFlag (KeyCode.AltMask)) {
-			modifiers |= ConsoleModifiers.Alt;
-		}
-		if (key.HasFlag (KeyCode.CtrlMask)) {
-			modifiers |= ConsoleModifiers.Control;
-		}
-
-		return modifiers;
-	}
-
-	/// <summary>
-	/// Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.
-	/// </summary>
-	/// <param name="shift">The shift key.</param>
-	/// <param name="alt">The alt key.</param>
-	/// <param name="control">The control key.</param>
-	/// <returns>The console modifiers.</returns>
-	public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
-	{
-		var modifiers = new ConsoleModifiers ();
-		if (shift) {
-			modifiers |= ConsoleModifiers.Shift;
-		}
-		if (alt) {
-			modifiers |= ConsoleModifiers.Alt;
-		}
-		if (control) {
-			modifiers |= ConsoleModifiers.Control;
-		}
-
-		return modifiers;
-	}
-
-	/// <summary>
-	/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
-	/// </summary>
-	/// <param name="keyValue">The key as a unicode codepoint.</param>
-	/// <param name="modifiers">The modifier keys.</param>
-	/// <param name="scanCode">The resulting scan code.</param>
-	/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
-	static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
-	{
-		scanCode = 0;
-		if (keyValue == 0) {
-			return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
-				modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
-		}
-
-		uint outputChar = keyValue;
-		uint consoleKey;
-		if (keyValue > byte.MaxValue) {
-			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue);
-			if (sCode == null) {
-				consoleKey = (byte)(keyValue & byte.MaxValue);
-				sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)consoleKey);
-				if (sCode == null) {
-					consoleKey = 0;
-					outputChar = keyValue;
-				} else {
-					outputChar = (char)(keyValue >> 8);
-				}
-			} else {
-				consoleKey = (byte)sCode.VirtualKey;
-				outputChar = keyValue;
-			}
-		} else {
-			consoleKey = (byte)keyValue;
-			outputChar = '\0';
-		}
-
-		return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
-			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
-	}
-
-	// Used only by unit tests
-	internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers)
-	{
-		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z') {
-			return keyValue - 32;
-		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z') {
-			return keyValue + 32;
-		}
-
-		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý') {
-			return keyValue - 32;
-		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý') {
-			return keyValue + 32;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0') {
-			return keyValue + 13;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0') {
-			return keyValue - 13;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7') {
-			return keyValue - 16;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7') {
-			return keyValue + 16;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7') {
-			return keyValue - 8;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7') {
-			return keyValue + 8;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'') {
-			return keyValue + 24;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'') {
-			return keyValue - 24;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«') {
-			return keyValue + 16;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«') {
-			return keyValue - 16;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\') {
-			return keyValue + 32;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\') {
-			return keyValue - 32;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+') {
-			return keyValue - 1;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+') {
-			return keyValue + 1;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´') {
-			return keyValue - 84;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´') {
-			return keyValue + 84;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º') {
-			return keyValue - 16;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º') {
-			return keyValue + 16;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~') {
-			return keyValue - 32;
-		} else if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~') {
-			return keyValue + 32;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<') {
-			return keyValue + 2;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<') {
-			return keyValue - 2;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',') {
-			return keyValue + 15;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',') {
-			return keyValue - 15;
-		}
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.') {
-			return keyValue + 12;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.') {
-			return keyValue - 12;
-		}
-
-		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-') {
-			return keyValue + 50;
-		} else if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-') {
-			return keyValue - 50;
-		}
-
-		return keyValue;
-	}
-
-	/// <summary>
-	/// Get the output character from the <see cref="GetConsoleKeyInfoFromKeyCode"/>, with the correct <see cref="ConsoleKey"/>
-	/// and the scan code used on <see cref="WindowsDriver"/>.
-	/// </summary>
-	/// <param name="unicodeChar">The unicode character.</param>
-	/// <param name="modifiers">The modifiers keys.</param>
-	/// <param name="consoleKey">The resulting console key.</param>
-	/// <param name="scanCode">The resulting scan code.</param>
-	/// <param name="isConsoleKey">Indicates if the <paramref name="unicodeChar"/> is a <see cref="ConsoleKey"/>.</param>
-	/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
-	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
-	internal static uint GetKeyCharFromUnicodeChar (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode, bool isConsoleKey = false)
-	{
-		uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
-		uint keyChar = decodedChar;
-		consoleKey = 0;
-		var mod = GetModifiers (modifiers);
-		scanCode = 0;
-		ScanCodeMapping scode = null;
-		if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey) {
-			scode = GetScanCode ("VirtualKey", decodedChar, mod);
-		}
-		if (isConsoleKey && scode != null) {
-			consoleKey = (uint)scode.VirtualKey;
-			keyChar = scode.UnicodeChar;
-			scanCode = scode.ScanCode;
-		}
-		if (scode == null) {
-			scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
-			if (scode != null) {
-				consoleKey = (uint)scode.VirtualKey;
-				keyChar = scode.UnicodeChar;
-				scanCode = scode.ScanCode;
-			}
-		}
-		if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
-			string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
-			for (int i = 0; i < stFormD.Length; i++) {
-				var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
-				if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
-					consoleKey = char.ToUpper (stFormD [i]);
-					scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
-					if (scode != null) {
-						scanCode = scode.ScanCode;
-					}
-				}
-			}
-		}
-		if (keyChar < 255 && consoleKey == 0 && scanCode == 0) {
-			scode = GetScanCode ("VirtualKey", keyChar, mod);
-			if (scode != null) {
-				consoleKey = (uint)scode.VirtualKey;
-				keyChar = scode.UnicodeChar;
-				scanCode = scode.ScanCode;
-			}
-		}
-
-		return keyChar;
-	}
-
-	/// <summary>
-	/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
-	/// </summary>
-	/// <param name="keyValue">The key value.</param>
-	/// <param name="isConsoleKey">Indicates if the <paramref name="keyValue"/> is a <see cref="ConsoleKey"/>.
-	/// <see langword="true"/> means the return value is in the ConsoleKey enum.
-	/// <see langword="false"/> means the return value can be mapped to a valid unicode character.
-	/// </param>
-	/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
-	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
-	internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey)
-	{
-		isConsoleKey = true;
-		keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
-
-		switch (keyValue) {
-		case KeyCode.Enter:
-			return (uint)ConsoleKey.Enter;
-		case KeyCode.CursorUp:
-			return (uint)ConsoleKey.UpArrow;
-		case KeyCode.CursorDown:
-			return (uint)ConsoleKey.DownArrow;
-		case KeyCode.CursorLeft:
-			return (uint)ConsoleKey.LeftArrow;
-		case KeyCode.CursorRight:
-			return (uint)ConsoleKey.RightArrow;
-		case KeyCode.PageUp:
-			return (uint)ConsoleKey.PageUp;
-		case KeyCode.PageDown:
-			return (uint)ConsoleKey.PageDown;
-		case KeyCode.Home:
-			return (uint)ConsoleKey.Home;
-		case KeyCode.End:
-			return (uint)ConsoleKey.End;
-		case KeyCode.Insert:
-			return (uint)ConsoleKey.Insert;
-		case KeyCode.Delete:
-			return (uint)ConsoleKey.Delete;
-		case KeyCode.F1:
-			return (uint)ConsoleKey.F1;
-		case KeyCode.F2:
-			return (uint)ConsoleKey.F2;
-		case KeyCode.F3:
-			return (uint)ConsoleKey.F3;
-		case KeyCode.F4:
-			return (uint)ConsoleKey.F4;
-		case KeyCode.F5:
-			return (uint)ConsoleKey.F5;
-		case KeyCode.F6:
-			return (uint)ConsoleKey.F6;
-		case KeyCode.F7:
-			return (uint)ConsoleKey.F7;
-		case KeyCode.F8:
-			return (uint)ConsoleKey.F8;
-		case KeyCode.F9:
-			return (uint)ConsoleKey.F9;
-		case KeyCode.F10:
-			return (uint)ConsoleKey.F10;
-		case KeyCode.F11:
-			return (uint)ConsoleKey.F11;
-		case KeyCode.F12:
-			return (uint)ConsoleKey.F12;
-		case KeyCode.F13:
-			return (uint)ConsoleKey.F13;
-		case KeyCode.F14:
-			return (uint)ConsoleKey.F14;
-		case KeyCode.F15:
-			return (uint)ConsoleKey.F15;
-		case KeyCode.F16:
-			return (uint)ConsoleKey.F16;
-		case KeyCode.F17:
-			return (uint)ConsoleKey.F17;
-		case KeyCode.F18:
-			return (uint)ConsoleKey.F18;
-		case KeyCode.F19:
-			return (uint)ConsoleKey.F19;
-		case KeyCode.F20:
-			return (uint)ConsoleKey.F20;
-		case KeyCode.F21:
-			return (uint)ConsoleKey.F21;
-		case KeyCode.F22:
-			return (uint)ConsoleKey.F22;
-		case KeyCode.F23:
-			return (uint)ConsoleKey.F23;
-		case KeyCode.F24:
-			return (uint)ConsoleKey.F24;
-		case KeyCode.Tab | KeyCode.ShiftMask:
-			return (uint)ConsoleKey.Tab;
-		}
-
-		isConsoleKey = false;
-		return (uint)keyValue;
-	}
-
-	/// <summary>
-	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
-	/// </summary>
-	/// <param name="consoleKeyInfo">The console key.</param>
-	/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
-	public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
-	{
-		KeyCode keyCode;
-
-		switch (consoleKeyInfo.Key) {
-		case ConsoleKey.Enter:
-			keyCode = KeyCode.Enter;
-			break;
-		case ConsoleKey.Delete:
-			keyCode = KeyCode.Delete;
-			break;
-		case ConsoleKey.UpArrow:
-			keyCode = KeyCode.CursorUp;
-			break;
-		case ConsoleKey.DownArrow:
-			keyCode = KeyCode.CursorDown;
-			break;
-		case ConsoleKey.LeftArrow:
-			keyCode = KeyCode.CursorLeft;
-			break;
-		case ConsoleKey.RightArrow:
-			keyCode = KeyCode.CursorRight;
-			break;
-		case ConsoleKey.PageUp:
-			keyCode = KeyCode.PageUp;
-			break;
-		case ConsoleKey.PageDown:
-			keyCode = KeyCode.PageDown;
-			break;
-		case ConsoleKey.Home:
-			keyCode = KeyCode.Home;
-			break;
-		case ConsoleKey.End:
-			keyCode = KeyCode.End;
-			break;
-		case ConsoleKey.Insert:
-			keyCode = KeyCode.Insert;
-			break;
-		case ConsoleKey.F1:
-			keyCode = KeyCode.F1;
-			break;
-		case ConsoleKey.F2:
-			keyCode = KeyCode.F2;
-			break;
-		case ConsoleKey.F3:
-			keyCode = KeyCode.F3;
-			break;
-		case ConsoleKey.F4:
-			keyCode = KeyCode.F4;
-			break;
-		case ConsoleKey.F5:
-			keyCode = KeyCode.F5;
-			break;
-		case ConsoleKey.F6:
-			keyCode = KeyCode.F6;
-			break;
-		case ConsoleKey.F7:
-			keyCode = KeyCode.F7;
-			break;
-		case ConsoleKey.F8:
-			keyCode = KeyCode.F8;
-			break;
-		case ConsoleKey.F9:
-			keyCode = KeyCode.F9;
-			break;
-		case ConsoleKey.F10:
-			keyCode = KeyCode.F10;
-			break;
-		case ConsoleKey.F11:
-			keyCode = KeyCode.F11;
-			break;
-		case ConsoleKey.F12:
-			keyCode = KeyCode.F12;
-			break;
-		case ConsoleKey.F13:
-			keyCode = KeyCode.F13;
-			break;
-		case ConsoleKey.F14:
-			keyCode = KeyCode.F14;
-			break;
-		case ConsoleKey.F15:
-			keyCode = KeyCode.F15;
-			break;
-		case ConsoleKey.F16:
-			keyCode = KeyCode.F16;
-			break;
-		case ConsoleKey.F17:
-			keyCode = KeyCode.F17;
-			break;
-		case ConsoleKey.F18:
-			keyCode = KeyCode.F18;
-			break;
-		case ConsoleKey.F19:
-			keyCode = KeyCode.F19;
-			break;
-		case ConsoleKey.F20:
-			keyCode = KeyCode.F20;
-			break;
-		case ConsoleKey.F21:
-			keyCode = KeyCode.F21;
-			break;
-		case ConsoleKey.F22:
-			keyCode = KeyCode.F22;
-			break;
-		case ConsoleKey.F23:
-			keyCode = KeyCode.F23;
-			break;
-		case ConsoleKey.F24:
-			keyCode = KeyCode.F24;
-			break;
-		case ConsoleKey.Tab:
-			keyCode = KeyCode.Tab;
-			break;
-		default:
-			keyCode = (KeyCode)consoleKeyInfo.KeyChar;
-			break;
-		}
-		keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
-
-		return keyCode;
-	}
-
-	/// <summary>
-	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
-	/// </summary>
-	/// <param name="modifiers">The console modifiers.</param>
-	/// <param name="key">The key code.</param>
-	/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
-	public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
-	{
-		var keyMod = new KeyCode ();
-		if ((modifiers & ConsoleModifiers.Shift) != 0) {
-			keyMod = KeyCode.ShiftMask;
-		}
-		if ((modifiers & ConsoleModifiers.Control) != 0) {
-			keyMod |= KeyCode.CtrlMask;
-		}
-		if ((modifiers & ConsoleModifiers.Alt) != 0) {
-			keyMod |= KeyCode.AltMask;
-		}
-
-		return keyMod != KeyCode.Null ? keyMod | key : key;
-	}
-
-	/// <summary>
-	/// Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
-	/// </summary>
-	public enum VK : ushort {
-		/// <summary>
-		/// Left mouse button.
-		/// </summary>
-		LBUTTON = 0x01,
-
-		/// <summary>
-		/// Right mouse button.
-		/// </summary>
-		RBUTTON = 0x02,
-
-		/// <summary>
-		/// Control-break processing.
-		/// </summary>
-		CANCEL = 0x03,
-
-		/// <summary>
-		/// Middle mouse button (three-button mouse).
-		/// </summary>
-		MBUTTON = 0x04,
-
-		/// <summary>
-		/// X1 mouse button.
-		/// </summary>
-		XBUTTON1 = 0x05,
-
-		/// <summary>
-		/// X2 mouse button.
-		/// </summary>
-		XBUTTON2 = 0x06,
-
-		/// <summary>
-		/// BACKSPACE key.
-		/// </summary>
-		BACK = 0x08,
-
-		/// <summary>
-		/// TAB key.
-		/// </summary>
-		TAB = 0x09,
-
-		/// <summary>
-		/// CLEAR key.
-		/// </summary>
-		CLEAR = 0x0C,
-
-		/// <summary>
-		/// ENTER key.
-		/// </summary>
-		RETURN = 0x0D,
-
-		/// <summary>
-		/// SHIFT key.
-		/// </summary>
-		SHIFT = 0x10,
-
-		/// <summary>
-		/// CTRL key.
-		/// </summary>
-		CONTROL = 0x11,
-
-		/// <summary>
-		/// ALT key.
-		/// </summary>
-		MENU = 0x12,
-
-		/// <summary>
-		/// PAUSE key.
-		/// </summary>
-		PAUSE = 0x13,
-
-		/// <summary>
-		/// CAPS LOCK key.
-		/// </summary>
-		CAPITAL = 0x14,
-
-		/// <summary>
-		/// IME Kana mode.
-		/// </summary>
-		KANA = 0x15,
-
-		/// <summary>
-		/// IME Hangul mode.
-		/// </summary>
-		HANGUL = 0x15,
-
-		/// <summary>
-		/// IME Junja mode.
-		/// </summary>
-		JUNJA = 0x17,
-
-		/// <summary>
-		/// IME final mode.
-		/// </summary>
-		FINAL = 0x18,
-
-		/// <summary>
-		/// IME Hanja mode.
-		/// </summary>
-		HANJA = 0x19,
-
-		/// <summary>
-		/// IME Kanji mode.
-		/// </summary>
-		KANJI = 0x19,
-
-		/// <summary>
-		/// ESC key.
-		/// </summary>
-		ESCAPE = 0x1B,
-
-		/// <summary>
-		/// IME convert.
-		/// </summary>
-		CONVERT = 0x1C,
-
-		/// <summary>
-		/// IME nonconvert.
-		/// </summary>
-		NONCONVERT = 0x1D,
-
-		/// <summary>
-		/// IME accept.
-		/// </summary>
-		ACCEPT = 0x1E,
-
-		/// <summary>
-		/// IME mode change request.
-		/// </summary>
-		MODECHANGE = 0x1F,
-
-		/// <summary>
-		/// SPACEBAR.
-		/// </summary>
-		SPACE = 0x20,
-
-		/// <summary>
-		/// PAGE UP key.
-		/// </summary>
-		PRIOR = 0x21,
-
-		/// <summary>
-		/// PAGE DOWN key.
-		/// </summary>
-		NEXT = 0x22,
-
-		/// <summary>
-		/// END key.
-		/// </summary>
-		END = 0x23,
-
-		/// <summary>
-		/// HOME key.
-		/// </summary>
-		HOME = 0x24,
-
-		/// <summary>
-		/// LEFT ARROW key.
-		/// </summary>
-		LEFT = 0x25,
-
-		/// <summary>
-		/// UP ARROW key.
-		/// </summary>
-		UP = 0x26,
-
-		/// <summary>
-		/// RIGHT ARROW key.
-		/// </summary>
-		RIGHT = 0x27,
-
-		/// <summary>
-		/// DOWN ARROW key.
-		/// </summary>
-		DOWN = 0x28,
-
-		/// <summary>
-		/// SELECT key.
-		/// </summary>
-		SELECT = 0x29,
-
-		/// <summary>
-		/// PRINT key.
-		/// </summary>
-		PRINT = 0x2A,
-
-		/// <summary>
-		/// EXECUTE key
-		/// </summary>
-		EXECUTE = 0x2B,
-
-		/// <summary>
-		/// PRINT SCREEN key
-		/// </summary>
-		SNAPSHOT = 0x2C,
-
-		/// <summary>
-		/// INS key
-		/// </summary>
-		INSERT = 0x2D,
-
-		/// <summary>
-		/// DEL key
-		/// </summary>
-		DELETE = 0x2E,
-
-		/// <summary>
-		/// HELP key
-		/// </summary>
-		HELP = 0x2F,
-
-		/// <summary>
-		/// Left Windows key (Natural keyboard)
-		/// </summary>
-		LWIN = 0x5B,
-
-		/// <summary>
-		/// Right Windows key (Natural keyboard)
-		/// </summary>
-		RWIN = 0x5C,
-
-		/// <summary>
-		/// Applications key (Natural keyboard)
-		/// </summary>
-		APPS = 0x5D,
-
-		/// <summary>
-		/// Computer Sleep key
-		/// </summary>
-		SLEEP = 0x5F,
-
-		/// <summary>
-		/// Numeric keypad 0 key
-		/// </summary>
-		NUMPAD0 = 0x60,
-
-		/// <summary>
-		/// Numeric keypad 1 key
-		/// </summary>
-		NUMPAD1 = 0x61,
-
-		/// <summary>
-		/// Numeric keypad 2 key
-		/// </summary>
-		NUMPAD2 = 0x62,
-
-		/// <summary>
-		/// Numeric keypad 3 key
-		/// </summary>
-		NUMPAD3 = 0x63,
-
-		/// <summary>
-		/// Numeric keypad 4 key
-		/// </summary>
-		NUMPAD4 = 0x64,
-
-		/// <summary>
-		/// Numeric keypad 5 key
-		/// </summary>
-		NUMPAD5 = 0x65,
-
-		/// <summary>
-		/// Numeric keypad 6 key
-		/// </summary>
-		NUMPAD6 = 0x66,
-
-		/// <summary>
-		/// Numeric keypad 7 key
-		/// </summary>
-		NUMPAD7 = 0x67,
-
-		/// <summary>
-		/// Numeric keypad 8 key
-		/// </summary>
-		NUMPAD8 = 0x68,
-
-		/// <summary>
-		/// Numeric keypad 9 key
-		/// </summary>
-		NUMPAD9 = 0x69,
-
-		/// <summary>
-		/// Multiply key
-		/// </summary>
-		MULTIPLY = 0x6A,
-
-		/// <summary>
-		/// Add key
-		/// </summary>
-		ADD = 0x6B,
-
-		/// <summary>
-		/// Separator key
-		/// </summary>
-		SEPARATOR = 0x6C,
-
-		/// <summary>
-		/// Subtract key
-		/// </summary>
-		SUBTRACT = 0x6D,
-
-		/// <summary>
-		/// Decimal key
-		/// </summary>
-		DECIMAL = 0x6E,
-
-		/// <summary>
-		/// Divide key
-		/// </summary>
-		DIVIDE = 0x6F,
-
-		/// <summary>
-		/// F1 key
-		/// </summary>
-		F1 = 0x70,
-
-		/// <summary>
-		/// F2 key
-		/// </summary>
-		F2 = 0x71,
-
-		/// <summary>
-		/// F3 key
-		/// </summary>
-		F3 = 0x72,
-
-		/// <summary>
-		/// F4 key
-		/// </summary>
-		F4 = 0x73,
-
-		/// <summary>
-		/// F5 key
-		/// </summary>
-		F5 = 0x74,
-
-		/// <summary>
-		/// F6 key
-		/// </summary>
-		F6 = 0x75,
-
-		/// <summary>
-		/// F7 key
-		/// </summary>
-		F7 = 0x76,
-
-		/// <summary>
-		/// F8 key
-		/// </summary>
-		F8 = 0x77,
-
-		/// <summary>
-		/// F9 key
-		/// </summary>
-		F9 = 0x78,
-
-		/// <summary>
-		/// F10 key
-		/// </summary>
-		F10 = 0x79,
-
-		/// <summary>
-		/// F11 key
-		/// </summary>
-		F11 = 0x7A,
-
-		/// <summary>
-		/// F12 key
-		/// </summary>
-		F12 = 0x7B,
-
-		/// <summary>
-		/// F13 key
-		/// </summary>
-		F13 = 0x7C,
-
-		/// <summary>
-		/// F14 key
-		/// </summary>
-		F14 = 0x7D,
-
-		/// <summary>
-		/// F15 key
-		/// </summary>
-		F15 = 0x7E,
-
-		/// <summary>
-		/// F16 key
-		/// </summary>
-		F16 = 0x7F,
-
-		/// <summary>
-		/// F17 key
-		/// </summary>
-		F17 = 0x80,
-
-		/// <summary>
-		/// F18 key
-		/// </summary>
-		F18 = 0x81,
-
-		/// <summary>
-		/// F19 key
-		/// </summary>
-		F19 = 0x82,
-
-		/// <summary>
-		/// F20 key
-		/// </summary>
-		F20 = 0x83,
-
-		/// <summary>
-		/// F21 key
-		/// </summary>
-		F21 = 0x84,
-
-		/// <summary>
-		/// F22 key
-		/// </summary>
-		F22 = 0x85,
-
-		/// <summary>
-		/// F23 key
-		/// </summary>
-		F23 = 0x86,
-
-		/// <summary>
-		/// F24 key
-		/// </summary>
-		F24 = 0x87,
-
-		/// <summary>
-		/// NUM LOCK key
-		/// </summary>
-		NUMLOCK = 0x90,
-
-		/// <summary>
-		/// SCROLL LOCK key
-		/// </summary>
-		SCROLL = 0x91,
-
-		/// <summary>
-		/// NEC PC-9800 kbd definition: '=' key on numpad
-		/// </summary>
-		OEM_NEC_EQUAL = 0x92,
-
-		/// <summary>
-		/// Fujitsu/OASYS kbd definition: 'Dictionary' key
-		/// </summary>
-		OEM_FJ_JISHO = 0x92,
-
-		/// <summary>
-		/// Fujitsu/OASYS kbd definition: 'Unregister word' key
-		/// </summary>
-		OEM_FJ_MASSHOU = 0x93,
-
-		/// <summary>
-		/// Fujitsu/OASYS kbd definition: 'Register word' key
-		/// </summary>
-		OEM_FJ_TOUROKU = 0x94,
-
-		/// <summary>
-		/// Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key
-		/// </summary>
-		OEM_FJ_LOYA = 0x95,
-
-		/// <summary>
-		/// Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key
-		/// </summary>
-		OEM_FJ_ROYA = 0x96,
-
-		/// <summary>
-		/// Left SHIFT key
-		/// </summary>
-		LSHIFT = 0xA0,
-
-		/// <summary>
-		/// Right SHIFT key
-		/// </summary>
-		RSHIFT = 0xA1,
-
-		/// <summary>
-		/// Left CONTROL key
-		/// </summary>
-		LCONTROL = 0xA2,
-
-		/// <summary>
-		/// Right CONTROL key
-		/// </summary>
-		RCONTROL = 0xA3,
-
-		/// <summary>
-		/// Left MENU key (Left Alt key)
-		/// </summary>
-		LMENU = 0xA4,
-
-		/// <summary>
-		/// Right MENU key (Right Alt key)
-		/// </summary>
-		RMENU = 0xA5,
-
-		/// <summary>
-		/// Browser Back key
-		/// </summary>
-		BROWSER_BACK = 0xA6,
-
-		/// <summary>
-		/// Browser Forward key
-		/// </summary>
-		BROWSER_FORWARD = 0xA7,
-
-		/// <summary>
-		/// Browser Refresh key
-		/// </summary>
-		BROWSER_REFRESH = 0xA8,
-
-		/// <summary>
-		/// Browser Stop key
-		/// </summary>
-		BROWSER_STOP = 0xA9,
-
-		/// <summary>
-		/// Browser Search key
-		/// </summary>
-		BROWSER_SEARCH = 0xAA,
-
-		/// <summary>
-		/// Browser Favorites key
-		/// </summary>
-		BROWSER_FAVORITES = 0xAB,
-
-		/// <summary>
-		/// Browser Home key
-		/// </summary>
-		BROWSER_HOME = 0xAC,
-
-		/// <summary>
-		/// Volume Mute key
-		/// </summary>
-		VOLUME_MUTE = 0xAD,
-
-		/// <summary>
-		/// Volume Down key
-		/// </summary>
-		VOLUME_DOWN = 0xAE,
-
-		/// <summary>
-		/// Volume Up key
-		/// </summary>
-		VOLUME_UP = 0xAF,
-
-		/// <summary>
-		/// Next Track key
-		/// </summary>
-		MEDIA_NEXT_TRACK = 0xB0,
-
-		/// <summary>
-		/// Previous Track key
-		/// </summary>
-		MEDIA_PREV_TRACK = 0xB1,
-
-		/// <summary>
-		/// Stop Media key
-		/// </summary>
-		MEDIA_STOP = 0xB2,
-
-		/// <summary>
-		/// Play/Pause Media key
-		/// </summary>
-		MEDIA_PLAY_PAUSE = 0xB3,
-
-		/// <summary>
-		/// Start Mail key
-		/// </summary>
-		LAUNCH_MAIL = 0xB4,
-
-		/// <summary>
-		/// Select Media key
-		/// </summary>
-		LAUNCH_MEDIA_SELECT = 0xB5,
-
-		/// <summary>
-		/// Start Application 1 key
-		/// </summary>
-		LAUNCH_APP1 = 0xB6,
-
-		/// <summary>
-		/// Start Application 2 key
-		/// </summary>
-		LAUNCH_APP2 = 0xB7,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
-		/// </summary>
-		OEM_1 = 0xBA,
-
-		/// <summary>
-		/// For any country/region, the '+' key
-		/// </summary>
-		OEM_PLUS = 0xBB,
-
-		/// <summary>
-		/// For any country/region, the ',' key
-		/// </summary>
-		OEM_COMMA = 0xBC,
-
-		/// <summary>
-		/// For any country/region, the '-' key
-		/// </summary>
-		OEM_MINUS = 0xBD,
-
-		/// <summary>
-		/// For any country/region, the '.' key
-		/// </summary>
-		OEM_PERIOD = 0xBE,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
-		/// </summary>
-		OEM_2 = 0xBF,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
-		/// </summary>
-		OEM_3 = 0xC0,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
-		/// </summary>
-		OEM_4 = 0xDB,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
-		/// </summary>
-		OEM_5 = 0xDC,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
-		/// </summary>
-		OEM_6 = 0xDD,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
-		/// </summary>
-		OEM_7 = 0xDE,
-
-		/// <summary>
-		/// Used for miscellaneous characters; it can vary by keyboard.
-		/// </summary>
-		OEM_8 = 0xDF,
-
-		/// <summary>
-		/// 'AX' key on Japanese AX kbd
-		/// </summary>
-		OEM_AX = 0xE1,
-
-		/// <summary>
-		/// Either the angle bracket key or the backslash key on the RT 102-key keyboard
-		/// </summary>
-		OEM_102 = 0xE2,
-
-		/// <summary>
-		/// Help key on ICO
-		/// </summary>
-		ICO_HELP = 0xE3,
-
-		/// <summary>
-		/// 00 key on ICO
-		/// </summary>
-		ICO_00 = 0xE4,
-
-		/// <summary>
-		/// Process key
-		/// </summary>
-		PROCESSKEY = 0xE5,
-
-		/// <summary>
-		/// Clear key on ICO
-		/// </summary>
-		ICO_CLEAR = 0xE6,
-
-		/// <summary>
-		/// Packet key to be used to pass Unicode characters as if they were keystrokes
-		/// </summary>
-		PACKET = 0xE7,
-
-		/// <summary>
-		/// Reset key
-		/// </summary>
-		OEM_RESET = 0xE9,
-
-		/// <summary>
-		/// Jump key
-		/// </summary>
-		OEM_JUMP = 0xEA,
-
-		/// <summary>
-		/// PA1 key
-		/// </summary>
-		OEM_PA1 = 0xEB,
-
-		/// <summary>
-		/// PA2 key
-		/// </summary>
-		OEM_PA2 = 0xEC,
-
-		/// <summary>
-		/// PA3 key
-		/// </summary>
-		OEM_PA3 = 0xED,
-
-		/// <summary>
-		/// WsCtrl key
-		/// </summary>
-		OEM_WSCTRL = 0xEE,
-
-		/// <summary>
-		/// CuSel key
-		/// </summary>
-		OEM_CUSEL = 0xEF,
-
-		/// <summary>
-		/// Attn key
-		/// </summary>
-		OEM_ATTN = 0xF0,
-
-		/// <summary>
-		/// Finish key
-		/// </summary>
-		OEM_FINISH = 0xF1,
-
-		/// <summary>
-		/// Copy key
-		/// </summary>
-		OEM_COPY = 0xF2,
-
-		/// <summary>
-		/// Auto key
-		/// </summary>
-		OEM_AUTO = 0xF3,
-
-		/// <summary>
-		/// Enlw key
-		/// </summary>
-		OEM_ENLW = 0xF4,
-
-		/// <summary>
-		/// BackTab key
-		/// </summary>
-		OEM_BACKTAB = 0xF5,
-
-		/// <summary>
-		/// Attn key
-		/// </summary>
-		ATTN = 0xF6,
-
-		/// <summary>
-		/// CrSel key
-		/// </summary>
-		CRSEL = 0xF7,
-
-		/// <summary>
-		/// ExSel key
-		/// </summary>
-		EXSEL = 0xF8,
-
-		/// <summary>
-		/// Erase EOF key
-		/// </summary>
-		EREOF = 0xF9,
-
-		/// <summary>
-		/// Play key
-		/// </summary>
-		PLAY = 0xFA,
-
-		/// <summary>
-		/// Zoom key
-		/// </summary>
-		ZOOM = 0xFB,
-
-		/// <summary>
-		/// Reserved
-		/// </summary>
-		NONAME = 0xFC,
-
-		/// <summary>
-		/// PA1 key
-		/// </summary>
-		PA1 = 0xFD,
-
-		/// <summary>
-		/// Clear key
-		/// </summary>
-		OEM_CLEAR = 0xFE
-	}
-
-	// BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout
-	//         It should be deleted.
-	static HashSet<ScanCodeMapping> _scanCodes = new HashSet<ScanCodeMapping> {
-		new (1, VK.ESCAPE, 0, '\u001B'), // Escape
-		new (1, VK.ESCAPE, ConsoleModifiers.Shift, '\u001B'),
-		new (2, (VK)'1', 0, '1'), // D1
-		new (2, (VK)'1', ConsoleModifiers.Shift, '!'),
-		new (3, (VK)'2', 0, '2'), // D2
-		new (3, (VK)'2', ConsoleModifiers.Shift, '\"'), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (")
-		new (3, (VK)'2', ConsoleModifiers.Alt | ConsoleModifiers.Control, '@'),
-		new (4, (VK)'3', 0, '3'), // D3
-		new (4, (VK)'3', ConsoleModifiers.Shift, '#'),
-		new (4, (VK)'3', ConsoleModifiers.Alt | ConsoleModifiers.Control, '£'),
-		new (5, (VK)'4', 0, '4'), // D4
-		new (5, (VK)'4', ConsoleModifiers.Shift, '$'),
-		new (5, (VK)'4', ConsoleModifiers.Alt | ConsoleModifiers.Control, '§'),
-		new (6, (VK)'5', 0, '5'), // D5
-		new (6, (VK)'5', ConsoleModifiers.Shift, '%'),
-		new (6, (VK)'5', ConsoleModifiers.Alt | ConsoleModifiers.Control, '€'),
-		new (7, (VK)'6', 0, '6'), // D6
-		new (7, (VK)'6', ConsoleModifiers.Shift, '&'),
-		new (8, (VK)'7', 0, '7'), // D7
-		new (8, (VK)'7', ConsoleModifiers.Shift, '/'),
-		new (8, (VK)'7', ConsoleModifiers.Alt | ConsoleModifiers.Control, '{'),
-		new (9, (VK)'8', 0, '8'), // D8
-		new (9, (VK)'8', ConsoleModifiers.Shift, '('),
-		new (9, (VK)'8', ConsoleModifiers.Alt | ConsoleModifiers.Control, '['),
-		new (10, (VK)'9', 0, '9'), // D9
-		new (10, (VK)'9', ConsoleModifiers.Shift, ')'),
-		new (10, (VK)'9', ConsoleModifiers.Alt | ConsoleModifiers.Control, ']'),
-		new (11, (VK)'0', 0, '0'), // D0
-		new (11, (VK)'0', ConsoleModifiers.Shift, '='),
-		new (11, (VK)'0', ConsoleModifiers.Alt | ConsoleModifiers.Control, '}'),
-		new (12, VK.OEM_4, 0, '\''), // Oem4
-		new (12, VK.OEM_4, ConsoleModifiers.Shift, '?'),
-		new (13, VK.OEM_6, 0, '+'), // Oem6
-		new (13, VK.OEM_6, ConsoleModifiers.Shift, '*'),
-		new (14, VK.BACK, 0, '\u0008'), // Backspace
-		new (14, VK.BACK, ConsoleModifiers.Shift, '\u0008'),
-		new (15, VK.TAB, 0, '\u0009'), // Tab
-		new (15, VK.TAB, ConsoleModifiers.Shift, '\u000F'),
-		new (16, (VK)'Q', 0, 'q'), // Q
-		new (16, (VK)'Q', ConsoleModifiers.Shift, 'Q'),
-		new (17, (VK)'W', 0, 'w'), // W
-		new (17, (VK)'W', ConsoleModifiers.Shift, 'W'),
-		new (18, (VK)'E', 0, 'e'), // E
-		new (18, (VK)'E', ConsoleModifiers.Shift, 'E'),
-		new (19, (VK)'R', 0, 'r'), // R
-		new (19, (VK)'R', ConsoleModifiers.Shift, 'R'),
-		new (20, (VK)'T', 0, 't'), // T
-		new (20, (VK)'T', ConsoleModifiers.Shift, 'T'),
-		new (21, (VK)'Y', 0, 'y'), // Y
-		new (21, (VK)'Y', ConsoleModifiers.Shift, 'Y'),
-		new (22, (VK)'U', 0, 'u'), // U
-		new (22, (VK)'U', ConsoleModifiers.Shift, 'U'),
-		new (23, (VK)'I', 0, 'i'), // I
-		new (23, (VK)'I', ConsoleModifiers.Shift, 'I'),
-		new (24, (VK)'O', 0, 'o'), // O
-		new (24, (VK)'O', ConsoleModifiers.Shift, 'O'),
-		new (25, (VK)'P', 0, 'p'), // P
-		new (25, (VK)'P', ConsoleModifiers.Shift, 'P'),
-		new (26, VK.OEM_PLUS, 0, '+'), // OemPlus
-		new (26, VK.OEM_PLUS, ConsoleModifiers.Shift, '*'),
-		new (26, VK.OEM_PLUS, ConsoleModifiers.Alt | ConsoleModifiers.Control, '¨'),
-		new (27, VK.OEM_1, 0, '´'), // Oem1
-		new (27, VK.OEM_1, ConsoleModifiers.Shift, '`'),
-		new (28, VK.RETURN, 0, '\u000D'), // Enter
-		new (28, VK.RETURN, ConsoleModifiers.Shift, '\u000D'),
-		new (29, VK.CONTROL, 0, '\0'), // Control
-		new (29, VK.CONTROL, ConsoleModifiers.Shift, '\0'),
-		new (30, (VK)'A', 0, 'a'), // A
-		new (30, (VK)'A', ConsoleModifiers.Shift, 'A'),
-		new (31, (VK)'S', 0, 's'), // S
-		new (31, (VK)'S', ConsoleModifiers.Shift, 'S'),
-		new (32, (VK)'D', 0, 'd'), // D
-		new (32, (VK)'D', ConsoleModifiers.Shift, 'D'),
-		new (33, (VK)'F', 0, 'f'), // F
-		new (33, (VK)'F', ConsoleModifiers.Shift, 'F'),
-		new (34, (VK)'G', 0, 'g'), // G
-		new (34, (VK)'G', ConsoleModifiers.Shift, 'G'),
-		new (35, (VK)'H', 0, 'h'), // H
-		new (35, (VK)'H', ConsoleModifiers.Shift, 'H'),
-		new (36, (VK)'J', 0, 'j'), // J
-		new (36, (VK)'J', ConsoleModifiers.Shift, 'J'),
-		new (37, (VK)'K', 0, 'k'), // K
-		new (37, (VK)'K', ConsoleModifiers.Shift, 'K'),
-		new (38, (VK)'L', 0, 'l'), // L
-		new (38, (VK)'L', ConsoleModifiers.Shift, 'L'),
-		new (39, VK.OEM_3, 0, '`'), // Oem3 (Backtick/Grave)
-		new (39, VK.OEM_3, ConsoleModifiers.Shift, '~'),
-		new (40, VK.OEM_7, 0, '\''), // Oem7 (Single Quote)
-		new (40, VK.OEM_7, ConsoleModifiers.Shift, '\"'),
-		new (41, VK.OEM_5, 0, '\\'), // Oem5 (Backslash)
-		new (41, VK.OEM_5, ConsoleModifiers.Shift, '|'),
-		new (42, VK.LSHIFT, 0, '\0'), // Left Shift
-		new (42, VK.LSHIFT, ConsoleModifiers.Shift, '\0'),
-		new (43, VK.OEM_2, 0, '/'), // Oem2 (Forward Slash)
-		new (43, VK.OEM_2, ConsoleModifiers.Shift, '?'),
-		new (44, (VK)'Z', 0, 'z'), // Z
-		new (44, (VK)'Z', ConsoleModifiers.Shift, 'Z'),
-		new (45, (VK)'X', 0, 'x'), // X
-		new (45, (VK)'X', ConsoleModifiers.Shift, 'X'),
-		new (46, (VK)'C', 0, 'c'), // C
-		new (46, (VK)'C', ConsoleModifiers.Shift, 'C'),
-		new (47, (VK)'V', 0, 'v'), // V
-		new (47, (VK)'V', ConsoleModifiers.Shift, 'V'),
-		new (48, (VK)'B', 0, 'b'), // B
-		new (48, (VK)'B', ConsoleModifiers.Shift, 'B'),
-		new (49, (VK)'N', 0, 'n'), // N
-		new (49, (VK)'N', ConsoleModifiers.Shift, 'N'),
-		new (50, (VK)'M', 0, 'm'), // M
-		new (50, (VK)'M', ConsoleModifiers.Shift, 'M'),
-		new (51, VK.OEM_COMMA, 0, ','), // OemComma
-		new (51, VK.OEM_COMMA, ConsoleModifiers.Shift, '<'),
-		new (52, VK.OEM_PERIOD, 0, '.'), // OemPeriod
-		new (52, VK.OEM_PERIOD, ConsoleModifiers.Shift, '>'),
-		new (53, VK.OEM_MINUS, 0, '-'), // OemMinus
-		new (53, VK.OEM_MINUS, ConsoleModifiers.Shift, '_'),
-		new (54, VK.RSHIFT, 0, '\0'), // Right Shift
-		new (54, VK.RSHIFT, ConsoleModifiers.Shift, '\0'),
-		new (55, VK.PRINT, 0, '\0'), // Print Screen
-		new (55, VK.PRINT, ConsoleModifiers.Shift, '\0'),
-		new (56, VK.LMENU, 0, '\0'), // Alt
-		new (56, VK.LMENU, ConsoleModifiers.Shift, '\0'),
-		new (57, VK.SPACE, 0, ' '), // Spacebar
-		new (57, VK.SPACE, ConsoleModifiers.Shift, ' '),
-		new (58, VK.CAPITAL, 0, '\0'), // Caps Lock
-		new (58, VK.CAPITAL, ConsoleModifiers.Shift, '\0'),
-		new (59, VK.F1, 0, '\0'), // F1
-		new (59, VK.F1, ConsoleModifiers.Shift, '\0'),
-		new (60, VK.F2, 0, '\0'), // F2
-		new (60, VK.F2, ConsoleModifiers.Shift, '\0'),
-		new (61, VK.F3, 0, '\0'), // F3
-		new (61, VK.F3, ConsoleModifiers.Shift, '\0'),
-		new (62, VK.F4, 0, '\0'), // F4
-		new (62, VK.F4, ConsoleModifiers.Shift, '\0'),
-		new (63, VK.F5, 0, '\0'), // F5
-		new (63, VK.F5, ConsoleModifiers.Shift, '\0'),
-		new (64, VK.F6, 0, '\0'), // F6
-		new (64, VK.F6, ConsoleModifiers.Shift, '\0'),
-		new (65, VK.F7, 0, '\0'), // F7
-		new (65, VK.F7, ConsoleModifiers.Shift, '\0'),
-		new (66, VK.F8, 0, '\0'), // F8
-		new (66, VK.F8, ConsoleModifiers.Shift, '\0'),
-		new (67, VK.F9, 0, '\0'), // F9
-		new (67, VK.F9, ConsoleModifiers.Shift, '\0'),
-		new (68, VK.F10, 0, '\0'), // F10
-		new (68, VK.F10, ConsoleModifiers.Shift, '\0'),
-		new (69, VK.NUMLOCK, 0, '\0'), // Num Lock
-		new (69, VK.NUMLOCK, ConsoleModifiers.Shift, '\0'),
-		new (70, VK.SCROLL, 0, '\0'), // Scroll Lock
-		new (70, VK.SCROLL, ConsoleModifiers.Shift, '\0'),
-		new (71, VK.HOME, 0, '\0'), // Home
-		new (71, VK.HOME, ConsoleModifiers.Shift, '\0'),
-		new (72, VK.UP, 0, '\0'), // Up Arrow
-		new (72, VK.UP, ConsoleModifiers.Shift, '\0'),
-		new (73, VK.PRIOR, 0, '\0'), // Page Up
-		new (73, VK.PRIOR, ConsoleModifiers.Shift, '\0'),
-		new (74, VK.SUBTRACT, 0, '-'), // Subtract (Num Pad '-')
-		new (74, VK.SUBTRACT, ConsoleModifiers.Shift, '-'),
-		new (75, VK.LEFT, 0, '\0'), // Left Arrow
-		new (75, VK.LEFT, ConsoleModifiers.Shift, '\0'),
-		new (76, VK.CLEAR, 0, '\0'), // Center key (Num Pad 5 with Num Lock off)
-		new (76, VK.CLEAR, ConsoleModifiers.Shift, '\0'),
-		new (77, VK.RIGHT, 0, '\0'), // Right Arrow
-		new (77, VK.RIGHT, ConsoleModifiers.Shift, '\0'),
-		new (78, VK.ADD, 0, '+'), // Add (Num Pad '+')
-		new (78, VK.ADD, ConsoleModifiers.Shift, '+'),
-		new (79, VK.END, 0, '\0'), // End
-		new (79, VK.END, ConsoleModifiers.Shift, '\0'),
-		new (80, VK.DOWN, 0, '\0'), // Down Arrow
-		new (80, VK.DOWN, ConsoleModifiers.Shift, '\0'),
-		new (81, VK.NEXT, 0, '\0'), // Page Down
-		new (81, VK.NEXT, ConsoleModifiers.Shift, '\0'),
-		new (82, VK.INSERT, 0, '\0'), // Insert
-		new (82, VK.INSERT, ConsoleModifiers.Shift, '\0'),
-		new (83, VK.DELETE, 0, '\0'), // Delete
-		new (83, VK.DELETE, ConsoleModifiers.Shift, '\0'),
-		new (86, VK.OEM_102, 0, '<'), // OEM 102 (Typically '<' or '|' key next to Left Shift)
-		new (86, VK.OEM_102, ConsoleModifiers.Shift, '>'),
-		new (87, VK.F11, 0, '\0'), // F11
-		new (87, VK.F11, ConsoleModifiers.Shift, '\0'),
-		new (88, VK.F12, 0, '\0'), // F12
-		new (88, VK.F12, ConsoleModifiers.Shift, '\0')
-	};
-
-	/// <summary>
-	/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
-	/// </summary>
-	/// <param name="consoleKeyInfo">The console key info.</param>
-	/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
-	/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
-	/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
-	/// </remarks>
-	public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-	{
-		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-			return consoleKeyInfo;
-		}
-
-		return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
-	}
-
-	/// <summary>
-	/// Encode the <see cref="ConsoleKeyInfo.KeyChar"/> with the <see cref="ConsoleKeyInfo.Key"/>
-	/// if the first a byte length, otherwise only the KeyChar is considered and searched on the database.
-	/// </summary>
-	/// <param name="consoleKeyInfo">The console key info.</param>
-	/// <returns>The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar.</returns>
-	/// <remarks>This is useful to use with the <see cref="ConsoleKey.Packet"/>.</remarks>
-	public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo)
-	{
-		char keyChar = consoleKeyInfo.KeyChar;
-		ConsoleKey consoleKey = consoleKeyInfo.Key;
-		if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None) {
-			// try to get the ConsoleKey
-			var scode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyChar);
-			if (scode != null) {
-				consoleKey = (ConsoleKey)scode.VirtualKey;
-			}
-		}
-		if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None) {
-			keyChar = (char)(consoleKeyInfo.KeyChar << 8 | (byte)consoleKey);
-		}
-
-		return keyChar;
-	}
+    /// <summary>
+    ///     Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling
+    ///     thread.
+    /// </summary>
+    /// <param name="pwszKLID"></param>
+    /// <returns></returns>
+    [DllImport ("user32.dll")]
+    private static extern bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);
+
+    /// <summary>
+    ///     Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling
+    ///     thread.
+    /// </summary>
+    /// <returns></returns>
+    public static string GetKeyboardLayoutName ()
+    {
+        if (Environment.OSVersion.Platform != PlatformID.Win32NT)
+        {
+            return "none";
+        }
+
+        var klidSB = new StringBuilder ();
+        GetKeyboardLayoutName (klidSB);
+
+        return klidSB.ToString ();
+    }
+
+    private class ScanCodeMapping : IEquatable<ScanCodeMapping>
+    {
+        public readonly ConsoleModifiers Modifiers;
+        public readonly uint ScanCode;
+        public readonly uint UnicodeChar;
+        public readonly VK VirtualKey;
+
+        public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
+        {
+            ScanCode = scanCode;
+            VirtualKey = virtualKey;
+            Modifiers = modifiers;
+            UnicodeChar = unicodeChar;
+        }
+
+        public bool Equals (ScanCodeMapping other)
+        {
+            return ScanCode.Equals (other.ScanCode)
+                   && VirtualKey.Equals (other.VirtualKey)
+                   && Modifiers.Equals (other.Modifiers)
+                   && UnicodeChar.Equals (other.UnicodeChar);
+        }
+    }
+
+    private static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
+    {
+        if (modifiers.HasFlag (ConsoleModifiers.Shift)
+            && !modifiers.HasFlag (ConsoleModifiers.Alt)
+            && !modifiers.HasFlag (ConsoleModifiers.Control))
+        {
+            return ConsoleModifiers.Shift;
+        }
+
+        if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
+        {
+            return modifiers;
+        }
+
+        return 0;
+    }
+
+    private static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
+    {
+        switch (propName)
+        {
+            case "UnicodeChar":
+                ScanCodeMapping sCode =
+                    _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
+
+                if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
+                {
+                    return _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue && e.Modifiers == 0);
+                }
+
+                return sCode;
+            case "VirtualKey":
+                sCode = _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers);
+
+                if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control))
+                {
+                    return _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)keyValue && e.Modifiers == 0);
+                }
+
+                return sCode;
+        }
+
+        return null;
+    }
+
+    // BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios
+    /// <summary>Get the scan code from a <see cref="ConsoleKeyInfo"/>.</summary>
+    /// <param name="consoleKeyInfo">The console key info.</param>
+    /// <returns>The value if apply.</returns>
+    public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+    {
+        ConsoleModifiers mod = GetModifiers (consoleKeyInfo.Modifiers);
+        ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod);
+
+        if (scode != null)
+        {
+            return scode.ScanCode;
+        }
+
+        return 0;
+    }
+
+    // BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator
+    /// <summary>Gets the <see cref="ConsoleKeyInfo"/> from the provided <see cref="KeyCode"/>.</summary>
+    /// <param name="key">The key code.</param>
+    /// <returns>The console key info.</returns>
+    public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
+    {
+        ConsoleModifiers modifiers = MapToConsoleModifiers (key);
+        uint keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey);
+
+        if (isConsoleKey)
+        {
+            ConsoleModifiers mod = GetModifiers (modifiers);
+            ScanCodeMapping scode = GetScanCode ("VirtualKey", keyValue, mod);
+
+            if (scode != null)
+            {
+                return new ConsoleKeyInfo (
+                                           (char)scode.UnicodeChar,
+                                           (ConsoleKey)scode.VirtualKey,
+                                           modifiers.HasFlag (ConsoleModifiers.Shift),
+                                           modifiers.HasFlag (ConsoleModifiers.Alt),
+                                           modifiers.HasFlag (ConsoleModifiers.Control)
+                                          );
+            }
+        }
+        else
+        {
+            uint keyChar = GetKeyCharFromUnicodeChar (keyValue, modifiers, out uint consoleKey, out _, isConsoleKey);
+
+            if (consoleKey != 0)
+            {
+                return new ConsoleKeyInfo (
+                                           (char)keyChar,
+                                           (ConsoleKey)consoleKey,
+                                           modifiers.HasFlag (ConsoleModifiers.Shift),
+                                           modifiers.HasFlag (ConsoleModifiers.Alt),
+                                           modifiers.HasFlag (ConsoleModifiers.Control)
+                                          );
+            }
+        }
+
+        return new ConsoleKeyInfo (
+                                   (char)keyValue,
+                                   ConsoleKey.None,
+                                   modifiers.HasFlag (ConsoleModifiers.Shift),
+                                   modifiers.HasFlag (ConsoleModifiers.Alt),
+                                   modifiers.HasFlag (ConsoleModifiers.Control)
+                                  );
+    }
+
+    /// <summary>Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.</summary>
+    /// <param name="key">The key code.</param>
+    /// <returns>The console modifiers.</returns>
+    public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
+    {
+        var modifiers = new ConsoleModifiers ();
+
+        if (key.HasFlag (KeyCode.ShiftMask))
+        {
+            modifiers |= ConsoleModifiers.Shift;
+        }
+
+        if (key.HasFlag (KeyCode.AltMask))
+        {
+            modifiers |= ConsoleModifiers.Alt;
+        }
+
+        if (key.HasFlag (KeyCode.CtrlMask))
+        {
+            modifiers |= ConsoleModifiers.Control;
+        }
+
+        return modifiers;
+    }
+
+    /// <summary>Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.</summary>
+    /// <param name="shift">The shift key.</param>
+    /// <param name="alt">The alt key.</param>
+    /// <param name="control">The control key.</param>
+    /// <returns>The console modifiers.</returns>
+    public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
+    {
+        var modifiers = new ConsoleModifiers ();
+
+        if (shift)
+        {
+            modifiers |= ConsoleModifiers.Shift;
+        }
+
+        if (alt)
+        {
+            modifiers |= ConsoleModifiers.Alt;
+        }
+
+        if (control)
+        {
+            modifiers |= ConsoleModifiers.Control;
+        }
+
+        return modifiers;
+    }
+
+    /// <summary>
+    ///     Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and
+    ///     (Key)Key.CtrlMask).
+    /// </summary>
+    /// <param name="keyValue">The key as a unicode codepoint.</param>
+    /// <param name="modifiers">The modifier keys.</param>
+    /// <param name="scanCode">The resulting scan code.</param>
+    /// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
+    private static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (
+        uint keyValue,
+        ConsoleModifiers modifiers,
+        out uint scanCode
+    )
+    {
+        scanCode = 0;
+
+        if (keyValue == 0)
+        {
+            return new ConsoleKeyInfo (
+                                       (char)keyValue,
+                                       ConsoleKey.None,
+                                       modifiers.HasFlag (ConsoleModifiers.Shift),
+                                       modifiers.HasFlag (ConsoleModifiers.Alt),
+                                       modifiers.HasFlag (ConsoleModifiers.Control)
+                                      );
+        }
+
+        uint outputChar = keyValue;
+        uint consoleKey;
+
+        if (keyValue > byte.MaxValue)
+        {
+            ScanCodeMapping sCode = _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyValue);
+
+            if (sCode == null)
+            {
+                consoleKey = (byte)(keyValue & byte.MaxValue);
+                sCode = _scanCodes.FirstOrDefault (e => e.VirtualKey == (VK)consoleKey);
+
+                if (sCode == null)
+                {
+                    consoleKey = 0;
+                    outputChar = keyValue;
+                }
+                else
+                {
+                    outputChar = (char)(keyValue >> 8);
+                }
+            }
+            else
+            {
+                consoleKey = (byte)sCode.VirtualKey;
+                outputChar = keyValue;
+            }
+        }
+        else
+        {
+            consoleKey = (byte)keyValue;
+            outputChar = '\0';
+        }
+
+        return new ConsoleKeyInfo (
+                                   (char)outputChar,
+                                   (ConsoleKey)consoleKey,
+                                   modifiers.HasFlag (ConsoleModifiers.Shift),
+                                   modifiers.HasFlag (ConsoleModifiers.Alt),
+                                   modifiers.HasFlag (ConsoleModifiers.Control)
+                                  );
+    }
+
+    // Used only by unit tests
+    internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers)
+    {
+        if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z')
+        {
+            return keyValue - 32;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z')
+        {
+            return keyValue + 32;
+        }
+
+        if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý')
+        {
+            return keyValue - 32;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý')
+        {
+            return keyValue + 32;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0')
+        {
+            return keyValue + 13;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0')
+        {
+            return keyValue - 13;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7')
+        {
+            return keyValue - 16;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7')
+        {
+            return keyValue + 16;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7')
+        {
+            return keyValue - 8;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7')
+        {
+            return keyValue + 8;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'')
+        {
+            return keyValue + 24;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'')
+        {
+            return keyValue - 24;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«')
+        {
+            return keyValue + 16;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«')
+        {
+            return keyValue - 16;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\')
+        {
+            return keyValue + 32;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\')
+        {
+            return keyValue - 32;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+')
+        {
+            return keyValue - 1;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+')
+        {
+            return keyValue + 1;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´')
+        {
+            return keyValue - 84;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´')
+        {
+            return keyValue + 84;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º')
+        {
+            return keyValue - 16;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º')
+        {
+            return keyValue + 16;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~')
+        {
+            return keyValue - 32;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~')
+        {
+            return keyValue + 32;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<')
+        {
+            return keyValue + 2;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<')
+        {
+            return keyValue - 2;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',')
+        {
+            return keyValue + 15;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',')
+        {
+            return keyValue - 15;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.')
+        {
+            return keyValue + 12;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.')
+        {
+            return keyValue - 12;
+        }
+
+        if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-')
+        {
+            return keyValue + 50;
+        }
+
+        if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-')
+        {
+            return keyValue - 50;
+        }
+
+        return keyValue;
+    }
+
+    /// <summary>
+    ///     Get the output character from the <see cref="GetConsoleKeyInfoFromKeyCode"/>, with the correct
+    ///     <see cref="ConsoleKey"/> and the scan code used on <see cref="WindowsDriver"/>.
+    /// </summary>
+    /// <param name="unicodeChar">The unicode character.</param>
+    /// <param name="modifiers">The modifiers keys.</param>
+    /// <param name="consoleKey">The resulting console key.</param>
+    /// <param name="scanCode">The resulting scan code.</param>
+    /// <param name="isConsoleKey">Indicates if the <paramref name="unicodeChar"/> is a <see cref="ConsoleKey"/>.</param>
+    /// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
+    /// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+    internal static uint GetKeyCharFromUnicodeChar (
+        uint unicodeChar,
+        ConsoleModifiers modifiers,
+        out uint consoleKey,
+        out uint scanCode,
+        bool isConsoleKey = false
+    )
+    {
+        uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
+        uint keyChar = decodedChar;
+        consoleKey = 0;
+        ConsoleModifiers mod = GetModifiers (modifiers);
+        scanCode = 0;
+        ScanCodeMapping scode = null;
+
+        if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey)
+        {
+            scode = GetScanCode ("VirtualKey", decodedChar, mod);
+        }
+
+        if (isConsoleKey && scode != null)
+        {
+            consoleKey = (uint)scode.VirtualKey;
+            keyChar = scode.UnicodeChar;
+            scanCode = scode.ScanCode;
+        }
+
+        if (scode == null)
+        {
+            scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
+
+            if (scode != null)
+            {
+                consoleKey = (uint)scode.VirtualKey;
+                keyChar = scode.UnicodeChar;
+                scanCode = scode.ScanCode;
+            }
+        }
+
+        if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar))
+        {
+            string stFormD = ((char)decodedChar).ToString ().Normalize (NormalizationForm.FormD);
+
+            for (var i = 0; i < stFormD.Length; i++)
+            {
+                UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
+
+                if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter)
+                {
+                    consoleKey = char.ToUpper (stFormD [i]);
+                    scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
+
+                    if (scode != null)
+                    {
+                        scanCode = scode.ScanCode;
+                    }
+                }
+            }
+        }
+
+        if (keyChar < 255 && consoleKey == 0 && scanCode == 0)
+        {
+            scode = GetScanCode ("VirtualKey", keyChar, mod);
+
+            if (scode != null)
+            {
+                consoleKey = (uint)scode.VirtualKey;
+                keyChar = scode.UnicodeChar;
+                scanCode = scode.ScanCode;
+            }
+        }
+
+        return keyChar;
+    }
+
+    /// <summary>Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.</summary>
+    /// <param name="keyValue">The key value.</param>
+    /// <param name="isConsoleKey">
+    ///     Indicates if the <paramref name="keyValue"/> is a <see cref="ConsoleKey"/>.
+    ///     <see langword="true"/> means the return value is in the ConsoleKey enum. <see langword="false"/> means the return
+    ///     value can be mapped to a valid unicode character.
+    /// </param>
+    /// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
+    /// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+    internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey)
+    {
+        isConsoleKey = true;
+        keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
+
+        switch (keyValue)
+        {
+            case KeyCode.Enter:
+                return (uint)ConsoleKey.Enter;
+            case KeyCode.CursorUp:
+                return (uint)ConsoleKey.UpArrow;
+            case KeyCode.CursorDown:
+                return (uint)ConsoleKey.DownArrow;
+            case KeyCode.CursorLeft:
+                return (uint)ConsoleKey.LeftArrow;
+            case KeyCode.CursorRight:
+                return (uint)ConsoleKey.RightArrow;
+            case KeyCode.PageUp:
+                return (uint)ConsoleKey.PageUp;
+            case KeyCode.PageDown:
+                return (uint)ConsoleKey.PageDown;
+            case KeyCode.Home:
+                return (uint)ConsoleKey.Home;
+            case KeyCode.End:
+                return (uint)ConsoleKey.End;
+            case KeyCode.Insert:
+                return (uint)ConsoleKey.Insert;
+            case KeyCode.Delete:
+                return (uint)ConsoleKey.Delete;
+            case KeyCode.F1:
+                return (uint)ConsoleKey.F1;
+            case KeyCode.F2:
+                return (uint)ConsoleKey.F2;
+            case KeyCode.F3:
+                return (uint)ConsoleKey.F3;
+            case KeyCode.F4:
+                return (uint)ConsoleKey.F4;
+            case KeyCode.F5:
+                return (uint)ConsoleKey.F5;
+            case KeyCode.F6:
+                return (uint)ConsoleKey.F6;
+            case KeyCode.F7:
+                return (uint)ConsoleKey.F7;
+            case KeyCode.F8:
+                return (uint)ConsoleKey.F8;
+            case KeyCode.F9:
+                return (uint)ConsoleKey.F9;
+            case KeyCode.F10:
+                return (uint)ConsoleKey.F10;
+            case KeyCode.F11:
+                return (uint)ConsoleKey.F11;
+            case KeyCode.F12:
+                return (uint)ConsoleKey.F12;
+            case KeyCode.F13:
+                return (uint)ConsoleKey.F13;
+            case KeyCode.F14:
+                return (uint)ConsoleKey.F14;
+            case KeyCode.F15:
+                return (uint)ConsoleKey.F15;
+            case KeyCode.F16:
+                return (uint)ConsoleKey.F16;
+            case KeyCode.F17:
+                return (uint)ConsoleKey.F17;
+            case KeyCode.F18:
+                return (uint)ConsoleKey.F18;
+            case KeyCode.F19:
+                return (uint)ConsoleKey.F19;
+            case KeyCode.F20:
+                return (uint)ConsoleKey.F20;
+            case KeyCode.F21:
+                return (uint)ConsoleKey.F21;
+            case KeyCode.F22:
+                return (uint)ConsoleKey.F22;
+            case KeyCode.F23:
+                return (uint)ConsoleKey.F23;
+            case KeyCode.F24:
+                return (uint)ConsoleKey.F24;
+            case KeyCode.Tab | KeyCode.ShiftMask:
+                return (uint)ConsoleKey.Tab;
+        }
+
+        isConsoleKey = false;
+
+        return (uint)keyValue;
+    }
+
+    /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
+    /// <param name="consoleKeyInfo">The console key.</param>
+    /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+    public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
+    {
+        KeyCode keyCode;
+
+        switch (consoleKeyInfo.Key)
+        {
+            case ConsoleKey.Enter:
+                keyCode = KeyCode.Enter;
+
+                break;
+            case ConsoleKey.Delete:
+                keyCode = KeyCode.Delete;
+
+                break;
+            case ConsoleKey.UpArrow:
+                keyCode = KeyCode.CursorUp;
+
+                break;
+            case ConsoleKey.DownArrow:
+                keyCode = KeyCode.CursorDown;
+
+                break;
+            case ConsoleKey.LeftArrow:
+                keyCode = KeyCode.CursorLeft;
+
+                break;
+            case ConsoleKey.RightArrow:
+                keyCode = KeyCode.CursorRight;
+
+                break;
+            case ConsoleKey.PageUp:
+                keyCode = KeyCode.PageUp;
+
+                break;
+            case ConsoleKey.PageDown:
+                keyCode = KeyCode.PageDown;
+
+                break;
+            case ConsoleKey.Home:
+                keyCode = KeyCode.Home;
+
+                break;
+            case ConsoleKey.End:
+                keyCode = KeyCode.End;
+
+                break;
+            case ConsoleKey.Insert:
+                keyCode = KeyCode.Insert;
+
+                break;
+            case ConsoleKey.F1:
+                keyCode = KeyCode.F1;
+
+                break;
+            case ConsoleKey.F2:
+                keyCode = KeyCode.F2;
+
+                break;
+            case ConsoleKey.F3:
+                keyCode = KeyCode.F3;
+
+                break;
+            case ConsoleKey.F4:
+                keyCode = KeyCode.F4;
+
+                break;
+            case ConsoleKey.F5:
+                keyCode = KeyCode.F5;
+
+                break;
+            case ConsoleKey.F6:
+                keyCode = KeyCode.F6;
+
+                break;
+            case ConsoleKey.F7:
+                keyCode = KeyCode.F7;
+
+                break;
+            case ConsoleKey.F8:
+                keyCode = KeyCode.F8;
+
+                break;
+            case ConsoleKey.F9:
+                keyCode = KeyCode.F9;
+
+                break;
+            case ConsoleKey.F10:
+                keyCode = KeyCode.F10;
+
+                break;
+            case ConsoleKey.F11:
+                keyCode = KeyCode.F11;
+
+                break;
+            case ConsoleKey.F12:
+                keyCode = KeyCode.F12;
+
+                break;
+            case ConsoleKey.F13:
+                keyCode = KeyCode.F13;
+
+                break;
+            case ConsoleKey.F14:
+                keyCode = KeyCode.F14;
+
+                break;
+            case ConsoleKey.F15:
+                keyCode = KeyCode.F15;
+
+                break;
+            case ConsoleKey.F16:
+                keyCode = KeyCode.F16;
+
+                break;
+            case ConsoleKey.F17:
+                keyCode = KeyCode.F17;
+
+                break;
+            case ConsoleKey.F18:
+                keyCode = KeyCode.F18;
+
+                break;
+            case ConsoleKey.F19:
+                keyCode = KeyCode.F19;
+
+                break;
+            case ConsoleKey.F20:
+                keyCode = KeyCode.F20;
+
+                break;
+            case ConsoleKey.F21:
+                keyCode = KeyCode.F21;
+
+                break;
+            case ConsoleKey.F22:
+                keyCode = KeyCode.F22;
+
+                break;
+            case ConsoleKey.F23:
+                keyCode = KeyCode.F23;
+
+                break;
+            case ConsoleKey.F24:
+                keyCode = KeyCode.F24;
+
+                break;
+            case ConsoleKey.Tab:
+                keyCode = KeyCode.Tab;
+
+                break;
+            default:
+                keyCode = (KeyCode)consoleKeyInfo.KeyChar;
+
+                break;
+        }
+
+        keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
+
+        return keyCode;
+    }
+
+    /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
+    /// <param name="modifiers">The console modifiers.</param>
+    /// <param name="key">The key code.</param>
+    /// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
+    public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
+    {
+        var keyMod = new KeyCode ();
+
+        if ((modifiers & ConsoleModifiers.Shift) != 0)
+        {
+            keyMod = KeyCode.ShiftMask;
+        }
+
+        if ((modifiers & ConsoleModifiers.Control) != 0)
+        {
+            keyMod |= KeyCode.CtrlMask;
+        }
+
+        if ((modifiers & ConsoleModifiers.Alt) != 0)
+        {
+            keyMod |= KeyCode.AltMask;
+        }
+
+        return keyMod != KeyCode.Null ? keyMod | key : key;
+    }
+
+    /// <summary>Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes</summary>
+    public enum VK : ushort
+    {
+        /// <summary>Left mouse button.</summary>
+        LBUTTON = 0x01,
+
+        /// <summary>Right mouse button.</summary>
+        RBUTTON = 0x02,
+
+        /// <summary>Control-break processing.</summary>
+        CANCEL = 0x03,
+
+        /// <summary>Middle mouse button (three-button mouse).</summary>
+        MBUTTON = 0x04,
+
+        /// <summary>X1 mouse button.</summary>
+        XBUTTON1 = 0x05,
+
+        /// <summary>X2 mouse button.</summary>
+        XBUTTON2 = 0x06,
+
+        /// <summary>BACKSPACE key.</summary>
+        BACK = 0x08,
+
+        /// <summary>TAB key.</summary>
+        TAB = 0x09,
+
+        /// <summary>CLEAR key.</summary>
+        CLEAR = 0x0C,
+
+        /// <summary>ENTER key.</summary>
+        RETURN = 0x0D,
+
+        /// <summary>SHIFT key.</summary>
+        SHIFT = 0x10,
+
+        /// <summary>CTRL key.</summary>
+        CONTROL = 0x11,
+
+        /// <summary>ALT key.</summary>
+        MENU = 0x12,
+
+        /// <summary>PAUSE key.</summary>
+        PAUSE = 0x13,
+
+        /// <summary>CAPS LOCK key.</summary>
+        CAPITAL = 0x14,
+
+        /// <summary>IME Kana mode.</summary>
+        KANA = 0x15,
+
+        /// <summary>IME Hangul mode.</summary>
+        HANGUL = 0x15,
+
+        /// <summary>IME Junja mode.</summary>
+        JUNJA = 0x17,
+
+        /// <summary>IME final mode.</summary>
+        FINAL = 0x18,
+
+        /// <summary>IME Hanja mode.</summary>
+        HANJA = 0x19,
+
+        /// <summary>IME Kanji mode.</summary>
+        KANJI = 0x19,
+
+        /// <summary>ESC key.</summary>
+        ESCAPE = 0x1B,
+
+        /// <summary>IME convert.</summary>
+        CONVERT = 0x1C,
+
+        /// <summary>IME nonconvert.</summary>
+        NONCONVERT = 0x1D,
+
+        /// <summary>IME accept.</summary>
+        ACCEPT = 0x1E,
+
+        /// <summary>IME mode change request.</summary>
+        MODECHANGE = 0x1F,
+
+        /// <summary>SPACEBAR.</summary>
+        SPACE = 0x20,
+
+        /// <summary>PAGE UP key.</summary>
+        PRIOR = 0x21,
+
+        /// <summary>PAGE DOWN key.</summary>
+        NEXT = 0x22,
+
+        /// <summary>END key.</summary>
+        END = 0x23,
+
+        /// <summary>HOME key.</summary>
+        HOME = 0x24,
+
+        /// <summary>LEFT ARROW key.</summary>
+        LEFT = 0x25,
+
+        /// <summary>UP ARROW key.</summary>
+        UP = 0x26,
+
+        /// <summary>RIGHT ARROW key.</summary>
+        RIGHT = 0x27,
+
+        /// <summary>DOWN ARROW key.</summary>
+        DOWN = 0x28,
+
+        /// <summary>SELECT key.</summary>
+        SELECT = 0x29,
+
+        /// <summary>PRINT key.</summary>
+        PRINT = 0x2A,
+
+        /// <summary>EXECUTE key</summary>
+        EXECUTE = 0x2B,
+
+        /// <summary>PRINT SCREEN key</summary>
+        SNAPSHOT = 0x2C,
+
+        /// <summary>INS key</summary>
+        INSERT = 0x2D,
+
+        /// <summary>DEL key</summary>
+        DELETE = 0x2E,
+
+        /// <summary>HELP key</summary>
+        HELP = 0x2F,
+
+        /// <summary>Left Windows key (Natural keyboard)</summary>
+        LWIN = 0x5B,
+
+        /// <summary>Right Windows key (Natural keyboard)</summary>
+        RWIN = 0x5C,
+
+        /// <summary>Applications key (Natural keyboard)</summary>
+        APPS = 0x5D,
+
+        /// <summary>Computer Sleep key</summary>
+        SLEEP = 0x5F,
+
+        /// <summary>Numeric keypad 0 key</summary>
+        NUMPAD0 = 0x60,
+
+        /// <summary>Numeric keypad 1 key</summary>
+        NUMPAD1 = 0x61,
+
+        /// <summary>Numeric keypad 2 key</summary>
+        NUMPAD2 = 0x62,
+
+        /// <summary>Numeric keypad 3 key</summary>
+        NUMPAD3 = 0x63,
+
+        /// <summary>Numeric keypad 4 key</summary>
+        NUMPAD4 = 0x64,
+
+        /// <summary>Numeric keypad 5 key</summary>
+        NUMPAD5 = 0x65,
+
+        /// <summary>Numeric keypad 6 key</summary>
+        NUMPAD6 = 0x66,
+
+        /// <summary>Numeric keypad 7 key</summary>
+        NUMPAD7 = 0x67,
+
+        /// <summary>Numeric keypad 8 key</summary>
+        NUMPAD8 = 0x68,
+
+        /// <summary>Numeric keypad 9 key</summary>
+        NUMPAD9 = 0x69,
+
+        /// <summary>Multiply key</summary>
+        MULTIPLY = 0x6A,
+
+        /// <summary>Add key</summary>
+        ADD = 0x6B,
+
+        /// <summary>Separator key</summary>
+        SEPARATOR = 0x6C,
+
+        /// <summary>Subtract key</summary>
+        SUBTRACT = 0x6D,
+
+        /// <summary>Decimal key</summary>
+        DECIMAL = 0x6E,
+
+        /// <summary>Divide key</summary>
+        DIVIDE = 0x6F,
+
+        /// <summary>F1 key</summary>
+        F1 = 0x70,
+
+        /// <summary>F2 key</summary>
+        F2 = 0x71,
+
+        /// <summary>F3 key</summary>
+        F3 = 0x72,
+
+        /// <summary>F4 key</summary>
+        F4 = 0x73,
+
+        /// <summary>F5 key</summary>
+        F5 = 0x74,
+
+        /// <summary>F6 key</summary>
+        F6 = 0x75,
+
+        /// <summary>F7 key</summary>
+        F7 = 0x76,
+
+        /// <summary>F8 key</summary>
+        F8 = 0x77,
+
+        /// <summary>F9 key</summary>
+        F9 = 0x78,
+
+        /// <summary>F10 key</summary>
+        F10 = 0x79,
+
+        /// <summary>F11 key</summary>
+        F11 = 0x7A,
+
+        /// <summary>F12 key</summary>
+        F12 = 0x7B,
+
+        /// <summary>F13 key</summary>
+        F13 = 0x7C,
+
+        /// <summary>F14 key</summary>
+        F14 = 0x7D,
+
+        /// <summary>F15 key</summary>
+        F15 = 0x7E,
+
+        /// <summary>F16 key</summary>
+        F16 = 0x7F,
+
+        /// <summary>F17 key</summary>
+        F17 = 0x80,
+
+        /// <summary>F18 key</summary>
+        F18 = 0x81,
+
+        /// <summary>F19 key</summary>
+        F19 = 0x82,
+
+        /// <summary>F20 key</summary>
+        F20 = 0x83,
+
+        /// <summary>F21 key</summary>
+        F21 = 0x84,
+
+        /// <summary>F22 key</summary>
+        F22 = 0x85,
+
+        /// <summary>F23 key</summary>
+        F23 = 0x86,
+
+        /// <summary>F24 key</summary>
+        F24 = 0x87,
+
+        /// <summary>NUM LOCK key</summary>
+        NUMLOCK = 0x90,
+
+        /// <summary>SCROLL LOCK key</summary>
+        SCROLL = 0x91,
+
+        /// <summary>NEC PC-9800 kbd definition: '=' key on numpad</summary>
+        OEM_NEC_EQUAL = 0x92,
+
+        /// <summary>Fujitsu/OASYS kbd definition: 'Dictionary' key</summary>
+        OEM_FJ_JISHO = 0x92,
+
+        /// <summary>Fujitsu/OASYS kbd definition: 'Unregister word' key</summary>
+        OEM_FJ_MASSHOU = 0x93,
+
+        /// <summary>Fujitsu/OASYS kbd definition: 'Register word' key</summary>
+        OEM_FJ_TOUROKU = 0x94,
+
+        /// <summary>Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key</summary>
+        OEM_FJ_LOYA = 0x95,
+
+        /// <summary>Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key</summary>
+        OEM_FJ_ROYA = 0x96,
+
+        /// <summary>Left SHIFT key</summary>
+        LSHIFT = 0xA0,
+
+        /// <summary>Right SHIFT key</summary>
+        RSHIFT = 0xA1,
+
+        /// <summary>Left CONTROL key</summary>
+        LCONTROL = 0xA2,
+
+        /// <summary>Right CONTROL key</summary>
+        RCONTROL = 0xA3,
+
+        /// <summary>Left MENU key (Left Alt key)</summary>
+        LMENU = 0xA4,
+
+        /// <summary>Right MENU key (Right Alt key)</summary>
+        RMENU = 0xA5,
+
+        /// <summary>Browser Back key</summary>
+        BROWSER_BACK = 0xA6,
+
+        /// <summary>Browser Forward key</summary>
+        BROWSER_FORWARD = 0xA7,
+
+        /// <summary>Browser Refresh key</summary>
+        BROWSER_REFRESH = 0xA8,
+
+        /// <summary>Browser Stop key</summary>
+        BROWSER_STOP = 0xA9,
+
+        /// <summary>Browser Search key</summary>
+        BROWSER_SEARCH = 0xAA,
+
+        /// <summary>Browser Favorites key</summary>
+        BROWSER_FAVORITES = 0xAB,
+
+        /// <summary>Browser Home key</summary>
+        BROWSER_HOME = 0xAC,
+
+        /// <summary>Volume Mute key</summary>
+        VOLUME_MUTE = 0xAD,
+
+        /// <summary>Volume Down key</summary>
+        VOLUME_DOWN = 0xAE,
+
+        /// <summary>Volume Up key</summary>
+        VOLUME_UP = 0xAF,
+
+        /// <summary>Next Track key</summary>
+        MEDIA_NEXT_TRACK = 0xB0,
+
+        /// <summary>Previous Track key</summary>
+        MEDIA_PREV_TRACK = 0xB1,
+
+        /// <summary>Stop Media key</summary>
+        MEDIA_STOP = 0xB2,
+
+        /// <summary>Play/Pause Media key</summary>
+        MEDIA_PLAY_PAUSE = 0xB3,
+
+        /// <summary>Start Mail key</summary>
+        LAUNCH_MAIL = 0xB4,
+
+        /// <summary>Select Media key</summary>
+        LAUNCH_MEDIA_SELECT = 0xB5,
+
+        /// <summary>Start Application 1 key</summary>
+        LAUNCH_APP1 = 0xB6,
+
+        /// <summary>Start Application 2 key</summary>
+        LAUNCH_APP2 = 0xB7,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key</summary>
+        OEM_1 = 0xBA,
+
+        /// <summary>For any country/region, the '+' key</summary>
+        OEM_PLUS = 0xBB,
+
+        /// <summary>For any country/region, the ',' key</summary>
+        OEM_COMMA = 0xBC,
+
+        /// <summary>For any country/region, the '-' key</summary>
+        OEM_MINUS = 0xBD,
+
+        /// <summary>For any country/region, the '.' key</summary>
+        OEM_PERIOD = 0xBE,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key</summary>
+        OEM_2 = 0xBF,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key</summary>
+        OEM_3 = 0xC0,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key</summary>
+        OEM_4 = 0xDB,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key</summary>
+        OEM_5 = 0xDC,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key</summary>
+        OEM_6 = 0xDD,
+
+        /// <summary>
+        ///     Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the
+        ///     'single-quote/double-quote' key
+        /// </summary>
+        OEM_7 = 0xDE,
+
+        /// <summary>Used for miscellaneous characters; it can vary by keyboard.</summary>
+        OEM_8 = 0xDF,
+
+        /// <summary>'AX' key on Japanese AX kbd</summary>
+        OEM_AX = 0xE1,
+
+        /// <summary>Either the angle bracket key or the backslash key on the RT 102-key keyboard</summary>
+        OEM_102 = 0xE2,
+
+        /// <summary>Help key on ICO</summary>
+        ICO_HELP = 0xE3,
+
+        /// <summary>00 key on ICO</summary>
+        ICO_00 = 0xE4,
+
+        /// <summary>Process key</summary>
+        PROCESSKEY = 0xE5,
+
+        /// <summary>Clear key on ICO</summary>
+        ICO_CLEAR = 0xE6,
+
+        /// <summary>Packet key to be used to pass Unicode characters as if they were keystrokes</summary>
+        PACKET = 0xE7,
+
+        /// <summary>Reset key</summary>
+        OEM_RESET = 0xE9,
+
+        /// <summary>Jump key</summary>
+        OEM_JUMP = 0xEA,
+
+        /// <summary>PA1 key</summary>
+        OEM_PA1 = 0xEB,
+
+        /// <summary>PA2 key</summary>
+        OEM_PA2 = 0xEC,
+
+        /// <summary>PA3 key</summary>
+        OEM_PA3 = 0xED,
+
+        /// <summary>WsCtrl key</summary>
+        OEM_WSCTRL = 0xEE,
+
+        /// <summary>CuSel key</summary>
+        OEM_CUSEL = 0xEF,
+
+        /// <summary>Attn key</summary>
+        OEM_ATTN = 0xF0,
+
+        /// <summary>Finish key</summary>
+        OEM_FINISH = 0xF1,
+
+        /// <summary>Copy key</summary>
+        OEM_COPY = 0xF2,
+
+        /// <summary>Auto key</summary>
+        OEM_AUTO = 0xF3,
+
+        /// <summary>Enlw key</summary>
+        OEM_ENLW = 0xF4,
+
+        /// <summary>BackTab key</summary>
+        OEM_BACKTAB = 0xF5,
+
+        /// <summary>Attn key</summary>
+        ATTN = 0xF6,
+
+        /// <summary>CrSel key</summary>
+        CRSEL = 0xF7,
+
+        /// <summary>ExSel key</summary>
+        EXSEL = 0xF8,
+
+        /// <summary>Erase EOF key</summary>
+        EREOF = 0xF9,
+
+        /// <summary>Play key</summary>
+        PLAY = 0xFA,
+
+        /// <summary>Zoom key</summary>
+        ZOOM = 0xFB,
+
+        /// <summary>Reserved</summary>
+        NONAME = 0xFC,
+
+        /// <summary>PA1 key</summary>
+        PA1 = 0xFD,
+
+        /// <summary>Clear key</summary>
+        OEM_CLEAR = 0xFE
+    }
+
+    // BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout
+    //         It should be deleted.
+    private static readonly HashSet<ScanCodeMapping> _scanCodes = new ()
+    {
+        new ScanCodeMapping (
+                             1,
+                             VK.ESCAPE,
+                             0,
+                             '\u001B'
+                            ), // Escape
+        new ScanCodeMapping (
+                             1,
+                             VK.ESCAPE,
+                             ConsoleModifiers.Shift,
+                             '\u001B'
+                            ),
+        new ScanCodeMapping (
+                             2,
+                             (VK)'1',
+                             0,
+                             '1'
+                            ), // D1
+        new ScanCodeMapping (
+                             2,
+                             (VK)'1',
+                             ConsoleModifiers.Shift,
+                             '!'
+                            ),
+        new ScanCodeMapping (
+                             3,
+                             (VK)'2',
+                             0,
+                             '2'
+                            ), // D2
+        new ScanCodeMapping (
+                             3,
+                             (VK)'2',
+                             ConsoleModifiers.Shift,
+                             '\"'
+                            ), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (")
+        new ScanCodeMapping (
+                             3,
+                             (VK)'2',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '@'
+                            ),
+        new ScanCodeMapping (
+                             4,
+                             (VK)'3',
+                             0,
+                             '3'
+                            ), // D3
+        new ScanCodeMapping (
+                             4,
+                             (VK)'3',
+                             ConsoleModifiers.Shift,
+                             '#'
+                            ),
+        new ScanCodeMapping (
+                             4,
+                             (VK)'3',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '£'
+                            ),
+        new ScanCodeMapping (
+                             5,
+                             (VK)'4',
+                             0,
+                             '4'
+                            ), // D4
+        new ScanCodeMapping (
+                             5,
+                             (VK)'4',
+                             ConsoleModifiers.Shift,
+                             '$'
+                            ),
+        new ScanCodeMapping (
+                             5,
+                             (VK)'4',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '§'
+                            ),
+        new ScanCodeMapping (
+                             6,
+                             (VK)'5',
+                             0,
+                             '5'
+                            ), // D5
+        new ScanCodeMapping (
+                             6,
+                             (VK)'5',
+                             ConsoleModifiers.Shift,
+                             '%'
+                            ),
+        new ScanCodeMapping (
+                             6,
+                             (VK)'5',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '€'
+                            ),
+        new ScanCodeMapping (
+                             7,
+                             (VK)'6',
+                             0,
+                             '6'
+                            ), // D6
+        new ScanCodeMapping (
+                             7,
+                             (VK)'6',
+                             ConsoleModifiers.Shift,
+                             '&'
+                            ),
+        new ScanCodeMapping (
+                             8,
+                             (VK)'7',
+                             0,
+                             '7'
+                            ), // D7
+        new ScanCodeMapping (
+                             8,
+                             (VK)'7',
+                             ConsoleModifiers.Shift,
+                             '/'
+                            ),
+        new ScanCodeMapping (
+                             8,
+                             (VK)'7',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '{'
+                            ),
+        new ScanCodeMapping (
+                             9,
+                             (VK)'8',
+                             0,
+                             '8'
+                            ), // D8
+        new ScanCodeMapping (
+                             9,
+                             (VK)'8',
+                             ConsoleModifiers.Shift,
+                             '('
+                            ),
+        new ScanCodeMapping (
+                             9,
+                             (VK)'8',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '['
+                            ),
+        new ScanCodeMapping (
+                             10,
+                             (VK)'9',
+                             0,
+                             '9'
+                            ), // D9
+        new ScanCodeMapping (
+                             10,
+                             (VK)'9',
+                             ConsoleModifiers.Shift,
+                             ')'
+                            ),
+        new ScanCodeMapping (
+                             10,
+                             (VK)'9',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             ']'
+                            ),
+        new ScanCodeMapping (
+                             11,
+                             (VK)'0',
+                             0,
+                             '0'
+                            ), // D0
+        new ScanCodeMapping (
+                             11,
+                             (VK)'0',
+                             ConsoleModifiers.Shift,
+                             '='
+                            ),
+        new ScanCodeMapping (
+                             11,
+                             (VK)'0',
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '}'
+                            ),
+        new ScanCodeMapping (
+                             12,
+                             VK.OEM_4,
+                             0,
+                             '\''
+                            ), // Oem4
+        new ScanCodeMapping (
+                             12,
+                             VK.OEM_4,
+                             ConsoleModifiers.Shift,
+                             '?'
+                            ),
+        new ScanCodeMapping (
+                             13,
+                             VK.OEM_6,
+                             0,
+                             '+'
+                            ), // Oem6
+        new ScanCodeMapping (
+                             13,
+                             VK.OEM_6,
+                             ConsoleModifiers.Shift,
+                             '*'
+                            ),
+        new ScanCodeMapping (
+                             14,
+                             VK.BACK,
+                             0,
+                             '\u0008'
+                            ), // Backspace
+        new ScanCodeMapping (
+                             14,
+                             VK.BACK,
+                             ConsoleModifiers.Shift,
+                             '\u0008'
+                            ),
+        new ScanCodeMapping (
+                             15,
+                             VK.TAB,
+                             0,
+                             '\u0009'
+                            ), // Tab
+        new ScanCodeMapping (
+                             15,
+                             VK.TAB,
+                             ConsoleModifiers.Shift,
+                             '\u000F'
+                            ),
+        new ScanCodeMapping (
+                             16,
+                             (VK)'Q',
+                             0,
+                             'q'
+                            ), // Q
+        new ScanCodeMapping (
+                             16,
+                             (VK)'Q',
+                             ConsoleModifiers.Shift,
+                             'Q'
+                            ),
+        new ScanCodeMapping (
+                             17,
+                             (VK)'W',
+                             0,
+                             'w'
+                            ), // W
+        new ScanCodeMapping (
+                             17,
+                             (VK)'W',
+                             ConsoleModifiers.Shift,
+                             'W'
+                            ),
+        new ScanCodeMapping (
+                             18,
+                             (VK)'E',
+                             0,
+                             'e'
+                            ), // E
+        new ScanCodeMapping (
+                             18,
+                             (VK)'E',
+                             ConsoleModifiers.Shift,
+                             'E'
+                            ),
+        new ScanCodeMapping (
+                             19,
+                             (VK)'R',
+                             0,
+                             'r'
+                            ), // R
+        new ScanCodeMapping (
+                             19,
+                             (VK)'R',
+                             ConsoleModifiers.Shift,
+                             'R'
+                            ),
+        new ScanCodeMapping (
+                             20,
+                             (VK)'T',
+                             0,
+                             't'
+                            ), // T
+        new ScanCodeMapping (
+                             20,
+                             (VK)'T',
+                             ConsoleModifiers.Shift,
+                             'T'
+                            ),
+        new ScanCodeMapping (
+                             21,
+                             (VK)'Y',
+                             0,
+                             'y'
+                            ), // Y
+        new ScanCodeMapping (
+                             21,
+                             (VK)'Y',
+                             ConsoleModifiers.Shift,
+                             'Y'
+                            ),
+        new ScanCodeMapping (
+                             22,
+                             (VK)'U',
+                             0,
+                             'u'
+                            ), // U
+        new ScanCodeMapping (
+                             22,
+                             (VK)'U',
+                             ConsoleModifiers.Shift,
+                             'U'
+                            ),
+        new ScanCodeMapping (
+                             23,
+                             (VK)'I',
+                             0,
+                             'i'
+                            ), // I
+        new ScanCodeMapping (
+                             23,
+                             (VK)'I',
+                             ConsoleModifiers.Shift,
+                             'I'
+                            ),
+        new ScanCodeMapping (
+                             24,
+                             (VK)'O',
+                             0,
+                             'o'
+                            ), // O
+        new ScanCodeMapping (
+                             24,
+                             (VK)'O',
+                             ConsoleModifiers.Shift,
+                             'O'
+                            ),
+        new ScanCodeMapping (
+                             25,
+                             (VK)'P',
+                             0,
+                             'p'
+                            ), // P
+        new ScanCodeMapping (
+                             25,
+                             (VK)'P',
+                             ConsoleModifiers.Shift,
+                             'P'
+                            ),
+        new ScanCodeMapping (
+                             26,
+                             VK.OEM_PLUS,
+                             0,
+                             '+'
+                            ), // OemPlus
+        new ScanCodeMapping (
+                             26,
+                             VK.OEM_PLUS,
+                             ConsoleModifiers.Shift,
+                             '*'
+                            ),
+        new ScanCodeMapping (
+                             26,
+                             VK.OEM_PLUS,
+                             ConsoleModifiers.Alt
+                             | ConsoleModifiers.Control,
+                             '¨'
+                            ),
+        new ScanCodeMapping (
+                             27,
+                             VK.OEM_1,
+                             0,
+                             '´'
+                            ), // Oem1
+        new ScanCodeMapping (
+                             27,
+                             VK.OEM_1,
+                             ConsoleModifiers.Shift,
+                             '`'
+                            ),
+        new ScanCodeMapping (
+                             28,
+                             VK.RETURN,
+                             0,
+                             '\u000D'
+                            ), // Enter
+        new ScanCodeMapping (
+                             28,
+                             VK.RETURN,
+                             ConsoleModifiers.Shift,
+                             '\u000D'
+                            ),
+        new ScanCodeMapping (
+                             29,
+                             VK.CONTROL,
+                             0,
+                             '\0'
+                            ), // Control
+        new ScanCodeMapping (
+                             29,
+                             VK.CONTROL,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             30,
+                             (VK)'A',
+                             0,
+                             'a'
+                            ), // A
+        new ScanCodeMapping (
+                             30,
+                             (VK)'A',
+                             ConsoleModifiers.Shift,
+                             'A'
+                            ),
+        new ScanCodeMapping (
+                             31,
+                             (VK)'S',
+                             0,
+                             's'
+                            ), // S
+        new ScanCodeMapping (
+                             31,
+                             (VK)'S',
+                             ConsoleModifiers.Shift,
+                             'S'
+                            ),
+        new ScanCodeMapping (
+                             32,
+                             (VK)'D',
+                             0,
+                             'd'
+                            ), // D
+        new ScanCodeMapping (
+                             32,
+                             (VK)'D',
+                             ConsoleModifiers.Shift,
+                             'D'
+                            ),
+        new ScanCodeMapping (
+                             33,
+                             (VK)'F',
+                             0,
+                             'f'
+                            ), // F
+        new ScanCodeMapping (
+                             33,
+                             (VK)'F',
+                             ConsoleModifiers.Shift,
+                             'F'
+                            ),
+        new ScanCodeMapping (
+                             34,
+                             (VK)'G',
+                             0,
+                             'g'
+                            ), // G
+        new ScanCodeMapping (
+                             34,
+                             (VK)'G',
+                             ConsoleModifiers.Shift,
+                             'G'
+                            ),
+        new ScanCodeMapping (
+                             35,
+                             (VK)'H',
+                             0,
+                             'h'
+                            ), // H
+        new ScanCodeMapping (
+                             35,
+                             (VK)'H',
+                             ConsoleModifiers.Shift,
+                             'H'
+                            ),
+        new ScanCodeMapping (
+                             36,
+                             (VK)'J',
+                             0,
+                             'j'
+                            ), // J
+        new ScanCodeMapping (
+                             36,
+                             (VK)'J',
+                             ConsoleModifiers.Shift,
+                             'J'
+                            ),
+        new ScanCodeMapping (
+                             37,
+                             (VK)'K',
+                             0,
+                             'k'
+                            ), // K
+        new ScanCodeMapping (
+                             37,
+                             (VK)'K',
+                             ConsoleModifiers.Shift,
+                             'K'
+                            ),
+        new ScanCodeMapping (
+                             38,
+                             (VK)'L',
+                             0,
+                             'l'
+                            ), // L
+        new ScanCodeMapping (
+                             38,
+                             (VK)'L',
+                             ConsoleModifiers.Shift,
+                             'L'
+                            ),
+        new ScanCodeMapping (
+                             39,
+                             VK.OEM_3,
+                             0,
+                             '`'
+                            ), // Oem3 (Backtick/Grave)
+        new ScanCodeMapping (
+                             39,
+                             VK.OEM_3,
+                             ConsoleModifiers.Shift,
+                             '~'
+                            ),
+        new ScanCodeMapping (
+                             40,
+                             VK.OEM_7,
+                             0,
+                             '\''
+                            ), // Oem7 (Single Quote)
+        new ScanCodeMapping (
+                             40,
+                             VK.OEM_7,
+                             ConsoleModifiers.Shift,
+                             '\"'
+                            ),
+        new ScanCodeMapping (
+                             41,
+                             VK.OEM_5,
+                             0,
+                             '\\'
+                            ), // Oem5 (Backslash)
+        new ScanCodeMapping (
+                             41,
+                             VK.OEM_5,
+                             ConsoleModifiers.Shift,
+                             '|'
+                            ),
+        new ScanCodeMapping (
+                             42,
+                             VK.LSHIFT,
+                             0,
+                             '\0'
+                            ), // Left Shift
+        new ScanCodeMapping (
+                             42,
+                             VK.LSHIFT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             43,
+                             VK.OEM_2,
+                             0,
+                             '/'
+                            ), // Oem2 (Forward Slash)
+        new ScanCodeMapping (
+                             43,
+                             VK.OEM_2,
+                             ConsoleModifiers.Shift,
+                             '?'
+                            ),
+        new ScanCodeMapping (
+                             44,
+                             (VK)'Z',
+                             0,
+                             'z'
+                            ), // Z
+        new ScanCodeMapping (
+                             44,
+                             (VK)'Z',
+                             ConsoleModifiers.Shift,
+                             'Z'
+                            ),
+        new ScanCodeMapping (
+                             45,
+                             (VK)'X',
+                             0,
+                             'x'
+                            ), // X
+        new ScanCodeMapping (
+                             45,
+                             (VK)'X',
+                             ConsoleModifiers.Shift,
+                             'X'
+                            ),
+        new ScanCodeMapping (
+                             46,
+                             (VK)'C',
+                             0,
+                             'c'
+                            ), // C
+        new ScanCodeMapping (
+                             46,
+                             (VK)'C',
+                             ConsoleModifiers.Shift,
+                             'C'
+                            ),
+        new ScanCodeMapping (
+                             47,
+                             (VK)'V',
+                             0,
+                             'v'
+                            ), // V
+        new ScanCodeMapping (
+                             47,
+                             (VK)'V',
+                             ConsoleModifiers.Shift,
+                             'V'
+                            ),
+        new ScanCodeMapping (
+                             48,
+                             (VK)'B',
+                             0,
+                             'b'
+                            ), // B
+        new ScanCodeMapping (
+                             48,
+                             (VK)'B',
+                             ConsoleModifiers.Shift,
+                             'B'
+                            ),
+        new ScanCodeMapping (
+                             49,
+                             (VK)'N',
+                             0,
+                             'n'
+                            ), // N
+        new ScanCodeMapping (
+                             49,
+                             (VK)'N',
+                             ConsoleModifiers.Shift,
+                             'N'
+                            ),
+        new ScanCodeMapping (
+                             50,
+                             (VK)'M',
+                             0,
+                             'm'
+                            ), // M
+        new ScanCodeMapping (
+                             50,
+                             (VK)'M',
+                             ConsoleModifiers.Shift,
+                             'M'
+                            ),
+        new ScanCodeMapping (
+                             51,
+                             VK.OEM_COMMA,
+                             0,
+                             ','
+                            ), // OemComma
+        new ScanCodeMapping (
+                             51,
+                             VK.OEM_COMMA,
+                             ConsoleModifiers.Shift,
+                             '<'
+                            ),
+        new ScanCodeMapping (
+                             52,
+                             VK.OEM_PERIOD,
+                             0,
+                             '.'
+                            ), // OemPeriod
+        new ScanCodeMapping (
+                             52,
+                             VK.OEM_PERIOD,
+                             ConsoleModifiers.Shift,
+                             '>'
+                            ),
+        new ScanCodeMapping (
+                             53,
+                             VK.OEM_MINUS,
+                             0,
+                             '-'
+                            ), // OemMinus
+        new ScanCodeMapping (
+                             53,
+                             VK.OEM_MINUS,
+                             ConsoleModifiers.Shift,
+                             '_'
+                            ),
+        new ScanCodeMapping (
+                             54,
+                             VK.RSHIFT,
+                             0,
+                             '\0'
+                            ), // Right Shift
+        new ScanCodeMapping (
+                             54,
+                             VK.RSHIFT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             55,
+                             VK.PRINT,
+                             0,
+                             '\0'
+                            ), // Print Screen
+        new ScanCodeMapping (
+                             55,
+                             VK.PRINT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             56,
+                             VK.LMENU,
+                             0,
+                             '\0'
+                            ), // Alt
+        new ScanCodeMapping (
+                             56,
+                             VK.LMENU,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             57,
+                             VK.SPACE,
+                             0,
+                             ' '
+                            ), // Spacebar
+        new ScanCodeMapping (
+                             57,
+                             VK.SPACE,
+                             ConsoleModifiers.Shift,
+                             ' '
+                            ),
+        new ScanCodeMapping (
+                             58,
+                             VK.CAPITAL,
+                             0,
+                             '\0'
+                            ), // Caps Lock
+        new ScanCodeMapping (
+                             58,
+                             VK.CAPITAL,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             59,
+                             VK.F1,
+                             0,
+                             '\0'
+                            ), // F1
+        new ScanCodeMapping (
+                             59,
+                             VK.F1,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             60,
+                             VK.F2,
+                             0,
+                             '\0'
+                            ), // F2
+        new ScanCodeMapping (
+                             60,
+                             VK.F2,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             61,
+                             VK.F3,
+                             0,
+                             '\0'
+                            ), // F3
+        new ScanCodeMapping (
+                             61,
+                             VK.F3,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             62,
+                             VK.F4,
+                             0,
+                             '\0'
+                            ), // F4
+        new ScanCodeMapping (
+                             62,
+                             VK.F4,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             63,
+                             VK.F5,
+                             0,
+                             '\0'
+                            ), // F5
+        new ScanCodeMapping (
+                             63,
+                             VK.F5,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             64,
+                             VK.F6,
+                             0,
+                             '\0'
+                            ), // F6
+        new ScanCodeMapping (
+                             64,
+                             VK.F6,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             65,
+                             VK.F7,
+                             0,
+                             '\0'
+                            ), // F7
+        new ScanCodeMapping (
+                             65,
+                             VK.F7,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             66,
+                             VK.F8,
+                             0,
+                             '\0'
+                            ), // F8
+        new ScanCodeMapping (
+                             66,
+                             VK.F8,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             67,
+                             VK.F9,
+                             0,
+                             '\0'
+                            ), // F9
+        new ScanCodeMapping (
+                             67,
+                             VK.F9,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             68,
+                             VK.F10,
+                             0,
+                             '\0'
+                            ), // F10
+        new ScanCodeMapping (
+                             68,
+                             VK.F10,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             69,
+                             VK.NUMLOCK,
+                             0,
+                             '\0'
+                            ), // Num Lock
+        new ScanCodeMapping (
+                             69,
+                             VK.NUMLOCK,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             70,
+                             VK.SCROLL,
+                             0,
+                             '\0'
+                            ), // Scroll Lock
+        new ScanCodeMapping (
+                             70,
+                             VK.SCROLL,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             71,
+                             VK.HOME,
+                             0,
+                             '\0'
+                            ), // Home
+        new ScanCodeMapping (
+                             71,
+                             VK.HOME,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             72,
+                             VK.UP,
+                             0,
+                             '\0'
+                            ), // Up Arrow
+        new ScanCodeMapping (
+                             72,
+                             VK.UP,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             73,
+                             VK.PRIOR,
+                             0,
+                             '\0'
+                            ), // Page Up
+        new ScanCodeMapping (
+                             73,
+                             VK.PRIOR,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             74,
+                             VK.SUBTRACT,
+                             0,
+                             '-'
+                            ), // Subtract (Num Pad '-')
+        new ScanCodeMapping (
+                             74,
+                             VK.SUBTRACT,
+                             ConsoleModifiers.Shift,
+                             '-'
+                            ),
+        new ScanCodeMapping (
+                             75,
+                             VK.LEFT,
+                             0,
+                             '\0'
+                            ), // Left Arrow
+        new ScanCodeMapping (
+                             75,
+                             VK.LEFT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             76,
+                             VK.CLEAR,
+                             0,
+                             '\0'
+                            ), // Center key (Num Pad 5 with Num Lock off)
+        new ScanCodeMapping (
+                             76,
+                             VK.CLEAR,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             77,
+                             VK.RIGHT,
+                             0,
+                             '\0'
+                            ), // Right Arrow
+        new ScanCodeMapping (
+                             77,
+                             VK.RIGHT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             78,
+                             VK.ADD,
+                             0,
+                             '+'
+                            ), // Add (Num Pad '+')
+        new ScanCodeMapping (
+                             78,
+                             VK.ADD,
+                             ConsoleModifiers.Shift,
+                             '+'
+                            ),
+        new ScanCodeMapping (
+                             79,
+                             VK.END,
+                             0,
+                             '\0'
+                            ), // End
+        new ScanCodeMapping (
+                             79,
+                             VK.END,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             80,
+                             VK.DOWN,
+                             0,
+                             '\0'
+                            ), // Down Arrow
+        new ScanCodeMapping (
+                             80,
+                             VK.DOWN,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             81,
+                             VK.NEXT,
+                             0,
+                             '\0'
+                            ), // Page Down
+        new ScanCodeMapping (
+                             81,
+                             VK.NEXT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             82,
+                             VK.INSERT,
+                             0,
+                             '\0'
+                            ), // Insert
+        new ScanCodeMapping (
+                             82,
+                             VK.INSERT,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             83,
+                             VK.DELETE,
+                             0,
+                             '\0'
+                            ), // Delete
+        new ScanCodeMapping (
+                             83,
+                             VK.DELETE,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             86,
+                             VK.OEM_102,
+                             0,
+                             '<'
+                            ), // OEM 102 (Typically '<' or '|' key next to Left Shift)
+        new ScanCodeMapping (
+                             86,
+                             VK.OEM_102,
+                             ConsoleModifiers.Shift,
+                             '>'
+                            ),
+        new ScanCodeMapping (
+                             87,
+                             VK.F11,
+                             0,
+                             '\0'
+                            ), // F11
+        new ScanCodeMapping (
+                             87,
+                             VK.F11,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            ),
+        new ScanCodeMapping (
+                             88,
+                             VK.F12,
+                             0,
+                             '\0'
+                            ), // F12
+        new ScanCodeMapping (
+                             88,
+                             VK.F12,
+                             ConsoleModifiers.Shift,
+                             '\0'
+                            )
+    };
+
+    /// <summary>Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.</summary>
+    /// <param name="consoleKeyInfo">The console key info.</param>
+    /// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+    /// <remarks>
+    ///     If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be a
+    ///     <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
+    /// </remarks>
+    public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+    {
+        if (consoleKeyInfo.Key != ConsoleKey.Packet)
+        {
+            return consoleKeyInfo;
+        }
+
+        return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+    }
+
+    /// <summary>
+    ///     Encode the <see cref="ConsoleKeyInfo.KeyChar"/> with the <see cref="ConsoleKeyInfo.Key"/> if the first a byte
+    ///     length, otherwise only the KeyChar is considered and searched on the database.
+    /// </summary>
+    /// <param name="consoleKeyInfo">The console key info.</param>
+    /// <returns>The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar.</returns>
+    /// <remarks>This is useful to use with the <see cref="ConsoleKey.Packet"/>.</remarks>
+    public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo)
+    {
+        char keyChar = consoleKeyInfo.KeyChar;
+        ConsoleKey consoleKey = consoleKeyInfo.Key;
+
+        if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None)
+        {
+            // try to get the ConsoleKey
+            ScanCodeMapping scode = _scanCodes.FirstOrDefault (e => e.UnicodeChar == keyChar);
+
+            if (scode != null)
+            {
+                consoleKey = (ConsoleKey)scode.VirtualKey;
+            }
+        }
+
+        if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None)
+        {
+            keyChar = (char)((consoleKeyInfo.KeyChar << 8) | (byte)consoleKey);
+        }
+
+        return keyChar;
+    }
 }

+ 256 - 212
Terminal.Gui/ConsoleDrivers/CursesDriver/ClipboardImpl.cs

@@ -1,225 +1,269 @@
-using System;
-using System.Runtime.InteropServices;
+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 ()
-	{
+/// <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>
+internal class CursesClipboard : ClipboardBase
+{
+    private string _xclipPath = string.Empty;
+    public CursesClipboard () { IsSupported = CheckSupport (); }
+    public override bool IsSupported { get; }
+
+    protected override string GetClipboardDataImpl ()
+    {
+        string tempFileName = Path.GetTempFileName ();
+        var xclipargs = "-selection clipboard -o";
+
+        try
+        {
+            (int exitCode, string result) =
+                ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
+
+            if (exitCode == 0)
+            {
+                if (Application.Driver is CursesDriver)
+                {
+                    Curses.raw ();
+                    Curses.noecho ();
+                }
+
+                return File.ReadAllText (tempFileName);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new NotSupportedException ($"\"{_xclipPath} {xclipargs}\" failed.", e);
+        }
+        finally
+        {
+            File.Delete (tempFileName);
+        }
+
+        return string.Empty;
+    }
+
+    protected override void SetClipboardDataImpl (string text)
+    {
+        var xclipargs = "-selection clipboard -i";
+
+        try
+        {
+            (int exitCode, _) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs}", text);
+
+            if (exitCode == 0 && Application.Driver is CursesDriver)
+            {
+                Curses.raw ();
+                Curses.noecho ();
+            }
+        }
+        catch (Exception e)
+        {
+            throw new NotSupportedException ($"\"{_xclipPath} {xclipargs} < {text}\" failed", e);
+        }
+    }
+
+    private 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.
-		}
+        try
+        {
+            (int exitCode, string 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);
-		}
-	}
+        return false;
+    }
 }
+
 /// <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);
+///     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>
+internal class MacOSXClipboard : ClipboardBase
+{
+    private readonly nint _allocRegister = sel_registerName ("alloc");
+    private readonly nint _clearContentsRegister = sel_registerName ("clearContents");
+    private readonly nint _generalPasteboard;
+    private readonly nint _generalPasteboardRegister = sel_registerName ("generalPasteboard");
+    private readonly nint _initWithUtf8Register = sel_registerName ("initWithUTF8String:");
+    private readonly nint _nsPasteboard = objc_getClass ("NSPasteboard");
+    private readonly nint _nsString = objc_getClass ("NSString");
+    private readonly nint _nsStringPboardType;
+    private readonly nint _setStringRegister = sel_registerName ("setString:forType:");
+    private readonly nint _stringForTypeRegister = sel_registerName ("stringForType:");
+    private readonly nint _utf8Register = sel_registerName ("UTF8String");
+    private readonly nint _utfTextType;
+
+    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; }
+
+    protected override string GetClipboardDataImpl ()
+    {
+        nint ptr = objc_msgSend (_generalPasteboard, _stringForTypeRegister, _nsStringPboardType);
+        nint charArray = objc_msgSend (ptr, _utf8Register);
+
+        return Marshal.PtrToStringAnsi (charArray);
+    }
+
+    protected override void SetClipboardDataImpl (string text)
+    {
+        nint 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 (nint))
+            {
+                objc_msgSend (str, sel_registerName ("release"));
+            }
+        }
+    }
+
+    private bool CheckSupport ()
+    {
+        (int exitCode, string 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 ();
+    }
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint objc_getClass (string className);
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint objc_msgSend (nint receiver, nint selector);
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint objc_msgSend (nint receiver, nint selector, string arg1);
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint objc_msgSend (nint receiver, nint selector, nint arg1);
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint objc_msgSend (nint receiver, nint selector, nint arg1, nint arg2);
+
+    [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
+    private static extern nint 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. 
+///     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 ();
-			}
-		}
-	}
+internal class WSLClipboard : ClipboardBase
+{
+    private static string _powershellPath = string.Empty;
+    public override bool IsSupported => CheckSupport ();
+
+    protected override string GetClipboardDataImpl ()
+    {
+        if (!IsSupported)
+        {
+            return string.Empty;
+        }
+
+        (int exitCode, string 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;
+        }
+
+        (int exitCode, string output) = ClipboardProcessRunner.Process (
+                                                                        _powershellPath,
+                                                                        $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
+                                                                       );
+
+        if (exitCode == 0)
+        {
+            if (Application.Driver is CursesDriver)
+            {
+                Curses.raw ();
+                Curses.noecho ();
+            }
+        }
+    }
+
+    private bool CheckSupport ()
+    {
+        if (string.IsNullOrEmpty (_powershellPath))
+        {
+            // Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
+            (int exitCode, string 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);
+    }
 }

+ 1007 - 784
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -1,795 +1,1018 @@
 //
 // Driver.cs: Curses-based Driver
 //
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
+
 using System.Runtime.InteropServices;
-using System.Text;
 using Terminal.Gui.ConsoleDrivers;
 using Unix.Terminal;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// This is the Curses driver for the gui.cs/Terminal framework.
-/// </summary>
-class CursesDriver : ConsoleDriver {
-	public override int Cols {
-		get => Curses.Cols;
-		internal set {
-			Curses.Cols = value;
-			ClearContents();
-		}
-	}
-
-	public override int Rows {
-		get => Curses.Lines;
-		internal set {
-			Curses.Lines = value;
-			ClearContents();
-		}
-	}
-
-	CursorVisibility? _initialCursorVisibility = null;
-	CursorVisibility? _currentCursorVisibility = null;
-
-	public override string GetVersionInfo () => $"{Curses.curses_version ()}";
-	UnixMainLoop _mainLoopDriver = null;
-
-	public override bool SupportsTrueColor => false;
-
-	object _processInputToken;
-
-	internal override MainLoop Init ()
-	{
-		_mainLoopDriver = new UnixMainLoop (this);
-		if (!RunningUnitTests) {
-
-			_window = Curses.initscr ();
-			Curses.set_escdelay (10);
-
-			// Ensures that all procedures are performed at some previous closing.
-			Curses.doupdate ();
-
-			// 
-			// 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;
-
-			case 2:
-				_currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box;
-				Curses.curs_set (2);
-				break;
-
-			default:
-				_currentCursorVisibility = _initialCursorVisibility = null;
-				break;
-			}
-			if (!Curses.HasColors) {
-				throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does.");
-			}
-
-			Curses.raw ();
-			Curses.noecho ();
-
-			Curses.Window.Standard.keypad (true);
-
-			Curses.StartColor ();
-			Curses.UseDefaultColors ();
-
-			if (!RunningUnitTests) {
-				Curses.timeout (0);
-			}
-
-			_processInputToken = _mainLoopDriver?.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
-				ProcessInput ();
-				return true;
-			});
-		}
-
-		CurrentAttribute = new Attribute (ColorName.White, ColorName.Black);
-
-		if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
-			Clipboard = new FakeDriver.FakeClipboard ();
-		} else {
-			if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				Clipboard = new MacOSXClipboard ();
-			} else {
-				if (Is_WSL_Platform ()) {
-					Clipboard = new WSLClipboard ();
-				} else {
-					Clipboard = new CursesClipboard ();
-				}
-			}
-		}
-
-		ClearContents ();
-		StartReportingMouseMoves ();
-
-		if (!RunningUnitTests) {
-			Curses.CheckWinChange ();
-			Curses.refresh ();
-		}
-		return new MainLoop (_mainLoopDriver);
-	}
-
-	public override void Move (int col, int row)
-	{
-		base.Move (col, row);
-
-		if (RunningUnitTests) {
-			return;
-		}
-
-		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);
-		}
-	}
-
-	public override bool IsRuneSupported (Rune rune) =>
-		// See Issue #2615 - CursesDriver is broken with non-BMP characters
-		base.IsRuneSupported (rune) && rune.IsBmp;
-
-	public override void Refresh ()
-	{
-		UpdateScreen ();
-		UpdateCursor ();
-	}
-
-	internal void ProcessWinChange ()
-	{
-		if (!RunningUnitTests && Curses.CheckWinChange ()) {
-			ClearContents ();
-			OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
-		}
-	}
-
-	#region Color Handling
-	/// <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)
-	{
-		short v = (short)((int)foreground | background << 4);
-
-		// TODO: for TrueColor - Use InitExtendedPair
-		Curses.InitColorPair (v, foreground, background);
-		return new Attribute (
-			Curses.ColorPair (v),
-			CursesColorNumberToColorName (foreground),
-			CursesColorNumberToColorName (background));
-	}
-
-	/// <inheritdoc/>
-	/// <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 foreground, Color background)
-	{
-		if (!RunningUnitTests) {
-			return MakeColor (ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()), ColorNameToCursesColorNumber (background.GetClosestNamedColor ()));
-		} else {
-			return new Attribute (
-				0,
-				foreground,
-				background);
-		}
-	}
-
-	static short ColorNameToCursesColorNumber (ColorName color)
-	{
-		switch (color) {
-		case ColorName.Black:
-			return Curses.COLOR_BLACK;
-		case ColorName.Blue:
-			return Curses.COLOR_BLUE;
-		case ColorName.Green:
-			return Curses.COLOR_GREEN;
-		case ColorName.Cyan:
-			return Curses.COLOR_CYAN;
-		case ColorName.Red:
-			return Curses.COLOR_RED;
-		case ColorName.Magenta:
-			return Curses.COLOR_MAGENTA;
-		case ColorName.Yellow:
-			return Curses.COLOR_YELLOW;
-		case ColorName.Gray:
-			return Curses.COLOR_WHITE;
-		case ColorName.DarkGray:
-			return Curses.COLOR_GRAY;
-		case ColorName.BrightBlue:
-			return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
-		case ColorName.BrightGreen:
-			return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
-		case ColorName.BrightCyan:
-			return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
-		case ColorName.BrightRed:
-			return Curses.COLOR_RED | Curses.COLOR_GRAY;
-		case ColorName.BrightMagenta:
-			return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
-		case ColorName.BrightYellow:
-			return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
-		case ColorName.White:
-			return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
-		}
-		throw new ArgumentException ("Invalid color code");
-	}
-
-	static ColorName CursesColorNumberToColorName (short color)
-	{
-		switch (color) {
-		case Curses.COLOR_BLACK:
-			return ColorName.Black;
-		case Curses.COLOR_BLUE:
-			return ColorName.Blue;
-		case Curses.COLOR_GREEN:
-			return ColorName.Green;
-		case Curses.COLOR_CYAN:
-			return ColorName.Cyan;
-		case Curses.COLOR_RED:
-			return ColorName.Red;
-		case Curses.COLOR_MAGENTA:
-			return ColorName.Magenta;
-		case Curses.COLOR_YELLOW:
-			return ColorName.Yellow;
-		case Curses.COLOR_WHITE:
-			return ColorName.Gray;
-		case Curses.COLOR_GRAY:
-			return ColorName.DarkGray;
-		case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
-			return ColorName.BrightBlue;
-		case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
-			return ColorName.BrightGreen;
-		case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
-			return ColorName.BrightCyan;
-		case Curses.COLOR_RED | Curses.COLOR_GRAY:
-			return ColorName.BrightRed;
-		case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
-			return ColorName.BrightMagenta;
-		case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
-			return ColorName.BrightYellow;
-		case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
-			return ColorName.White;
-		}
-		throw new ArgumentException ("Invalid curses color code");
-	}
-	#endregion
-
-	public override void UpdateCursor ()
-	{
-		EnsureCursorVisibility ();
-
-		if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) {
-			Curses.move (Row, Col);
-		}
-	}
-
-	internal override void End ()
-	{
-		StopReportingMouseMoves ();
-		SetCursorVisibility (CursorVisibility.Default);
-
-		if (_mainLoopDriver != null) {
-			_mainLoopDriver.RemoveWatch (_processInputToken);
-		}
-
-		if (RunningUnitTests) {
-			return;
-		}
-		// throws away any typeahead that has been typed by
-		// the user and has not yet been read by the program.
-		Curses.flushinp ();
-
-		Curses.endwin ();
-	}
-
-	public override void UpdateScreen ()
-	{
-		for (int row = 0; row < Rows; row++) {
-			if (!_dirtyLines [row]) {
-				continue;
-			}
-			_dirtyLines [row] = false;
-
-			for (int col = 0; col < Cols; col++) {
-				if (Contents [row, col].IsDirty == false) {
-					continue;
-				}
-				if (RunningUnitTests) {
-					// In unit tests, we don't want to actually write to the screen.
-					continue;
-				}
-				Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
-
-				var rune = Contents [row, col].Rune;
-				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 ());
-					}
-
-				} 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, '*');
-					}
-				}
-			}
-		}
-
-		if (!RunningUnitTests) {
-			Curses.move (Row, Col);
-			_window.wrefresh ();
-		}
-	}
-
-	public Curses.Window _window;
-
-	static KeyCode MapCursesKey (int cursesKey)
-	{
-		switch (cursesKey) {
-		case Curses.KeyF1: return KeyCode.F1;
-		case Curses.KeyF2: return KeyCode.F2;
-		case Curses.KeyF3: return KeyCode.F3;
-		case Curses.KeyF4: return KeyCode.F4;
-		case Curses.KeyF5: return KeyCode.F5;
-		case Curses.KeyF6: return KeyCode.F6;
-		case Curses.KeyF7: return KeyCode.F7;
-		case Curses.KeyF8: return KeyCode.F8;
-		case Curses.KeyF9: return KeyCode.F9;
-		case Curses.KeyF10: return KeyCode.F10;
-		case Curses.KeyF11: return KeyCode.F11;
-		case Curses.KeyF12: return KeyCode.F12;
-		case Curses.KeyUp: return KeyCode.CursorUp;
-		case Curses.KeyDown: return KeyCode.CursorDown;
-		case Curses.KeyLeft: return KeyCode.CursorLeft;
-		case Curses.KeyRight: return KeyCode.CursorRight;
-		case Curses.KeyHome: return KeyCode.Home;
-		case Curses.KeyEnd: return KeyCode.End;
-		case Curses.KeyNPage: return KeyCode.PageDown;
-		case Curses.KeyPPage: return KeyCode.PageUp;
-		case Curses.KeyDeleteChar: return KeyCode.Delete;
-		case Curses.KeyInsertChar: return KeyCode.Insert;
-		case Curses.KeyTab: return KeyCode.Tab;
-		case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
-		case Curses.KeyBackspace: return KeyCode.Backspace;
-		case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
-		case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
-		case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
-		case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
-		case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
-		case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
-		case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
-		case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
-		case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
-		case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
-		case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
-		case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
-		case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
-		case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
-		case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
-		case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
-		case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
-		case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
-		case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
-		case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
-		case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
-		case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
-		case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
-		case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
-		case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
-		case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
-		case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
-		case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
-		case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
-		default: return KeyCode.Null;
-		}
-	}
-
-	internal void ProcessInput ()
-	{
-		int wch;
-		int code = Curses.get_wch (out wch);
-		//System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
-		if (code == Curses.ERR) {
-			return;
-		}
-		var k = KeyCode.Null;
-
-		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) {
-					Key kea = null;
-					var cki = new ConsoleKeyInfo [] {
-						new ((char)KeyCode.Esc, 0, false, false, false),
-						new ('[', 0, false, false, false),
-						new ('<', 0, false, false, false)
-					};
-					code = 0;
-					HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
-				}
-				return;
-			}
-			k = MapCursesKey (wch);
-			if (wch >= 277 && wch <= 288) {
-				// Shift+(F1 - F12)
-				wch -= 12;
-				k = KeyCode.ShiftMask | MapCursesKey (wch);
-			} else if (wch >= 289 && wch <= 300) {
-				// Ctrl+(F1 - F12)
-				wch -= 24;
-				k = KeyCode.CtrlMask | MapCursesKey (wch);
-			} else if (wch >= 301 && wch <= 312) {
-				// Ctrl+Shift+(F1 - F12)
-				wch -= 36;
-				k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
-			} else if (wch >= 313 && wch <= 324) {
-				// Alt+(F1 - F12)
-				wch -= 48;
-				k = KeyCode.AltMask | MapCursesKey (wch);
-			} else if (wch >= 325 && wch <= 327) {
-				// Shift+Alt+(F1 - F3)
-				wch -= 60;
-				k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
-			}
-			OnKeyDown (new Key (k));
-			OnKeyUp (new Key (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);
-
-			if (code == Curses.KEY_CODE_YES) {
-				k = KeyCode.AltMask | MapCursesKey (wch);
-			}
-			Key key = null;
-			if (code == 0) {
-
-				// The ESC-number handling, debatable.
-				// Simulates the AltMask itself by pressing Alt + Space.
-				if (wch2 == (int)KeyCode.Space) {
-					k = KeyCode.AltMask;
-				} else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z) {
-					k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
-				} else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64) {
-					k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
-				} else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) {
-					k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
-				} else if (wch2 == Curses.KeyCSI) {
-					var cki = new ConsoleKeyInfo [] {
-						new ((char)KeyCode.Esc, 0, false, false, false),
-						new ('[', 0, false, false, false)
-					};
-					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 (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) {
-						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
-					}
-					if (wch2 == 0) {
-						k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
-					} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
-						k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
-					} else if (wch2 < 256) {
-						k = (KeyCode)wch2;// | KeyCode.AltMask;
-					} else {
-						k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
-					}
-				} 
-				key = new Key (k);
-			} else {
-				key = new Key (KeyCode.Esc);
-			}
-			OnKeyDown (key);
-			OnKeyUp (key);
-		} else if (wch == Curses.KeyTab) {
-			k = MapCursesKey (wch);
-			OnKeyDown (new Key (k));
-			OnKeyUp (new Key (k));
-		} else {
-			// Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
-			k = (KeyCode)wch;
-			if (wch == 0) {
-				k = KeyCode.CtrlMask | KeyCode.Space;
-			} else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64) {
-				if ((KeyCode)(wch + 64) != KeyCode.J) {
-					k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
-				}
-			} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
-				k = (KeyCode)wch | KeyCode.ShiftMask;
-			} 
-			
-			if (wch == '\n' || wch == '\r') {
-				k = KeyCode.Enter;
-			}
-			OnKeyDown (new Key (k));
-			OnKeyUp (new Key (k));
-		}
-	}
-
-	void HandleEscSeqResponse (ref int code, ref KeyCode k, ref int wch2, ref Key keyEventArgs, 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 var mouseFlags, out var pos, out _, ProcessMouseEvent);
-				if (isKeyMouse) {
-					foreach (var mf in mouseFlags) {
-						ProcessMouseEvent (mf, pos);
-					}
-					cki = null;
-					if (wch2 == 27) {
-						cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0,
-							false, false, false), cki);
-					}
-				} else {
-					k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
-					keyEventArgs = new Key (k);
-					OnKeyDown (keyEventArgs);
-				}
-			} else {
-				cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
-			}
-		}
-	}
-
-	MouseFlags _lastMouseFlags;
-
-	void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
-	{
-		bool WasButtonReleased (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Released) ||
-							flag.HasFlag (MouseFlags.Button2Released) ||
-							flag.HasFlag (MouseFlags.Button3Released) ||
-							flag.HasFlag (MouseFlags.Button4Released);
-
-		bool IsButtonNotPressed (MouseFlags flag) => !flag.HasFlag (MouseFlags.Button1Pressed) &&
-								!flag.HasFlag (MouseFlags.Button2Pressed) &&
-								!flag.HasFlag (MouseFlags.Button3Pressed) &&
-								!flag.HasFlag (MouseFlags.Button4Pressed);
-
-		bool IsButtonClickedOrDoubleClicked (MouseFlags flag) => 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);
-
-		if (WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags) ||
-		IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0) {
-			return;
-		}
-
-		_lastMouseFlags = mouseFlag;
-
-		var me = new MouseEvent () {
-			Flags = mouseFlag,
-			X = pos.X,
-			Y = pos.Y
-		};
-		OnMouseEvent (new MouseEventEventArgs (me));
-	}
-
-
-	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;
-		//}
-		(int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
-		if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
-			return true;
-		}
-		return false;
-	}
-
-	public override void Suspend ()
-	{
-		StopReportingMouseMoves ();
-		if (!RunningUnitTests) {
-			Platform.Suspend ();
-			Curses.Window.Standard.redrawwin ();
-			Curses.refresh ();
-		}
-		StartReportingMouseMoves ();
-	}
-
-	public void StartReportingMouseMoves ()
-	{
-		if (!RunningUnitTests) {
-			Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-		}
-	}
-
-	public void StopReportingMouseMoves ()
-	{
-		if (!RunningUnitTests) {
-			Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-		}
-	}
-
-	/// <inheritdoc/>
-	public override bool GetCursorVisibility (out CursorVisibility visibility)
-	{
-		visibility = CursorVisibility.Invisible;
-
-		if (!_currentCursorVisibility.HasValue) {
-			return false;
-		}
-
-		visibility = _currentCursorVisibility.Value;
-
-		return true;
-	}
-
-	/// <inheritdoc/>
-	public override bool SetCursorVisibility (CursorVisibility visibility)
-	{
-		if (_initialCursorVisibility.HasValue == false) {
-			return false;
-		}
-
-		if (!RunningUnitTests) {
-			Curses.curs_set ((int)visibility >> 16 & 0x000000FF);
-		}
-
-		if (visibility != CursorVisibility.Invisible) {
-			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)((int)visibility >> 24 & 0xFF)));
-		}
-
-		_currentCursorVisibility = visibility;
-
-		return true;
-	}
-
-	/// <inheritdoc/>
-	public override bool EnsureCursorVisibility () => false;
-
-	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
-	{
-		KeyCode key;
-
-		if (consoleKey == ConsoleKey.Packet) {
-			var mod = new ConsoleModifiers ();
-			if (shift) {
-				mod |= ConsoleModifiers.Shift;
-			}
-			if (alt) {
-				mod |= ConsoleModifiers.Alt;
-			}
-			if (control) {
-				mod |= ConsoleModifiers.Control;
-			}
-			var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
-			cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
-			key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo);
-		} else {
-			key = (KeyCode)keyChar;
-		}
-
-		OnKeyDown (new Key (key));
-		OnKeyUp (new Key (key));
-		//OnKeyPressed (new KeyEventArgsEventArgs (key));
-	}
+/// <summary>This is the Curses driver for the gui.cs/Terminal framework.</summary>
+internal class CursesDriver : ConsoleDriver
+{
+    public Curses.Window _window;
+    private CursorVisibility? _currentCursorVisibility;
+    private CursorVisibility? _initialCursorVisibility;
+    private MouseFlags _lastMouseFlags;
+    private UnixMainLoop _mainLoopDriver;
+    private object _processInputToken;
+
+    public override int Cols
+    {
+        get => Curses.Cols;
+        internal set
+        {
+            Curses.Cols = value;
+            ClearContents ();
+        }
+    }
+
+    public override int Rows
+    {
+        get => Curses.Lines;
+        internal set
+        {
+            Curses.Lines = value;
+            ClearContents ();
+        }
+    }
+
+    public override bool SupportsTrueColor => false;
+
+    /// <inheritdoc/>
+    public override bool EnsureCursorVisibility () { return false; }
+
+    /// <inheritdoc/>
+    public override bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        visibility = CursorVisibility.Invisible;
+
+        if (!_currentCursorVisibility.HasValue)
+        {
+            return false;
+        }
+
+        visibility = _currentCursorVisibility.Value;
+
+        return true;
+    }
+
+    public override string GetVersionInfo () { return $"{Curses.curses_version ()}"; }
+
+    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;
+        //}
+        (int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
+
+        if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL"))
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    public override bool IsRuneSupported (Rune rune)
+    {
+        // See Issue #2615 - CursesDriver is broken with non-BMP characters
+        return base.IsRuneSupported (rune) && rune.IsBmp;
+    }
+
+    public override void Move (int col, int row)
+    {
+        base.Move (col, row);
+
+        if (RunningUnitTests)
+        {
+            return;
+        }
+
+        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);
+        }
+    }
+
+    public override void Refresh ()
+    {
+        UpdateScreen ();
+        UpdateCursor ();
+    }
+
+    public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
+    {
+        KeyCode key;
+
+        if (consoleKey == ConsoleKey.Packet)
+        {
+            var mod = new ConsoleModifiers ();
+
+            if (shift)
+            {
+                mod |= ConsoleModifiers.Shift;
+            }
+
+            if (alt)
+            {
+                mod |= ConsoleModifiers.Alt;
+            }
+
+            if (control)
+            {
+                mod |= ConsoleModifiers.Control;
+            }
+
+            var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+            cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+            key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo);
+        }
+        else
+        {
+            key = (KeyCode)keyChar;
+        }
+
+        OnKeyDown (new Key (key));
+        OnKeyUp (new Key (key));
+
+        //OnKeyPressed (new KeyEventArgsEventArgs (key));
+    }
+
+    /// <inheritdoc/>
+    public override bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        if (_initialCursorVisibility.HasValue == false)
+        {
+            return false;
+        }
+
+        if (!RunningUnitTests)
+        {
+            Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
+        }
+
+        if (visibility != CursorVisibility.Invisible)
+        {
+            Console.Out.Write (
+                               EscSeqUtils.CSI_SetCursorStyle (
+                                                               (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24)
+                                                                                            & 0xFF)
+                                                              )
+                              );
+        }
+
+        _currentCursorVisibility = visibility;
+
+        return true;
+    }
+
+    public void StartReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+        }
+    }
+
+    public void StopReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+        }
+    }
+
+    public override void Suspend ()
+    {
+        StopReportingMouseMoves ();
+
+        if (!RunningUnitTests)
+        {
+            Platform.Suspend ();
+            Curses.Window.Standard.redrawwin ();
+            Curses.refresh ();
+        }
+
+        StartReportingMouseMoves ();
+    }
+
+    public override void UpdateCursor ()
+    {
+        EnsureCursorVisibility ();
+
+        if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
+        {
+            Curses.move (Row, Col);
+        }
+    }
+
+    public override void UpdateScreen ()
+    {
+        for (var row = 0; row < Rows; row++)
+        {
+            if (!_dirtyLines [row])
+            {
+                continue;
+            }
+
+            _dirtyLines [row] = false;
+
+            for (var col = 0; col < Cols; col++)
+            {
+                if (Contents [row, col].IsDirty == false)
+                {
+                    continue;
+                }
+
+                if (RunningUnitTests)
+                {
+                    // In unit tests, we don't want to actually write to the screen.
+                    continue;
+                }
+
+                Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
+
+                Rune rune = Contents [row, col].Rune;
+
+                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 ());
+                    }
+                }
+                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, '*');
+                    }
+                }
+            }
+        }
+
+        if (!RunningUnitTests)
+        {
+            Curses.move (Row, Col);
+            _window.wrefresh ();
+        }
+    }
+
+    internal override void End ()
+    {
+        StopReportingMouseMoves ();
+        SetCursorVisibility (CursorVisibility.Default);
+
+        if (_mainLoopDriver != null)
+        {
+            _mainLoopDriver.RemoveWatch (_processInputToken);
+        }
+
+        if (RunningUnitTests)
+        {
+            return;
+        }
+
+        // throws away any typeahead that has been typed by
+        // the user and has not yet been read by the program.
+        Curses.flushinp ();
+
+        Curses.endwin ();
+    }
+
+    internal override MainLoop Init ()
+    {
+        _mainLoopDriver = new UnixMainLoop (this);
+
+        if (!RunningUnitTests)
+        {
+            _window = Curses.initscr ();
+            Curses.set_escdelay (10);
+
+            // Ensures that all procedures are performed at some previous closing.
+            Curses.doupdate ();
+
+            // 
+            // 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;
+
+                case 2:
+                    _currentCursorVisibility = _initialCursorVisibility = CursorVisibility.Box;
+                    Curses.curs_set (2);
+
+                    break;
+
+                default:
+                    _currentCursorVisibility = _initialCursorVisibility = null;
+
+                    break;
+            }
+
+            if (!Curses.HasColors)
+            {
+                throw new InvalidOperationException ("V2 - This should never happen. File an Issue if it does.");
+            }
+
+            Curses.raw ();
+            Curses.noecho ();
+
+            Curses.Window.Standard.keypad (true);
+
+            Curses.StartColor ();
+            Curses.UseDefaultColors ();
+
+            if (!RunningUnitTests)
+            {
+                Curses.timeout (0);
+            }
+
+            _processInputToken = _mainLoopDriver?.AddWatch (
+                                                            0,
+                                                            UnixMainLoop.Condition.PollIn,
+                                                            x =>
+                                                            {
+                                                                ProcessInput ();
+
+                                                                return true;
+                                                            }
+                                                           );
+        }
+
+        CurrentAttribute = new Attribute (ColorName.White, ColorName.Black);
+
+        if (Environment.OSVersion.Platform == PlatformID.Win32NT)
+        {
+            Clipboard = new FakeDriver.FakeClipboard ();
+        }
+        else
+        {
+            if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+            {
+                Clipboard = new MacOSXClipboard ();
+            }
+            else
+            {
+                if (Is_WSL_Platform ())
+                {
+                    Clipboard = new WSLClipboard ();
+                }
+                else
+                {
+                    Clipboard = new CursesClipboard ();
+                }
+            }
+        }
+
+        ClearContents ();
+        StartReportingMouseMoves ();
+
+        if (!RunningUnitTests)
+        {
+            Curses.CheckWinChange ();
+            Curses.refresh ();
+        }
+
+        return new MainLoop (_mainLoopDriver);
+    }
+
+    internal void ProcessInput ()
+    {
+        int wch;
+        int code = Curses.get_wch (out wch);
+
+        //System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
+        if (code == Curses.ERR)
+        {
+            return;
+        }
+
+        var k = KeyCode.Null;
+
+        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)
+                {
+                    Key kea = null;
+
+                    ConsoleKeyInfo [] cki =
+                    {
+                        new ((char)KeyCode.Esc, 0, false, false, false),
+                        new ('[', 0, false, false, false),
+                        new ('<', 0, false, false, false)
+                    };
+                    code = 0;
+                    HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
+                }
+
+                return;
+            }
+
+            k = MapCursesKey (wch);
+
+            if (wch >= 277 && wch <= 288)
+            {
+                // Shift+(F1 - F12)
+                wch -= 12;
+                k = KeyCode.ShiftMask | MapCursesKey (wch);
+            }
+            else if (wch >= 289 && wch <= 300)
+            {
+                // Ctrl+(F1 - F12)
+                wch -= 24;
+                k = KeyCode.CtrlMask | MapCursesKey (wch);
+            }
+            else if (wch >= 301 && wch <= 312)
+            {
+                // Ctrl+Shift+(F1 - F12)
+                wch -= 36;
+                k = KeyCode.CtrlMask | KeyCode.ShiftMask | MapCursesKey (wch);
+            }
+            else if (wch >= 313 && wch <= 324)
+            {
+                // Alt+(F1 - F12)
+                wch -= 48;
+                k = KeyCode.AltMask | MapCursesKey (wch);
+            }
+            else if (wch >= 325 && wch <= 327)
+            {
+                // Shift+Alt+(F1 - F3)
+                wch -= 60;
+                k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
+            }
+
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (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);
+
+            if (code == Curses.KEY_CODE_YES)
+            {
+                k = KeyCode.AltMask | MapCursesKey (wch);
+            }
+
+            Key key = null;
+
+            if (code == 0)
+            {
+                // The ESC-number handling, debatable.
+                // Simulates the AltMask itself by pressing Alt + Space.
+                if (wch2 == (int)KeyCode.Space)
+                {
+                    k = KeyCode.AltMask;
+                }
+                else if (wch2 - (int)KeyCode.Space >= (uint)KeyCode.A
+                         && wch2 - (int)KeyCode.Space <= (uint)KeyCode.Z)
+                {
+                    k = (KeyCode)((uint)KeyCode.AltMask + (wch2 - (int)KeyCode.Space));
+                }
+                else if (wch2 >= (uint)KeyCode.A - 64 && wch2 <= (uint)KeyCode.Z - 64)
+                {
+                    k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + (wch2 + 64));
+                }
+                else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9)
+                {
+                    k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
+                }
+                else if (wch2 == Curses.KeyCSI)
+                {
+                    ConsoleKeyInfo [] cki =
+                    {
+                        new ((char)KeyCode.Esc, 0, false, false, false), new ('[', 0, false, false, false)
+                    };
+                    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 (((KeyCode)wch2 & KeyCode.CtrlMask) != 0)
+                    {
+                        k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
+                    }
+
+                    if (wch2 == 0)
+                    {
+                        k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
+                    }
+                    else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
+                    {
+                        k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
+                    }
+                    else if (wch2 < 256)
+                    {
+                        k = (KeyCode)wch2; // | KeyCode.AltMask;
+                    }
+                    else
+                    {
+                        k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
+                    }
+                }
+
+                key = new Key (k);
+            }
+            else
+            {
+                key = new Key (KeyCode.Esc);
+            }
+
+            OnKeyDown (key);
+            OnKeyUp (key);
+        }
+        else if (wch == Curses.KeyTab)
+        {
+            k = MapCursesKey (wch);
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+        }
+        else
+        {
+            // Unfortunately there are no way to differentiate Ctrl+alfa and Ctrl+Shift+alfa.
+            k = (KeyCode)wch;
+
+            if (wch == 0)
+            {
+                k = KeyCode.CtrlMask | KeyCode.Space;
+            }
+            else if (wch >= (uint)KeyCode.A - 64 && wch <= (uint)KeyCode.Z - 64)
+            {
+                if ((KeyCode)(wch + 64) != KeyCode.J)
+                {
+                    k = KeyCode.CtrlMask | (KeyCode)(wch + 64);
+                }
+            }
+            else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z)
+            {
+                k = (KeyCode)wch | KeyCode.ShiftMask;
+            }
+
+            if (wch == '\n' || wch == '\r')
+            {
+                k = KeyCode.Enter;
+            }
+
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+        }
+    }
+
+    internal void ProcessWinChange ()
+    {
+        if (!RunningUnitTests && Curses.CheckWinChange ())
+        {
+            ClearContents ();
+            OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
+        }
+    }
+
+    private void HandleEscSeqResponse (
+        ref int code,
+        ref KeyCode k,
+        ref int wch2,
+        ref Key keyEventArgs,
+        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 _,
+                                          ProcessMouseEvent
+                                         );
+
+                if (isKeyMouse)
+                {
+                    foreach (MouseFlags mf in mouseFlags)
+                    {
+                        ProcessMouseEvent (mf, pos);
+                    }
+
+                    cki = null;
+
+                    if (wch2 == 27)
+                    {
+                        cki = EscSeqUtils.ResizeArray (
+                                                       new ConsoleKeyInfo (
+                                                                           (char)KeyCode.Esc,
+                                                                           0,
+                                                                           false,
+                                                                           false,
+                                                                           false
+                                                                          ),
+                                                       cki
+                                                      );
+                    }
+                }
+                else
+                {
+                    k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+                    keyEventArgs = new Key (k);
+                    OnKeyDown (keyEventArgs);
+                }
+            }
+            else
+            {
+                cki = EscSeqUtils.ResizeArray (consoleKeyInfo, cki);
+            }
+        }
+    }
+
+    private static KeyCode MapCursesKey (int cursesKey)
+    {
+        switch (cursesKey)
+        {
+            case Curses.KeyF1: return KeyCode.F1;
+            case Curses.KeyF2: return KeyCode.F2;
+            case Curses.KeyF3: return KeyCode.F3;
+            case Curses.KeyF4: return KeyCode.F4;
+            case Curses.KeyF5: return KeyCode.F5;
+            case Curses.KeyF6: return KeyCode.F6;
+            case Curses.KeyF7: return KeyCode.F7;
+            case Curses.KeyF8: return KeyCode.F8;
+            case Curses.KeyF9: return KeyCode.F9;
+            case Curses.KeyF10: return KeyCode.F10;
+            case Curses.KeyF11: return KeyCode.F11;
+            case Curses.KeyF12: return KeyCode.F12;
+            case Curses.KeyUp: return KeyCode.CursorUp;
+            case Curses.KeyDown: return KeyCode.CursorDown;
+            case Curses.KeyLeft: return KeyCode.CursorLeft;
+            case Curses.KeyRight: return KeyCode.CursorRight;
+            case Curses.KeyHome: return KeyCode.Home;
+            case Curses.KeyEnd: return KeyCode.End;
+            case Curses.KeyNPage: return KeyCode.PageDown;
+            case Curses.KeyPPage: return KeyCode.PageUp;
+            case Curses.KeyDeleteChar: return KeyCode.Delete;
+            case Curses.KeyInsertChar: return KeyCode.Insert;
+            case Curses.KeyTab: return KeyCode.Tab;
+            case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
+            case Curses.KeyBackspace: return KeyCode.Backspace;
+            case Curses.ShiftKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask;
+            case Curses.ShiftKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask;
+            case Curses.ShiftKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask;
+            case Curses.ShiftKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask;
+            case Curses.ShiftKeyHome: return KeyCode.Home | KeyCode.ShiftMask;
+            case Curses.ShiftKeyEnd: return KeyCode.End | KeyCode.ShiftMask;
+            case Curses.ShiftKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask;
+            case Curses.ShiftKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask;
+            case Curses.AltKeyUp: return KeyCode.CursorUp | KeyCode.AltMask;
+            case Curses.AltKeyDown: return KeyCode.CursorDown | KeyCode.AltMask;
+            case Curses.AltKeyLeft: return KeyCode.CursorLeft | KeyCode.AltMask;
+            case Curses.AltKeyRight: return KeyCode.CursorRight | KeyCode.AltMask;
+            case Curses.AltKeyHome: return KeyCode.Home | KeyCode.AltMask;
+            case Curses.AltKeyEnd: return KeyCode.End | KeyCode.AltMask;
+            case Curses.AltKeyNPage: return KeyCode.PageDown | KeyCode.AltMask;
+            case Curses.AltKeyPPage: return KeyCode.PageUp | KeyCode.AltMask;
+            case Curses.CtrlKeyUp: return KeyCode.CursorUp | KeyCode.CtrlMask;
+            case Curses.CtrlKeyDown: return KeyCode.CursorDown | KeyCode.CtrlMask;
+            case Curses.CtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.CtrlMask;
+            case Curses.CtrlKeyRight: return KeyCode.CursorRight | KeyCode.CtrlMask;
+            case Curses.CtrlKeyHome: return KeyCode.Home | KeyCode.CtrlMask;
+            case Curses.CtrlKeyEnd: return KeyCode.End | KeyCode.CtrlMask;
+            case Curses.CtrlKeyNPage: return KeyCode.PageDown | KeyCode.CtrlMask;
+            case Curses.CtrlKeyPPage: return KeyCode.PageUp | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftCtrlKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.CtrlMask;
+            case Curses.ShiftAltKeyUp: return KeyCode.CursorUp | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyDown: return KeyCode.CursorDown | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyLeft: return KeyCode.CursorLeft | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyRight: return KeyCode.CursorRight | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyNPage: return KeyCode.PageDown | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyPPage: return KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyHome: return KeyCode.Home | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.ShiftAltKeyEnd: return KeyCode.End | KeyCode.ShiftMask | KeyCode.AltMask;
+            case Curses.AltCtrlKeyNPage: return KeyCode.PageDown | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyPPage: return KeyCode.PageUp | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyHome: return KeyCode.Home | KeyCode.AltMask | KeyCode.CtrlMask;
+            case Curses.AltCtrlKeyEnd: return KeyCode.End | KeyCode.AltMask | KeyCode.CtrlMask;
+            default: return KeyCode.Null;
+        }
+    }
+
+    private void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
+    {
+        bool WasButtonReleased (MouseFlags flag)
+        {
+            return flag.HasFlag (MouseFlags.Button1Released)
+                   || flag.HasFlag (MouseFlags.Button2Released)
+                   || flag.HasFlag (MouseFlags.Button3Released)
+                   || flag.HasFlag (MouseFlags.Button4Released);
+        }
+
+        bool IsButtonNotPressed (MouseFlags flag)
+        {
+            return !flag.HasFlag (MouseFlags.Button1Pressed)
+                   && !flag.HasFlag (MouseFlags.Button2Pressed)
+                   && !flag.HasFlag (MouseFlags.Button3Pressed)
+                   && !flag.HasFlag (MouseFlags.Button4Pressed);
+        }
+
+        bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
+        {
+            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);
+        }
+
+        if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0))
+        {
+            return;
+        }
+
+        _lastMouseFlags = mouseFlag;
+
+        var me = new MouseEvent { Flags = mouseFlag, X = pos.X, Y = pos.Y };
+        OnMouseEvent (new MouseEventEventArgs (me));
+    }
+
+    #region Color Handling
+
+    /// <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>
+    private static Attribute MakeColor (short foreground, short background)
+    {
+        var v = (short)(foreground | (background << 4));
+
+        // TODO: for TrueColor - Use InitExtendedPair
+        Curses.InitColorPair (v, foreground, background);
+
+        return new Attribute (
+                              Curses.ColorPair (v),
+                              CursesColorNumberToColorName (foreground),
+                              CursesColorNumberToColorName (background)
+                             );
+    }
+
+    /// <inheritdoc/>
+    /// <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 foreground, Color background)
+    {
+        if (!RunningUnitTests)
+        {
+            return MakeColor (
+                              ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()),
+                              ColorNameToCursesColorNumber (background.GetClosestNamedColor ())
+                             );
+        }
+
+        return new Attribute (
+                              0,
+                              foreground,
+                              background
+                             );
+    }
+
+    private static short ColorNameToCursesColorNumber (ColorName color)
+    {
+        switch (color)
+        {
+            case ColorName.Black:
+                return Curses.COLOR_BLACK;
+            case ColorName.Blue:
+                return Curses.COLOR_BLUE;
+            case ColorName.Green:
+                return Curses.COLOR_GREEN;
+            case ColorName.Cyan:
+                return Curses.COLOR_CYAN;
+            case ColorName.Red:
+                return Curses.COLOR_RED;
+            case ColorName.Magenta:
+                return Curses.COLOR_MAGENTA;
+            case ColorName.Yellow:
+                return Curses.COLOR_YELLOW;
+            case ColorName.Gray:
+                return Curses.COLOR_WHITE;
+            case ColorName.DarkGray:
+                return Curses.COLOR_GRAY;
+            case ColorName.BrightBlue:
+                return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
+            case ColorName.BrightGreen:
+                return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
+            case ColorName.BrightCyan:
+                return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
+            case ColorName.BrightRed:
+                return Curses.COLOR_RED | Curses.COLOR_GRAY;
+            case ColorName.BrightMagenta:
+                return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
+            case ColorName.BrightYellow:
+                return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
+            case ColorName.White:
+                return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
+        }
+
+        throw new ArgumentException ("Invalid color code");
+    }
+
+    private static ColorName CursesColorNumberToColorName (short color)
+    {
+        switch (color)
+        {
+            case Curses.COLOR_BLACK:
+                return ColorName.Black;
+            case Curses.COLOR_BLUE:
+                return ColorName.Blue;
+            case Curses.COLOR_GREEN:
+                return ColorName.Green;
+            case Curses.COLOR_CYAN:
+                return ColorName.Cyan;
+            case Curses.COLOR_RED:
+                return ColorName.Red;
+            case Curses.COLOR_MAGENTA:
+                return ColorName.Magenta;
+            case Curses.COLOR_YELLOW:
+                return ColorName.Yellow;
+            case Curses.COLOR_WHITE:
+                return ColorName.Gray;
+            case Curses.COLOR_GRAY:
+                return ColorName.DarkGray;
+            case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
+                return ColorName.BrightBlue;
+            case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
+                return ColorName.BrightGreen;
+            case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
+                return ColorName.BrightCyan;
+            case Curses.COLOR_RED | Curses.COLOR_GRAY:
+                return ColorName.BrightRed;
+            case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
+                return ColorName.BrightMagenta;
+            case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
+                return ColorName.BrightYellow;
+            case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
+                return ColorName.White;
+        }
+
+        throw new ArgumentException ("Invalid curses color code");
+    }
+
+    #endregion
 }
 
-static class Platform {
-	[DllImport ("libc")]
-	extern static int uname (IntPtr buf);
-
-	[DllImport ("libc")]
-	extern static 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);
-		}
-	}
-
-	/// <summary>
-	/// Suspends the process by sending SIGTSTP to itself
-	/// </summary>
-	/// <returns>The suspend.</returns>
-	public static bool Suspend ()
-	{
-		int signal = GetSuspendSignal ();
-		if (signal == -1) {
-			return false;
-		}
-		killpg (0, signal);
-		return true;
-	}
-}
+internal static class Platform
+{
+    private static int _suspendSignal;
+
+    /// <summary>Suspends the process by sending SIGTSTP to itself</summary>
+    /// <returns>The suspend.</returns>
+    public static bool Suspend ()
+    {
+        int signal = GetSuspendSignal ();
+
+        if (signal == -1)
+        {
+            return false;
+        }
+
+        killpg (0, signal);
+
+        return true;
+    }
+
+    private static int GetSuspendSignal ()
+    {
+        if (_suspendSignal != 0)
+        {
+            return _suspendSignal;
+        }
+
+        nint 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);
+        }
+    }
+
+    [DllImport ("libc")]
+    private static extern int killpg (int pgrp, int pid);
+
+    [DllImport ("libc")]
+    private static extern int uname (nint buf);
+}

+ 230 - 214
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -1,219 +1,235 @@
 //
 // mainloop.cs: Linux/Curses MainLoop implementation.
 //
-using System;
-using System.Collections.Generic;
+
 using System.Runtime.InteropServices;
-using static Terminal.Gui.NetEvents;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Unix main loop, suitable for using on Posix systems
-	/// </summary>
-	/// <remarks>
-	/// In addition to the general functions of the MainLoop, the Unix version
-	/// can watch file descriptors using the AddWatch methods.
-	/// </remarks>
-	internal class UnixMainLoop : IMainLoopDriver {
-		private CursesDriver _cursesDriver;
-		public UnixMainLoop (ConsoleDriver consoleDriver = null)
-		{
-			// UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does.
-			_cursesDriver = (CursesDriver)Application.Driver;
-		}
-
-		public const int KEY_RESIZE = unchecked((int)0xffffffffffffffff);
-
-		[StructLayout (LayoutKind.Sequential)]
-		struct Pollfd {
-			public int fd;
-			public short events, revents;
-		}
-
-		/// <summary>
-		///	Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.
-		/// </summary>
-		[Flags]
-		public enum Condition : short {
-			/// <summary>
-			/// There is data to read
-			/// </summary>
-			PollIn = 1,
-			/// <summary>
-			/// Writing to the specified descriptor will not block
-			/// </summary>
-			PollOut = 4,
-			/// <summary>
-			/// There is urgent data to read
-			/// </summary>
-			PollPri = 2,
-			/// <summary>
-			///  Error condition on output
-			/// </summary>
-			PollErr = 8,
-			/// <summary>
-			/// Hang-up on output
-			/// </summary>
-			PollHup = 16,
-			/// <summary>
-			/// File descriptor is not open.
-			/// </summary>
-			PollNval = 32
-		}
-
-		class Watch {
-			public int File;
-			public Condition Condition;
-			public Func<MainLoop, bool> Callback;
-		}
-
-		readonly Dictionary<int, Watch> _descriptorWatchers = new Dictionary<int, Watch> ();
-
-		[DllImport ("libc")]
-		extern static int poll ([In, Out] Pollfd [] ufds, uint nfds, int timeout);
-
-		[DllImport ("libc")]
-		extern static int pipe ([In, Out] int [] pipes);
-
-		[DllImport ("libc")]
-		extern static int read (int fd, IntPtr buf, IntPtr n);
-
-		[DllImport ("libc")]
-		extern static int write (int fd, IntPtr buf, IntPtr n);
-
-		Pollfd [] _pollMap;
-		bool _pollDirty = true;
-		readonly int [] _wakeUpPipes = new int [2];
-		static readonly IntPtr _ignore = Marshal.AllocHGlobal (1);
-		MainLoop _mainLoop;
-		bool _winChanged;
-
-		void IMainLoopDriver.Wakeup ()
-		{
-			if (!ConsoleDriver.RunningUnitTests) {
-				write (_wakeUpPipes [1], _ignore, (IntPtr)1);
-			}
-		}
-
-		void IMainLoopDriver.Setup (MainLoop mainLoop)
-		{
-			this._mainLoop = mainLoop;
-			if (ConsoleDriver.RunningUnitTests) {
-				return;
-			}
-
-			try {
-				pipe (_wakeUpPipes);
-				AddWatch (_wakeUpPipes [0], Condition.PollIn, ml => {
-					read (_wakeUpPipes [0], _ignore, (IntPtr)1);
-					return true;
-				});
-			} catch (DllNotFoundException e) {
-				throw new NotSupportedException ("libc not found", e);
-			}
-		}
-
-		/// <summary>
-		///	Removes an active watch from the mainloop.
-		/// </summary>
-		/// <remarks>
-		///	The token parameter is the value returned from AddWatch
-		/// </remarks>
-		internal void RemoveWatch (object token)
-		{
-			if (!ConsoleDriver.RunningUnitTests) {
-				if (token is not Watch watch) {
-					return;
-				}
-				_descriptorWatchers.Remove (watch.File);
-			}
-		}
-
-		/// <summary>
-		///  Watches a file descriptor for activity.
-		/// </summary>
-		/// <remarks>
-		///  When the condition is met, the provided callback
-		///  is invoked.  If the callback returns false, the
-		///  watch is automatically removed.
-		///
-		///  The return value is a token that represents this watch, you can
-		///  use this token to remove the watch by calling RemoveWatch.
-		/// </remarks>
-		internal object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop, bool> callback)
-		{
-			if (callback == null) {
-				throw new ArgumentNullException (nameof (callback));
-			}
-
-			var watch = new Watch () { Condition = condition, Callback = callback, File = fileDescriptor };
-			_descriptorWatchers [fileDescriptor] = watch;
-			_pollDirty = true;
-			return watch;
-		}
-
-		void UpdatePollMap ()
-		{
-			if (!_pollDirty) {
-				return;
-			}
-			_pollDirty = false;
-
-			_pollMap = new Pollfd [_descriptorWatchers.Count];
-			var i = 0;
-			foreach (var fd in _descriptorWatchers.Keys) {
-				_pollMap [i].fd = fd;
-				_pollMap [i].events = (short)_descriptorWatchers [fd].Condition;
-				i++;
-			}
-		}
-
-		bool IMainLoopDriver.EventsPending ()
-		{
-			UpdatePollMap ();
-
-			var checkTimersResult = _mainLoop.CheckTimersAndIdleHandlers (out var pollTimeout);
-
-			var n = poll (_pollMap, (uint)_pollMap.Length, pollTimeout);
-
-			if (n == KEY_RESIZE) {
-				_winChanged = true;
-			}
-
-			return checkTimersResult || n >= KEY_RESIZE;
-		}
-
-		void IMainLoopDriver.Iteration ()
-		{
-			if (_winChanged) {
-				_winChanged = false;
-				_cursesDriver.ProcessInput ();
-				// This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426
-				_cursesDriver.ProcessWinChange ();
-			}
-			if (_pollMap == null) return;
-			foreach (var p in _pollMap) {
-				Watch watch;
-
-				if (p.revents == 0) {
-					continue;
-				}
-
-				if (!_descriptorWatchers.TryGetValue (p.fd, out watch)) {
-					continue;
-				}
-
-				if (!watch.Callback (this._mainLoop)) {
-					_descriptorWatchers.Remove (p.fd);
-				}
-			}
-		}
-
-		void IMainLoopDriver.TearDown ()
-		{
-			_descriptorWatchers?.Clear ();
-
-			_mainLoop = null;
-		}
-	}
+
+namespace Terminal.Gui;
+
+/// <summary>Unix main loop, suitable for using on Posix systems</summary>
+/// <remarks>
+///     In addition to the general functions of the MainLoop, the Unix version can watch file descriptors using the
+///     AddWatch methods.
+/// </remarks>
+internal class UnixMainLoop : IMainLoopDriver
+{
+    /// <summary>Condition on which to wake up from file descriptor activity.  These match the Linux/BSD poll definitions.</summary>
+    [Flags]
+    public enum Condition : short
+    {
+        /// <summary>There is data to read</summary>
+        PollIn = 1,
+
+        /// <summary>Writing to the specified descriptor will not block</summary>
+        PollOut = 4,
+
+        /// <summary>There is urgent data to read</summary>
+        PollPri = 2,
+
+        /// <summary>Error condition on output</summary>
+        PollErr = 8,
+
+        /// <summary>Hang-up on output</summary>
+        PollHup = 16,
+
+        /// <summary>File descriptor is not open.</summary>
+        PollNval = 32
+    }
+
+    public const int KEY_RESIZE = unchecked ((int)0xffffffffffffffff);
+    private static readonly nint _ignore = Marshal.AllocHGlobal (1);
+
+    private readonly CursesDriver _cursesDriver;
+    private readonly Dictionary<int, Watch> _descriptorWatchers = new ();
+    private readonly int [] _wakeUpPipes = new int [2];
+    private MainLoop _mainLoop;
+    private bool _pollDirty = true;
+    private Pollfd [] _pollMap;
+    private bool _winChanged;
+
+    public UnixMainLoop (ConsoleDriver consoleDriver = null)
+    {
+        // UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does.
+        _cursesDriver = (CursesDriver)Application.Driver;
+    }
+
+    void IMainLoopDriver.Wakeup ()
+    {
+        if (!ConsoleDriver.RunningUnitTests)
+        {
+            write (_wakeUpPipes [1], _ignore, 1);
+        }
+    }
+
+    void IMainLoopDriver.Setup (MainLoop mainLoop)
+    {
+        _mainLoop = mainLoop;
+
+        if (ConsoleDriver.RunningUnitTests)
+        {
+            return;
+        }
+
+        try
+        {
+            pipe (_wakeUpPipes);
+
+            AddWatch (
+                      _wakeUpPipes [0],
+                      Condition.PollIn,
+                      ml =>
+                      {
+                          read (_wakeUpPipes [0], _ignore, 1);
+
+                          return true;
+                      }
+                     );
+        }
+        catch (DllNotFoundException e)
+        {
+            throw new NotSupportedException ("libc not found", e);
+        }
+    }
+
+    bool IMainLoopDriver.EventsPending ()
+    {
+        UpdatePollMap ();
+
+        bool checkTimersResult = _mainLoop.CheckTimersAndIdleHandlers (out int pollTimeout);
+
+        int n = poll (_pollMap, (uint)_pollMap.Length, pollTimeout);
+
+        if (n == KEY_RESIZE)
+        {
+            _winChanged = true;
+        }
+
+        return checkTimersResult || n >= KEY_RESIZE;
+    }
+
+    void IMainLoopDriver.Iteration ()
+    {
+        if (_winChanged)
+        {
+            _winChanged = false;
+            _cursesDriver.ProcessInput ();
+
+            // This is needed on the mac. See https://github.com/gui-cs/Terminal.Gui/pull/2922#discussion_r1365992426
+            _cursesDriver.ProcessWinChange ();
+        }
+
+        if (_pollMap == null)
+        {
+            return;
+        }
+
+        foreach (Pollfd p in _pollMap)
+        {
+            Watch watch;
+
+            if (p.revents == 0)
+            {
+                continue;
+            }
+
+            if (!_descriptorWatchers.TryGetValue (p.fd, out watch))
+            {
+                continue;
+            }
+
+            if (!watch.Callback (_mainLoop))
+            {
+                _descriptorWatchers.Remove (p.fd);
+            }
+        }
+    }
+
+    void IMainLoopDriver.TearDown ()
+    {
+        _descriptorWatchers?.Clear ();
+
+        _mainLoop = null;
+    }
+
+    /// <summary>Watches a file descriptor for activity.</summary>
+    /// <remarks>
+    ///     When the condition is met, the provided callback is invoked.  If the callback returns false, the watch is
+    ///     automatically removed. The return value is a token that represents this watch, you can use this token to remove the
+    ///     watch by calling RemoveWatch.
+    /// </remarks>
+    internal object AddWatch (int fileDescriptor, Condition condition, Func<MainLoop, bool> callback)
+    {
+        if (callback == null)
+        {
+            throw new ArgumentNullException (nameof (callback));
+        }
+
+        var watch = new Watch { Condition = condition, Callback = callback, File = fileDescriptor };
+        _descriptorWatchers [fileDescriptor] = watch;
+        _pollDirty = true;
+
+        return watch;
+    }
+
+    /// <summary>Removes an active watch from the mainloop.</summary>
+    /// <remarks>The token parameter is the value returned from AddWatch</remarks>
+    internal void RemoveWatch (object token)
+    {
+        if (!ConsoleDriver.RunningUnitTests)
+        {
+            if (token is not Watch watch)
+            {
+                return;
+            }
+
+            _descriptorWatchers.Remove (watch.File);
+        }
+    }
+
+    [DllImport ("libc")]
+    private static extern int pipe ([In] [Out] int [] pipes);
+
+    [DllImport ("libc")]
+    private static extern int poll ([In] [Out] Pollfd [] ufds, uint nfds, int timeout);
+
+    [DllImport ("libc")]
+    private static extern int read (int fd, nint buf, nint n);
+
+    private void UpdatePollMap ()
+    {
+        if (!_pollDirty)
+        {
+            return;
+        }
+
+        _pollDirty = false;
+
+        _pollMap = new Pollfd [_descriptorWatchers.Count];
+        var i = 0;
+
+        foreach (int fd in _descriptorWatchers.Keys)
+        {
+            _pollMap [i].fd = fd;
+            _pollMap [i].events = (short)_descriptorWatchers [fd].Condition;
+            i++;
+        }
+    }
+
+    [DllImport ("libc")]
+    private static extern int write (int fd, nint buf, nint n);
+
+    [StructLayout (LayoutKind.Sequential)]
+    private struct Pollfd
+    {
+        public int fd;
+        public short events;
+        public readonly short revents;
+    }
+
+    private class Watch
+    {
+        public Func<MainLoop, bool> Callback;
+        public Condition Condition;
+        public int File;
+    }
 }

+ 303 - 249
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -1,5 +1,3 @@
-
-
 // Copyright 2015 gRPC authors.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,265 +11,321 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-#define GUICS
 
-using System;
-using System.IO;
-using System.Reflection;
+#define GUICS
 using System.Runtime.InteropServices;
-using System.Threading;
-
-namespace Unix.Terminal {
-	/// <summary>
-	/// Represents a dynamically loaded unmanaged library in a (partially) platform independent manner.
-	/// First, the native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows).
-	/// dlsym or GetProcAddress are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c>
-	/// transforms the addresses into delegates to native methods.
-	/// See http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
-	/// </summary>
-	internal class UnmanagedLibrary {
-		const string UnityEngineApplicationClassName = "UnityEngine.Application, UnityEngine";
-		const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android";
-		const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS";
-		static bool IsWindows, IsLinux, IsMacOS;
-		static bool Is64Bit;
+
+namespace Unix.Terminal;
+
+/// <summary>
+///     Represents a dynamically loaded unmanaged library in a (partially) platform independent manner. First, the
+///     native library is loaded using dlopen (on Unix systems) or using LoadLibrary (on Windows). dlsym or GetProcAddress
+///     are then used to obtain symbol addresses. <c>Marshal.GetDelegateForFunctionPointer</c> transforms the addresses
+///     into delegates to native methods. See
+///     http://stackoverflow.com/questions/13461989/p-invoke-to-dynamically-loaded-library-on-mono.
+/// </summary>
+internal class UnmanagedLibrary
+{
+    private const string UnityEngineApplicationClassName = "UnityEngine.Application, UnityEngine";
+    private const string XamarinAndroidObjectClassName = "Java.Lang.Object, Mono.Android";
+    private const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS";
+    private static readonly bool IsWindows;
+    private static readonly bool IsLinux;
+    private static readonly bool Is64Bit;
 #if GUICS
-		static bool IsMono;
+    private static readonly bool IsMono;
 #else
 		static bool IsMono, IsUnity, IsXamarinIOS, IsXamarinAndroid, IsXamarin;
 #endif
-		static bool IsNetCore;
-
-		public static bool IsMacOSPlatform => IsMacOS;
-
-		[DllImport ("libc")]
-		static extern int uname (IntPtr buf);
-
-		static string GetUname ()
-		{
-			var buffer = Marshal.AllocHGlobal (8192);
-			try {
-				if (uname (buffer) == 0) {
-					return Marshal.PtrToStringAnsi (buffer);
-				}
-				return string.Empty;
-			} catch {
-				return string.Empty;
-			} finally {
-				if (buffer != IntPtr.Zero) {
-					Marshal.FreeHGlobal (buffer);
-				}
-			}
-		}
-
-		static UnmanagedLibrary ()
-		{
-			var platform = Environment.OSVersion.Platform;
-
-			IsMacOS = (platform == PlatformID.Unix && GetUname () == "Darwin");
-			IsLinux = (platform == PlatformID.Unix && !IsMacOS);
-			IsWindows = (platform == PlatformID.Win32NT || platform == PlatformID.Win32S || platform == PlatformID.Win32Windows);
-			Is64Bit = Marshal.SizeOf (typeof (IntPtr)) == 8;
-			IsMono = Type.GetType ("Mono.Runtime") != null;
-			if (!IsMono) {
-				IsNetCore = Type.GetType ("System.MathF") != null;
-			}
+    private static bool IsNetCore;
+    public static bool IsMacOSPlatform { get; }
+
+    [DllImport ("libc")]
+    private static extern int uname (nint buf);
+
+    private static string GetUname ()
+    {
+        nint buffer = Marshal.AllocHGlobal (8192);
+
+        try
+        {
+            if (uname (buffer) == 0)
+            {
+                return Marshal.PtrToStringAnsi (buffer);
+            }
+
+            return string.Empty;
+        }
+        catch
+        {
+            return string.Empty;
+        }
+        finally
+        {
+            if (buffer != nint.Zero)
+            {
+                Marshal.FreeHGlobal (buffer);
+            }
+        }
+    }
+
+    static UnmanagedLibrary ()
+    {
+        PlatformID platform = Environment.OSVersion.Platform;
+
+        IsMacOSPlatform = platform == PlatformID.Unix && GetUname () == "Darwin";
+        IsLinux = platform == PlatformID.Unix && !IsMacOSPlatform;
+
+        IsWindows = platform == PlatformID.Win32NT
+                    || platform == PlatformID.Win32S
+                    || platform == PlatformID.Win32Windows;
+        Is64Bit = Marshal.SizeOf (typeof (nint)) == 8;
+        IsMono = Type.GetType ("Mono.Runtime") != null;
+
+        if (!IsMono)
+        {
+            IsNetCore = Type.GetType ("System.MathF") != null;
+        }
 #if GUICS
-			//IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false;
+
+        //IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false;
 #else
 			IsUnity = Type.GetType (UnityEngineApplicationClassName) != null;
 			IsXamarinIOS = Type.GetType (XamarinIOSObjectClassName) != null;
 			IsXamarinAndroid = Type.GetType (XamarinAndroidObjectClassName) != null;
 			IsXamarin = IsXamarinIOS || IsXamarinAndroid;
 #endif
+    }
+
+    // flags for dlopen
+    private const int RTLD_LAZY = 1;
+    private const int RTLD_GLOBAL = 8;
+    public readonly string LibraryPath;
+    public nint NativeLibraryHandle { get; }
+
+    //
+    // if isFullPath is set to true, the provided array of libraries are full paths
+    // and are tested for the file existing, otherwise the file is merely the name
+    // of the shared library that we pass to dlopen
+    //
+    public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
+    {
+        if (isFullPath)
+        {
+            LibraryPath = FirstValidLibraryPath (libraryPathAlternatives);
+            NativeLibraryHandle = PlatformSpecificLoadLibrary (LibraryPath);
+        }
+        else
+        {
+            foreach (string lib in libraryPathAlternatives)
+            {
+                NativeLibraryHandle = PlatformSpecificLoadLibrary (lib);
+
+                if (NativeLibraryHandle != nint.Zero)
+                {
+                    LibraryPath = lib;
+
+                    break;
+                }
+            }
+        }
+
+        if (NativeLibraryHandle == nint.Zero)
+        {
+            throw new IOException ($"Error loading native library \"{string.Join (", ", libraryPathAlternatives)}\"");
+        }
+    }
+
+    /// <summary>Loads symbol in a platform specific way.</summary>
+    /// <param name="symbolName"></param>
+    /// <returns></returns>
+    public nint LoadSymbol (string symbolName)
+    {
+        if (IsWindows)
+        {
+            // See http://stackoverflow.com/questions/10473310 for background on this.
+            if (Is64Bit)
+            {
+                return Windows.GetProcAddress (NativeLibraryHandle, symbolName);
+            }
+
+            // Yes, we could potentially predict the size... but it's a lot simpler to just try
+            // all the candidates. Most functions have a suffix of @0, @4 or @8 so we won't be trying
+            // many options - and if it takes a little bit longer to fail if we've really got the wrong
+            // library, that's not a big problem. This is only called once per function in the native library.
+            symbolName = "_" + symbolName + "@";
+
+            for (var stackSize = 0; stackSize < 128; stackSize += 4)
+            {
+                nint candidate = Windows.GetProcAddress (NativeLibraryHandle, symbolName + stackSize);
+
+                if (candidate != nint.Zero)
+                {
+                    return candidate;
+                }
+            }
+
+            // Fail.
+            return nint.Zero;
+        }
+
+        if (IsLinux)
+        {
+            if (IsMono)
+            {
+                return Mono.dlsym (NativeLibraryHandle, symbolName);
+            }
+
+            if (IsNetCore)
+            {
+                return CoreCLR.dlsym (NativeLibraryHandle, symbolName);
+            }
+
+            return Linux.dlsym (NativeLibraryHandle, symbolName);
+        }
+
+        if (IsMacOSPlatform)
+        {
+            return MacOSX.dlsym (NativeLibraryHandle, symbolName);
+        }
+
+        throw new InvalidOperationException ("Unsupported platform.");
+    }
+
+    public T GetNativeMethodDelegate<T> (string methodName)
+        where T : class
+    {
+        nint ptr = LoadSymbol (methodName);
+
+        if (ptr == nint.Zero)
+        {
+            throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
+        }
+
+        return Marshal.GetDelegateForFunctionPointer<T> (ptr); // non-generic version is obsolete
+    }
+
+    /// <summary>Loads library in a platform specific way.</summary>
+    private static nint PlatformSpecificLoadLibrary (string libraryPath)
+    {
+        if (IsWindows)
+        {
+            return Windows.LoadLibrary (libraryPath);
+        }
+
+        if (IsLinux)
+        {
+            if (IsMono)
+            {
+                return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+            }
+
+            if (IsNetCore)
+            {
+                try
+                {
+                    return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+                }
+                catch (Exception)
+                {
+                    IsNetCore = false;
+                }
+            }
+
+            return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+        }
+
+        if (IsMacOSPlatform)
+        {
+            return MacOSX.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+        }
+
+        throw new InvalidOperationException ("Unsupported platform.");
+    }
+
+    private static string FirstValidLibraryPath (string [] libraryPathAlternatives)
+    {
+        foreach (string path in libraryPathAlternatives)
+        {
+            if (File.Exists (path))
+            {
+                return path;
+            }
+        }
+
+        throw new FileNotFoundException (
+                                         string.Format (
+                                                        "Error loading native library. Not found in any of the possible locations: {0}",
+                                                        string.Join (",", libraryPathAlternatives)
+                                                       )
+                                        );
+    }
+
+    private static class Windows
+    {
+        [DllImport ("kernel32.dll")]
+        internal static extern nint GetProcAddress (nint hModule, string procName);
+
+        [DllImport ("kernel32.dll")]
+        internal static extern nint LoadLibrary (string filename);
+    }
+
+    private static class Linux
+    {
+        [DllImport ("libdl.so")]
+        internal static extern nint dlopen (string filename, int flags);
+
+        [DllImport ("libdl.so")]
+        internal static extern nint dlsym (nint handle, string symbol);
+    }
+
+    private static class MacOSX
+    {
+        [DllImport ("libSystem.dylib")]
+        internal static extern nint dlopen (string filename, int flags);
+
+        [DllImport ("libSystem.dylib")]
+        internal static extern nint dlsym (nint handle, string symbol);
+    }
+
+    /// <summary>
+    ///     On Linux systems, using using dlopen and dlsym results in DllNotFoundException("libdl.so not found") if
+    ///     libc6-dev is not installed. As a workaround, we load symbols for dlopen and dlsym from the current process as on
+    ///     Linux Mono sure is linked against these symbols.
+    /// </summary>
+    private static class Mono
+    {
+        [DllImport ("__Internal")]
+        internal static extern nint dlopen (string filename, int flags);
+
+        [DllImport ("__Internal")]
+        internal static extern nint dlsym (nint handle, string symbol);
+    }
+
+    /// <summary>
+    ///     Similarly as for Mono on Linux, we load symbols for dlopen and dlsym from the "libcoreclr.so", to avoid the
+    ///     dependency on libc-dev Linux.
+    /// </summary>
+    private static class CoreCLR
+    {
+        // Custom resolver to support true single-file apps
+        // (those which run directly from bundle; in-memory).
+        //	 -1 on Unix means self-referencing binary (libcoreclr.so)
+        //	 0 means fallback to CoreCLR's internal resolution
+        // Note: meaning of -1 stay the same even for non-single-file form factors.
+        static CoreCLR ()
+        {
+            NativeLibrary.SetDllImportResolver (
+                                                typeof (CoreCLR).Assembly,
+                                                (libraryName, assembly, searchPath) =>
+                                                    libraryName == "libcoreclr.so" ? -1 : nint.Zero
+                                               );
+        }
+
+        [DllImport ("libcoreclr.so")]
+        internal static extern nint dlopen (string filename, int flags);
 
-		}
-
-		// flags for dlopen
-		const int RTLD_LAZY = 1;
-		const int RTLD_GLOBAL = 8;
-
-		public readonly string LibraryPath;
-		readonly IntPtr handle;
-
-		public IntPtr NativeLibraryHandle => handle;
-
-		//
-		// if isFullPath is set to true, the provided array of libraries are full paths
-		// and are tested for the file existing, otherwise the file is merely the name
-		// of the shared library that we pass to dlopen
-		//
-		public UnmanagedLibrary (string [] libraryPathAlternatives, bool isFullPath)
-		{
-			if (isFullPath) {
-				this.LibraryPath = FirstValidLibraryPath (libraryPathAlternatives);
-				this.handle = PlatformSpecificLoadLibrary (this.LibraryPath);
-			} else {
-				foreach (var lib in libraryPathAlternatives) {
-					this.handle = PlatformSpecificLoadLibrary (lib);
-					if (this.handle != IntPtr.Zero) {
-						this.LibraryPath = lib;
-						break;
-					}
-				}
-			}
-
-			if (this.handle == IntPtr.Zero) {
-				throw new IOException ($"Error loading native library \"{string.Join (", ", libraryPathAlternatives)}\"");
-			}
-		}
-
-		/// <summary>
-		/// Loads symbol in a platform specific way.
-		/// </summary>
-		/// <param name="symbolName"></param>
-		/// <returns></returns>
-		public IntPtr LoadSymbol (string symbolName)
-		{
-			if (IsWindows) {
-				// See http://stackoverflow.com/questions/10473310 for background on this.
-				if (Is64Bit) {
-					return Windows.GetProcAddress (this.handle, symbolName);
-				} else {
-					// Yes, we could potentially predict the size... but it's a lot simpler to just try
-					// all the candidates. Most functions have a suffix of @0, @4 or @8 so we won't be trying
-					// many options - and if it takes a little bit longer to fail if we've really got the wrong
-					// library, that's not a big problem. This is only called once per function in the native library.
-					symbolName = "_" + symbolName + "@";
-					for (int stackSize = 0; stackSize < 128; stackSize += 4) {
-						IntPtr candidate = Windows.GetProcAddress (this.handle, symbolName + stackSize);
-						if (candidate != IntPtr.Zero) {
-							return candidate;
-						}
-					}
-					// Fail.
-					return IntPtr.Zero;
-				}
-			}
-			if (IsLinux) {
-				if (IsMono) {
-					return Mono.dlsym (this.handle, symbolName);
-				}
-				if (IsNetCore) {
-					return CoreCLR.dlsym (this.handle, symbolName);
-				}
-				return Linux.dlsym (this.handle, symbolName);
-			}
-			if (IsMacOS) {
-				return MacOSX.dlsym (this.handle, symbolName);
-			}
-			throw new InvalidOperationException ("Unsupported platform.");
-		}
-
-		public T GetNativeMethodDelegate<T> (string methodName)
-			where T : class
-		{
-			var ptr = LoadSymbol (methodName);
-			if (ptr == IntPtr.Zero) {
-				throw new MissingMethodException (string.Format ("The native method \"{0}\" does not exist", methodName));
-			}
-			return Marshal.GetDelegateForFunctionPointer<T> (ptr);  // non-generic version is obsolete
-		}
-
-		/// <summary>
-		/// Loads library in a platform specific way.
-		/// </summary>
-		static IntPtr PlatformSpecificLoadLibrary (string libraryPath)
-		{
-			if (IsWindows) {
-				return Windows.LoadLibrary (libraryPath);
-			}
-			if (IsLinux) {
-				if (IsMono) {
-					return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
-				}
-				if (IsNetCore) {
-					try {
-						return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
-					} catch (Exception) {
-
-						IsNetCore = false;
-					}
-				}
-				return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
-			}
-			if (IsMacOS) {
-				return MacOSX.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
-			}
-			throw new InvalidOperationException ("Unsupported platform.");
-		}
-
-		static string FirstValidLibraryPath (string [] libraryPathAlternatives)
-		{
-			foreach (var path in libraryPathAlternatives) {
-				if (File.Exists (path)) {
-					return path;
-				}
-			}
-			throw new FileNotFoundException (
-				String.Format ("Error loading native library. Not found in any of the possible locations: {0}",
-				string.Join (",", libraryPathAlternatives)));
-		}
-
-		static class Windows {
-			[DllImport ("kernel32.dll")]
-			internal static extern IntPtr LoadLibrary (string filename);
-
-			[DllImport ("kernel32.dll")]
-			internal static extern IntPtr GetProcAddress (IntPtr hModule, string procName);
-		}
-
-		static class Linux {
-			[DllImport ("libdl.so")]
-			internal static extern IntPtr dlopen (string filename, int flags);
-
-			[DllImport ("libdl.so")]
-			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
-		}
-
-		static class MacOSX {
-			[DllImport ("libSystem.dylib")]
-			internal static extern IntPtr dlopen (string filename, int flags);
-
-			[DllImport ("libSystem.dylib")]
-			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
-		}
-
-		/// <summary>
-		/// On Linux systems, using using dlopen and dlsym results in
-		/// DllNotFoundException("libdl.so not found") if libc6-dev
-		/// is not installed. As a workaround, we load symbols for
-		/// dlopen and dlsym from the current process as on Linux
-		/// Mono sure is linked against these symbols.
-		/// </summary>
-		static class Mono {
-			[DllImport ("__Internal")]
-			internal static extern IntPtr dlopen (string filename, int flags);
-
-			[DllImport ("__Internal")]
-			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
-		}
-
-		/// <summary>
-		/// Similarly as for Mono on Linux, we load symbols for
-		/// dlopen and dlsym from the "libcoreclr.so",
-		/// to avoid the dependency on libc-dev Linux.
-		/// </summary>
-		static class CoreCLR {
-			// Custom resolver to support true single-file apps
-			// (those which run directly from bundle; in-memory).
-			//	 -1 on Unix means self-referencing binary (libcoreclr.so)
-			//	 0 means fallback to CoreCLR's internal resolution
-			// Note: meaning of -1 stay the same even for non-single-file form factors.
-			static CoreCLR() =>  NativeLibrary.SetDllImportResolver(typeof(CoreCLR).Assembly,
-				(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) =>
-					libraryName == "libcoreclr.so" ? (IntPtr)(-1) : IntPtr.Zero);
-
-			[DllImport ("libcoreclr.so")]
-			internal static extern IntPtr dlopen (string filename, int flags);
-
-			[DllImport ("libcoreclr.so")]
-			internal static extern IntPtr dlsym (IntPtr handle, string symbol);
-		}
-	}
+        [DllImport ("libcoreclr.so")]
+        internal static extern nint dlsym (nint handle, string symbol);
+    }
 }

+ 693 - 583
Terminal.Gui/ConsoleDrivers/CursesDriver/binding.cs

@@ -41,597 +41,707 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-using System;
+
 using System.Runtime.InteropServices;
 using Terminal.Gui;
 
-namespace Unix.Terminal {
+namespace Unix.Terminal;
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 
-	public partial class Curses {
-		//[StructLayout (LayoutKind.Sequential)]
-		//public struct winsize {
-		//	public ushort ws_row;
-		//	public ushort ws_col;
-		//	public ushort ws_xpixel;   /* unused */
-		//	public ushort ws_ypixel;   /* unused */
-		//};
-
-		[StructLayout (LayoutKind.Sequential)]
-		public struct MouseEvent {
-			public short ID;
-			public int X, Y, Z;
-			public Event ButtonState;
-		}
-
-		static int lines, cols;
-		static Window main_window;
-		static IntPtr curses_handle, curscr_ptr, lines_ptr, cols_ptr;
-
-		// If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5"
-		//static bool use_naked_driver;
-
-		static UnmanagedLibrary curses_library;
-
-		static NativeMethods methods;
-
-		[DllImport ("libc")]
-		public extern static int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale);
-
-		//[DllImport ("libc")]
-		//public extern static int ioctl (int fd, int cmd, out winsize argp);
-
-		static void LoadMethods ()
-		{
-			var libs = UnmanagedLibrary.IsMacOSPlatform ? new string [] { "libncurses.dylib" } : new string [] { "libncursesw.so.6", "libncursesw.so.5" };
-			var attempts = 1;
-			while (true) {
-				try {
-					curses_library = new UnmanagedLibrary (libs, false);
-					methods = new NativeMethods (curses_library);
-					break;
-				} catch (Exception ex) {
-
-					if (attempts == 1) {
-						attempts++;
-						var (exitCode, result) = ClipboardProcessRunner.Bash ("cat /etc/os-release", waitForOutput: true);
-						if (exitCode == 0 && result.Contains ("opensuse")) {
-							libs [0] = "libncursesw.so.5";
-						}
-					} else {
-						throw ex.GetBaseException ();
-					}
-				}
-			}
-		}
-
-		static void FindNCurses ()
-		{
-			LoadMethods ();
-			curses_handle = methods.UnmanagedLibrary.NativeLibraryHandle;
-
-			stdscr = read_static_ptr ("stdscr");
-			curscr_ptr = get_ptr ("curscr");
-			lines_ptr = get_ptr ("LINES");
-			cols_ptr = get_ptr ("COLS");
-		}
-
-		static public string curses_version ()
-		{
-			var v = methods.curses_version ();
-			return $"{Marshal.PtrToStringAnsi (v)}, {curses_library.LibraryPath}";
-
-		}
-
-		static public Window initscr ()
-		{
-			setlocale (LC_ALL, "");
-			FindNCurses ();
-
-			// Prevents the terminal from being locked after exiting.
-			reset_shell_mode ();
-
-			main_window = new Window (methods.initscr ());
-			try {
-				console_sharp_get_dims (out lines, out cols);
-			} catch (DllNotFoundException) {
-				endwin ();
-				Console.Error.WriteLine ("Unable to find the @MONO_CURSES@ native library\n" +
-							 "this is different than the managed mono-curses.dll\n\n" +
-							 "Typically you need to install to a LD_LIBRARY_PATH directory\n" +
-							 "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig");
-				Environment.Exit (1);
-			}
-
-			//Console.Error.WriteLine ($"using curses {Curses.curses_version ()}");
-
-			return main_window;
-		}
-
-		public static int Lines {
-			get {
-				return lines;
-			}
-			internal set {
-				// For unit tests
-				lines = value;
-			}
-		}
-
-		public static int Cols {
-			get {
-				return cols;
-			}
-			internal set {
-				// For unit tests
-				cols = value;
-			}
-		}
-
-		//
-		// Returns true if the window changed since the last invocation, as a
-		// side effect, the Lines and Cols properties are updated
-		//
-		public static bool CheckWinChange ()
-		{
-			int l, c;
-
-			console_sharp_get_dims (out l, out c);
-
-			if (l < 1) {
-				l = 1;
-			}
-			if (l != lines || c != cols) {
-				lines = l;
-				cols = c;
-				return true;
-			}
-			return false;
-		}
-
-		public static int addstr (string format, params object [] args)
-		{
-			var s = string.Format (format, args);
-			return addwstr (s);
-		}
-
-		static char [] r = new char [1];
-
-		//
-		// Have to wrap the native addch, as it can not
-		// display unicode characters, we have to use addstr
-		// for that.   but we need addch to render special ACS
-		// characters
-		//
-		public static int addch (int ch)
-		{
-			if (ch < 127 || ch > 0xffff)
-				return methods.addch (ch);
-			char c = (char)ch;
-			return addwstr (new String (c, 1));
-		}
-
-		public static int mvaddch (int y, int x, int ch)
-		{
-			if (ch < 127 || ch > 0xffff)
-				return methods.mvaddch (y, x, ch);
-			char c = (char)ch;
-			return mvaddwstr (y, x, new String (c, 1));
-		}
-
-		static IntPtr stdscr;
-
-		static IntPtr get_ptr (string key)
-		{
-			var ptr = curses_library.LoadSymbol (key);
-
-			if (ptr == IntPtr.Zero)
-				throw new Exception ("Could not load the key " + key);
-			return ptr;
-		}
-
-		internal static IntPtr read_static_ptr (string key)
-		{
-			var ptr = get_ptr (key);
-			return Marshal.ReadIntPtr (ptr);
-		}
-
-		internal static IntPtr console_sharp_get_stdscr () => stdscr;
-
-		internal static IntPtr console_sharp_get_curscr ()
-		{
-			return Marshal.ReadIntPtr (curscr_ptr);
-		}
-
-		internal static void console_sharp_get_dims (out int lines, out int cols)
-		{
-			lines = Marshal.ReadInt32 (lines_ptr);
-			cols = Marshal.ReadInt32 (cols_ptr);
-
-			//int cmd;
-			//if (UnmanagedLibrary.IsMacOSPlatform) {
-			//	cmd = TIOCGWINSZ_MAC;
-			//} else {
-			//	cmd = TIOCGWINSZ;
-			//}
-
-			//if (ioctl (1, cmd, out winsize ws) == 0) {
-			//	lines = ws.ws_row;
-			//	cols = ws.ws_col;
-
-			//	if (lines == Lines && cols == Cols) {
-			//		return;
-			//	}
-
-			//	resizeterm (lines, cols);
-			//} else {
-			//	lines = Lines;
-			//	cols = Cols;
-			//}
-		}
-
-		public static Event mousemask (Event newmask, out Event oldmask)
-		{
-			IntPtr e;
-			var ret = (Event)(methods.mousemask ((IntPtr)newmask, out e));
-			oldmask = (Event)e;
-			return ret;
-		}
-
-		// We encode ESC + char (what Alt-char generates) as 0x2000 + char
-		public const int KeyAlt = 0x2000;
-
-		static public int IsAlt (int key)
-		{
-			if ((key & KeyAlt) != 0)
-				return key & ~KeyAlt;
-			return 0;
-		}
-
-		public static int StartColor () => methods.start_color ();
-		public static bool HasColors => methods.has_colors ();
-		/// <summary>
-		/// The init_pair routine changes the definition of a color-pair.It takes
-		/// three arguments: the number of the color-pair to be changed, the  fore-
-		/// ground color number, and the background color number.For portable ap-
-		/// plications:
-		/// 
-		/// o The first argument must be a legal color pair  value.If  default
-		/// colors are used (see use_default_colors(3x)) the upper limit is ad-
-		/// justed to allow for extra pairs which use a default color in  fore-
-		/// ground and/or background.
-		/// 
-		/// o The second and third arguments must be legal color values.
-		/// 
-		/// If the  color-pair was previously initialized, the screen is refreshed
-		/// and all occurrences of that color-pair are changed to the new defini-
-		/// tion.
-		/// 
-		/// As an  extension,  ncurses allows you to set color pair 0 via the as-
-		/// sume_default_colors (3x) routine, or to specify the use of default  col-
-		/// ors (color number  -1) if you first invoke the use_default_colors (3x)
-		/// routine.
-		/// </summary>
-		/// <param name="pair"></param>
-		/// <param name="foreground"></param>
-		/// <param name="background"></param>
-		/// <returns></returns>
-		public static int InitColorPair (short pair, short foreground, short background) => methods.init_pair (pair, foreground, background);
-		// TODO: Upgrade to ncurses 6.1 and use the extended version
-		//public static int InitExtendedPair (int pair, int foreground, int background) => methods.init_extended_pair (pair, foreground, background);
-		public static int UseDefaultColors () => methods.use_default_colors ();
-		public static int ColorPairs => methods.COLOR_PAIRS ();
-
-		//
-		// The proxy methods to call into each version
-		//
-		static public int endwin () => methods.endwin ();
-		static public bool isendwin () => methods.isendwin ();
-		static public int cbreak () => methods.cbreak ();
-		static public int nocbreak () => methods.nocbreak ();
-		static public int echo () => methods.echo ();
-		static public int noecho () => methods.noecho ();
-		static public int halfdelay (int t) => methods.halfdelay (t);
-		static public int raw () => methods.raw ();
-		static public int noraw () => methods.noraw ();
-		static public void noqiflush () => methods.noqiflush ();
-		static public void qiflush () => methods.qiflush ();
-		static public int typeahead (IntPtr fd) => methods.typeahead (fd);
-		static public int timeout (int delay) => methods.timeout (delay);
-		static public int wtimeout (IntPtr win, int delay) => methods.wtimeout (win, delay);
-		static public int notimeout (IntPtr win, bool bf) => methods.notimeout (win, bf);
-		static public int keypad (IntPtr win, bool bf) => methods.keypad (win, bf);
-		static public int meta (IntPtr win, bool bf) => methods.meta (win, bf);
-		static public int intrflush (IntPtr win, bool bf) => methods.intrflush (win, bf);
-		static public int clearok (IntPtr win, bool bf) => methods.clearok (win, bf);
-		static public int idlok (IntPtr win, bool bf) => methods.idlok (win, bf);
-		static public void idcok (IntPtr win, bool bf) => methods.idcok (win, bf);
-		static public void immedok (IntPtr win, bool bf) => methods.immedok (win, bf);
-		static public int leaveok (IntPtr win, bool bf) => methods.leaveok (win, bf);
-		static public int wsetscrreg (IntPtr win, int top, int bot) => methods.wsetscrreg (win, top, bot);
-		static public int scrollok (IntPtr win, bool bf) => methods.scrollok (win, bf);
-		static public int nl () => methods.nl ();
-		static public int nonl () => methods.nonl ();
-		static public int setscrreg (int top, int bot) => methods.setscrreg (top, bot);
-		static public int refresh () => methods.refresh ();
-		static public int doupdate () => methods.doupdate ();
-		static public int wrefresh (IntPtr win) => methods.wrefresh (win);
-		static public int redrawwin (IntPtr win) => methods.redrawwin (win);
-		//static public int wredrawwin (IntPtr win, int beg_line, int num_lines) => methods.wredrawwin (win, beg_line, num_lines);
-		static public int wnoutrefresh (IntPtr win) => methods.wnoutrefresh (win);
-		static public int move (int line, int col) => methods.move (line, col);
-		static public int curs_set (int visibility) => methods.curs_set (visibility);
-		//static public int addch (int ch) => methods.addch (ch);
-		static public int echochar (int ch) => methods.echochar (ch);
-		static public int addwstr (string s) => methods.addwstr (s);
-		static public int mvaddwstr (int y, int x, string s) => methods.mvaddwstr (y, x, s);
-		static public int wmove (IntPtr win, int line, int col) => methods.wmove (win, line, col);
-		static public int waddch (IntPtr win, int ch) => methods.waddch (win, ch);
-		//static public int wechochar (IntPtr win, int ch) => methods.wechochar (win, ch);
-		static public int attron (int attrs) => methods.attron (attrs);
-		static public int attroff (int attrs) => methods.attroff (attrs);
-		static public int attrset (int attrs) => methods.attrset (attrs);
-		static public int getch () => methods.getch ();
-		static public int get_wch (out int sequence) => methods.get_wch (out sequence);
-		static public int ungetch (int ch) => methods.ungetch (ch);
-		static public int mvgetch (int y, int x) => methods.mvgetch (y, x);
-		static public bool has_colors () => methods.has_colors ();
-		static public int start_color () => methods.start_color ();
-		static public int init_pair (short pair, short f, short b) => methods.init_pair (pair, f, b);
-		static public int use_default_colors () => methods.use_default_colors ();
-		static public int COLOR_PAIRS () => methods.COLOR_PAIRS ();
-		static public uint getmouse (out MouseEvent ev) => methods.getmouse (out ev);
-		static public uint ungetmouse (ref MouseEvent ev) => methods.ungetmouse (ref ev);
-		static public int mouseinterval (int interval) => methods.mouseinterval (interval);
-		static public bool is_term_resized (int lines, int columns) => methods.is_term_resized (lines, columns);
-		static public int resize_term (int lines, int columns) => methods.resize_term (lines, columns);
-		static public int resizeterm (int lines, int columns) => methods.resizeterm (lines, columns);
-		static public void use_env (bool f) => methods.use_env (f);
-		static public int flushinp () => methods.flushinp ();
-		static public int def_prog_mode () => methods.def_prog_mode ();
-		static public int def_shell_mode () => methods.def_shell_mode ();
-		static public int reset_prog_mode () => methods.reset_prog_mode ();
-		static public int reset_shell_mode () => methods.reset_shell_mode ();
-		static public int savetty () => methods.savetty ();
-		static public int resetty () => methods.resetty ();
-		static public int set_escdelay (int size) => methods.set_escdelay (size);
-	}
+public partial class Curses
+{
+    // We encode ESC + char (what Alt-char generates) as 0x2000 + char
+    public const int KeyAlt = 0x2000;
+    private static nint curses_handle, curscr_ptr, lines_ptr, cols_ptr;
+
+    // If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5"
+    //static bool use_naked_driver;
+    private static UnmanagedLibrary curses_library;
+    private static int lines, cols;
+    private static Window main_window;
+    private static NativeMethods methods;
+    private static char [] r = new char [1];
+    private static nint stdscr;
+    public static int ColorPairs => methods.COLOR_PAIRS ();
+
+    public static int Cols
+    {
+        get => cols;
+        internal set =>
+
+            // For unit tests
+            cols = value;
+    }
+
+    public static bool HasColors => methods.has_colors ();
+
+    public static int Lines
+    {
+        get => lines;
+        internal set =>
+
+            // For unit tests
+            lines = value;
+    }
+
+    //
+    // Have to wrap the native addch, as it can not
+    // display unicode characters, we have to use addstr
+    // for that.   but we need addch to render special ACS
+    // characters
+    //
+    public static int addch (int ch)
+    {
+        if (ch < 127 || ch > 0xffff)
+        {
+            return methods.addch (ch);
+        }
+
+        var c = (char)ch;
+
+        return addwstr (new string (c, 1));
+    }
+
+    public static int addstr (string format, params object [] args)
+    {
+        string s = string.Format (format, args);
+
+        return addwstr (s);
+    }
+
+    public static int addwstr (string s) { return methods.addwstr (s); }
+    public static int attroff (int attrs) { return methods.attroff (attrs); }
+
+    //static public int wechochar (IntPtr win, int ch) => methods.wechochar (win, ch);
+    public static int attron (int attrs) { return methods.attron (attrs); }
+    public static int attrset (int attrs) { return methods.attrset (attrs); }
+    public static int cbreak () { return methods.cbreak (); }
+
+    //
+    // Returns true if the window changed since the last invocation, as a
+    // side effect, the Lines and Cols properties are updated
+    //
+    public static bool CheckWinChange ()
+    {
+        int l, c;
+
+        console_sharp_get_dims (out l, out c);
+
+        if (l < 1)
+        {
+            l = 1;
+        }
+
+        if (l != lines || c != cols)
+        {
+            lines = l;
+            cols = c;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    public static int clearok (nint win, bool bf) { return methods.clearok (win, bf); }
+    public static int COLOR_PAIRS () { return methods.COLOR_PAIRS (); }
+    public static int curs_set (int visibility) { return methods.curs_set (visibility); }
+
+    public static string curses_version ()
+    {
+        nint v = methods.curses_version ();
+
+        return $"{Marshal.PtrToStringAnsi (v)}, {curses_library.LibraryPath}";
+    }
+
+    public static int def_prog_mode () { return methods.def_prog_mode (); }
+    public static int def_shell_mode () { return methods.def_shell_mode (); }
+    public static int doupdate () { return methods.doupdate (); }
+    public static int echo () { return methods.echo (); }
+
+    //static public int addch (int ch) => methods.addch (ch);
+    public static int echochar (int ch) { return methods.echochar (ch); }
+
+    //
+    // The proxy methods to call into each version
+    //
+    public static int endwin () { return methods.endwin (); }
+    public static int flushinp () { return methods.flushinp (); }
+    public static int get_wch (out int sequence) { return methods.get_wch (out sequence); }
+    public static int getch () { return methods.getch (); }
+    public static uint getmouse (out MouseEvent ev) { return methods.getmouse (out ev); }
+    public static int halfdelay (int t) { return methods.halfdelay (t); }
+    public static bool has_colors () { return methods.has_colors (); }
+    public static void idcok (nint win, bool bf) { methods.idcok (win, bf); }
+    public static int idlok (nint win, bool bf) { return methods.idlok (win, bf); }
+    public static void immedok (nint win, bool bf) { methods.immedok (win, bf); }
+    public static int init_pair (short pair, short f, short b) { return methods.init_pair (pair, f, b); }
+
+    /// <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) { return methods.init_pair (pair, foreground, background); }
+
+    public static Window initscr ()
+    {
+        setlocale (LC_ALL, "");
+        FindNCurses ();
+
+        // Prevents the terminal from being locked after exiting.
+        reset_shell_mode ();
+
+        main_window = new Window (methods.initscr ());
+
+        try
+        {
+            console_sharp_get_dims (out lines, out cols);
+        }
+        catch (DllNotFoundException)
+        {
+            endwin ();
+
+            Console.Error.WriteLine (
+                                     "Unable to find the @MONO_CURSES@ native library\n"
+                                     + "this is different than the managed mono-curses.dll\n\n"
+                                     + "Typically you need to install to a LD_LIBRARY_PATH directory\n"
+                                     + "or DYLD_LIBRARY_PATH directory or run /sbin/ldconfig"
+                                    );
+            Environment.Exit (1);
+        }
+
+        //Console.Error.WriteLine ($"using curses {Curses.curses_version ()}");
+
+        return main_window;
+    }
+
+    public static int intrflush (nint win, bool bf) { return methods.intrflush (win, bf); }
+    public static bool is_term_resized (int lines, int columns) { return methods.is_term_resized (lines, columns); }
+
+    public static int IsAlt (int key)
+    {
+        if ((key & KeyAlt) != 0)
+        {
+            return key & ~KeyAlt;
+        }
+
+        return 0;
+    }
+
+    public static bool isendwin () { return methods.isendwin (); }
+    public static int keypad (nint win, bool bf) { return methods.keypad (win, bf); }
+    public static int leaveok (nint win, bool bf) { return methods.leaveok (win, bf); }
+    public static int meta (nint win, bool bf) { return methods.meta (win, bf); }
+    public static int mouseinterval (int interval) { return methods.mouseinterval (interval); }
+
+    public static Event mousemask (Event newmask, out Event oldmask)
+    {
+        nint e;
+        var ret = (Event)methods.mousemask ((nint)newmask, out e);
+        oldmask = (Event)e;
+
+        return ret;
+    }
+
+    public static int move (int line, int col) { return methods.move (line, col); }
+
+    public static int mvaddch (int y, int x, int ch)
+    {
+        if (ch < 127 || ch > 0xffff)
+        {
+            return methods.mvaddch (y, x, ch);
+        }
+
+        var c = (char)ch;
+
+        return mvaddwstr (y, x, new string (c, 1));
+    }
+
+    public static int mvaddwstr (int y, int x, string s) { return methods.mvaddwstr (y, x, s); }
+    public static int mvgetch (int y, int x) { return methods.mvgetch (y, x); }
+    public static int nl () { return methods.nl (); }
+    public static int nocbreak () { return methods.nocbreak (); }
+    public static int noecho () { return methods.noecho (); }
+    public static int nonl () { return methods.nonl (); }
+    public static void noqiflush () { methods.noqiflush (); }
+    public static int noraw () { return methods.noraw (); }
+    public static int notimeout (nint win, bool bf) { return methods.notimeout (win, bf); }
+    public static void qiflush () { methods.qiflush (); }
+    public static int raw () { return methods.raw (); }
+    public static int redrawwin (nint win) { return methods.redrawwin (win); }
+    public static int refresh () { return methods.refresh (); }
+    public static int reset_prog_mode () { return methods.reset_prog_mode (); }
+    public static int reset_shell_mode () { return methods.reset_shell_mode (); }
+    public static int resetty () { return methods.resetty (); }
+    public static int resize_term (int lines, int columns) { return methods.resize_term (lines, columns); }
+    public static int resizeterm (int lines, int columns) { return methods.resizeterm (lines, columns); }
+    public static int savetty () { return methods.savetty (); }
+    public static int scrollok (nint win, bool bf) { return methods.scrollok (win, bf); }
+    public static int set_escdelay (int size) { return methods.set_escdelay (size); }
+
+    [DllImport ("libc")]
+    public static extern int setlocale (int cate, [MarshalAs (UnmanagedType.LPStr)] string locale);
+
+    public static int setscrreg (int top, int bot) { return methods.setscrreg (top, bot); }
+    public static int start_color () { return methods.start_color (); }
+    public static int StartColor () { return methods.start_color (); }
+    public static int timeout (int delay) { return methods.timeout (delay); }
+    public static int typeahead (nint fd) { return methods.typeahead (fd); }
+    public static int ungetch (int ch) { return methods.ungetch (ch); }
+    public static uint ungetmouse (ref MouseEvent ev) { return methods.ungetmouse (ref ev); }
+    public static int use_default_colors () { return methods.use_default_colors (); }
+    public static void use_env (bool f) { methods.use_env (f); }
+
+    // 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 () { return methods.use_default_colors (); }
+    public static int waddch (nint win, int ch) { return methods.waddch (win, ch); }
+    public static int wmove (nint win, int line, int col) { return methods.wmove (win, line, col); }
+
+    //static public int wredrawwin (IntPtr win, int beg_line, int num_lines) => methods.wredrawwin (win, beg_line, num_lines);
+    public static int wnoutrefresh (nint win) { return methods.wnoutrefresh (win); }
+    public static int wrefresh (nint win) { return methods.wrefresh (win); }
+    public static int wsetscrreg (nint win, int top, int bot) { return methods.wsetscrreg (win, top, bot); }
+    public static int wtimeout (nint win, int delay) { return methods.wtimeout (win, delay); }
+    internal static nint console_sharp_get_curscr () { return Marshal.ReadIntPtr (curscr_ptr); }
+
+    internal static void console_sharp_get_dims (out int lines, out int cols)
+    {
+        lines = Marshal.ReadInt32 (lines_ptr);
+        cols = Marshal.ReadInt32 (cols_ptr);
+
+        //int cmd;
+        //if (UnmanagedLibrary.IsMacOSPlatform) {
+        //	cmd = TIOCGWINSZ_MAC;
+        //} else {
+        //	cmd = TIOCGWINSZ;
+        //}
+
+        //if (ioctl (1, cmd, out winsize ws) == 0) {
+        //	lines = ws.ws_row;
+        //	cols = ws.ws_col;
+
+        //	if (lines == Lines && cols == Cols) {
+        //		return;
+        //	}
+
+        //	resizeterm (lines, cols);
+        //} else {
+        //	lines = Lines;
+        //	cols = Cols;
+        //}
+    }
+
+    internal static nint console_sharp_get_stdscr () { return stdscr; }
+
+    internal static nint read_static_ptr (string key)
+    {
+        nint ptr = get_ptr (key);
+
+        return Marshal.ReadIntPtr (ptr);
+    }
+
+    private static void FindNCurses ()
+    {
+        LoadMethods ();
+        curses_handle = methods.UnmanagedLibrary.NativeLibraryHandle;
+
+        stdscr = read_static_ptr ("stdscr");
+        curscr_ptr = get_ptr ("curscr");
+        lines_ptr = get_ptr ("LINES");
+        cols_ptr = get_ptr ("COLS");
+    }
+
+    private static nint get_ptr (string key)
+    {
+        nint ptr = curses_library.LoadSymbol (key);
+
+        if (ptr == nint.Zero)
+        {
+            throw new Exception ("Could not load the key " + key);
+        }
+
+        return ptr;
+    }
+
+    //[DllImport ("libc")]
+    //public extern static int ioctl (int fd, int cmd, out winsize argp);
+
+    private static void LoadMethods ()
+    {
+        string [] libs = UnmanagedLibrary.IsMacOSPlatform
+                             ? new [] { "libncurses.dylib" }
+                             : new [] { "libncursesw.so.6", "libncursesw.so.5" };
+        var attempts = 1;
+
+        while (true)
+        {
+            try
+            {
+                curses_library = new UnmanagedLibrary (libs, false);
+                methods = new NativeMethods (curses_library);
+
+                break;
+            }
+            catch (Exception ex)
+            {
+                if (attempts == 1)
+                {
+                    attempts++;
+
+                    (int exitCode, string result) =
+                        ClipboardProcessRunner.Bash ("cat /etc/os-release", waitForOutput: true);
+
+                    if (exitCode == 0 && result.Contains ("opensuse"))
+                    {
+                        libs [0] = "libncursesw.so.5";
+                    }
+                }
+                else
+                {
+                    throw ex.GetBaseException ();
+                }
+            }
+        }
+    }
+
+    //[StructLayout (LayoutKind.Sequential)]
+    //public struct winsize {
+    //	public ushort ws_row;
+    //	public ushort ws_col;
+    //	public ushort ws_xpixel;   /* unused */
+    //	public ushort ws_ypixel;   /* unused */
+    //};
+
+    [StructLayout (LayoutKind.Sequential)]
+    public struct MouseEvent
+    {
+        public short ID;
+        public int X, Y, Z;
+        public Event ButtonState;
+    }
+}
 
 #pragma warning disable RCS1102 // Make class static.'
-	internal class Delegates {
+internal class Delegates
+{
 #pragma warning restore RCS1102 // Make class static.
 #pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
-		public delegate IntPtr initscr ();
-		public delegate int endwin ();
-		public delegate bool isendwin ();
-		public delegate int cbreak ();
-		public delegate int nocbreak ();
-		public delegate int echo ();
-		public delegate int noecho ();
-		public delegate int halfdelay (int t);
-		public delegate int raw ();
-		public delegate int noraw ();
-		public delegate void noqiflush ();
-		public delegate void qiflush ();
-		public delegate int typeahead (IntPtr fd);
-		public delegate int timeout (int delay);
-		public delegate int wtimeout (IntPtr win, int delay);
-		public delegate int notimeout (IntPtr win, bool bf);
-		public delegate int keypad (IntPtr win, bool bf);
-		public delegate int meta (IntPtr win, bool bf);
-		public delegate int intrflush (IntPtr win, bool bf);
-		public delegate int clearok (IntPtr win, bool bf);
-		public delegate int idlok (IntPtr win, bool bf);
-		public delegate void idcok (IntPtr win, bool bf);
-		public delegate void immedok (IntPtr win, bool bf);
-		public delegate int leaveok (IntPtr win, bool bf);
-		public delegate int wsetscrreg (IntPtr win, int top, int bot);
-		public delegate int scrollok (IntPtr win, bool bf);
-		public delegate int nl ();
-		public delegate int nonl ();
-		public delegate int setscrreg (int top, int bot);
-		public delegate int refresh ();
-		public delegate int doupdate ();
-		public delegate int wrefresh (IntPtr win);
-		public delegate int redrawwin (IntPtr win);
-		//public delegate int wredrawwin (IntPtr win, int beg_line, int num_lines);
-		public delegate int wnoutrefresh (IntPtr win);
-		public delegate int move (int line, int col);
-		public delegate int curs_set (int visibility);
-		public delegate int addch (int ch);
-		public delegate int echochar (int ch);
-		public delegate int mvaddch (int y, int x, int ch);
-		public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
-		public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s);
-		public delegate int wmove (IntPtr win, int line, int col);
-		public delegate int waddch (IntPtr win, int ch);
-		public delegate int attron (int attrs);
-		public delegate int attroff (int attrs);
-		public delegate int attrset (int attrs);
-		public delegate int getch ();
-		public delegate int get_wch (out int sequence);
-		public delegate int ungetch (int ch);
-		public delegate int mvgetch (int y, int x);
-		public delegate bool has_colors ();
-		public delegate int start_color ();
-		public delegate int init_pair (short pair, short f, short b);
-		public delegate int use_default_colors ();
-		public delegate int COLOR_PAIRS ();
-		public delegate uint getmouse (out Curses.MouseEvent ev);
-		public delegate uint ungetmouse (ref Curses.MouseEvent ev);
-		public delegate int mouseinterval (int interval);
-		public delegate IntPtr mousemask (IntPtr newmask, out IntPtr oldMask);
-		public delegate bool is_term_resized (int lines, int columns);
-		public delegate int resize_term (int lines, int columns);
-		public delegate int resizeterm (int lines, int columns);
-		public delegate void use_env (bool f);
-		public delegate int flushinp ();
-		public delegate int def_prog_mode ();
-		public delegate int def_shell_mode ();
-		public delegate int reset_prog_mode ();
-		public delegate int reset_shell_mode ();
-		public delegate int savetty ();
-		public delegate int resetty ();
-		public delegate int set_escdelay (int size);
-		public delegate IntPtr curses_version ();
-	}
-
-	internal class NativeMethods {
-		public readonly Delegates.initscr initscr;
-		public readonly Delegates.endwin endwin;
-		public readonly Delegates.isendwin isendwin;
-		public readonly Delegates.cbreak cbreak;
-		public readonly Delegates.nocbreak nocbreak;
-		public readonly Delegates.echo echo;
-		public readonly Delegates.noecho noecho;
-		public readonly Delegates.halfdelay halfdelay;
-		public readonly Delegates.raw raw;
-		public readonly Delegates.noraw noraw;
-		public readonly Delegates.noqiflush noqiflush;
-		public readonly Delegates.qiflush qiflush;
-		public readonly Delegates.typeahead typeahead;
-		public readonly Delegates.timeout timeout;
-		public readonly Delegates.wtimeout wtimeout;
-		public readonly Delegates.notimeout notimeout;
-		public readonly Delegates.keypad keypad;
-		public readonly Delegates.meta meta;
-		public readonly Delegates.intrflush intrflush;
-		public readonly Delegates.clearok clearok;
-		public readonly Delegates.idlok idlok;
-		public readonly Delegates.idcok idcok;
-		public readonly Delegates.immedok immedok;
-		public readonly Delegates.leaveok leaveok;
-		public readonly Delegates.wsetscrreg wsetscrreg;
-		public readonly Delegates.scrollok scrollok;
-		public readonly Delegates.nl nl;
-		public readonly Delegates.nonl nonl;
-		public readonly Delegates.setscrreg setscrreg;
-		public readonly Delegates.refresh refresh;
-		public readonly Delegates.doupdate doupdate;
-		public readonly Delegates.wrefresh wrefresh;
-		public readonly Delegates.redrawwin redrawwin;
-		//public readonly Delegates.wredrawwin wredrawwin;
-		public readonly Delegates.wnoutrefresh wnoutrefresh;
-		public readonly Delegates.move move;
-		public readonly Delegates.curs_set curs_set;
-		public readonly Delegates.addch addch;
-		public readonly Delegates.echochar echochar;
-		public readonly Delegates.mvaddch mvaddch;
-		public readonly Delegates.addwstr addwstr;
-		public readonly Delegates.mvaddwstr mvaddwstr;
-		public readonly Delegates.wmove wmove;
-		public readonly Delegates.waddch waddch;
-		//public readonly Delegates.wechochar wechochar;
-		public readonly Delegates.attron attron;
-		public readonly Delegates.attroff attroff;
-		public readonly Delegates.attrset attrset;
-		public readonly Delegates.getch getch;
-		public readonly Delegates.get_wch get_wch;
-		public readonly Delegates.ungetch ungetch;
-		public readonly Delegates.mvgetch mvgetch;
-		public readonly Delegates.has_colors has_colors;
-		public readonly Delegates.start_color start_color;
-		public readonly Delegates.init_pair init_pair;
-		public readonly Delegates.use_default_colors use_default_colors;
-		public readonly Delegates.COLOR_PAIRS COLOR_PAIRS;
-		public readonly Delegates.getmouse getmouse;
-		public readonly Delegates.ungetmouse ungetmouse;
-		public readonly Delegates.mouseinterval mouseinterval;
-		public readonly Delegates.mousemask mousemask;
-		public readonly Delegates.is_term_resized is_term_resized;
-		public readonly Delegates.resize_term resize_term;
-		public readonly Delegates.resizeterm resizeterm;
-		public readonly Delegates.use_env use_env;
-		public readonly Delegates.flushinp flushinp;
-		public readonly Delegates.def_prog_mode def_prog_mode;
-		public readonly Delegates.def_shell_mode def_shell_mode;
-		public readonly Delegates.reset_prog_mode reset_prog_mode;
-		public readonly Delegates.reset_shell_mode reset_shell_mode;
-		public readonly Delegates.savetty savetty;
-		public readonly Delegates.resetty resetty;
-		public readonly Delegates.set_escdelay set_escdelay;
-		public readonly Delegates.curses_version curses_version;
-		public UnmanagedLibrary UnmanagedLibrary;
-
-		public NativeMethods (UnmanagedLibrary lib)
-		{
-			this.UnmanagedLibrary = lib;
-			initscr = lib.GetNativeMethodDelegate<Delegates.initscr> ("initscr");
-			endwin = lib.GetNativeMethodDelegate<Delegates.endwin> ("endwin");
-			isendwin = lib.GetNativeMethodDelegate<Delegates.isendwin> ("isendwin");
-			cbreak = lib.GetNativeMethodDelegate<Delegates.cbreak> ("cbreak");
-			nocbreak = lib.GetNativeMethodDelegate<Delegates.nocbreak> ("nocbreak");
-			echo = lib.GetNativeMethodDelegate<Delegates.echo> ("echo");
-			noecho = lib.GetNativeMethodDelegate<Delegates.noecho> ("noecho");
-			halfdelay = lib.GetNativeMethodDelegate<Delegates.halfdelay> ("halfdelay");
-			raw = lib.GetNativeMethodDelegate<Delegates.raw> ("raw");
-			noraw = lib.GetNativeMethodDelegate<Delegates.noraw> ("noraw");
-			noqiflush = lib.GetNativeMethodDelegate<Delegates.noqiflush> ("noqiflush");
-			qiflush = lib.GetNativeMethodDelegate<Delegates.qiflush> ("qiflush");
-			typeahead = lib.GetNativeMethodDelegate<Delegates.typeahead> ("typeahead");
-			timeout = lib.GetNativeMethodDelegate<Delegates.timeout> ("timeout");
-			wtimeout = lib.GetNativeMethodDelegate<Delegates.wtimeout> ("wtimeout");
-			notimeout = lib.GetNativeMethodDelegate<Delegates.notimeout> ("notimeout");
-			keypad = lib.GetNativeMethodDelegate<Delegates.keypad> ("keypad");
-			meta = lib.GetNativeMethodDelegate<Delegates.meta> ("meta");
-			intrflush = lib.GetNativeMethodDelegate<Delegates.intrflush> ("intrflush");
-			clearok = lib.GetNativeMethodDelegate<Delegates.clearok> ("clearok");
-			idlok = lib.GetNativeMethodDelegate<Delegates.idlok> ("idlok");
-			idcok = lib.GetNativeMethodDelegate<Delegates.idcok> ("idcok");
-			immedok = lib.GetNativeMethodDelegate<Delegates.immedok> ("immedok");
-			leaveok = lib.GetNativeMethodDelegate<Delegates.leaveok> ("leaveok");
-			wsetscrreg = lib.GetNativeMethodDelegate<Delegates.wsetscrreg> ("wsetscrreg");
-			scrollok = lib.GetNativeMethodDelegate<Delegates.scrollok> ("scrollok");
-			nl = lib.GetNativeMethodDelegate<Delegates.nl> ("nl");
-			nonl = lib.GetNativeMethodDelegate<Delegates.nonl> ("nonl");
-			setscrreg = lib.GetNativeMethodDelegate<Delegates.setscrreg> ("setscrreg");
-			refresh = lib.GetNativeMethodDelegate<Delegates.refresh> ("refresh");
-			doupdate = lib.GetNativeMethodDelegate<Delegates.doupdate> ("doupdate");
-			wrefresh = lib.GetNativeMethodDelegate<Delegates.wrefresh> ("wrefresh");
-			redrawwin = lib.GetNativeMethodDelegate<Delegates.redrawwin> ("redrawwin");
-			//wredrawwin = lib.GetNativeMethodDelegate<Delegates.wredrawwin> ("wredrawwin");
-			wnoutrefresh = lib.GetNativeMethodDelegate<Delegates.wnoutrefresh> ("wnoutrefresh");
-			move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
-			curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
-			addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
-			echochar = lib.GetNativeMethodDelegate<Delegates.echochar> ("echochar");
-			mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
-			addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
-			mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
-			wmove = lib.GetNativeMethodDelegate<Delegates.wmove> ("wmove");
-			waddch = lib.GetNativeMethodDelegate<Delegates.waddch> ("waddch");
-			attron = lib.GetNativeMethodDelegate<Delegates.attron> ("attron");
-			attroff = lib.GetNativeMethodDelegate<Delegates.attroff> ("attroff");
-			attrset = lib.GetNativeMethodDelegate<Delegates.attrset> ("attrset");
-			getch = lib.GetNativeMethodDelegate<Delegates.getch> ("getch");
-			get_wch = lib.GetNativeMethodDelegate<Delegates.get_wch> ("get_wch");
-			ungetch = lib.GetNativeMethodDelegate<Delegates.ungetch> ("ungetch");
-			mvgetch = lib.GetNativeMethodDelegate<Delegates.mvgetch> ("mvgetch");
-			has_colors = lib.GetNativeMethodDelegate<Delegates.has_colors> ("has_colors");
-			start_color = lib.GetNativeMethodDelegate<Delegates.start_color> ("start_color");
-			init_pair = lib.GetNativeMethodDelegate<Delegates.init_pair> ("init_pair");
-			use_default_colors = lib.GetNativeMethodDelegate<Delegates.use_default_colors> ("use_default_colors");
-			COLOR_PAIRS = lib.GetNativeMethodDelegate<Delegates.COLOR_PAIRS> ("COLOR_PAIRS");
-			getmouse = lib.GetNativeMethodDelegate<Delegates.getmouse> ("getmouse");
-			ungetmouse = lib.GetNativeMethodDelegate<Delegates.ungetmouse> ("ungetmouse");
-			mouseinterval = lib.GetNativeMethodDelegate<Delegates.mouseinterval> ("mouseinterval");
-			mousemask = lib.GetNativeMethodDelegate<Delegates.mousemask> ("mousemask");
-			is_term_resized = lib.GetNativeMethodDelegate<Delegates.is_term_resized> ("is_term_resized");
-			resize_term = lib.GetNativeMethodDelegate<Delegates.resize_term> ("resize_term");
-			resizeterm = lib.GetNativeMethodDelegate<Delegates.resizeterm> ("resizeterm");
-			use_env = lib.GetNativeMethodDelegate<Delegates.use_env> ("use_env");
-			flushinp = lib.GetNativeMethodDelegate<Delegates.flushinp> ("flushinp");
-			def_prog_mode = lib.GetNativeMethodDelegate<Delegates.def_prog_mode> ("def_prog_mode");
-			def_shell_mode = lib.GetNativeMethodDelegate<Delegates.def_shell_mode> ("def_shell_mode");
-			reset_prog_mode = lib.GetNativeMethodDelegate<Delegates.reset_prog_mode> ("reset_prog_mode");
-			reset_shell_mode = lib.GetNativeMethodDelegate<Delegates.reset_shell_mode> ("reset_shell_mode");
-			savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
-			resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
-			set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
-			curses_version = lib.GetNativeMethodDelegate<Delegates.curses_version> ("curses_version");
-		}
-	}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.
+    public delegate nint initscr ();
+
+    public delegate int endwin ();
+
+    public delegate bool isendwin ();
+
+    public delegate int cbreak ();
+
+    public delegate int nocbreak ();
+
+    public delegate int echo ();
+
+    public delegate int noecho ();
+
+    public delegate int halfdelay (int t);
+
+    public delegate int raw ();
+
+    public delegate int noraw ();
+
+    public delegate void noqiflush ();
+
+    public delegate void qiflush ();
+
+    public delegate int typeahead (nint fd);
+
+    public delegate int timeout (int delay);
+
+    public delegate int wtimeout (nint win, int delay);
+
+    public delegate int notimeout (nint win, bool bf);
+
+    public delegate int keypad (nint win, bool bf);
+
+    public delegate int meta (nint win, bool bf);
+
+    public delegate int intrflush (nint win, bool bf);
+
+    public delegate int clearok (nint win, bool bf);
+
+    public delegate int idlok (nint win, bool bf);
+
+    public delegate void idcok (nint win, bool bf);
+
+    public delegate void immedok (nint win, bool bf);
+
+    public delegate int leaveok (nint win, bool bf);
+
+    public delegate int wsetscrreg (nint win, int top, int bot);
+
+    public delegate int scrollok (nint win, bool bf);
+
+    public delegate int nl ();
+
+    public delegate int nonl ();
+
+    public delegate int setscrreg (int top, int bot);
+
+    public delegate int refresh ();
+
+    public delegate int doupdate ();
+
+    public delegate int wrefresh (nint win);
+
+    public delegate int redrawwin (nint win);
+
+    //public delegate int wredrawwin (IntPtr win, int beg_line, int num_lines);
+    public delegate int wnoutrefresh (nint win);
+
+    public delegate int move (int line, int col);
+
+    public delegate int curs_set (int visibility);
 
+    public delegate int addch (int ch);
+
+    public delegate int echochar (int ch);
+
+    public delegate int mvaddch (int y, int x, int ch);
+
+    public delegate int addwstr ([MarshalAs (UnmanagedType.LPWStr)] string s);
+
+    public delegate int mvaddwstr (int y, int x, [MarshalAs (UnmanagedType.LPWStr)] string s);
+
+    public delegate int wmove (nint win, int line, int col);
+
+    public delegate int waddch (nint win, int ch);
+
+    public delegate int attron (int attrs);
+
+    public delegate int attroff (int attrs);
+
+    public delegate int attrset (int attrs);
+
+    public delegate int getch ();
+
+    public delegate int get_wch (out int sequence);
+
+    public delegate int ungetch (int ch);
+
+    public delegate int mvgetch (int y, int x);
+
+    public delegate bool has_colors ();
+
+    public delegate int start_color ();
+
+    public delegate int init_pair (short pair, short f, short b);
+
+    public delegate int use_default_colors ();
+
+    public delegate int COLOR_PAIRS ();
+
+    public delegate uint getmouse (out Curses.MouseEvent ev);
+
+    public delegate uint ungetmouse (ref Curses.MouseEvent ev);
+
+    public delegate int mouseinterval (int interval);
+
+    public delegate nint mousemask (nint newmask, out nint oldMask);
+
+    public delegate bool is_term_resized (int lines, int columns);
+
+    public delegate int resize_term (int lines, int columns);
+
+    public delegate int resizeterm (int lines, int columns);
+
+    public delegate void use_env (bool f);
+
+    public delegate int flushinp ();
+
+    public delegate int def_prog_mode ();
+
+    public delegate int def_shell_mode ();
+
+    public delegate int reset_prog_mode ();
+
+    public delegate int reset_shell_mode ();
+
+    public delegate int savetty ();
+
+    public delegate int resetty ();
+
+    public delegate int set_escdelay (int size);
+
+    public delegate nint curses_version ();
 }
+
+internal class NativeMethods
+{
+    public readonly Delegates.addch addch;
+    public readonly Delegates.addwstr addwstr;
+    public readonly Delegates.attroff attroff;
+
+    //public readonly Delegates.wechochar wechochar;
+    public readonly Delegates.attron attron;
+    public readonly Delegates.attrset attrset;
+    public readonly Delegates.cbreak cbreak;
+    public readonly Delegates.clearok clearok;
+    public readonly Delegates.COLOR_PAIRS COLOR_PAIRS;
+    public readonly Delegates.curs_set curs_set;
+    public readonly Delegates.curses_version curses_version;
+    public readonly Delegates.def_prog_mode def_prog_mode;
+    public readonly Delegates.def_shell_mode def_shell_mode;
+    public readonly Delegates.doupdate doupdate;
+    public readonly Delegates.echo echo;
+    public readonly Delegates.echochar echochar;
+    public readonly Delegates.endwin endwin;
+    public readonly Delegates.flushinp flushinp;
+    public readonly Delegates.get_wch get_wch;
+    public readonly Delegates.getch getch;
+    public readonly Delegates.getmouse getmouse;
+    public readonly Delegates.halfdelay halfdelay;
+    public readonly Delegates.has_colors has_colors;
+    public readonly Delegates.idcok idcok;
+    public readonly Delegates.idlok idlok;
+    public readonly Delegates.immedok immedok;
+    public readonly Delegates.init_pair init_pair;
+    public readonly Delegates.initscr initscr;
+    public readonly Delegates.intrflush intrflush;
+    public readonly Delegates.is_term_resized is_term_resized;
+    public readonly Delegates.isendwin isendwin;
+    public readonly Delegates.keypad keypad;
+    public readonly Delegates.leaveok leaveok;
+    public readonly Delegates.meta meta;
+    public readonly Delegates.mouseinterval mouseinterval;
+    public readonly Delegates.mousemask mousemask;
+    public readonly Delegates.move move;
+    public readonly Delegates.mvaddch mvaddch;
+    public readonly Delegates.mvaddwstr mvaddwstr;
+    public readonly Delegates.mvgetch mvgetch;
+    public readonly Delegates.nl nl;
+    public readonly Delegates.nocbreak nocbreak;
+    public readonly Delegates.noecho noecho;
+    public readonly Delegates.nonl nonl;
+    public readonly Delegates.noqiflush noqiflush;
+    public readonly Delegates.noraw noraw;
+    public readonly Delegates.notimeout notimeout;
+    public readonly Delegates.qiflush qiflush;
+    public readonly Delegates.raw raw;
+    public readonly Delegates.redrawwin redrawwin;
+    public readonly Delegates.refresh refresh;
+    public readonly Delegates.reset_prog_mode reset_prog_mode;
+    public readonly Delegates.reset_shell_mode reset_shell_mode;
+    public readonly Delegates.resetty resetty;
+    public readonly Delegates.resize_term resize_term;
+    public readonly Delegates.resizeterm resizeterm;
+    public readonly Delegates.savetty savetty;
+    public readonly Delegates.scrollok scrollok;
+    public readonly Delegates.set_escdelay set_escdelay;
+    public readonly Delegates.setscrreg setscrreg;
+    public readonly Delegates.start_color start_color;
+    public readonly Delegates.timeout timeout;
+    public readonly Delegates.typeahead typeahead;
+    public readonly Delegates.ungetch ungetch;
+    public readonly Delegates.ungetmouse ungetmouse;
+    public readonly Delegates.use_default_colors use_default_colors;
+    public readonly Delegates.use_env use_env;
+    public readonly Delegates.waddch waddch;
+    public readonly Delegates.wmove wmove;
+
+    //public readonly Delegates.wredrawwin wredrawwin;
+    public readonly Delegates.wnoutrefresh wnoutrefresh;
+    public readonly Delegates.wrefresh wrefresh;
+    public readonly Delegates.wsetscrreg wsetscrreg;
+    public readonly Delegates.wtimeout wtimeout;
+    public UnmanagedLibrary UnmanagedLibrary;
+
+    public NativeMethods (UnmanagedLibrary lib)
+    {
+        UnmanagedLibrary = lib;
+        initscr = lib.GetNativeMethodDelegate<Delegates.initscr> ("initscr");
+        endwin = lib.GetNativeMethodDelegate<Delegates.endwin> ("endwin");
+        isendwin = lib.GetNativeMethodDelegate<Delegates.isendwin> ("isendwin");
+        cbreak = lib.GetNativeMethodDelegate<Delegates.cbreak> ("cbreak");
+        nocbreak = lib.GetNativeMethodDelegate<Delegates.nocbreak> ("nocbreak");
+        echo = lib.GetNativeMethodDelegate<Delegates.echo> ("echo");
+        noecho = lib.GetNativeMethodDelegate<Delegates.noecho> ("noecho");
+        halfdelay = lib.GetNativeMethodDelegate<Delegates.halfdelay> ("halfdelay");
+        raw = lib.GetNativeMethodDelegate<Delegates.raw> ("raw");
+        noraw = lib.GetNativeMethodDelegate<Delegates.noraw> ("noraw");
+        noqiflush = lib.GetNativeMethodDelegate<Delegates.noqiflush> ("noqiflush");
+        qiflush = lib.GetNativeMethodDelegate<Delegates.qiflush> ("qiflush");
+        typeahead = lib.GetNativeMethodDelegate<Delegates.typeahead> ("typeahead");
+        timeout = lib.GetNativeMethodDelegate<Delegates.timeout> ("timeout");
+        wtimeout = lib.GetNativeMethodDelegate<Delegates.wtimeout> ("wtimeout");
+        notimeout = lib.GetNativeMethodDelegate<Delegates.notimeout> ("notimeout");
+        keypad = lib.GetNativeMethodDelegate<Delegates.keypad> ("keypad");
+        meta = lib.GetNativeMethodDelegate<Delegates.meta> ("meta");
+        intrflush = lib.GetNativeMethodDelegate<Delegates.intrflush> ("intrflush");
+        clearok = lib.GetNativeMethodDelegate<Delegates.clearok> ("clearok");
+        idlok = lib.GetNativeMethodDelegate<Delegates.idlok> ("idlok");
+        idcok = lib.GetNativeMethodDelegate<Delegates.idcok> ("idcok");
+        immedok = lib.GetNativeMethodDelegate<Delegates.immedok> ("immedok");
+        leaveok = lib.GetNativeMethodDelegate<Delegates.leaveok> ("leaveok");
+        wsetscrreg = lib.GetNativeMethodDelegate<Delegates.wsetscrreg> ("wsetscrreg");
+        scrollok = lib.GetNativeMethodDelegate<Delegates.scrollok> ("scrollok");
+        nl = lib.GetNativeMethodDelegate<Delegates.nl> ("nl");
+        nonl = lib.GetNativeMethodDelegate<Delegates.nonl> ("nonl");
+        setscrreg = lib.GetNativeMethodDelegate<Delegates.setscrreg> ("setscrreg");
+        refresh = lib.GetNativeMethodDelegate<Delegates.refresh> ("refresh");
+        doupdate = lib.GetNativeMethodDelegate<Delegates.doupdate> ("doupdate");
+        wrefresh = lib.GetNativeMethodDelegate<Delegates.wrefresh> ("wrefresh");
+        redrawwin = lib.GetNativeMethodDelegate<Delegates.redrawwin> ("redrawwin");
+
+        //wredrawwin = lib.GetNativeMethodDelegate<Delegates.wredrawwin> ("wredrawwin");
+        wnoutrefresh = lib.GetNativeMethodDelegate<Delegates.wnoutrefresh> ("wnoutrefresh");
+        move = lib.GetNativeMethodDelegate<Delegates.move> ("move");
+        curs_set = lib.GetNativeMethodDelegate<Delegates.curs_set> ("curs_set");
+        addch = lib.GetNativeMethodDelegate<Delegates.addch> ("addch");
+        echochar = lib.GetNativeMethodDelegate<Delegates.echochar> ("echochar");
+        mvaddch = lib.GetNativeMethodDelegate<Delegates.mvaddch> ("mvaddch");
+        addwstr = lib.GetNativeMethodDelegate<Delegates.addwstr> ("addwstr");
+        mvaddwstr = lib.GetNativeMethodDelegate<Delegates.mvaddwstr> ("mvaddwstr");
+        wmove = lib.GetNativeMethodDelegate<Delegates.wmove> ("wmove");
+        waddch = lib.GetNativeMethodDelegate<Delegates.waddch> ("waddch");
+        attron = lib.GetNativeMethodDelegate<Delegates.attron> ("attron");
+        attroff = lib.GetNativeMethodDelegate<Delegates.attroff> ("attroff");
+        attrset = lib.GetNativeMethodDelegate<Delegates.attrset> ("attrset");
+        getch = lib.GetNativeMethodDelegate<Delegates.getch> ("getch");
+        get_wch = lib.GetNativeMethodDelegate<Delegates.get_wch> ("get_wch");
+        ungetch = lib.GetNativeMethodDelegate<Delegates.ungetch> ("ungetch");
+        mvgetch = lib.GetNativeMethodDelegate<Delegates.mvgetch> ("mvgetch");
+        has_colors = lib.GetNativeMethodDelegate<Delegates.has_colors> ("has_colors");
+        start_color = lib.GetNativeMethodDelegate<Delegates.start_color> ("start_color");
+        init_pair = lib.GetNativeMethodDelegate<Delegates.init_pair> ("init_pair");
+        use_default_colors = lib.GetNativeMethodDelegate<Delegates.use_default_colors> ("use_default_colors");
+        COLOR_PAIRS = lib.GetNativeMethodDelegate<Delegates.COLOR_PAIRS> ("COLOR_PAIRS");
+        getmouse = lib.GetNativeMethodDelegate<Delegates.getmouse> ("getmouse");
+        ungetmouse = lib.GetNativeMethodDelegate<Delegates.ungetmouse> ("ungetmouse");
+        mouseinterval = lib.GetNativeMethodDelegate<Delegates.mouseinterval> ("mouseinterval");
+        mousemask = lib.GetNativeMethodDelegate<Delegates.mousemask> ("mousemask");
+        is_term_resized = lib.GetNativeMethodDelegate<Delegates.is_term_resized> ("is_term_resized");
+        resize_term = lib.GetNativeMethodDelegate<Delegates.resize_term> ("resize_term");
+        resizeterm = lib.GetNativeMethodDelegate<Delegates.resizeterm> ("resizeterm");
+        use_env = lib.GetNativeMethodDelegate<Delegates.use_env> ("use_env");
+        flushinp = lib.GetNativeMethodDelegate<Delegates.flushinp> ("flushinp");
+        def_prog_mode = lib.GetNativeMethodDelegate<Delegates.def_prog_mode> ("def_prog_mode");
+        def_shell_mode = lib.GetNativeMethodDelegate<Delegates.def_shell_mode> ("def_shell_mode");
+        reset_prog_mode = lib.GetNativeMethodDelegate<Delegates.reset_prog_mode> ("reset_prog_mode");
+        reset_shell_mode = lib.GetNativeMethodDelegate<Delegates.reset_shell_mode> ("reset_shell_mode");
+        savetty = lib.GetNativeMethodDelegate<Delegates.savetty> ("savetty");
+        resetty = lib.GetNativeMethodDelegate<Delegates.resetty> ("resetty");
+        set_escdelay = lib.GetNativeMethodDelegate<Delegates.set_escdelay> ("set_escdelay");
+        curses_version = lib.GetNativeMethodDelegate<Delegates.curses_version> ("curses_version");
+    }
+}
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language.

+ 165 - 172
Terminal.Gui/ConsoleDrivers/CursesDriver/constants.cs

@@ -4,182 +4,175 @@
 
 //#define XTERM1006
 
-using System;
+using System.Runtime.InteropServices;
 
-namespace Unix.Terminal {
+namespace Unix.Terminal;
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-	public partial class Curses {
-		public const int A_NORMAL = unchecked((int)0x0);
-		public const int A_STANDOUT = unchecked((int)0x10000);
-		public const int A_UNDERLINE = unchecked((int)0x20000);
-		public const int A_REVERSE = unchecked((int)0x40000);
-		public const int A_BLINK = unchecked((int)0x80000);
-		public const int A_DIM = unchecked((int)0x100000);
-		public const int A_BOLD = unchecked((int)0x200000);
-		public const int A_PROTECT = unchecked((int)0x1000000);
-		public const int A_INVIS = unchecked((int)0x800000);
-		public const int ACS_LLCORNER = unchecked((int)0x40006d);
-		public const int ACS_LRCORNER = unchecked((int)0x40006a);
-		public const int ACS_HLINE = unchecked((int)0x400071);
-		public const int ACS_ULCORNER = unchecked((int)0x40006c);
-		public const int ACS_URCORNER = unchecked((int)0x40006b);
-		public const int ACS_VLINE = unchecked((int)0x400078);
-		public const int ACS_LTEE = unchecked((int)0x400074);
-		public const int ACS_RTEE = unchecked((int)0x400075);
-		public const int ACS_BTEE = unchecked((int)0x400076);
-		public const int ACS_TTEE = unchecked((int)0x400077);
-		public const int ACS_PLUS = unchecked((int)0x40006e);
-		public const int ACS_S1 = unchecked((int)0x40006f);
-		public const int ACS_S9 = unchecked((int)0x400073);
-		public const int ACS_DIAMOND = unchecked((int)0x400060);
-		public const int ACS_CKBOARD = unchecked((int)0x400061);
-		public const int ACS_DEGREE = unchecked((int)0x400066);
-		public const int ACS_PLMINUS = unchecked((int)0x400067);
-		public const int ACS_BULLET = unchecked((int)0x40007e);
-		public const int ACS_LARROW = unchecked((int)0x40002c);
-		public const int ACS_RARROW = unchecked((int)0x40002b);
-		public const int ACS_DARROW = unchecked((int)0x40002e);
-		public const int ACS_UARROW = unchecked((int)0x40002d);
-		public const int ACS_BOARD = unchecked((int)0x400068);
-		public const int ACS_LANTERN = unchecked((int)0x400069);
-		public const int ACS_BLOCK = unchecked((int)0x400030);
-		public const int COLOR_BLACK = unchecked((int)0x0);
-		public const int COLOR_RED = unchecked((int)0x1);
-		public const int COLOR_GREEN = unchecked((int)0x2);
-		public const int COLOR_YELLOW = unchecked((int)0x3);
-		public const int COLOR_BLUE = unchecked((int)0x4);
-		public const int COLOR_MAGENTA = unchecked((int)0x5);
-		public const int COLOR_CYAN = unchecked((int)0x6);
-		public const int COLOR_WHITE = unchecked((int)0x7);
-		public const int COLOR_GRAY = unchecked((int)0x8);
-		public const int KEY_CODE_YES = unchecked((int)0x100);
-		public const int ERR = unchecked((int)0xffffffff);
-		public const int TIOCGWINSZ  = unchecked((int)0x5413);
-		public const int TIOCGWINSZ_MAC  = unchecked((int)0x40087468);
-
-		[Flags]
-		public enum Event : long {
-			Button1Pressed = unchecked((int)0x2),
-			Button1Released = unchecked((int)0x1),
-			Button1Clicked = unchecked((int)0x4),
-			Button1DoubleClicked = unchecked((int)0x8),
-			Button1TripleClicked = unchecked((int)0x10),
-			Button2Pressed = unchecked((int)0x40),
-			Button2Released = unchecked((int)0x20),
-			Button2Clicked = unchecked((int)0x80),
-			Button2DoubleClicked = unchecked((int)0x100),
-			Button2TripleClicked = unchecked((int)0x200),
-			Button3Pressed = unchecked((int)0x800),
-			Button3Released = unchecked((int)0x400),
-			Button3Clicked = unchecked((int)0x1000),
-			Button3DoubleClicked = unchecked((int)0x2000),
-			Button3TripleClicked = unchecked((int)0x4000),
-			ButtonWheeledUp = unchecked((int)0x10000),
-			ButtonWheeledDown = unchecked((int)0x200000),
-			Button4Pressed = unchecked((int)0x80000),
-			Button4Released = unchecked((int)0x40000),
-			Button4Clicked = unchecked((int)0x100000),
-			Button4DoubleClicked = unchecked((int)0x20000),
-			Button4TripleClicked = unchecked((int)0x400000),
-			ButtonShift = unchecked((int)0x4000000),
-			ButtonCtrl = unchecked((int)0x2000000),
-			ButtonAlt = unchecked((int)0x8000000),
-			ReportMousePosition = unchecked((int)0x10000000),
-			AllEvents = unchecked((int)0x7ffffff),
-		}
+public partial class Curses
+{
+    public const int A_NORMAL = 0x0;
+    public const int A_STANDOUT = 0x10000;
+    public const int A_UNDERLINE = 0x20000;
+    public const int A_REVERSE = 0x40000;
+    public const int A_BLINK = 0x80000;
+    public const int A_DIM = 0x100000;
+    public const int A_BOLD = 0x200000;
+    public const int A_PROTECT = 0x1000000;
+    public const int A_INVIS = 0x800000;
+    public const int ACS_LLCORNER = 0x40006d;
+    public const int ACS_LRCORNER = 0x40006a;
+    public const int ACS_HLINE = 0x400071;
+    public const int ACS_ULCORNER = 0x40006c;
+    public const int ACS_URCORNER = 0x40006b;
+    public const int ACS_VLINE = 0x400078;
+    public const int ACS_LTEE = 0x400074;
+    public const int ACS_RTEE = 0x400075;
+    public const int ACS_BTEE = 0x400076;
+    public const int ACS_TTEE = 0x400077;
+    public const int ACS_PLUS = 0x40006e;
+    public const int ACS_S1 = 0x40006f;
+    public const int ACS_S9 = 0x400073;
+    public const int ACS_DIAMOND = 0x400060;
+    public const int ACS_CKBOARD = 0x400061;
+    public const int ACS_DEGREE = 0x400066;
+    public const int ACS_PLMINUS = 0x400067;
+    public const int ACS_BULLET = 0x40007e;
+    public const int ACS_LARROW = 0x40002c;
+    public const int ACS_RARROW = 0x40002b;
+    public const int ACS_DARROW = 0x40002e;
+    public const int ACS_UARROW = 0x40002d;
+    public const int ACS_BOARD = 0x400068;
+    public const int ACS_LANTERN = 0x400069;
+    public const int ACS_BLOCK = 0x400030;
+    public const int COLOR_BLACK = 0x0;
+    public const int COLOR_RED = 0x1;
+    public const int COLOR_GREEN = 0x2;
+    public const int COLOR_YELLOW = 0x3;
+    public const int COLOR_BLUE = 0x4;
+    public const int COLOR_MAGENTA = 0x5;
+    public const int COLOR_CYAN = 0x6;
+    public const int COLOR_WHITE = 0x7;
+    public const int COLOR_GRAY = 0x8;
+    public const int KEY_CODE_YES = 0x100;
+    public const int ERR = unchecked ((int)0xffffffff);
+    public const int TIOCGWINSZ = 0x5413;
+    public const int TIOCGWINSZ_MAC = 0x40087468;
+    [Flags]
+    public enum Event : long
+    {
+        Button1Pressed = 0x2,
+        Button1Released = 0x1,
+        Button1Clicked = 0x4,
+        Button1DoubleClicked = 0x8,
+        Button1TripleClicked = 0x10,
+        Button2Pressed = 0x40,
+        Button2Released = 0x20,
+        Button2Clicked = 0x80,
+        Button2DoubleClicked = 0x100,
+        Button2TripleClicked = 0x200,
+        Button3Pressed = 0x800,
+        Button3Released = 0x400,
+        Button3Clicked = 0x1000,
+        Button3DoubleClicked = 0x2000,
+        Button3TripleClicked = 0x4000,
+        ButtonWheeledUp = 0x10000,
+        ButtonWheeledDown = 0x200000,
+        Button4Pressed = 0x80000,
+        Button4Released = 0x40000,
+        Button4Clicked = 0x100000,
+        Button4DoubleClicked = 0x20000,
+        Button4TripleClicked = 0x400000,
+        ButtonShift = 0x4000000,
+        ButtonCtrl = 0x2000000,
+        ButtonAlt = 0x8000000,
+        ReportMousePosition = 0x10000000,
+        AllEvents = 0x7ffffff
+    }
 #if XTERM1006
-		public const int LeftRightUpNPagePPage= unchecked((int)0x8);
-		public const int DownEnd = unchecked((int)0x6);
-		public const int Home = unchecked((int)0x7);
+    public const int LeftRightUpNPagePPage = unchecked ((int)0x8);
+    public const int DownEnd = unchecked ((int)0x6);
+    public const int Home = unchecked ((int)0x7);
 #else
-		public const int LeftRightUpNPagePPage = unchecked((int)0x0);
-		public const int DownEnd = unchecked((int)0x0);
-		public const int Home = unchecked((int)0x0);
+    public const int LeftRightUpNPagePPage = 0x0;
+    public const int DownEnd = 0x0;
+    public const int Home = 0x0;
 #endif
-		public const int KeyBackspace = unchecked((int)0x107);
-		public const int KeyUp = unchecked((int)0x103);
-		public const int KeyDown = unchecked((int)0x102);
-		public const int KeyLeft = unchecked((int)0x104);
-		public const int KeyRight = unchecked((int)0x105);
-		public const int KeyNPage = unchecked((int)0x152);
-		public const int KeyPPage = unchecked((int)0x153);
-		public const int KeyHome = unchecked((int)0x106);
-		public const int KeyMouse = unchecked((int)0x199);
-		public const int KeyCSI = unchecked((int)0x5b);
-		public const int KeyEnd = unchecked((int)0x168);
-		public const int KeyDeleteChar = unchecked((int)0x14a);
-		public const int KeyInsertChar = unchecked((int)0x14b);
-		public const int KeyTab = unchecked((int)0x009);
-		public const int KeyBackTab = unchecked((int)0x161);
-		public const int KeyF1 = unchecked((int)0x109);
-		public const int KeyF2 = unchecked((int)0x10a);
-		public const int KeyF3 = unchecked((int)0x10b);
-		public const int KeyF4 = unchecked((int)0x10c);
-		public const int KeyF5 = unchecked((int)0x10d);
-		public const int KeyF6 = unchecked((int)0x10e);
-		public const int KeyF7 = unchecked((int)0x10f);
-		public const int KeyF8 = unchecked((int)0x110);
-		public const int KeyF9 = unchecked((int)0x111);
-		public const int KeyF10 = unchecked((int)0x112);
-		public const int KeyF11 = unchecked((int)0x113);
-		public const int KeyF12 = unchecked((int)0x114);
-		public const int KeyResize = unchecked((int)0x19a);
-		public const int ShiftKeyUp = unchecked((int)0x151);
-		public const int ShiftKeyDown = unchecked((int)0x150);
-		public const int ShiftKeyLeft = unchecked((int)0x189);
-		public const int ShiftKeyRight = unchecked((int)0x192);
-		public const int ShiftKeyNPage = unchecked((int)0x18c);
-		public const int ShiftKeyPPage = unchecked((int)0x18e);
-		public const int ShiftKeyHome = unchecked((int)0x187);
-		public const int ShiftKeyEnd = unchecked((int)0x182);
-		public const int AltKeyUp = unchecked((int)0x234 + LeftRightUpNPagePPage);
-		public const int AltKeyDown = unchecked((int)0x20b + DownEnd);
-		public const int AltKeyLeft = unchecked((int)0x21f + LeftRightUpNPagePPage);
-		public const int AltKeyRight = unchecked((int)0x22e + LeftRightUpNPagePPage);
-		public const int AltKeyNPage = unchecked((int)0x224 + LeftRightUpNPagePPage);
-		public const int AltKeyPPage = unchecked((int)0x229 + LeftRightUpNPagePPage);
-		public const int AltKeyHome = unchecked((int)0x215 + Home);
-		public const int AltKeyEnd = unchecked((int)0x210 + DownEnd);
-		public const int CtrlKeyUp = unchecked((int)0x236 + LeftRightUpNPagePPage);
-		public const int CtrlKeyDown = unchecked((int)0x20d + DownEnd);
-		public const int CtrlKeyLeft = unchecked((int)0x221 + LeftRightUpNPagePPage);
-		public const int CtrlKeyRight = unchecked((int)0x230 + LeftRightUpNPagePPage);
-		public const int CtrlKeyNPage = unchecked((int)0x226 + LeftRightUpNPagePPage);
-		public const int CtrlKeyPPage = unchecked((int)0x22b + LeftRightUpNPagePPage);
-		public const int CtrlKeyHome = unchecked((int)0x217 + Home);
-		public const int CtrlKeyEnd = unchecked((int)0x212 + DownEnd);
-		public const int ShiftCtrlKeyUp = unchecked((int)0x237 + LeftRightUpNPagePPage);
-		public const int ShiftCtrlKeyDown = unchecked((int)0x20e + DownEnd);
-		public const int ShiftCtrlKeyLeft = unchecked((int)0x222 + LeftRightUpNPagePPage);
-		public const int ShiftCtrlKeyRight = unchecked((int)0x231 + LeftRightUpNPagePPage);
-		public const int ShiftCtrlKeyNPage = unchecked((int)0x227 + LeftRightUpNPagePPage);
-		public const int ShiftCtrlKeyPPage = unchecked((int)0x22c + LeftRightUpNPagePPage);
-		public const int ShiftCtrlKeyHome = unchecked((int)0x218 + Home);
-		public const int ShiftCtrlKeyEnd = unchecked((int)0x213 + DownEnd);
-		public const int ShiftAltKeyUp = unchecked((int)0x235 + LeftRightUpNPagePPage);
-		public const int ShiftAltKeyDown = unchecked((int)0x20c + DownEnd);
-		public const int ShiftAltKeyLeft = unchecked((int)0x220 + LeftRightUpNPagePPage);
-		public const int ShiftAltKeyRight = unchecked((int)0x22f + LeftRightUpNPagePPage);
-		public const int ShiftAltKeyNPage = unchecked((int)0x225 + LeftRightUpNPagePPage);
-		public const int ShiftAltKeyPPage = unchecked((int)0x22a + LeftRightUpNPagePPage);
-		public const int ShiftAltKeyHome = unchecked((int)0x216 + Home);
-		public const int ShiftAltKeyEnd = unchecked((int)0x211 + DownEnd);
-		public const int AltCtrlKeyNPage = unchecked((int)0x228 + LeftRightUpNPagePPage);
-		public const int AltCtrlKeyPPage = unchecked((int)0x22d + LeftRightUpNPagePPage);
-		public const int AltCtrlKeyHome = unchecked((int)0x219 + Home);
-		public const int AltCtrlKeyEnd = unchecked((int)0x214 + DownEnd);
-
-		// see #949
-		static public int LC_ALL { get; private set; }
-		static Curses ()
-		{
-			LC_ALL = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (System.Runtime.InteropServices.OSPlatform.OSX) ? 0 : 6;
-		}
+    public const int KeyBackspace = 0x107;
+    public const int KeyUp = 0x103;
+    public const int KeyDown = 0x102;
+    public const int KeyLeft = 0x104;
+    public const int KeyRight = 0x105;
+    public const int KeyNPage = 0x152;
+    public const int KeyPPage = 0x153;
+    public const int KeyHome = 0x106;
+    public const int KeyMouse = 0x199;
+    public const int KeyCSI = 0x5b;
+    public const int KeyEnd = 0x168;
+    public const int KeyDeleteChar = 0x14a;
+    public const int KeyInsertChar = 0x14b;
+    public const int KeyTab = 0x009;
+    public const int KeyBackTab = 0x161;
+    public const int KeyF1 = 0x109;
+    public const int KeyF2 = 0x10a;
+    public const int KeyF3 = 0x10b;
+    public const int KeyF4 = 0x10c;
+    public const int KeyF5 = 0x10d;
+    public const int KeyF6 = 0x10e;
+    public const int KeyF7 = 0x10f;
+    public const int KeyF8 = 0x110;
+    public const int KeyF9 = 0x111;
+    public const int KeyF10 = 0x112;
+    public const int KeyF11 = 0x113;
+    public const int KeyF12 = 0x114;
+    public const int KeyResize = 0x19a;
+    public const int ShiftKeyUp = 0x151;
+    public const int ShiftKeyDown = 0x150;
+    public const int ShiftKeyLeft = 0x189;
+    public const int ShiftKeyRight = 0x192;
+    public const int ShiftKeyNPage = 0x18c;
+    public const int ShiftKeyPPage = 0x18e;
+    public const int ShiftKeyHome = 0x187;
+    public const int ShiftKeyEnd = 0x182;
+    public const int AltKeyUp = unchecked (0x234 + LeftRightUpNPagePPage);
+    public const int AltKeyDown = unchecked (0x20b + DownEnd);
+    public const int AltKeyLeft = unchecked (0x21f + LeftRightUpNPagePPage);
+    public const int AltKeyRight = unchecked (0x22e + LeftRightUpNPagePPage);
+    public const int AltKeyNPage = unchecked (0x224 + LeftRightUpNPagePPage);
+    public const int AltKeyPPage = unchecked (0x229 + LeftRightUpNPagePPage);
+    public const int AltKeyHome = unchecked (0x215 + Home);
+    public const int AltKeyEnd = unchecked (0x210 + DownEnd);
+    public const int CtrlKeyUp = unchecked (0x236 + LeftRightUpNPagePPage);
+    public const int CtrlKeyDown = unchecked (0x20d + DownEnd);
+    public const int CtrlKeyLeft = unchecked (0x221 + LeftRightUpNPagePPage);
+    public const int CtrlKeyRight = unchecked (0x230 + LeftRightUpNPagePPage);
+    public const int CtrlKeyNPage = unchecked (0x226 + LeftRightUpNPagePPage);
+    public const int CtrlKeyPPage = unchecked (0x22b + LeftRightUpNPagePPage);
+    public const int CtrlKeyHome = unchecked (0x217 + Home);
+    public const int CtrlKeyEnd = unchecked (0x212 + DownEnd);
+    public const int ShiftCtrlKeyUp = unchecked (0x237 + LeftRightUpNPagePPage);
+    public const int ShiftCtrlKeyDown = unchecked (0x20e + DownEnd);
+    public const int ShiftCtrlKeyLeft = unchecked (0x222 + LeftRightUpNPagePPage);
+    public const int ShiftCtrlKeyRight = unchecked (0x231 + LeftRightUpNPagePPage);
+    public const int ShiftCtrlKeyNPage = unchecked (0x227 + LeftRightUpNPagePPage);
+    public const int ShiftCtrlKeyPPage = unchecked (0x22c + LeftRightUpNPagePPage);
+    public const int ShiftCtrlKeyHome = unchecked (0x218 + Home);
+    public const int ShiftCtrlKeyEnd = unchecked (0x213 + DownEnd);
+    public const int ShiftAltKeyUp = unchecked (0x235 + LeftRightUpNPagePPage);
+    public const int ShiftAltKeyDown = unchecked (0x20c + DownEnd);
+    public const int ShiftAltKeyLeft = unchecked (0x220 + LeftRightUpNPagePPage);
+    public const int ShiftAltKeyRight = unchecked (0x22f + LeftRightUpNPagePPage);
+    public const int ShiftAltKeyNPage = unchecked (0x225 + LeftRightUpNPagePPage);
+    public const int ShiftAltKeyPPage = unchecked (0x22a + LeftRightUpNPagePPage);
+    public const int ShiftAltKeyHome = unchecked (0x216 + Home);
+    public const int ShiftAltKeyEnd = unchecked (0x211 + DownEnd);
+    public const int AltCtrlKeyNPage = unchecked (0x228 + LeftRightUpNPagePPage);
+    public const int AltCtrlKeyPPage = unchecked (0x22d + LeftRightUpNPagePPage);
+    public const int AltCtrlKeyHome = unchecked (0x219 + Home);
+    public const int AltCtrlKeyEnd = unchecked (0x214 + DownEnd);
 
-		static public int ColorPair (int n)
-		{
-			return 0 + n * 256;
-		}
-	}
-#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+    // see #949
+    public static int LC_ALL { get; }
+    static Curses () { LC_ALL = RuntimeInformation.IsOSPlatform (OSPlatform.OSX) ? 0 : 6; }
+    public static int ColorPair (int n) { return 0 + n * 256; }
 }
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member

+ 48 - 139
Terminal.Gui/ConsoleDrivers/CursesDriver/handles.cs

@@ -25,153 +25,62 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-using System;
 
-namespace Unix.Terminal {
+namespace Unix.Terminal;
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
-	public partial class Curses {
-		public class Window {
-			public readonly IntPtr Handle;
-			static Window curscr;
-			static Window stdscr;
-
-			static Window ()
-			{
-				Curses.initscr ();
-				stdscr = new Window (Curses.console_sharp_get_stdscr ());
-				curscr = new Window (Curses.console_sharp_get_curscr ());
-			}
-
-			internal Window (IntPtr handle)
-			{
-				Handle = handle;
-			}
-
-			static public Window Standard {
-				get {
-					return stdscr;
-				}
-			}
-
-			static public Window Current {
-				get {
-					return curscr;
-				}
-			}
-
-			public int wtimeout (int delay)
-			{
-				return Curses.wtimeout (Handle, delay);
-			}
-
-			public int notimeout (bool bf)
-			{
-				return Curses.notimeout (Handle, bf);
-			}
-
-			public int keypad (bool bf)
-			{
-				return Curses.keypad (Handle, bf);
-			}
-
-			public int meta (bool bf)
-			{
-				return Curses.meta (Handle, bf);
-			}
-
-			public int intrflush (bool bf)
-			{
-				return Curses.intrflush (Handle, bf);
-			}
-
-			public int clearok (bool bf)
-			{
-				return Curses.clearok (Handle, bf);
-			}
-
-			public int idlok (bool bf)
-			{
-				return Curses.idlok (Handle, bf);
-			}
-
-			public void idcok (bool bf)
-			{
-				Curses.idcok (Handle, bf);
-			}
-
-			public void immedok (bool bf)
-			{
-				Curses.immedok (Handle, bf);
-			}
-
-			public int leaveok (bool bf)
-			{
-				return Curses.leaveok (Handle, bf);
-			}
-
-			public int setscrreg (int top, int bot)
-			{
-				return Curses.wsetscrreg (Handle, top, bot);
-			}
-
-			public int scrollok (bool bf)
-			{
-				return Curses.scrollok (Handle, bf);
-			}
-
-			public int wrefresh ()
-			{
-				return Curses.wrefresh (Handle);
-			}
-
-			public int redrawwin ()
-			{
-				return Curses.redrawwin (Handle);
-			}
-
+public partial class Curses
+{
+    public class Window
+    {
+        public readonly nint Handle;
+
+        static Window ()
+        {
+            initscr ();
+            Standard = new Window (console_sharp_get_stdscr ());
+            Current = new Window (console_sharp_get_curscr ());
+        }
+
+        internal Window (nint handle) { Handle = handle; }
+        public static Window Standard { get; }
+        public static Window Current { get; }
+        public int wtimeout (int delay) { return Curses.wtimeout (Handle, delay); }
+        public int notimeout (bool bf) { return Curses.notimeout (Handle, bf); }
+        public int keypad (bool bf) { return Curses.keypad (Handle, bf); }
+        public int meta (bool bf) { return Curses.meta (Handle, bf); }
+        public int intrflush (bool bf) { return Curses.intrflush (Handle, bf); }
+        public int clearok (bool bf) { return Curses.clearok (Handle, bf); }
+        public int idlok (bool bf) { return Curses.idlok (Handle, bf); }
+        public void idcok (bool bf) { Curses.idcok (Handle, bf); }
+        public void immedok (bool bf) { Curses.immedok (Handle, bf); }
+        public int leaveok (bool bf) { return Curses.leaveok (Handle, bf); }
+        public int setscrreg (int top, int bot) { return wsetscrreg (Handle, top, bot); }
+        public int scrollok (bool bf) { return Curses.scrollok (Handle, bf); }
+        public int wrefresh () { return Curses.wrefresh (Handle); }
+        public int redrawwin () { return Curses.redrawwin (Handle); }
 #if false
 			public int wredrawwin (int beg_line, int num_lines)
 			{
 				return Curses.wredrawwin (Handle, beg_line, num_lines);
 			}
 #endif
-			public int wnoutrefresh ()
-			{
-				return Curses.wnoutrefresh (Handle);
-			}
-	
-			public int move (int line, int col)
-			{
-				return Curses.wmove (Handle, line, col);
-			}
-	
-			public int addch (char ch)
-			{
-				return Curses.waddch (Handle, ch);
-			}
-
-			//public int echochar (char ch)
-			//{
-			//	return Curses.wechochar (Handle, ch);
-			//}
-
-			public int refresh ()
-			{
-				return Curses.wrefresh (Handle);
-			}
-		}
-	
-	 	// Currently unused, to do later
-	 	internal class Screen {
-	 		public readonly IntPtr Handle;
-	 		
-	 		internal Screen (IntPtr handle)
-	 		{
-	 			Handle = handle;
-	 		}
-	 	}
+        public int wnoutrefresh () { return Curses.wnoutrefresh (Handle); }
+        public int move (int line, int col) { return wmove (Handle, line, col); }
+        public int addch (char ch) { return waddch (Handle, ch); }
+
+        //public int echochar (char ch)
+        //{
+        //	return Curses.wechochar (Handle, ch);
+        //}
+        public int refresh () { return Curses.wrefresh (Handle); }
+    }
+
+    // Currently unused, to do later
+    internal class Screen
+    {
+        public readonly nint Handle;
+        internal Screen (nint handle) { Handle = handle; }
+    }
 
 #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
-	}
-
 }

+ 124 - 113
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqReq.cs

@@ -1,114 +1,125 @@
-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;
-			}
-		}
-	}
+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>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;
+    }
+
+    /// <summary>Gets the number of unfinished requests.</summary>
+    public int NumOutstanding { get; set; }
+
+    /// <summary>Gets the number of requests.</summary>
+    public int NumRequests { get; }
+
+    /// <summary>Gets the Escape Sequence Termintor (e.g. ESC[8t ... t is the terminator).</summary>
+    public string Terminator { get; }
+}
+
+// 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 ();
+
+    /// <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)
+        {
+            EscSeqReqStatus 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>
+    ///     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)
+        {
+            EscSeqReqStatus found = Statuses.Find (x => x.Terminator == terminator);
+
+            if (found == null)
+            {
+                return false;
+            }
+
+            if (found != null && found.NumOutstanding > 0)
+            {
+                return true;
+            }
+
+            // 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;
+        }
+    }
+
+    /// <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)
+        {
+            EscSeqReqStatus 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);
+                }
+            }
+        }
+    }
 }

+ 1476 - 1200
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -1,1209 +1,1485 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
+using System.Diagnostics;
 using System.Management;
 using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
+
 /// <summary>
-/// Provides a platform-independent API for managing ANSI escape sequences.
+///     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/
+///     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)KeyCode.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>
-	/// <remarks>
-	/// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
-	/// Use Alternate Screen Buffer, xterm. 
-	/// </remarks>
-	public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
-
-	/// <summary>
-	/// ESC [ ? 1047 l - Restore xterm working buffer (with backscroll)
-	/// </summary>
-	/// <remarks>
-	/// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
-	/// Use Normal Screen Buffer, xterm.  Clear the screen first if in the Alternate Screen Buffer.
-	/// </remarks>
-	public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
-
-	/// <summary>
-	/// ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
-	/// </summary>
-	/// <remarks>
-	/// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
-	/// Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer,
-	/// clearing it first.
-	/// This control combines the effects of the 1047 and 1048 modes.
-	/// Use this with terminfo-based applications rather than the 47 mode.
-	/// </remarks>
-	public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
-
-	/// <summary>
-	/// ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
-	/// </summary>
-	/// <remarks>
-	/// From https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
-	/// Use Normal Screen Buffer and restore cursor as in DECRC, xterm.
-	/// resource.This combines the effects of the 1047 and 1048  modes.
-	/// </remarks>
-	public static readonly string CSI_RestoreCursorAndRestoreAltBufferWithBackscroll = 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="row">Origin is (1,1).</param>
-	/// <param name="col">Origin is (1,1).</param>
-	/// <returns></returns>
-	public static string CSI_SetCursorPosition (int row, int col) => $"{CSI}{row};{col}H";
-
-
-	//ESC [ <y> ; <x> f - HVP     Horizontal Vertical Position* Cursor moves to<x>; <y> coordinate within the viewport, where <x> is the column of the<y> line
-	//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 [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the foreground color.
-	/// </summary>
-	/// <param name="code">One of the 16 color codes.</param>
-	/// <returns></returns>
-	public static string CSI_SetForegroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code);
-
-	/// <summary>
-	/// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the background color.
-	/// </summary>
-	/// <param name="code">One of the 16 color codes.</param>
-	/// <returns></returns>
-	public static string CSI_SetBackgroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code + 10);
-
-	/// <summary>
-	/// ESC[38;5;{id}m - Set foreground color (256 colors)
-	/// </summary>
-	public static string CSI_SetForegroundColor256 (int color) => $"{CSI}38;5;{color}m";
-
-	/// <summary>
-	/// ESC[48;5;{id}m - Set background color (256 colors)
-	/// </summary>
-	public static string CSI_SetBackgroundColor256 (int color) => $"{CSI}48;5;{color}m";
-
-	/// <summary>
-	/// ESC[38;2;{r};{g};{b}m	Set foreground color as RGB.
-	/// </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;
-			}
-			if (!string.IsNullOrEmpty (terminator)) {
-				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);
-			} else {
-				// BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803
-				// This is caused by NetDriver depending on Console.KeyAvailable?
-				throw new InvalidOperationException ($"CSI response, but there's no terminator");
-				//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="terminator">The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or <see cref="CSI_SendDeviceAttributes2"/>.</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 terminator, string value, ref ConsoleModifiers mod)
-	{
-		ConsoleKey key;
-		switch (terminator) {
-		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 (point is null) {
-				point = pos;
-			}
-
-			if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0) {
-				Application.MainLoop.AddIdle (() => {
-					Task.Run (async () => await ProcessContinuousButtonPressedAsync (buttonState, continuousButtonPressedHandler));
-					return false;
-				});
-			} else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition)) {
-				point = pos;
-				// The isButtonPressed must always be true, otherwise we can lose the feature
-				// If mouse flags has ReportMousePosition this feature won't run
-				// but is always prepared with the new location
-				//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 view = Application.WantContinuousButtonPressedView;
-			if (view == null)
-				break;
-			if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
-				Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, point ?? Point.Empty));
-			}
-		}
-	}
-
-	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;
-	}
+public static class EscSeqUtils
+{
+    /// <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>
+    ///     Escape key code (ASCII 27/0x1B).
+    /// </summary>
+    public static readonly char KeyEsc = (char)KeyCode.Esc;
+
+    /// <summary>
+    ///     ESC [ - The CSI (Control Sequence Introducer).
+    /// </summary>
+    public static readonly string CSI = $"{KeyEsc}[";
+
+    /// <summary>
+    ///     ESC [ ? 1047 h - Activate xterm alternative buffer (no backscroll)
+    /// </summary>
+    /// <remarks>
+    ///     From
+    ///     https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+    ///     Use Alternate Screen Buffer, xterm.
+    /// </remarks>
+    public static readonly string CSI_ActivateAltBufferNoBackscroll = CSI + "?1047h";
+
+    /// <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>
+    ///     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 [ ? 1047 l - Restore xterm working buffer (with backscroll)
+    /// </summary>
+    /// <remarks>
+    ///     From
+    ///     https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+    ///     Use Normal Screen Buffer, xterm.  Clear the screen first if in the Alternate Screen Buffer.
+    /// </remarks>
+    public static readonly string CSI_RestoreAltBufferWithBackscroll = CSI + "?1047l";
+
+    /// <summary>
+    ///     ESC [ ? 1049 l - Restore cursor position and restore xterm working buffer (with backscroll)
+    /// </summary>
+    /// <remarks>
+    ///     From
+    ///     https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+    ///     Use Normal Screen Buffer and restore cursor as in DECRC, xterm.
+    ///     resource.This combines the effects of the 1047 and 1048  modes.
+    /// </remarks>
+    public static readonly string CSI_RestoreCursorAndRestoreAltBufferWithBackscroll = CSI + "?1049l";
+
+    /// <summary>
+    ///     ESC [ ? 1049 h - Save cursor position and activate xterm alternative buffer (no backscroll)
+    /// </summary>
+    /// <remarks>
+    ///     From
+    ///     https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
+    ///     Save cursor as in DECSC, xterm. After saving the cursor, switch to the Alternate Screen Buffer,
+    ///     clearing it first.
+    ///     This control combines the effects of the 1047 and 1048 modes.
+    ///     Use this with terminfo-based applications rather than the 47 mode.
+    /// </remarks>
+    public static readonly string CSI_SaveCursorAndActivateAltBufferNoBackscroll = CSI + "?1049h";
+
+    //private static bool isButtonReleased;
+    private static bool isButtonClicked;
+
+    private static bool isButtonDoubleClicked;
+
+    //private static MouseFlags? lastMouseButtonReleased;
+    private static bool isButtonPressed;
+    private static bool isButtonTripleClicked;
+
+    private static MouseFlags? lastMouseButtonPressed;
+    private static Point? point;
+
+    /// <summary>
+    ///     Control sequence for disabling mouse events.
+    /// </summary>
+    public static string CSI_DisableMouseEvents { get; set; } =
+        CSI_DisableAnyEventMouse + CSI_DisableUrxvtExtModeMouse + CSI_DisableSgrExtModeMouse;
+
+    /// <summary>
+    ///     Control sequence for enabling mouse events.
+    /// </summary>
+    public static string CSI_EnableMouseEvents { get; set; } =
+        CSI_EnableAnyEventMouse + CSI_EnableUrxvtExtModeMouse + CSI_EnableSgrExtModeMouse;
+
+    /// <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) { return $"{CSI}{(int)option}J"; }
+
+    /// <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 (Point);
+        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;
+                }
+
+                if (escSeqRequests != null && escSeqRequests.HasResponse (terminator))
+                {
+                    isResponse = true;
+                    escSeqRequests.Remove (terminator);
+
+                    return;
+                }
+
+                if (!string.IsNullOrEmpty (terminator))
+                {
+                    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);
+                }
+                else
+                {
+                    // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/2803
+                    // This is caused by NetDriver depending on Console.KeyAvailable?
+                    throw new InvalidOperationException ("CSI response, but there's no terminator");
+
+                    //newConsoleKeyInfo = new ConsoleKeyInfo ('\0',
+                    //	key,
+                    //	(mod & ConsoleModifiers.Shift) != 0,
+                    //	(mod & ConsoleModifiers.Alt) != 0,
+                    //	(mod & ConsoleModifiers.Control) != 0);
+                }
+
+                break;
+        }
+    }
+
+    /// <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="ConsoleKey"/> depending on terminating and value.
+    /// </summary>
+    /// <param name="terminator">
+    ///     The terminator indicating a reply to <see cref="CSI_SendDeviceAttributes"/> or
+    ///     <see cref="CSI_SendDeviceAttributes2"/>.
+    /// </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 terminator, string value, ref ConsoleModifiers mod)
+    {
+        ConsoleKey key;
+
+        switch (terminator)
+        {
+            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>
+    ///     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 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;
+        var values = new string [nSep];
+        var valueIdx = 0;
+        var terminating = "";
+
+        for (var i = 2; i < kChar.Length; i++)
+        {
+            char 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>
+    ///     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 = { };
+        var length = 0;
+
+        foreach (ConsoleKeyInfo kc in cki)
+        {
+            length++;
+            Array.Resize (ref kChar, length);
+            kChar [length - 1] = kc.KeyChar;
+        }
+
+        return kChar;
+    }
+
+    /// <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 ();
+        var buttonCode = 0;
+        var foundButtonCode = false;
+        var foundPoint = 0;
+        var value = "";
+        char [] kChar = GetKeyCharArray (cki);
+
+        //System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
+        for (var i = 0; i < kChar.Length; i++)
+        {
+            char 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 (point is null)
+            {
+                point = pos;
+            }
+
+            if ((mouseFlags [0] & MouseFlags.ReportMousePosition) == 0)
+            {
+                Application.MainLoop.AddIdle (
+                                              () =>
+                                              {
+                                                  Task.Run (
+                                                            async () => await ProcessContinuousButtonPressedAsync (
+                                                                         buttonState,
+                                                                         continuousButtonPressedHandler));
+
+                                                  return false;
+                                              });
+            }
+            else if (mouseFlags [0].HasFlag (MouseFlags.ReportMousePosition))
+            {
+                point = pos;
+
+                // The isButtonPressed must always be true, otherwise we can lose the feature
+                // If mouse flags has ReportMousePosition this feature won't run
+                // but is always prepared with the new location
+                //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}");
+        //}
+    }
+
+    // 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 (var 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;
+    }
+
+    /// <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;
+        char 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;
+    }
+
+    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 async Task ProcessButtonClickedAsync ()
+    {
+        await Task.Delay (300);
+        isButtonClicked = false;
+    }
+
+    private static async Task ProcessButtonDoubleClickedAsync ()
+    {
+        await Task.Delay (300);
+        isButtonDoubleClicked = false;
+    }
+
+    private static async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag, Action<MouseFlags, Point> continuousButtonPressedHandler)
+    {
+        while (isButtonPressed)
+        {
+            await Task.Delay (100);
+
+            View view = Application.WantContinuousButtonPressedView;
+
+            if (view == null)
+            {
+                break;
+            }
+
+            if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
+            {
+                Application.Invoke (() => continuousButtonPressedHandler (mouseFlag, point ?? Point.Empty));
+            }
+        }
+    }
+
+    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;
+    }
+
+    #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) { return $"{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="row">Origin is (1,1).</param>
+    /// <param name="col">Origin is (1,1).</param>
+    /// <returns></returns>
+    public static string CSI_SetCursorPosition (int row, int col) { return $"{CSI}{row};{col}H"; }
+
+    //ESC [ <y> ; <x> f - HVP     Horizontal Vertical Position* Cursor moves to<x>; <y> coordinate within the viewport, where <x> is the column of the<y> line
+    //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) { return $"{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) { return $"{CSI}{string.Join (";", parameters)}m"; }
+
+    /// <summary>
+    ///     ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])"/> to set the foreground color.
+    /// </summary>
+    /// <param name="code">One of the 16 color codes.</param>
+    /// <returns></returns>
+    public static string CSI_SetForegroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code); }
+
+    /// <summary>
+    ///     ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])"/> to set the background color.
+    /// </summary>
+    /// <param name="code">One of the 16 color codes.</param>
+    /// <returns></returns>
+    public static string CSI_SetBackgroundColor (AnsiColorCode code) { return CSI_SetGraphicsRendition ((int)code + 10); }
+
+    /// <summary>
+    ///     ESC[38;5;{id}m - Set foreground color (256 colors)
+    /// </summary>
+    public static string CSI_SetForegroundColor256 (int color) { return $"{CSI}38;5;{color}m"; }
+
+    /// <summary>
+    ///     ESC[48;5;{id}m - Set background color (256 colors)
+    /// </summary>
+    public static string CSI_SetBackgroundColor256 (int color) { return $"{CSI}48;5;{color}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) { return $"{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) { return $"{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
 }

+ 1698 - 1978
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -1,1990 +1,1710 @@
 //
 // FakeConsole.cs: A fake .NET Windows Console API implementation for unit tests.
 //
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
+
 using Terminal.Gui.ConsoleDrivers;
-using Rune = System.Text.Rune;
 
 namespace Terminal.Gui;
 
 #pragma warning disable RCS1138 // Add summary to documentation comment.
-/// <summary>
-/// 
-/// </summary>
-public static class FakeConsole {
+/// <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.
+    //
+    // Summary:
+    //	Gets or sets the width of the console window.
+    //
+    // Returns:
+    //	The width of the console window measured in columns.
+    //
+    // Exceptions:
+    //	T:System.ArgumentOutOfRangeException:
+    //	The value of the System.Console.WindowWidth property or the value of the System.Console.WindowHeight
+    //	property is less than or equal to 0.-or-The value of the System.Console.WindowHeight
+    //	property plus the value of the System.Console.WindowTop property is greater than
+    //	or equal to System.Int16.MaxValue.-or-The value of the System.Console.WindowWidth
+    //	property or the value of the System.Console.WindowHeight property is greater
+    //	than the largest possible window width or height for the current screen resolution
+    //	and console font.
+    //
+    //	T:System.IO.IOException:
+    //	Error reading or writing information.
 #pragma warning disable RCS1138 // Add summary to documentation comment.
 
-	/// <summary>
-	/// Specifies the initial console width.
-	/// </summary>
-	public const int WIDTH = 80;
-
-	/// <summary>
-	/// Specifies the initial console height.
-	/// </summary>
-	public const int HEIGHT = 25;
-
-	/// <summary>
-	/// 
-	/// </summary>
-	public static 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 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:
-	//	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:
-	//	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 error stream.
-	//
-	// Returns:
-	//	The standard error stream.
-	/// <summary>
-	/// 
-	/// </summary>
-	public static Stream OpenStandardError ()
-	{
-		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 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 input stream.
-	//
-	// Returns:
-	//	The standard input stream.
-	/// <summary>
-	/// 
-	/// </summary>
-	public static Stream OpenStandardInput ()
-	{
-		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 output stream.
-	//
-	// Returns:
-	//	The standard output stream.
-	/// <summary>
-	/// 
-	/// </summary>
-	public static Stream OpenStandardOutput ()
-	{
-		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:
-	//	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>
-	/// 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="KeyCode"/> onto <see cref="MockKeyPresses"/>.
-	/// </summary>
-	/// <param name="key"></param>
-	public static void PushMockKeyPress (KeyCode key)
-	{
-		MockKeyPresses.Push (new ConsoleKeyInfo (
-			(char)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask),
-			ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key,
-			key.HasFlag (KeyCode.ShiftMask),
-			key.HasFlag (KeyCode.AltMask),
-			key.HasFlag (KeyCode.CtrlMask)));
-	}
-
-	//
-	// 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:
-	//	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 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 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 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 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 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 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 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 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 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 object to the standard output
-	//	stream.
-	//
-	// Parameters:
-	//	value:
-	//	The value to write, or null.
-	//
-	// Exceptions:
-	//	T:System.IO.IOException:
-	//	An I/O error occurred.
-	/// <summary>
-	/// 
-	/// </summary>
-	/// <param name="value"></param>
-	public static void Write (object value)
-	{
-		if (value is Rune rune) {
-			Write ((char)rune.Value);
-		} else {
-			throw new NotImplementedException ();
-		}
-	}
-
-	//
-	// Summary:
-	//	Writes the text representation of the specified 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 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 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 ();
-	}
-
-	//
-	// 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 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 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 ();
-	}
-
-	//[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 ();
-	}
-
-	//[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 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 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 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 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 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 ();
-	}
-
-}
+    /// <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.
+    private static readonly 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.
+    private static readonly 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 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.
+    private static char [,] _buffer = new char [WindowWidth, WindowHeight];
+
+    /// <summary></summary>
+    public static void Clear ()
+    {
+        _buffer = new char [BufferWidth, BufferHeight];
+        SetCursorPosition (0, 0);
+    }
+
+    //
+    // 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:
+    //	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 error stream.
+    //
+    // Returns:
+    //	The standard error stream.
+    /// <summary></summary>
+    public static Stream OpenStandardError () { 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 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 input stream.
+    //
+    // Returns:
+    //	The standard input stream.
+    /// <summary></summary>
+    public static Stream OpenStandardInput () { 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 output stream.
+    //
+    // Returns:
+    //	The standard output stream.
+    /// <summary></summary>
+    public static Stream OpenStandardOutput () { 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:
+    //	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>A stack of keypresses to return when ReadKey is called.</summary>
+    public static Stack<ConsoleKeyInfo> MockKeyPresses = new ();
+
+    /// <summary>Helper to push a <see cref="KeyCode"/> onto <see cref="MockKeyPresses"/>.</summary>
+    /// <param name="key"></param>
+    public static void PushMockKeyPress (KeyCode key)
+    {
+        MockKeyPresses.Push (
+                             new ConsoleKeyInfo (
+                                                 (char)(key
+                                                        & ~KeyCode.CtrlMask
+                                                        & ~KeyCode.ShiftMask
+                                                        & ~KeyCode.AltMask),
+                                                 ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key,
+                                                 key.HasFlag (KeyCode.ShiftMask),
+                                                 key.HasFlag (KeyCode.AltMask),
+                                                 key.HasFlag (KeyCode.CtrlMask)
+                                                )
+                            );
+    }
+
+    //
+    // 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:
+    //	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 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 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 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 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 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 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 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 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 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 object to the standard output
+    //	stream.
+    //
+    // Parameters:
+    //	value:
+    //	The value to write, or null.
+    //
+    // Exceptions:
+    //	T:System.IO.IOException:
+    //	An I/O error occurred.
+    /// <summary></summary>
+    /// <param name="value"></param>
+    public static void Write (object value)
+    {
+        if (value is Rune rune)
+        {
+            Write ((char)rune.Value);
+        }
+        else
+        {
+            throw new NotImplementedException ();
+        }
+    }
+
+    //
+    // Summary:
+    //	Writes the text representation of the specified 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 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 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 (); }
+
+    //
+    // 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 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 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 (); }
+
+    //[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 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 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 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 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 (char ch in buffer)
+        {
+            _buffer [CursorLeft, CursorTop] += ch;
+        }
+    }
+
+    //
+    // 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 (); }
+
+    //[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 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 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 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 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 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 (); }
+}

+ 526 - 433
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -1,448 +1,541 @@
 //
 // FakeDriver.cs: A fake ConsoleDriver for unit tests. 
 //
-using System;
+
 using System.Diagnostics;
 using System.Runtime.InteropServices;
-using System.Text;
 using Terminal.Gui.ConsoleDrivers;
 
 // Alias Console to MockConsole so we don't accidentally use Console
-using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Implements a mock ConsoleDriver for unit testing
-/// </summary>
-public class FakeDriver : ConsoleDriver {
+/// <summary>Implements a mock ConsoleDriver for unit testing</summary>
+public class FakeDriver : ConsoleDriver
+{
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
 
-	public class Behaviors {
-		public 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;
-
-			// double check usage is correct
-			Debug.Assert (useFakeClipboard == false && fakeClipboardAlwaysThrowsNotSupportedException == false);
-			Debug.Assert (useFakeClipboard == false && fakeClipboardIsSupportedAlwaysTrue == false);
-		}
-	}
-
-	public static FakeDriver.Behaviors FakeBehaviors = new Behaviors ();
-
-	public override bool SupportsTrueColor => false;
-
-	public FakeDriver ()
-	{
-		Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-		Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-		if (FakeBehaviors.UseFakeClipboard) {
-			Clipboard = new FakeClipboard (FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException, FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse);
-		} else {
-			if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
-				Clipboard = new WindowsClipboard ();
-			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
-				Clipboard = new MacOSXClipboard ();
-			} else {
-				if (CursesDriver.Is_WSL_Platform ()) {
-					Clipboard = new WSLClipboard ();
-				} else {
-					Clipboard = new CursesClipboard ();
-				}
-			}
-		}
-	}
-
-	internal override void End ()
-	{
-		FakeConsole.ResetColor ();
-		FakeConsole.Clear ();
-	}
-
-	FakeMainLoop _mainLoopDriver = null;
-
-	internal override MainLoop Init ()
-	{
-		FakeConsole.MockKeyPresses.Clear ();
-
-		Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
-		Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
-		FakeConsole.Clear ();
-		ResizeScreen ();
-		CurrentAttribute = new Attribute (Color.White, Color.Black);
-		ClearContents ();
-
-		_mainLoopDriver = new FakeMainLoop (this);
-		_mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
-		return new MainLoop (_mainLoopDriver);
-	}
-
-
-	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;
-					}
-
-					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.GetClosestNamedColor ();
-						FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor ();
-					}
-					outputWidth++;
-					var rune = (Rune)Contents [row, col].Rune;
-					output.Append (rune.ToString ());
-					if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
-						WriteToConsole (output, ref lastCol, row, ref outputWidth);
-						FakeConsole.CursorLeft--;
-					}
-					Contents [row, col].IsDirty = false;
-				}
-			}
-			if (output.Length > 0) {
-				FakeConsole.CursorTop = row;
-				FakeConsole.CursorLeft = lastCol;
-
-				foreach (var c in output.ToString ()) {
-					FakeConsole.Write (c);
-				}
-			}
-		}
-		FakeConsole.CursorTop = 0;
-		FakeConsole.CursorLeft = 0;
-
-		//SetCursorVisibility (savedVisibitity);
-
-		void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
-		{
-			FakeConsole.CursorTop = row;
-			FakeConsole.CursorLeft = lastCol;
-			foreach (var c in output.ToString ()) {
-				FakeConsole.Write (c);
-			}
-
-			output.Clear ();
-			lastCol += outputWidth;
-			outputWidth = 0;
-		}
-
-		FakeConsole.CursorTop = savedRow;
-		FakeConsole.CursorLeft = savedCol;
-		FakeConsole.CursorVisible = savedCursorVisible;
-	}
-
-	public override void Refresh ()
-	{
-		UpdateScreen ();
-		UpdateCursor ();
-	}
-
-	#region Color Handling
-	///// <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 (
-	//		platformColor: 0,//((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
-	//		foreground: foreground,
-	//		background: background
-	//	);
-	//}
-	#endregion
-
-
-	KeyCode MapKey (ConsoleKeyInfo keyInfo)
-	{
-		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc);
-		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab);
-		case ConsoleKey.Clear:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Clear);
-		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home);
-		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End);
-		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight);
-		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp);
-		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown);
-		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp);
-		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown);
-		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter);
-		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace);
-		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete);
-		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert);
-		case ConsoleKey.PrintScreen:
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.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 KeyCode.Null;
-			}
-
-			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
-		}
-
-		var key = keyInfo.Key;
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.KeyChar != (uint)key) {
-				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
-			}
-			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)
-			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
-			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
-				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta));
-			}
-			var alphaBase = ((keyInfo.Modifiers != ConsoleModifiers.Shift)) ? 'A' : 'a';
-			return (KeyCode)((uint)alphaBase + delta);
-		}
-
-		return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
-	}
-
-	private CursorVisibility _savedCursorVisibility;
-
-	void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
-	{
-		if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-			consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-		}
-
-		var map = MapKey (consoleKeyInfo);
-		OnKeyDown (new Key (map));
-		OnKeyUp (new Key (map));
-		//OnKeyPressed (new KeyEventArgs (map));
-	}
-
-	/// <inheritdoc/>
-	public override bool GetCursorVisibility (out CursorVisibility visibility)
-	{
-		visibility = FakeConsole.CursorVisible
-			? CursorVisibility.Default
-			: CursorVisibility.Invisible;
-
-		return FakeConsole.CursorVisible;
-	}
-
-	/// <inheritdoc/>
-	public override bool SetCursorVisibility (CursorVisibility visibility)
-	{
-		_savedCursorVisibility = visibility;
-		return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
-	}
-
-	/// <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;
-		}
-
-		SetCursorVisibility (_savedCursorVisibility);
-		return FakeConsole.CursorVisible;
-	}
-
-	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
-	{
-		MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
-	}
-
-	public void SetBufferSize (int width, int height)
-	{
-		FakeConsole.SetBufferSize (width, height);
-		Cols = width;
-		Rows = height;
-		SetWindowSize (width, height);
-		ProcessResize ();
-	}
-
-	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 void SetWindowPosition (int left, int top)
-	{
-		if (Left > 0 || Top > 0) {
-			Left = 0;
-			Top = 0;
-		}
-		FakeConsole.SetWindowPosition (Left, Top);
-	}
-
-	void ProcessResize ()
-	{
-		ResizeScreen ();
-		ClearContents ();
-		OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
-	}
-
-	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;
-			}
-		}
-
-		Clip = new Rect (0, 0, Cols, Rows);
-	}
-
-	public override void UpdateCursor ()
-	{
-		if (!EnsureCursorVisibility ()) {
-			return;
-		}
-
-		// 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) { }
-	}
-
-	#region Not Implemented
-	public override void Suspend ()
-	{
-		return;
-		//throw new NotImplementedException ();
-	}
-	#endregion
-
-	public class FakeClipboard : ClipboardBase {
-		public Exception FakeException = null;
-
-		string _contents = string.Empty;
-
-		bool _isSupportedAlwaysFalse = false;
-
-		public override bool IsSupported => !_isSupportedAlwaysFalse;
-
-		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 void SetClipboardDataImpl (string text)
-		{
-			if (text == null) {
-				throw new ArgumentNullException (nameof (text));
-			}
-			if (FakeException != null) {
-				throw FakeException;
-			}
-			_contents = text;
-		}
-	}
+    public class Behaviors
+    {
+        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);
+        }
+
+        public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
+        public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
+        public bool UseFakeClipboard { get; internal set; }
+    }
+
+    public static Behaviors FakeBehaviors = new ();
+    public override bool SupportsTrueColor => false;
+
+    public FakeDriver ()
+    {
+        Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+        Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
+
+        if (FakeBehaviors.UseFakeClipboard)
+        {
+            Clipboard = new FakeClipboard (
+                                           FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
+                                           FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse
+                                          );
+        }
+        else
+        {
+            if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
+            {
+                Clipboard = new WindowsClipboard ();
+            }
+            else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+            {
+                Clipboard = new MacOSXClipboard ();
+            }
+            else
+            {
+                if (CursesDriver.Is_WSL_Platform ())
+                {
+                    Clipboard = new WSLClipboard ();
+                }
+                else
+                {
+                    Clipboard = new CursesClipboard ();
+                }
+            }
+        }
+    }
+
+    internal override void End ()
+    {
+        FakeConsole.ResetColor ();
+        FakeConsole.Clear ();
+    }
+
+    private FakeMainLoop _mainLoopDriver;
+
+    internal override MainLoop Init ()
+    {
+        FakeConsole.MockKeyPresses.Clear ();
+
+        Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
+        Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
+        FakeConsole.Clear ();
+        ResizeScreen ();
+        CurrentAttribute = new Attribute (Color.White, Color.Black);
+        ClearContents ();
+
+        _mainLoopDriver = new FakeMainLoop (this);
+        _mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
+
+        return new MainLoop (_mainLoopDriver);
+    }
+
+    public override void UpdateScreen ()
+    {
+        int savedRow = FakeConsole.CursorTop;
+        int savedCol = FakeConsole.CursorLeft;
+        bool savedCursorVisible = FakeConsole.CursorVisible;
+
+        var top = 0;
+        var left = 0;
+        int rows = Rows;
+        int cols = Cols;
+        var output = new StringBuilder ();
+        var redrawAttr = new Attribute ();
+        int lastCol = -1;
+
+        for (int row = top; row < rows; row++)
+        {
+            if (!_dirtyLines [row])
+            {
+                continue;
+            }
+
+            FakeConsole.CursorTop = row;
+            FakeConsole.CursorLeft = 0;
+
+            _dirtyLines [row] = false;
+            output.Clear ();
+
+            for (int 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;
+                    }
+
+                    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.GetClosestNamedColor ();
+                        FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor ();
+                    }
+
+                    outputWidth++;
+                    Rune rune = Contents [row, col].Rune;
+                    output.Append (rune.ToString ());
+
+                    if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+                    {
+                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        FakeConsole.CursorLeft--;
+                    }
+
+                    Contents [row, col].IsDirty = false;
+                }
+            }
+
+            if (output.Length > 0)
+            {
+                FakeConsole.CursorTop = row;
+                FakeConsole.CursorLeft = lastCol;
+
+                foreach (char c in output.ToString ())
+                {
+                    FakeConsole.Write (c);
+                }
+            }
+        }
+
+        FakeConsole.CursorTop = 0;
+        FakeConsole.CursorLeft = 0;
+
+        //SetCursorVisibility (savedVisibitity);
+
+        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+        {
+            FakeConsole.CursorTop = row;
+            FakeConsole.CursorLeft = lastCol;
+
+            foreach (char c in output.ToString ())
+            {
+                FakeConsole.Write (c);
+            }
+
+            output.Clear ();
+            lastCol += outputWidth;
+            outputWidth = 0;
+        }
+
+        FakeConsole.CursorTop = savedRow;
+        FakeConsole.CursorLeft = savedCol;
+        FakeConsole.CursorVisible = savedCursorVisible;
+    }
+
+    public override void Refresh ()
+    {
+        UpdateScreen ();
+        UpdateCursor ();
+    }
+
+    #region Color Handling
+
+    ///// <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 (
+    //		platformColor: 0,//((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
+    //		foreground: foreground,
+    //		background: background
+    //	);
+    //}
+
+    #endregion
+
+    private KeyCode MapKey (ConsoleKeyInfo keyInfo)
+    {
+        switch (keyInfo.Key)
+        {
+            case ConsoleKey.Escape:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc);
+            case ConsoleKey.Tab:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab);
+            case ConsoleKey.Clear:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Clear);
+            case ConsoleKey.Home:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home);
+            case ConsoleKey.End:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End);
+            case ConsoleKey.LeftArrow:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft);
+            case ConsoleKey.RightArrow:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight);
+            case ConsoleKey.UpArrow:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp);
+            case ConsoleKey.DownArrow:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown);
+            case ConsoleKey.PageUp:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp);
+            case ConsoleKey.PageDown:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown);
+            case ConsoleKey.Enter:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter);
+            case ConsoleKey.Spacebar:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (
+                                                                keyInfo.Modifiers,
+                                                                keyInfo.KeyChar == 0
+                                                                    ? KeyCode.Space
+                                                                    : (KeyCode)keyInfo.KeyChar
+                                                               );
+            case ConsoleKey.Backspace:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace);
+            case ConsoleKey.Delete:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete);
+            case ConsoleKey.Insert:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert);
+            case ConsoleKey.PrintScreen:
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.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 KeyCode.Null;
+                }
+
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+        }
+
+        ConsoleKey key = keyInfo.Key;
+
+        if (key >= ConsoleKey.A && key <= ConsoleKey.Z)
+        {
+            int delta = key - ConsoleKey.A;
+
+            if (keyInfo.KeyChar != (uint)key)
+            {
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+            }
+
+            if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)
+                || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
+                || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift))
+            {
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta));
+            }
+
+            char alphaBase = keyInfo.Modifiers != ConsoleModifiers.Shift ? 'A' : 'a';
+
+            return (KeyCode)((uint)alphaBase + delta);
+        }
+
+        return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+    }
+
+    private CursorVisibility _savedCursorVisibility;
+
+    private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
+    {
+        if (consoleKeyInfo.Key == ConsoleKey.Packet)
+        {
+            consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+        }
+
+        KeyCode map = MapKey (consoleKeyInfo);
+        OnKeyDown (new Key (map));
+        OnKeyUp (new Key (map));
+
+        //OnKeyPressed (new KeyEventArgs (map));
+    }
+
+    /// <inheritdoc/>
+    public override bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        visibility = FakeConsole.CursorVisible
+                         ? CursorVisibility.Default
+                         : CursorVisibility.Invisible;
+
+        return FakeConsole.CursorVisible;
+    }
+
+    /// <inheritdoc/>
+    public override bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _savedCursorVisibility = visibility;
+
+        return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
+    }
+
+    /// <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;
+        }
+
+        SetCursorVisibility (_savedCursorVisibility);
+
+        return FakeConsole.CursorVisible;
+    }
+
+    public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+    {
+        MockKeyPressedHandler (new ConsoleKeyInfo (keyChar, key, shift, alt, control));
+    }
+
+    public void SetBufferSize (int width, int height)
+    {
+        FakeConsole.SetBufferSize (width, height);
+        Cols = width;
+        Rows = height;
+        SetWindowSize (width, height);
+        ProcessResize ();
+    }
+
+    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 void SetWindowPosition (int left, int top)
+    {
+        if (Left > 0 || Top > 0)
+        {
+            Left = 0;
+            Top = 0;
+        }
+
+        FakeConsole.SetWindowPosition (Left, Top);
+    }
+
+    private void ProcessResize ()
+    {
+        ResizeScreen ();
+        ClearContents ();
+        OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
+    }
+
+    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 (IOException)
+            {
+                return;
+            }
+            catch (ArgumentOutOfRangeException)
+            {
+                return;
+            }
+        }
+
+        Clip = new Rect (0, 0, Cols, Rows);
+    }
+
+    public override void UpdateCursor ()
+    {
+        if (!EnsureCursorVisibility ())
+        {
+            return;
+        }
+
+        // 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 (IOException)
+        { }
+        catch (ArgumentOutOfRangeException)
+        { }
+    }
+
+    #region Not Implemented
+
+    public override void Suspend ()
+    {
+        //throw new NotImplementedException ();
+    }
+
+    #endregion
+
+    public class FakeClipboard : ClipboardBase
+    {
+        public Exception FakeException;
+
+        private readonly bool _isSupportedAlwaysFalse;
+        private string _contents = string.Empty;
+
+        public FakeClipboard (
+            bool fakeClipboardThrowsNotSupportedException = false,
+            bool isSupportedAlwaysFalse = false
+        )
+        {
+            _isSupportedAlwaysFalse = isSupportedAlwaysFalse;
+
+            if (fakeClipboardThrowsNotSupportedException)
+            {
+                FakeException = new NotSupportedException ("Fake clipboard exception");
+            }
+        }
+
+        public override bool IsSupported => !_isSupportedAlwaysFalse;
+
+        protected override string GetClipboardDataImpl ()
+        {
+            if (FakeException != null)
+            {
+                throw FakeException;
+            }
+
+            return _contents;
+        }
+
+        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
-}
+}

+ 36 - 41
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -1,42 +1,37 @@
-using System;
-using static Terminal.Gui.NetEvents;
-
-namespace Terminal.Gui;
-
-internal class FakeMainLoop : IMainLoopDriver {
-
-	public Action<ConsoleKeyInfo> MockKeyPressed;
-
-	public FakeMainLoop (ConsoleDriver consoleDriver = null)
-	{
-		// No implementation needed for FakeMainLoop
-	}
-
-	public void Setup (MainLoop mainLoop)
-	{
-		// No implementation needed for FakeMainLoop
-	}
-
-	public void Wakeup ()
-	{
-		// No implementation needed for FakeMainLoop
-	}
-
-	public bool EventsPending ()
-	{
-		// Always return true for FakeMainLoop
-		return true;
-	}
-
-	public void Iteration ()
-	{
-		if (FakeConsole.MockKeyPresses.Count > 0) {
-			MockKeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
-		}
-	}
-
-	public void TearDown ()
-	{
-	}
+namespace Terminal.Gui;
+
+internal class FakeMainLoop : IMainLoopDriver
+{
+    public Action<ConsoleKeyInfo> MockKeyPressed;
+
+    public FakeMainLoop (ConsoleDriver consoleDriver = null)
+    {
+        // No implementation needed for FakeMainLoop
+    }
+
+    public void Setup (MainLoop mainLoop)
+    {
+        // No implementation needed for FakeMainLoop
+    }
+
+    public void Wakeup ()
+    {
+        // No implementation needed for FakeMainLoop
+    }
+
+    public bool EventsPending ()
+    {
+        // Always return true for FakeMainLoop
+        return true;
+    }
+
+    public void Iteration ()
+    {
+        if (FakeConsole.MockKeyPresses.Count > 0)
+        {
+            MockKeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
+        }
+    }
+
+    public void TearDown () { }
 }
-

+ 1755 - 1377
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1,1401 +1,1779 @@
 //
 // NetDriver.cs: The System.Console-based .NET driver, works on Windows and Unix, but is not particularly efficient.
 //
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
+
 using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.NetEvents;
-using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
 
-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 ()}.");
-			}
-		}
-
-		_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 ()}.");
-			}
-		}
-	}
-
-	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 ()}.");
-		}
-	}
-
-	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)]
-	extern static IntPtr GetStdHandle (int nStdHandle);
-
-	[DllImport ("kernel32.dll")]
-	extern static bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
-
-	[DllImport ("kernel32.dll")]
-	extern static bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
-
-	[DllImport ("kernel32.dll")]
-	extern static uint GetLastError ();
+internal class NetWinVTConsole
+{
+    private const uint DISABLE_NEWLINE_AUTO_RETURN = 8;
+    private const uint ENABLE_ECHO_INPUT = 4;
+    private const uint ENABLE_EXTENDED_FLAGS = 128;
+    private const uint ENABLE_INSERT_MODE = 32;
+    private const uint ENABLE_LINE_INPUT = 2;
+    private const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
+    private const uint ENABLE_MOUSE_INPUT = 16;
+
+    // Input modes.
+    private const uint ENABLE_PROCESSED_INPUT = 1;
+
+    // Output modes.
+    private const uint ENABLE_PROCESSED_OUTPUT = 1;
+    private const uint ENABLE_QUICK_EDIT_MODE = 64;
+    private const uint ENABLE_VIRTUAL_TERMINAL_INPUT = 512;
+    private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
+    private const uint ENABLE_WINDOW_INPUT = 8;
+    private const uint ENABLE_WRAP_AT_EOL_OUTPUT = 2;
+    private const int STD_ERROR_HANDLE = -12;
+    private const int STD_INPUT_HANDLE = -10;
+    private const int STD_OUTPUT_HANDLE = -11;
+
+    private readonly nint _errorHandle;
+    private readonly nint _inputHandle;
+    private readonly uint _originalErrorConsoleMode;
+    private readonly uint _originalInputConsoleMode;
+    private readonly uint _originalOutputConsoleMode;
+    private readonly nint _outputHandle;
+
+    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 ()}.");
+            }
+        }
+
+        _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 ()}.");
+            }
+        }
+    }
+
+    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 ()}.");
+        }
+    }
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
+
+    [DllImport ("kernel32.dll")]
+    private static extern uint GetLastError ();
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint GetStdHandle (int nStdHandle);
+
+    [DllImport ("kernel32.dll")]
+    private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
 }
 
-class NetEvents : IDisposable {
-	readonly ManualResetEventSlim _inputReady = new (false);
-	CancellationTokenSource _inputReadyCancellationTokenSource;
-
-	readonly ManualResetEventSlim _waitForStart = new (false);
-	//CancellationTokenSource _waitForStartCancellationTokenSource;
-
-	readonly ManualResetEventSlim _winChange = new (false);
-
-	readonly Queue<InputResult?> _inputQueue = new ();
-
-	readonly ConsoleDriver _consoleDriver;
-	ConsoleKeyInfo [] _cki;
-	bool _isEscSeq;
-
+internal class NetEvents : IDisposable
+{
+    private readonly ManualResetEventSlim _inputReady = new (false);
+    private CancellationTokenSource _inputReadyCancellationTokenSource;
+    private readonly ManualResetEventSlim _waitForStart = new (false);
+
+    //CancellationTokenSource _waitForStartCancellationTokenSource;
+    private readonly ManualResetEventSlim _winChange = new (false);
+    private readonly Queue<InputResult?> _inputQueue = new ();
+    private readonly ConsoleDriver _consoleDriver;
+    private ConsoleKeyInfo [] _cki;
+    private bool _isEscSeq;
 #if PROCESS_REQUEST
-		bool _neededProcessRequest;
+    bool _neededProcessRequest;
 #endif
-	public EscSeqRequests EscSeqRequests { get; } = new ();
-
-	public NetEvents (ConsoleDriver consoleDriver)
-	{
-		_consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
-		_inputReadyCancellationTokenSource = new CancellationTokenSource ();
-
-		Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
-
-		Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
-	}
-
-	public InputResult? DequeueInput ()
-	{
-		while (_inputReadyCancellationTokenSource != null && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
-			_waitForStart.Set ();
-			_winChange.Set ();
-
-			try {
-				if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
-					if (_inputQueue.Count == 0) {
-						_inputReady.Wait (_inputReadyCancellationTokenSource.Token);
-					}
-				}
-
-			} catch (OperationCanceledException) {
-				return null;
-			} finally {
-				_inputReady.Reset ();
-			}
+    public EscSeqRequests EscSeqRequests { get; } = new ();
+
+    public NetEvents (ConsoleDriver consoleDriver)
+    {
+        _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
+        _inputReadyCancellationTokenSource = new CancellationTokenSource ();
+
+        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
+
+        Task.Run (CheckWindowSizeChange, _inputReadyCancellationTokenSource.Token);
+    }
+
+    public InputResult? DequeueInput ()
+    {
+        while (_inputReadyCancellationTokenSource != null
+               && !_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+        {
+            _waitForStart.Set ();
+            _winChange.Set ();
+
+            try
+            {
+                if (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+                {
+                    if (_inputQueue.Count == 0)
+                    {
+                        _inputReady.Wait (_inputReadyCancellationTokenSource.Token);
+                    }
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return null;
+            }
+            finally
+            {
+                _inputReady.Reset ();
+            }
 
 #if PROCESS_REQUEST
-				_neededProcessRequest = false;
+            _neededProcessRequest = false;
 #endif
-			if (_inputQueue.Count > 0) {
-				return _inputQueue.Dequeue ();
-			}
-		}
-		return null;
-	}
-
-	static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
-	{
-		// if there is a key available, return it without waiting
-		//  (or dispatching work to the thread queue)
-		if (Console.KeyAvailable) {
-			return Console.ReadKey (intercept);
-		}
-
-		while (!cancellationToken.IsCancellationRequested) {
-			Task.Delay (100);
-			if (Console.KeyAvailable) {
-				return Console.ReadKey (intercept);
-			}
-		}
-		cancellationToken.ThrowIfCancellationRequested ();
-		return default;
-	}
-
-	void ProcessInputQueue ()
-	{
-		while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
-
-			try {
-				_waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
-			} catch (OperationCanceledException) {
-
-				return;
-			}
-			_waitForStart.Reset ();
-
-			if (_inputQueue.Count == 0) {
-				ConsoleKey key = 0;
-				ConsoleModifiers mod = 0;
-				ConsoleKeyInfo newConsoleKeyInfo = default;
-
-				while (true) {
-					if (_inputReadyCancellationTokenSource.Token.IsCancellationRequested) {
-						return;
-					}
-					ConsoleKeyInfo consoleKeyInfo;
-					try {
-						consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token, true);
-					} catch (OperationCanceledException) {
-						return;
-					}
-					if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq
-					|| consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
-
-						if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
-							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.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)KeyCode.Esc && _isEscSeq && _cki != null) {
-						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
-						_cki = null;
-						if (Console.KeyAvailable) {
-							_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
-						} else {
-							ProcessMapConsoleKeyInfo (consoleKeyInfo);
-						}
-						break;
-					} else {
-						ProcessMapConsoleKeyInfo (consoleKeyInfo);
-						break;
-					}
-				}
-			}
-
-			_inputReady.Set ();
-		}
-
-		void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-		{
-			_inputQueue.Enqueue (new InputResult {
-				EventType = EventType.Key,
-				ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
-			});
-			_isEscSeq = false;
-		}
-	}
-
-	void CheckWindowSizeChange ()
-	{
-		void RequestWindowSize (CancellationToken cancellationToken)
-		{
-			while (!cancellationToken.IsCancellationRequested) {
-				// Wait for a while then check if screen has changed sizes
-				Task.Delay (500, cancellationToken);
-
-				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 (EnqueueWindowSizeEvent (
-					Math.Max (Console.WindowHeight, 0),
-					Math.Max (Console.WindowWidth, 0),
-					buffHeight,
-					buffWidth)) {
-
-					return;
-				}
-			}
-			cancellationToken.ThrowIfCancellationRequested ();
-		}
-
-		while (true) {
-			if (_inputReadyCancellationTokenSource.IsCancellationRequested) {
-				return;
-			}
-			_winChange.Wait (_inputReadyCancellationTokenSource.Token);
-			_winChange.Reset ();
-			try {
-				RequestWindowSize (_inputReadyCancellationTokenSource.Token);
-			} catch (OperationCanceledException) {
-				return;
-			}
-			_inputReady.Set ();
-		}
-	}
-
-	/// <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;
-		}
-		int w = Math.Max (winWidth, 0);
-		int h = Math.Max (winHeight, 0);
-		_inputQueue.Enqueue (new InputResult () {
-			EventType = EventType.WindowSize,
-			WindowSizeEvent = new WindowSizeEvent () {
-				Size = new Size (w, h)
-			}
-		});
-		return true;
-	}
-
-	// 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 string c1Control, out string code, out string [] values, out string terminating,
-			out bool isMouse, out var mouseFlags,
-			out var pos, out bool 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);
-	}
-
-	MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
-	{
-		MouseButtonState mbs = default;
-		foreach (object 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;
-	}
-
-	Point _lastCursorPosition;
-
-	void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
-	{
-		switch (terminating) {
-		// BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
-		case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
-			var point = new Point {
-				X = int.Parse (values [1]) - 1,
-				Y = int.Parse (values [0]) - 1
-			};
-			if (_lastCursorPosition.Y != point.Y) {
-				_lastCursorPosition = point;
-				var eventType = EventType.WindowPosition;
-				var winPositionEv = new WindowPositionEvent () {
-					CursorPosition = point
-				};
-				_inputQueue.Enqueue (new InputResult () {
-					EventType = eventType,
-					WindowPositionEvent = winPositionEv
-				});
-			} else {
-				return;
-			}
-			break;
-
-		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;
-		}
-
-		_inputReady.Set ();
-	}
-
-	void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
-	{
-		var eventType = EventType.RequestResponse;
-		var requestRespEv = new RequestResponseEvent () {
-			ResultTuple = (c1Control, code, values, terminating)
-		};
-		_inputQueue.Enqueue (new InputResult () {
-			EventType = eventType,
-			RequestResponseEvent = requestRespEv
-		});
-	}
-
-	void HandleMouseEvent (MouseButtonState buttonState, Point pos)
-	{
-		var mouseEvent = new MouseEvent () {
-			Position = pos,
-			ButtonState = buttonState
-		};
-
-		_inputQueue.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;
-
-		public override readonly string ToString ()
-		{
-			return EventType switch {
-				EventType.Key => ToString (ConsoleKeyInfo),
-				EventType.Mouse => MouseEvent.ToString (),
-				//EventType.WindowSize => WindowSize.ToString (),
-				//EventType.RequestResponse => RequestResponse.ToString (),
-				_ => "Unknown event type: " + EventType
-			};
-		}
-
-		/// <summary>
-		/// Prints a ConsoleKeyInfoEx structure
-		/// </summary>
-		/// <param name="cki"></param>
-		/// <returns></returns>
-		public readonly string ToString (ConsoleKeyInfo cki)
-		{
-			var ke = new Key ((KeyCode)cki.KeyChar);
-			var sb = new StringBuilder ();
-			sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
-			sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
-			sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
-			sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
-			sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
-			var s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
-			return $"[ConsoleKeyInfo({s})]";
-		}
-	}
-
-	void HandleKeyboardEvent (ConsoleKeyInfo cki)
-	{
-		var inputResult = new InputResult {
-			EventType = EventType.Key,
-			ConsoleKeyInfo = cki
-		};
-
-		_inputQueue.Enqueue (inputResult);
-	}
-
-	public void Dispose ()
-	{
-		_inputReadyCancellationTokenSource?.Cancel ();
-		_inputReadyCancellationTokenSource?.Dispose ();
-		_inputReadyCancellationTokenSource = null;
-
-		try {
-			// throws away any typeahead that has been typed by
-			// the user and has not yet been read by the program.
-			while (Console.KeyAvailable) {
-				Console.ReadKey (true);
-			}
-		} catch (InvalidOperationException) {
-			// Ignore - Console input has already been closed
-		}
-	}
+            if (_inputQueue.Count > 0)
+            {
+                return _inputQueue.Dequeue ();
+            }
+        }
+
+        return null;
+    }
+
+    private static ConsoleKeyInfo ReadConsoleKeyInfo (CancellationToken cancellationToken, bool intercept = true)
+    {
+        // if there is a key available, return it without waiting
+        //  (or dispatching work to the thread queue)
+        if (Console.KeyAvailable)
+        {
+            return Console.ReadKey (intercept);
+        }
+
+        while (!cancellationToken.IsCancellationRequested)
+        {
+            Task.Delay (100);
+
+            if (Console.KeyAvailable)
+            {
+                return Console.ReadKey (intercept);
+            }
+        }
+
+        cancellationToken.ThrowIfCancellationRequested ();
+
+        return default (ConsoleKeyInfo);
+    }
+
+    private void ProcessInputQueue ()
+    {
+        while (!_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+        {
+            try
+            {
+                _waitForStart.Wait (_inputReadyCancellationTokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+
+            _waitForStart.Reset ();
+
+            if (_inputQueue.Count == 0)
+            {
+                ConsoleKey key = 0;
+                ConsoleModifiers mod = 0;
+                ConsoleKeyInfo newConsoleKeyInfo = default;
+
+                while (true)
+                {
+                    if (_inputReadyCancellationTokenSource.Token.IsCancellationRequested)
+                    {
+                        return;
+                    }
+
+                    ConsoleKeyInfo consoleKeyInfo;
+
+                    try
+                    {
+                        consoleKeyInfo = ReadConsoleKeyInfo (_inputReadyCancellationTokenSource.Token);
+                    }
+                    catch (OperationCanceledException)
+                    {
+                        return;
+                    }
+
+                    if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
+                        || (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq))
+                    {
+                        if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)
+                        {
+                            _cki = EscSeqUtils.ResizeArray (
+                                                            new ConsoleKeyInfo (
+                                                                                (char)KeyCode.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;
+                    }
+
+                    if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && _isEscSeq && _cki != null)
+                    {
+                        ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
+                        _cki = null;
+
+                        if (Console.KeyAvailable)
+                        {
+                            _cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
+                        }
+                        else
+                        {
+                            ProcessMapConsoleKeyInfo (consoleKeyInfo);
+                        }
+
+                        break;
+                    }
+
+                    ProcessMapConsoleKeyInfo (consoleKeyInfo);
+
+                    break;
+                }
+            }
+
+            _inputReady.Set ();
+        }
+
+        void ProcessMapConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+        {
+            _inputQueue.Enqueue (
+                                 new InputResult
+                                 {
+                                     EventType = EventType.Key, ConsoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (consoleKeyInfo)
+                                 }
+                                );
+            _isEscSeq = false;
+        }
+    }
+
+    private void CheckWindowSizeChange ()
+    {
+        void RequestWindowSize (CancellationToken cancellationToken)
+        {
+            while (!cancellationToken.IsCancellationRequested)
+            {
+                // Wait for a while then check if screen has changed sizes
+                Task.Delay (500, cancellationToken);
+
+                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 (EnqueueWindowSizeEvent (
+                                            Math.Max (Console.WindowHeight, 0),
+                                            Math.Max (Console.WindowWidth, 0),
+                                            buffHeight,
+                                            buffWidth
+                                           ))
+                {
+                    return;
+                }
+            }
+
+            cancellationToken.ThrowIfCancellationRequested ();
+        }
+
+        while (true)
+        {
+            if (_inputReadyCancellationTokenSource.IsCancellationRequested)
+            {
+                return;
+            }
+
+            _winChange.Wait (_inputReadyCancellationTokenSource.Token);
+            _winChange.Reset ();
+
+            try
+            {
+                RequestWindowSize (_inputReadyCancellationTokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+
+            _inputReady.Set ();
+        }
+    }
+
+    /// <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>
+    private bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
+    {
+        if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows)
+        {
+            return false;
+        }
+
+        int w = Math.Max (winWidth, 0);
+        int h = Math.Max (winHeight, 0);
+
+        _inputQueue.Enqueue (
+                             new InputResult
+                             {
+                                 EventType = EventType.WindowSize, WindowSizeEvent = new WindowSizeEvent { Size = new Size (w, h) }
+                             }
+                            );
+
+        return true;
+    }
+
+    // Process a CSI sequence received by the driver (key pressed, mouse event, or request/response event)
+    private 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 string c1Control,
+                                  out string code,
+                                  out string [] values,
+                                  out string terminating,
+                                  out bool isMouse,
+                                  out List<MouseFlags> mouseFlags,
+                                  out Point pos,
+                                  out bool isReq,
+                                  (f, p) => HandleMouseEvent (MapMouseFlags (f), p)
+                                 );
+
+        if (isMouse)
+        {
+            foreach (MouseFlags mf in mouseFlags)
+            {
+                HandleMouseEvent (MapMouseFlags (mf), pos);
+            }
+
+            return;
+        }
+
+        if (isReq)
+        {
+            HandleRequestResponseEvent (c1Control, code, values, terminating);
+
+            return;
+        }
+
+        HandleKeyboardEvent (newConsoleKeyInfo);
+    }
+
+    private MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
+    {
+        MouseButtonState mbs = default;
+
+        foreach (object 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;
+    }
+
+    private Point _lastCursorPosition;
+
+    private void HandleRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
+    {
+        switch (terminating)
+        {
+            // BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
+            case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
+                var point = new Point { X = int.Parse (values [1]) - 1, Y = int.Parse (values [0]) - 1 };
+
+                if (_lastCursorPosition.Y != point.Y)
+                {
+                    _lastCursorPosition = point;
+                    var eventType = EventType.WindowPosition;
+                    var winPositionEv = new WindowPositionEvent { CursorPosition = point };
+
+                    _inputQueue.Enqueue (
+                                         new InputResult { EventType = eventType, WindowPositionEvent = winPositionEv }
+                                        );
+                }
+                else
+                {
+                    return;
+                }
+
+                break;
+
+            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;
+        }
+
+        _inputReady.Set ();
+    }
+
+    private void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
+    {
+        var eventType = EventType.RequestResponse;
+        var requestRespEv = new RequestResponseEvent { ResultTuple = (c1Control, code, values, terminating) };
+
+        _inputQueue.Enqueue (
+                             new InputResult { EventType = eventType, RequestResponseEvent = requestRespEv }
+                            );
+    }
+
+    private void HandleMouseEvent (MouseButtonState buttonState, Point pos)
+    {
+        var mouseEvent = new MouseEvent { Position = pos, ButtonState = buttonState };
+
+        _inputQueue.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;
+
+        public readonly override string ToString ()
+        {
+            return EventType switch
+                   {
+                       EventType.Key => ToString (ConsoleKeyInfo),
+                       EventType.Mouse => MouseEvent.ToString (),
+
+                       //EventType.WindowSize => WindowSize.ToString (),
+                       //EventType.RequestResponse => RequestResponse.ToString (),
+                       _ => "Unknown event type: " + EventType
+                   };
+        }
+
+        /// <summary>Prints a ConsoleKeyInfoEx structure</summary>
+        /// <param name="cki"></param>
+        /// <returns></returns>
+        public readonly string ToString (ConsoleKeyInfo cki)
+        {
+            var ke = new Key ((KeyCode)cki.KeyChar);
+            var sb = new StringBuilder ();
+            sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
+            sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
+            sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
+            sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
+            sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
+            string s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
+
+            return $"[ConsoleKeyInfo({s})]";
+        }
+    }
+
+    private void HandleKeyboardEvent (ConsoleKeyInfo cki)
+    {
+        var inputResult = new InputResult { EventType = EventType.Key, ConsoleKeyInfo = cki };
+
+        _inputQueue.Enqueue (inputResult);
+    }
+
+    public void Dispose ()
+    {
+        _inputReadyCancellationTokenSource?.Cancel ();
+        _inputReadyCancellationTokenSource?.Dispose ();
+        _inputReadyCancellationTokenSource = null;
+
+        try
+        {
+            // throws away any typeahead that has been typed by
+            // the user and has not yet been read by the program.
+            while (Console.KeyAvailable)
+            {
+                Console.ReadKey (true);
+            }
+        }
+        catch (InvalidOperationException)
+        {
+            // Ignore - Console input has already been closed
+        }
+    }
 }
 
-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;
-
-	NetMainLoop _mainLoopDriver = null;
-
-	public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || IsWinPlatform && Environment.OSVersion.Version.Build >= 14931;
-
-	public NetWinVTConsole NetWinConsole { get; private set; }
-
-	public bool IsWinPlatform { get; private set; }
-
-	internal override MainLoop Init ()
-	{
-		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.
-			}
-		}
-		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 ();
-			}
-		}
-
-		if (!RunningUnitTests) {
-			Console.TreatControlCAsInput = true;
-
-			Cols = Console.WindowWidth;
-			Rows = Console.WindowHeight;
-
-			//Enable alternative screen buffer.
-			Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
-
-			//Set cursor key to application.
-			Console.Out.Write (EscSeqUtils.CSI_HideCursor);
-
-		} else {
-			// We are being run in an environment that does not support a console
-			// such as a unit test, or a pipe.
-			Cols = 80;
-			Rows = 24;
-		}
-
-		ResizeScreen ();
-		ClearContents ();
-		CurrentAttribute = new Attribute (Color.White, Color.Black);
-
-		StartReportingMouseMoves ();
-
-		_mainLoopDriver = new NetMainLoop (this);
-		_mainLoopDriver.ProcessInput = ProcessInput;
-		return new MainLoop (_mainLoopDriver);
-	}
-
-	internal override void End ()
-	{
-		if (IsWinPlatform) {
-			NetWinConsole?.Cleanup ();
-		}
-
-		StopReportingMouseMoves ();
-
-		if (!RunningUnitTests) {
-			Console.ResetColor ();
-
-			//Disable alternative screen buffer.
-			Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
-
-			//Set cursor key to cursor.
-			Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
-			Console.Out.Close ();
-		}
-	}
-
-
-	#region Size and Position Handling
-	volatile bool _winSizeChanging;
-
-	void SetWindowPosition (int col, int row)
-	{
-		if (!RunningUnitTests) {
-			Top = Console.WindowTop;
-			Left = Console.WindowLeft;
-		} else {
-			Top = row;
-			Left = col;
-		}
-	}
-
-	public virtual void ResizeScreen ()
-	{
-		// Not supported on Unix.
-		if (IsWinPlatform) {
-			// Can raise an exception while is still resizing.
-			try {
+internal class NetDriver : ConsoleDriver
+{
+    private const int COLOR_BLACK = 30;
+    private const int COLOR_BLUE = 34;
+    private const int COLOR_BRIGHT_BLACK = 90;
+    private const int COLOR_BRIGHT_BLUE = 94;
+    private const int COLOR_BRIGHT_CYAN = 96;
+    private const int COLOR_BRIGHT_GREEN = 92;
+    private const int COLOR_BRIGHT_MAGENTA = 95;
+    private const int COLOR_BRIGHT_RED = 91;
+    private const int COLOR_BRIGHT_WHITE = 97;
+    private const int COLOR_BRIGHT_YELLOW = 93;
+    private const int COLOR_CYAN = 36;
+    private const int COLOR_GREEN = 32;
+    private const int COLOR_MAGENTA = 35;
+    private const int COLOR_RED = 31;
+    private const int COLOR_WHITE = 37;
+    private const int COLOR_YELLOW = 33;
+    private NetMainLoop _mainLoopDriver;
+    public bool IsWinPlatform { get; private set; }
+    public NetWinVTConsole NetWinConsole { get; private set; }
+
+    public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix
+                                              || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
+
+    public override void Refresh ()
+    {
+        UpdateScreen ();
+        UpdateCursor ();
+    }
+
+    public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+    {
+        var input = new InputResult
+        {
+            EventType = EventType.Key, ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
+        };
+
+        try
+        {
+            ProcessInput (input);
+        }
+        catch (OverflowException)
+        { }
+    }
+
+    #region Not Implemented
+
+    public override void Suspend () { throw new NotImplementedException (); }
+
+    #endregion
+
+    public override void UpdateScreen ()
+    {
+        if (RunningUnitTests
+            || _winSizeChanging
+            || Console.WindowHeight < 1
+            || Contents.Length != Rows * Cols
+            || Rows != Console.WindowHeight)
+        {
+            return;
+        }
+
+        var top = 0;
+        var left = 0;
+        int rows = Rows;
+        int cols = Cols;
+        var output = new StringBuilder ();
+        var redrawAttr = new Attribute ();
+        int lastCol = -1;
+
+        CursorVisibility? savedVisibitity = _cachedCursorVisibility;
+        SetCursorVisibility (CursorVisibility.Invisible);
+
+        for (int row = top; row < rows; row++)
+        {
+            if (Console.WindowHeight < 1)
+            {
+                return;
+            }
+
+            if (!_dirtyLines [row])
+            {
+                continue;
+            }
+
+            if (!SetCursorPosition (0, row))
+            {
+                return;
+            }
+
+            _dirtyLines [row] = false;
+            output.Clear ();
+
+            for (int 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;
+                    }
+
+                    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;
+
+                        if (Force16Colors)
+                        {
+                            output.Append (
+                                           EscSeqUtils.CSI_SetGraphicsRendition (
+                                                                                 MapColors (
+                                                                                            (ConsoleColor)attr.Background.GetClosestNamedColor (),
+                                                                                            false
+                                                                                           ),
+                                                                                 MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor ())
+                                                                                )
+                                          );
+                        }
+                        else
+                        {
+                            output.Append (
+                                           EscSeqUtils.CSI_SetForegroundColorRGB (
+                                                                                  attr.Foreground.R,
+                                                                                  attr.Foreground.G,
+                                                                                  attr.Foreground.B
+                                                                                 )
+                                          );
+
+                            output.Append (
+                                           EscSeqUtils.CSI_SetBackgroundColorRGB (
+                                                                                  attr.Background.R,
+                                                                                  attr.Background.G,
+                                                                                  attr.Background.B
+                                                                                 )
+                                          );
+                        }
+                    }
+
+                    outputWidth++;
+                    Rune rune = Contents [row, col].Rune;
+                    output.Append (rune);
+
+                    if (Contents [row, col].CombiningMarks.Count > 0)
+                    {
+                        // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+                        // compatible with the driver architecture. Any CMs (except in the first col)
+                        // are correctly combined with the base char, but are ALSO treated as 1 column
+                        // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+                        // 
+                        // For now, we just ignore the list of CMs.
+                        //foreach (var combMark in Contents [row, col].CombiningMarks) {
+                        //	output.Append (combMark);
+                        //}
+                        // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                    }
+                    else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+                    {
+                        WriteToConsole (output, ref lastCol, row, ref outputWidth);
+                        SetCursorPosition (col - 1, row);
+                    }
+
+                    Contents [row, col].IsDirty = false;
+                }
+            }
+
+            if (output.Length > 0)
+            {
+                SetCursorPosition (lastCol, row);
+                Console.Write (output);
+            }
+        }
+
+        SetCursorPosition (0, 0);
+
+        _cachedCursorVisibility = savedVisibitity;
+
+        void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+        {
+            SetCursorPosition (lastCol, row);
+            Console.Write (output);
+            output.Clear ();
+            lastCol += outputWidth;
+            outputWidth = 0;
+        }
+    }
+
+    internal override void End ()
+    {
+        if (IsWinPlatform)
+        {
+            NetWinConsole?.Cleanup ();
+        }
+
+        StopReportingMouseMoves ();
+
+        if (!RunningUnitTests)
+        {
+            Console.ResetColor ();
+
+            //Disable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
+
+            //Set cursor key to cursor.
+            Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+            Console.Out.Close ();
+        }
+    }
+
+    internal override MainLoop Init ()
+    {
+        PlatformID 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.
+            }
+        }
+
+        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 ();
+            }
+        }
+
+        if (!RunningUnitTests)
+        {
+            Console.TreatControlCAsInput = true;
+
+            Cols = Console.WindowWidth;
+            Rows = Console.WindowHeight;
+
+            //Enable alternative screen buffer.
+            Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+            //Set cursor key to application.
+            Console.Out.Write (EscSeqUtils.CSI_HideCursor);
+        }
+        else
+        {
+            // We are being run in an environment that does not support a console
+            // such as a unit test, or a pipe.
+            Cols = 80;
+            Rows = 24;
+        }
+
+        ResizeScreen ();
+        ClearContents ();
+        CurrentAttribute = new Attribute (Color.White, Color.Black);
+
+        StartReportingMouseMoves ();
+
+        _mainLoopDriver = new NetMainLoop (this);
+        _mainLoopDriver.ProcessInput = ProcessInput;
+
+        return new MainLoop (_mainLoopDriver);
+    }
+
+    private void ProcessInput (InputResult inputEvent)
+    {
+        switch (inputEvent.EventType)
+        {
+            case EventType.Key:
+                ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+
+                //if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+                //	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+                //}
+
+                //Debug.WriteLine ($"event: {inputEvent}");
+
+                KeyCode map = MapKey (consoleKeyInfo);
+
+                if (map == KeyCode.Null)
+                {
+                    break;
+                }
+
+                OnKeyDown (new Key (map));
+                OnKeyUp (new Key (map));
+
+                break;
+            case EventType.Mouse:
+                OnMouseEvent (new MouseEventEventArgs (ToDriverMouse (inputEvent.MouseEvent)));
+
+                break;
+            case 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;
+                OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
+
+                break;
+            case EventType.RequestResponse:
+                break;
+            case EventType.WindowPosition:
+                break;
+            default:
+                throw new ArgumentOutOfRangeException ();
+        }
+    }
+
+    #region Size and Position Handling
+
+    private volatile bool _winSizeChanging;
+
+    private void SetWindowPosition (int col, int row)
+    {
+        if (!RunningUnitTests)
+        {
+            Top = Console.WindowTop;
+            Left = Console.WindowLeft;
+        }
+        else
+        {
+            Top = row;
+            Left = col;
+        }
+    }
+
+    public virtual void ResizeScreen ()
+    {
+        // Not supported on Unix.
+        if (IsWinPlatform)
+        {
+            // Can raise an exception while is still resizing.
+            try
+            {
 #pragma warning disable CA1416
-				if (Console.WindowHeight > 0) {
-					Console.CursorTop = 0;
-					Console.CursorLeft = 0;
-					Console.WindowTop = 0;
-					Console.WindowLeft = 0;
-					if (Console.WindowHeight > Rows) {
-						Console.SetWindowSize (Cols, Rows);
-					}
-					Console.SetBufferSize (Cols, Rows);
-				}
+                if (Console.WindowHeight > 0)
+                {
+                    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 (IOException) {
-				Clip = new Rect (0, 0, Cols, Rows);
-			} catch (ArgumentOutOfRangeException) {
-				Clip = new Rect (0, 0, Cols, Rows);
-			}
-		} else {
-			Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
-		}
-
-
-		Clip = new Rect (0, 0, Cols, Rows);
-	}
-	#endregion
-
-	public override void Refresh ()
-	{
-		UpdateScreen ();
-		UpdateCursor ();
-	}
-
-	public override void UpdateScreen ()
-	{
-		if (RunningUnitTests || _winSizeChanging || Console.WindowHeight < 1 || Contents.Length != Rows * Cols || Rows != Console.WindowHeight) {
-			return;
-		}
-
-		int top = 0;
-		int left = 0;
-		int rows = Rows;
-		int cols = Cols;
-		var output = new StringBuilder ();
-		var redrawAttr = new Attribute ();
-		int lastCol = -1;
-
-		var savedVisibitity = _cachedCursorVisibility;
-		SetCursorVisibility (CursorVisibility.Invisible);
-
-		for (int row = top; row < rows; row++) {
-			if (Console.WindowHeight < 1) {
-				return;
-			}
-			if (!_dirtyLines [row]) {
-				continue;
-			}
-			if (!SetCursorPosition (0, row)) {
-				return;
-			}
-			_dirtyLines [row] = false;
-			output.Clear ();
-			for (int col = left; col < cols; col++) {
-				lastCol = -1;
-				int 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;
-					}
-
-					if (lastCol == -1) {
-						lastCol = col;
-					}
-
-					var attr = Contents [row, col].Attribute.Value;
-					// Performance: Only send the escape sequence if the attribute has changed.
-					if (attr != redrawAttr) {
-						redrawAttr = attr;
-
-						if (Force16Colors) {
-							output.Append (EscSeqUtils.CSI_SetGraphicsRendition (
-												MapColors ((ConsoleColor)attr.Background.GetClosestNamedColor (), false), MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor ())));
-						} else {
-							output.Append (EscSeqUtils.CSI_SetForegroundColorRGB (attr.Foreground.R, attr.Foreground.G, attr.Foreground.B));
-							output.Append (EscSeqUtils.CSI_SetBackgroundColorRGB (attr.Background.R, attr.Background.G, attr.Background.B));
-						}
-
-					}
-					outputWidth++;
-					var rune = (Rune)Contents [row, col].Rune;
-					output.Append (rune);
-					if (Contents [row, col].CombiningMarks.Count > 0) {
-						// AtlasEngine does not support NON-NORMALIZED combining marks in a way
-						// compatible with the driver architecture. Any CMs (except in the first col)
-						// are correctly combined with the base char, but are ALSO treated as 1 column
-						// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
-						// 
-						// For now, we just ignore the list of CMs.
-						//foreach (var combMark in Contents [row, col].CombiningMarks) {
-						//	output.Append (combMark);
-						//}
-						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
-					} else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
-						WriteToConsole (output, ref lastCol, row, ref outputWidth);
-						SetCursorPosition (col - 1, row);
-					}
-					Contents [row, col].IsDirty = false;
-				}
-			}
-			if (output.Length > 0) {
-				SetCursorPosition (lastCol, row);
-				Console.Write (output);
-			}
-		}
-		SetCursorPosition (0, 0);
-
-		_cachedCursorVisibility = savedVisibitity;
-
-		void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
-		{
-			SetCursorPosition (lastCol, row);
-			Console.Write (output);
-			output.Clear ();
-			lastCol += outputWidth;
-			outputWidth = 0;
-		}
-	}
-
-	#region Color Handling
-	// Cache the list of ConsoleColor values.
-	static readonly HashSet<int> ConsoleColorValues = new (
-		Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
-	);
-
-	// Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
-	static Dictionary<ConsoleColor, int> colorMap = new () {
-		{ 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) => colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 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 (
-	//		platformColor: ((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
-	//		foreground: foreground,
-	//		background: background
-	//	);
-	//}
-	#endregion
-
-	#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;
-			}
-		} else {
-			// + 1 is needed because non-Windows is based on 1 instead of 0 and
-			// Console.CursorTop/CursorLeft isn't reliable.
-			Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
-			return true;
-		}
-	}
-
-	CursorVisibility? _cachedCursorVisibility;
-
-	public override void UpdateCursor ()
-	{
-		EnsureCursorVisibility ();
-
-		if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) {
-			SetCursorPosition (Col, Row);
-			SetWindowPosition (0, Row);
-		}
-	}
-
-	public override bool GetCursorVisibility (out CursorVisibility visibility)
-	{
-		visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
-		return visibility == CursorVisibility.Default;
-	}
-
-	public override bool SetCursorVisibility (CursorVisibility visibility)
-	{
-		_cachedCursorVisibility = visibility;
-		bool isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default;
-		Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
-		return isVisible;
-	}
-
-	public override bool EnsureCursorVisibility ()
-	{
-		if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) {
-			GetCursorVisibility (out var cursorVisibility);
-			_cachedCursorVisibility = cursorVisibility;
-			SetCursorVisibility (CursorVisibility.Invisible);
-			return false;
-		}
-
-		SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
-		return _cachedCursorVisibility == CursorVisibility.Default;
-	}
-	#endregion
-
-	#region Mouse Handling
-	public void StartReportingMouseMoves ()
-	{
-		if (!RunningUnitTests) {
-			Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
-		}
-	}
-
-	public void StopReportingMouseMoves ()
-	{
-		if (!RunningUnitTests) {
-			Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
-		}
-	}
-
-	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
-	{
-		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
-
-		MouseFlags mouseFlag = 0;
-
-		if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button1Pressed;
-		}
-		if ((me.ButtonState & MouseButtonState.Button1Released) != 0) {
-			mouseFlag |= MouseFlags.Button1Released;
-		}
-		if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button1Clicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1DoubleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1TripleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button2Pressed;
-		}
-		if ((me.ButtonState & MouseButtonState.Button2Released) != 0) {
-			mouseFlag |= MouseFlags.Button2Released;
-		}
-		if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button2Clicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2DoubleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2TripleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button3Pressed;
-		}
-		if ((me.ButtonState & MouseButtonState.Button3Released) != 0) {
-			mouseFlag |= MouseFlags.Button3Released;
-		}
-		if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button3Clicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3DoubleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3TripleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) {
-			mouseFlag |= MouseFlags.WheeledUp;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) {
-			mouseFlag |= MouseFlags.WheeledDown;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) {
-			mouseFlag |= MouseFlags.WheeledLeft;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) {
-			mouseFlag |= MouseFlags.WheeledRight;
-		}
-		if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button4Pressed;
-		}
-		if ((me.ButtonState & MouseButtonState.Button4Released) != 0) {
-			mouseFlag |= MouseFlags.Button4Released;
-		}
-		if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button4Clicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4DoubleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4TripleClicked;
-		}
-		if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) {
-			mouseFlag |= MouseFlags.ReportMousePosition;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) {
-			mouseFlag |= MouseFlags.ButtonShift;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) {
-			mouseFlag |= MouseFlags.ButtonCtrl;
-		}
-		if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) {
-			mouseFlag |= MouseFlags.ButtonAlt;
-		}
-
-		return new MouseEvent () {
-			X = me.Position.X,
-			Y = me.Position.Y,
-			Flags = mouseFlag
-		};
-	}
-	#endregion Mouse Handling
-
-	#region Keyboard Handling
-	ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-	{
-		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-			return consoleKeyInfo;
-		}
-
-		var mod = consoleKeyInfo.Modifiers;
-		bool shift = (mod & ConsoleModifiers.Shift) != 0;
-		bool alt = (mod & ConsoleModifiers.Alt) != 0;
-		bool control = (mod & ConsoleModifiers.Control) != 0;
-
-		var cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-
-		return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
-	}
-
-	KeyCode MapKey (ConsoleKeyInfo keyInfo)
-	{
-		switch (keyInfo.Key) {
-		case ConsoleKey.OemPeriod:
-		case ConsoleKey.OemComma:
-		case ConsoleKey.OemPlus:
-		case ConsoleKey.OemMinus:
-		case ConsoleKey.Packet:
-		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:
-			if (keyInfo.KeyChar == 0) {
-				// If the keyChar is 0, keyInfo.Key value is not a printable character. 
-
-				return KeyCode.Null;// MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
-			} else {
-				if (keyInfo.Modifiers != ConsoleModifiers.Shift) {
-					// If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
-					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
-				}
-
-				// Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
-				// and passing on Shift would be redundant.
-				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
-			}
-		}
-
-		var key = keyInfo.Key;
-		// A..Z are special cased:
-		// - Alone, they represent lowercase a...z
-		// - With ShiftMask they are A..Z
-		// - If CapsLock is on the above is reversed.
-		// - If Alt and/or Ctrl are present, treat as upper case
-		if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) {
-			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
-				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
-			}
-
-			if (keyInfo.Modifiers == ConsoleModifiers.Shift) {
-				// If ShiftMask is on  add the ShiftMask
-				if (char.IsUpper (keyInfo.KeyChar)) {
-					return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask;
-				}
-			}
-			return (KeyCode)(uint)keyInfo.KeyChar;
-		}
-
-		// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
-		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) {
-			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.Key));
-		}
-
-		// Handle control keys (e.g. CursorUp)
-		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) {
-			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
-		}
-
-
-		return (KeyCode)(uint)keyInfo.KeyChar;
-	}
-	#endregion Keyboard Handling
-
-	void ProcessInput (InputResult inputEvent)
-	{
-		switch (inputEvent.EventType) {
-		case NetEvents.EventType.Key:
-			var consoleKeyInfo = inputEvent.ConsoleKeyInfo;
-			//if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-			//	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-			//}
-
-			//Debug.WriteLine ($"event: {inputEvent}");
-
-			var map = MapKey (consoleKeyInfo);
-
-			if (map == KeyCode.Null) {
-				break;
-			}
-
-			OnKeyDown (new Key (map));
-			OnKeyUp (new Key (map));
-			break;
-		case NetEvents.EventType.Mouse:
-			OnMouseEvent (new MouseEventEventArgs (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;
-			OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
-			break;
-		case NetEvents.EventType.RequestResponse:
-			break;
-		case NetEvents.EventType.WindowPosition:
-			break;
-		default:
-			throw new ArgumentOutOfRangeException ();
-		}
-	}
-
-	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
-	{
-		var input = new InputResult {
-			EventType = NetEvents.EventType.Key,
-			ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
-		};
-
-		try {
-			ProcessInput (input);
-		} catch (OverflowException) { }
-	}
-
-
-	#region Not Implemented
-	public override void Suspend () => throw new NotImplementedException ();
-	#endregion
+            }
+            catch (IOException)
+            {
+                Clip = new Rect (0, 0, Cols, Rows);
+            }
+            catch (ArgumentOutOfRangeException)
+            {
+                Clip = new Rect (0, 0, Cols, Rows);
+            }
+        }
+        else
+        {
+            Console.Out.Write (EscSeqUtils.CSI_SetTerminalWindowSize (Rows, Cols));
+        }
+
+        Clip = new Rect (0, 0, Cols, Rows);
+    }
+
+    #endregion
+
+    #region Color Handling
+
+    // Cache the list of ConsoleColor values.
+    private static readonly HashSet<int> ConsoleColorValues = new (
+                                                                   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 readonly Dictionary<ConsoleColor, int> colorMap = new ()
+    {
+        { 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.
+    private int MapColors (ConsoleColor color, bool isForeground = true)
+    {
+        return colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 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 (
+    //		platformColor: ((((int)foreground.ColorName) & 0xffff) << 16) | (((int)background.ColorName) & 0xffff),
+    //		foreground: foreground,
+    //		background: background
+    //	);
+    //}
+
+    #endregion
+
+    #region Cursor Handling
+
+    private 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;
+            }
+        }
+
+        // + 1 is needed because non-Windows is based on 1 instead of 0 and
+        // Console.CursorTop/CursorLeft isn't reliable.
+        Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
+
+        return true;
+    }
+
+    private CursorVisibility? _cachedCursorVisibility;
+
+    public override void UpdateCursor ()
+    {
+        EnsureCursorVisibility ();
+
+        if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
+        {
+            SetCursorPosition (Col, Row);
+            SetWindowPosition (0, Row);
+        }
+    }
+
+    public override bool GetCursorVisibility (out CursorVisibility visibility)
+    {
+        visibility = _cachedCursorVisibility ?? CursorVisibility.Default;
+
+        return visibility == CursorVisibility.Default;
+    }
+
+    public override bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _cachedCursorVisibility = visibility;
+
+        bool isVisible = RunningUnitTests
+                             ? visibility == CursorVisibility.Default
+                             : Console.CursorVisible = visibility == CursorVisibility.Default;
+        Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+
+        return isVisible;
+    }
+
+    public override bool EnsureCursorVisibility ()
+    {
+        if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
+        {
+            GetCursorVisibility (out CursorVisibility cursorVisibility);
+            _cachedCursorVisibility = cursorVisibility;
+            SetCursorVisibility (CursorVisibility.Invisible);
+
+            return false;
+        }
+
+        SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default);
+
+        return _cachedCursorVisibility == CursorVisibility.Default;
+    }
+
+    #endregion
+
+    #region Mouse Handling
+
+    public void StartReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+        }
+    }
+
+    public void StopReportingMouseMoves ()
+    {
+        if (!RunningUnitTests)
+        {
+            Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
+        }
+    }
+
+    private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
+    {
+        //System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+
+        MouseFlags mouseFlag = 0;
+
+        if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button1TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button2TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button3TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledUp;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledDown;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledLeft;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0)
+        {
+            mouseFlag |= MouseFlags.WheeledRight;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Pressed;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Released) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Released;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4Clicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4DoubleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0)
+        {
+            mouseFlag |= MouseFlags.Button4TripleClicked;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0)
+        {
+            mouseFlag |= MouseFlags.ReportMousePosition;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonShift) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonShift;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonCtrl;
+        }
+
+        if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0)
+        {
+            mouseFlag |= MouseFlags.ButtonAlt;
+        }
+
+        return new MouseEvent { X = me.Position.X, Y = me.Position.Y, Flags = mouseFlag };
+    }
+
+    #endregion Mouse Handling
+
+    #region Keyboard Handling
+
+    private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+    {
+        if (consoleKeyInfo.Key != ConsoleKey.Packet)
+        {
+            return consoleKeyInfo;
+        }
+
+        ConsoleModifiers mod = consoleKeyInfo.Modifiers;
+        bool shift = (mod & ConsoleModifiers.Shift) != 0;
+        bool alt = (mod & ConsoleModifiers.Alt) != 0;
+        bool control = (mod & ConsoleModifiers.Control) != 0;
+
+        ConsoleKeyInfo cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+
+        return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
+    }
+
+    private KeyCode MapKey (ConsoleKeyInfo keyInfo)
+    {
+        switch (keyInfo.Key)
+        {
+            case ConsoleKey.OemPeriod:
+            case ConsoleKey.OemComma:
+            case ConsoleKey.OemPlus:
+            case ConsoleKey.OemMinus:
+            case ConsoleKey.Packet:
+            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:
+                if (keyInfo.KeyChar == 0)
+                {
+                    // If the keyChar is 0, keyInfo.Key value is not a printable character. 
+
+                    return KeyCode.Null; // MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
+                }
+
+                if (keyInfo.Modifiers != ConsoleModifiers.Shift)
+                {
+                    // If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
+                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+                }
+
+                // Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+                // and passing on Shift would be redundant.
+                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+        }
+
+        ConsoleKey key = keyInfo.Key;
+
+        // A..Z are special cased:
+        // - Alone, they represent lowercase a...z
+        // - With ShiftMask they are A..Z
+        // - If CapsLock is on the above is reversed.
+        // - If Alt and/or Ctrl are present, treat as upper case
+        if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z)
+        {
+            if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
+                || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
+            {
+                return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+            }
+
+            if (keyInfo.Modifiers == ConsoleModifiers.Shift)
+            {
+                // If ShiftMask is on  add the ShiftMask
+                if (char.IsUpper (keyInfo.KeyChar))
+                {
+                    return (KeyCode)(uint)keyInfo.Key | KeyCode.ShiftMask;
+                }
+            }
+
+            return (KeyCode)keyInfo.KeyChar;
+        }
+
+        // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+        if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
+        {
+            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.Key);
+        }
+
+        // Handle control keys (e.g. CursorUp)
+        if (keyInfo.Key != ConsoleKey.None
+            && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
+        {
+            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
+        }
+
+        return (KeyCode)keyInfo.KeyChar;
+    }
+
+    #endregion Keyboard Handling
 }
 
 /// <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.
+///     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>
-class NetMainLoop : IMainLoopDriver {
-	readonly ManualResetEventSlim _eventReady = new (false);
-	readonly ManualResetEventSlim _waitForProbe = new (false);
-	readonly Queue<InputResult?> _resultQueue = new ();
-	MainLoop _mainLoop;
-	CancellationTokenSource _eventReadyTokenSource = new ();
-	readonly CancellationTokenSource _inputHandlerTokenSource = new ();
-	internal NetEvents _netEvents;
-
-	/// <summary>
-	/// Invoked when a Key is pressed.
-	/// </summary>
-	internal Action<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>
-	/// <exception cref="ArgumentNullException"></exception>
-	public NetMainLoop (ConsoleDriver consoleDriver = null)
-	{
-		if (consoleDriver == null) {
-			throw new ArgumentNullException (nameof (consoleDriver));
-		}
-		_netEvents = new NetEvents (consoleDriver);
-	}
-
-	void NetInputHandler ()
-	{
-		while (_mainLoop != null) {
-			try {
-				if (!_inputHandlerTokenSource.IsCancellationRequested) {
-					_waitForProbe.Wait (_inputHandlerTokenSource.Token);
-				}
-
-			} catch (OperationCanceledException) {
-				return;
-			} finally {
-				if (_waitForProbe.IsSet) {
-					_waitForProbe.Reset ();
-				}
-			}
-
-			if (_inputHandlerTokenSource.IsCancellationRequested) {
-				return;
-			}
-			if (_resultQueue.Count == 0) {
-				_resultQueue.Enqueue (_netEvents.DequeueInput ());
-			}
-			try {
-				while (_resultQueue.Peek () == null) {
-					_resultQueue.Dequeue ();
-				}
-				if (_resultQueue.Count > 0) {
-					_eventReady.Set ();
-				}
-			} catch (InvalidOperationException) {
-				// Ignore
-			}
-		}
-	}
-
-	void IMainLoopDriver.Setup (MainLoop mainLoop)
-	{
-		_mainLoop = mainLoop;
-		Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
-	}
-
-	void IMainLoopDriver.Wakeup () => _eventReady.Set ();
-
-	bool IMainLoopDriver.EventsPending ()
-	{
-		_waitForProbe.Set ();
-
-		if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) {
-			return true;
-		}
-
-		try {
-			if (!_eventReadyTokenSource.IsCancellationRequested) {
-				// Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
-				// are no timers, but there IS an idle handler waiting.
-				_eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
-			}
-		} catch (OperationCanceledException) {
-			return true;
-		} finally {
-			_eventReady.Reset ();
-		}
-
-		if (!_eventReadyTokenSource.IsCancellationRequested) {
-			return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
-		}
-
-		_eventReadyTokenSource.Dispose ();
-		_eventReadyTokenSource = new CancellationTokenSource ();
-		return true;
-	}
-
-	void IMainLoopDriver.Iteration ()
-	{
-		while (_resultQueue.Count > 0) {
-			ProcessInput?.Invoke (_resultQueue.Dequeue ().Value);
-		}
-	}
-
-	void IMainLoopDriver.TearDown ()
-	{
-		_inputHandlerTokenSource?.Cancel ();
-		_inputHandlerTokenSource?.Dispose ();
-		_eventReadyTokenSource?.Cancel ();
-		_eventReadyTokenSource?.Dispose ();
-
-		_eventReady?.Dispose ();
-
-		_resultQueue?.Clear ();
-		_waitForProbe?.Dispose ();
-		_netEvents?.Dispose ();
-		_netEvents = null;
-
-		_mainLoop = null;
-	}
-}
+/// <remarks>This implementation is used for NetDriver.</remarks>
+internal class NetMainLoop : IMainLoopDriver
+{
+    internal NetEvents _netEvents;
+
+    /// <summary>Invoked when a Key is pressed.</summary>
+    internal Action<InputResult> ProcessInput;
+
+    private readonly ManualResetEventSlim _eventReady = new (false);
+    private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
+    private readonly Queue<InputResult?> _resultQueue = new ();
+    private readonly ManualResetEventSlim _waitForProbe = new (false);
+    private CancellationTokenSource _eventReadyTokenSource = new ();
+    private MainLoop _mainLoop;
+
+    /// <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>
+    /// <exception cref="ArgumentNullException"></exception>
+    public NetMainLoop (ConsoleDriver consoleDriver = null)
+    {
+        if (consoleDriver == null)
+        {
+            throw new ArgumentNullException (nameof (consoleDriver));
+        }
+
+        _netEvents = new NetEvents (consoleDriver);
+    }
+
+    void IMainLoopDriver.Setup (MainLoop mainLoop)
+    {
+        _mainLoop = mainLoop;
+        Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
+    }
+
+    void IMainLoopDriver.Wakeup () { _eventReady.Set (); }
+
+    bool IMainLoopDriver.EventsPending ()
+    {
+        _waitForProbe.Set ();
+
+        if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout))
+        {
+            return true;
+        }
+
+        try
+        {
+            if (!_eventReadyTokenSource.IsCancellationRequested)
+            {
+                // Note: ManualResetEventSlim.Wait will wait indefinitely if the timeout is -1. The timeout is -1 when there
+                // are no timers, but there IS an idle handler waiting.
+                _eventReady.Wait (waitTimeout, _eventReadyTokenSource.Token);
+            }
+        }
+        catch (OperationCanceledException)
+        {
+            return true;
+        }
+        finally
+        {
+            _eventReady.Reset ();
+        }
+
+        if (!_eventReadyTokenSource.IsCancellationRequested)
+        {
+            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+        }
+
+        _eventReadyTokenSource.Dispose ();
+        _eventReadyTokenSource = new CancellationTokenSource ();
+
+        return true;
+    }
+
+    void IMainLoopDriver.Iteration ()
+    {
+        while (_resultQueue.Count > 0)
+        {
+            ProcessInput?.Invoke (_resultQueue.Dequeue ().Value);
+        }
+    }
+
+    void IMainLoopDriver.TearDown ()
+    {
+        _inputHandlerTokenSource?.Cancel ();
+        _inputHandlerTokenSource?.Dispose ();
+        _eventReadyTokenSource?.Cancel ();
+        _eventReadyTokenSource?.Dispose ();
+
+        _eventReady?.Dispose ();
+
+        _resultQueue?.Clear ();
+        _waitForProbe?.Dispose ();
+        _netEvents?.Dispose ();
+        _netEvents = null;
+
+        _mainLoop = null;
+    }
+
+    private void NetInputHandler ()
+    {
+        while (_mainLoop != null)
+        {
+            try
+            {
+                if (!_inputHandlerTokenSource.IsCancellationRequested)
+                {
+                    _waitForProbe.Wait (_inputHandlerTokenSource.Token);
+                }
+            }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
+            finally
+            {
+                if (_waitForProbe.IsSet)
+                {
+                    _waitForProbe.Reset ();
+                }
+            }
+
+            if (_inputHandlerTokenSource.IsCancellationRequested)
+            {
+                return;
+            }
+
+            if (_resultQueue.Count == 0)
+            {
+                _resultQueue.Enqueue (_netEvents.DequeueInput ());
+            }
+
+            try
+            {
+                while (_resultQueue.Peek () == null)
+                {
+                    _resultQueue.Dequeue ();
+                }
+
+                if (_resultQueue.Count > 0)
+                {
+                    _eventReady.Set ();
+                }
+            }
+            catch (InvalidOperationException)
+            {
+                // Ignore
+            }
+        }
+    }
+}

文件差异内容过多而无法显示
+ 843 - 702
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs


+ 52 - 83
Terminal.Gui/Drawing/AnsiColorCode.cs

@@ -1,87 +1,56 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for background
-/// color.
+///     The 16 foreground color codes used by ANSI Esc sequences for 256 color terminals. Add 10 to these values for
+///     background color.
 /// </summary>
-public enum AnsiColorCode {
-	/// <summary>
-	/// The ANSI color code for Black.
-	/// </summary>
-	BLACK = 30,
-
-	/// <summary>
-	/// The ANSI color code for Red.
-	/// </summary>
-	RED = 31,
-
-	/// <summary>
-	/// The ANSI color code for Green.
-	/// </summary>
-	GREEN = 32,
-
-	/// <summary>
-	/// The ANSI color code for Yellow.
-	/// </summary>
-	YELLOW = 33,
-
-	/// <summary>
-	/// The ANSI color code for Blue.
-	/// </summary>
-	BLUE = 34,
-
-	/// <summary>
-	/// The ANSI color code for Magenta.
-	/// </summary>
-	MAGENTA = 35,
-
-	/// <summary>
-	/// The ANSI color code for Cyan.
-	/// </summary>
-	CYAN = 36,
-
-	/// <summary>
-	/// The ANSI color code for White.
-	/// </summary>
-	WHITE = 37,
-
-	/// <summary>
-	/// The ANSI color code for Bright Black.
-	/// </summary>
-	BRIGHT_BLACK = 90,
-
-	/// <summary>
-	/// The ANSI color code for Bright Red.
-	/// </summary>
-	BRIGHT_RED = 91,
-
-	/// <summary>
-	/// The ANSI color code for Bright Green.
-	/// </summary>
-	BRIGHT_GREEN = 92,
-
-	/// <summary>
-	/// The ANSI color code for Bright Yellow.
-	/// </summary>
-	BRIGHT_YELLOW = 93,
-
-	/// <summary>
-	/// The ANSI color code for Bright Blue.
-	/// </summary>
-	BRIGHT_BLUE = 94,
-
-	/// <summary>
-	/// The ANSI color code for Bright Magenta.
-	/// </summary>
-	BRIGHT_MAGENTA = 95,
-
-	/// <summary>
-	/// The ANSI color code for Bright Cyan.
-	/// </summary>
-	BRIGHT_CYAN = 96,
-
-	/// <summary>
-	/// The ANSI color code for Bright White.
-	/// </summary>
-	BRIGHT_WHITE = 97
-}
+public enum AnsiColorCode
+{
+    /// <summary>The ANSI color code for Black.</summary>
+    BLACK = 30,
+
+    /// <summary>The ANSI color code for Red.</summary>
+    RED = 31,
+
+    /// <summary>The ANSI color code for Green.</summary>
+    GREEN = 32,
+
+    /// <summary>The ANSI color code for Yellow.</summary>
+    YELLOW = 33,
+
+    /// <summary>The ANSI color code for Blue.</summary>
+    BLUE = 34,
+
+    /// <summary>The ANSI color code for Magenta.</summary>
+    MAGENTA = 35,
+
+    /// <summary>The ANSI color code for Cyan.</summary>
+    CYAN = 36,
+
+    /// <summary>The ANSI color code for White.</summary>
+    WHITE = 37,
+
+    /// <summary>The ANSI color code for Bright Black.</summary>
+    BRIGHT_BLACK = 90,
+
+    /// <summary>The ANSI color code for Bright Red.</summary>
+    BRIGHT_RED = 91,
+
+    /// <summary>The ANSI color code for Bright Green.</summary>
+    BRIGHT_GREEN = 92,
+
+    /// <summary>The ANSI color code for Bright Yellow.</summary>
+    BRIGHT_YELLOW = 93,
+
+    /// <summary>The ANSI color code for Bright Blue.</summary>
+    BRIGHT_BLUE = 94,
+
+    /// <summary>The ANSI color code for Bright Magenta.</summary>
+    BRIGHT_MAGENTA = 95,
+
+    /// <summary>The ANSI color code for Bright Cyan.</summary>
+    BRIGHT_CYAN = 96,
+
+    /// <summary>The ANSI color code for Bright White.</summary>
+    BRIGHT_WHITE = 97
+}

+ 138 - 163
Terminal.Gui/Drawing/Attribute.cs

@@ -1,171 +1,146 @@
 #nullable enable
 using System.Text.Json.Serialization;
+
 namespace Terminal.Gui;
 
-/// <summary>
-/// Attributes represent how text is styled when displayed in the terminal.
-/// </summary>
+/// <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.
+///     <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 readonly struct Attribute : IEquatable<Attribute> {
-	/// <summary>
-	/// Default empty attribute.
-	/// </summary>
-	public static readonly Attribute Default = new (Color.White, ColorName.Black);
-
-	/// <summary>
-	/// The <see cref="ConsoleDriver"/>-specific color value.
-	/// </summary>
-	[JsonIgnore (Condition = JsonIgnoreCondition.Always)]
-	internal int PlatformColor { get; }
-
-	/// <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>
-	/// Initializes a new instance with default values.
-	/// </summary>
-	public Attribute ()
-	{
-		PlatformColor = -1;
-		Foreground = Default.Foreground;
-		Background = Default.Background;
-	}
-
-	/// <summary>
-	/// Initializes a new instance from an existing instance.
-	/// </summary>
-	public Attribute (in Attribute attr)
-	{
-		PlatformColor = -1;
-		Foreground = attr.Foreground;
-		Background = attr.Background;
-	}
-
-	/// <summary>
-	/// Initializes a new instance with platform specific color value.
-	/// </summary>
-	/// <param name="platformColor">Value.</param>
-	internal Attribute (int platformColor)
-	{
-		PlatformColor = platformColor;
-		Foreground = Default.Foreground;
-		Background = Default.Background;
-	}
-
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Attribute"/> struct.
-	/// </summary>
-	/// <param name="platformColor">platform-dependent color value.</param>
-	/// <param name="foreground">Foreground</param>
-	/// <param name="background">Background</param>
-	internal Attribute (int platformColor, in Color foreground, in Color background)
-	{
-		Foreground = foreground;
-		Background = background;
-		PlatformColor = platformColor;
-	}
-
-	/// <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, Color background)
-	{
-		Foreground = foreground;
-		Background = background;
-
-		// TODO: Once CursesDriver supports truecolor all the PlatformColor stuff goes away
-		if (Application.Driver == null) {
-			PlatformColor = -1;
-			return;
-		}
-
-		var make = Application.Driver.MakeColor (foreground, background);
-		PlatformColor = make.PlatformColor;
-	}
-
-	/// <summary>
-	/// Initializes a new instance with a <see cref="ColorName"/> value. Both <see cref="Foreground"/> and
-	/// <see cref="Background"/> will be set to the specified color.
-	/// </summary>
-	/// <param name="colorName">Value.</param>
-	internal Attribute (ColorName colorName) : this (colorName, colorName) { }
-
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Attribute"/> struct.
-	/// </summary>
-	/// <param name="foregroundName">Foreground</param>
-	/// <param name="backgroundName">Background</param>
-	public Attribute (in ColorName foregroundName, in ColorName backgroundName) : this (new Color (foregroundName), new Color (backgroundName)) { }
-
-
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Attribute"/> struct.
-	/// </summary>
-	/// <param name="foregroundName">Foreground</param>
-	/// <param name="background">Background</param>
-	public Attribute (ColorName foregroundName, Color background) : this (new Color (foregroundName), background) { }
-
-	/// <summary>
-	/// Initializes a new instance of the <see cref="Attribute"/> struct.
-	/// </summary>
-	/// <param name="foreground">Foreground</param>
-	/// <param name="backgroundName">Background</param>
-	public Attribute (Color foreground, ColorName backgroundName) : this (foreground, new Color (backgroundName)) { }
-
-	/// <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) => obj is Attribute other && Equals (other);
-
-	/// <inheritdoc/>
-	public bool Equals (Attribute other) => PlatformColor == other.PlatformColor &&
-											Foreground == other.Foreground &&
-											Background == other.Background;
-
-	/// <inheritdoc/>
-	public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
-
-	/// <inheritdoc/>
-	public override string ToString () =>
-		// Note: Unit tests are dependent on this format
-		$"[{Foreground},{Background}]";
+public readonly struct Attribute : IEquatable<Attribute>
+{
+    /// <summary>Default empty attribute.</summary>
+    public static readonly Attribute Default = new (Color.White, ColorName.Black);
+
+    /// <summary>The <see cref="ConsoleDriver"/>-specific color value.</summary>
+    [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
+    internal int PlatformColor { get; }
+
+    /// <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>Initializes a new instance with default values.</summary>
+    public Attribute ()
+    {
+        PlatformColor = -1;
+        Foreground = Default.Foreground;
+        Background = Default.Background;
+    }
+
+    /// <summary>Initializes a new instance from an existing instance.</summary>
+    public Attribute (in Attribute attr)
+    {
+        PlatformColor = -1;
+        Foreground = attr.Foreground;
+        Background = attr.Background;
+    }
+
+    /// <summary>Initializes a new instance with platform specific color value.</summary>
+    /// <param name="platformColor">Value.</param>
+    internal Attribute (int platformColor)
+    {
+        PlatformColor = platformColor;
+        Foreground = Default.Foreground;
+        Background = Default.Background;
+    }
+
+    /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
+    /// <param name="platformColor">platform-dependent color value.</param>
+    /// <param name="foreground">Foreground</param>
+    /// <param name="background">Background</param>
+    internal Attribute (int platformColor, in Color foreground, in Color background)
+    {
+        Foreground = foreground;
+        Background = background;
+        PlatformColor = platformColor;
+    }
+
+    /// <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, Color background)
+    {
+        Foreground = foreground;
+        Background = background;
+
+        // TODO: Once CursesDriver supports truecolor all the PlatformColor stuff goes away
+        if (Application.Driver == null)
+        {
+            PlatformColor = -1;
+
+            return;
+        }
+
+        Attribute make = Application.Driver.MakeColor (foreground, background);
+        PlatformColor = make.PlatformColor;
+    }
+
+    /// <summary>
+    ///     Initializes a new instance with a <see cref="ColorName"/> value. Both <see cref="Foreground"/> and
+    ///     <see cref="Background"/> will be set to the specified color.
+    /// </summary>
+    /// <param name="colorName">Value.</param>
+    internal Attribute (ColorName colorName) : this (colorName, colorName) { }
+
+    /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
+    /// <param name="foregroundName">Foreground</param>
+    /// <param name="backgroundName">Background</param>
+    public Attribute (in ColorName foregroundName, in ColorName backgroundName) : this (
+                                                                                        new Color (foregroundName),
+                                                                                        new Color (backgroundName)
+                                                                                       )
+    { }
+
+    /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
+    /// <param name="foregroundName">Foreground</param>
+    /// <param name="background">Background</param>
+    public Attribute (ColorName foregroundName, Color background) : this (new Color (foregroundName), background) { }
+
+    /// <summary>Initializes a new instance of the <see cref="Attribute"/> struct.</summary>
+    /// <param name="foreground">Foreground</param>
+    /// <param name="backgroundName">Background</param>
+    public Attribute (Color foreground, ColorName backgroundName) : this (foreground, new Color (backgroundName)) { }
+
+    /// <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) { return 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) { return !(left == right); }
+
+    /// <inheritdoc/>
+    public override bool Equals (object? obj) { return obj is Attribute other && Equals (other); }
+
+    /// <inheritdoc/>
+    public bool Equals (Attribute other) { return PlatformColor == other.PlatformColor && Foreground == other.Foreground && Background == other.Background; }
+
+    /// <inheritdoc/>
+    public override int GetHashCode () { return HashCode.Combine (PlatformColor, Foreground, Background); }
+
+    /// <inheritdoc/>
+    public override string ToString ()
+    {
+        // Note: Unit tests are dependent on this format
+        return $"[{Foreground},{Background}]";
+    }
 }

+ 35 - 38
Terminal.Gui/Drawing/Cell.cs

@@ -1,46 +1,43 @@
-using System.Collections.Generic;
-using System.Text;
-
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
-/// Represents a single row/column in a Terminal.Gui rendering surface
-/// (e.g. <see cref="LineCanvas"/> and <see cref="ConsoleDriver"/>).
+///     Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
+///     <see cref="ConsoleDriver"/>).
 /// </summary>
-public class Cell {
-	Rune _rune;
-	/// <summary>
-	/// The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.
-	/// </summary>
-	public Rune Rune {
-		get => _rune;
-		set {
-			CombiningMarks.Clear ();
-			_rune = value;
-		}
-	}
+public class Cell
+{
+    private Rune _rune;
+
+    /// <summary>The attributes to use when drawing the Glyph.</summary>
+    public Attribute? Attribute { get; set; }
 
-	/// <summary>
-	/// The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence.
-	/// If <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
-	/// </summary>
-	/// <remarks>
-	/// Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a single Rune.
-	/// </remarks>
-	internal List<Rune> CombiningMarks { get; } = new List<Rune> ();
+    /// <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; }
 
-	/// <summary>
-	/// The attributes to use when drawing the Glyph.
-	/// </summary>
-	public Attribute? Attribute { get; set; }
+    /// <summary>The character to display. If <see cref="Rune"/> is <see langword="null"/>, then <see cref="Rune"/> is ignored.</summary>
+    public Rune Rune
+    {
+        get => _rune;
+        set
+        {
+            CombiningMarks.Clear ();
+            _rune = value;
+        }
+    }
 
-	/// <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; }
+    /// <summary>
+    ///     The combining marks for <see cref="Rune"/> that when combined makes this Cell a combining sequence. If
+    ///     <see cref="CombiningMarks"/> empty, then <see cref="CombiningMarks"/> is ignored.
+    /// </summary>
+    /// <remarks>
+    ///     Only valid in the rare case where <see cref="Rune"/> is a combining sequence that could not be normalized to a
+    ///     single Rune.
+    /// </remarks>
+    internal List<Rune> CombiningMarks { get; } = new ();
 
-	/// <inheritdoc />
-	public override string ToString () => $"[{Rune}, {Attribute}]";
+    /// <inheritdoc/>
+    public override string ToString () { return $"[{Rune}, {Attribute}]"; }
 }

+ 607 - 415
Terminal.Gui/Drawing/Color.Formatting.cs

@@ -6,419 +6,611 @@ using System.Runtime.CompilerServices;
 
 namespace Terminal.Gui;
 
-public readonly partial record struct Color {
-
-	/// <summary>
-	///   Converts the provided <see langword="string" /> to a new <see cref="Color" /> value.
-	/// </summary>
-	/// <param name="text">
-	///   The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
-	///   "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
-	/// </param>
-	/// <param name="formatProvider">
-	///   If specified and not <see langword="null" />, will be passed to <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
-	/// </param>
-	/// <returns>
-	///   A <see cref="Color" /> value equivalent to <paramref name="text" />, if parsing was successful.
-	/// </returns>
-	/// <remarks>
-	///   While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
-	/// </remarks>
-	/// <exception cref="ArgumentNullException">If <paramref name="text" /> is <see langword="null" />.</exception>
-	/// <exception cref="ArgumentException">
-	///   If <paramref name="text" /> is an empty string or consists of only whitespace characters.
-	/// </exception>
-	/// <exception cref="ColorParseException">
-	///   If thrown by <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
-	/// </exception>
-	[Pure]
-	[SkipLocalsInit]
-	public static Color Parse (string? text, IFormatProvider? formatProvider = null)
-	{
-		ArgumentException.ThrowIfNullOrWhiteSpace (text, nameof (text));
-		if ( text is { Length: < 3 } && formatProvider is null ) {
-			throw new ColorParseException (text, reason: "Provided text is too short to be any known color format.", badValue: text);
-		}
-		return Parse (text.AsSpan (), formatProvider ?? CultureInfo.InvariantCulture);
-	}
-
-	/// <summary>
-	///   Converts the provided <see cref="ReadOnlySpan{T}" /> of <see langword="char" /> to a new <see cref="Color" /> value.
-	/// </summary>
-	/// <param name="text">
-	///   The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
-	///   "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
-	/// </param>
-	/// <param name="formatProvider">
-	///   Optional <see cref="IFormatProvider" /> to provide parsing services for the input text.
-	///   <br />
-	///   Defaults to <see cref="CultureInfo.InvariantCulture" /> if <see langword="null" />.
-	///   <br />
-	///   If not null, must implement <see cref="ICustomColorFormatter" /> or will be ignored and <see cref="CultureInfo.InvariantCulture" /> will
-	///   be used.
-	/// </param>
-	/// <returns>
-	///   A <see cref="Color" /> value equivalent to <paramref name="text" />, if parsing was successful.
-	/// </returns>
-	/// <remarks>
-	///   While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
-	/// </remarks>
-	/// <exception cref="ArgumentException">
-	///   with an inner <see cref="FormatException" /> if <paramref name="text" /> was unable to be successfully parsed as a <see cref="Color" />,
-	///   for any reason.
-	/// </exception>
-	[Pure]
-	[SkipLocalsInit]
-	public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
-	{
-		return text switch {
-			// Null string or empty span provided
-			{ IsEmpty: true } when formatProvider is null => throw new ColorParseException (in text, "The text provided was null or empty.", in text),
-			// A valid ICustomColorFormatter was specified and the text wasn't null or empty
-			{ IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
-			// Input string is only whitespace
-			{ Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (in text, "The text provided consisted of only whitespace characters.", in text),
-			// Any string too short to possibly be any supported format.
-			{ Length: > 0 and < 4 } => throw new ColorParseException (in text, "Text was too short to be any possible supported format.", in text),
-			// The various hexadecimal cases
-			['#', ..] hexString => hexString switch {
-				// #RGB
-				['#', var rChar, var gChar, var bChar] chars when chars [1..].IsAllAsciiHexDigits () =>
-					new Color (byte.Parse ([rChar, rChar], NumberStyles.HexNumber), byte.Parse ([gChar, gChar], NumberStyles.HexNumber), byte.Parse ([bChar, bChar], NumberStyles.HexNumber)),
-				// #ARGB
-				['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..].IsAllAsciiHexDigits () =>
-					new Color (byte.Parse ([rChar, rChar], NumberStyles.HexNumber), byte.Parse ([gChar, gChar], NumberStyles.HexNumber), byte.Parse ([bChar, bChar], NumberStyles.HexNumber), byte.Parse ([aChar, aChar], NumberStyles.HexNumber)),
-				// #RRGGBB
-				['#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char] chars when chars [1..].IsAllAsciiHexDigits () =>
-					new Color (byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)),
-				// #AARRGGBB
-				['#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char, var g2Char, var b1Char, var b2Char] chars when chars [1..].IsAllAsciiHexDigits () =>
-					new Color (byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber), byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber), byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber), byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)),
-				_ => throw new ColorParseException (in hexString, $"Color hex string {hexString} was not in a supported format", in hexString)
-			},
-			// rgb(r,g,b) or rgb(r,g,b,a)
-			['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
-			// rgba(r,g,b,a) or rgba(r,g,b)
-			['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
-			// Attempt to parse as a named color from the ColorName enum
-			{ } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) => new Color (colorName),
-			// Any other input
-			_ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
-		};
-
-		[Pure]
-		[SkipLocalsInit]
-		static Color ParseRgbaFormat (in ReadOnlySpan<char> originalString, in int startIndex)
-		{
-			ReadOnlySpan<char> valuesSubstring = originalString [startIndex..^1];
-			Span<Range> valueRanges = stackalloc Range [4];
-			int rangeCount = valuesSubstring.Split (valueRanges, ',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
-
-			switch ( rangeCount ) {
-			case 3:
-			{
-				// rgba(r,g,b)
-				ParseRgbValues (in valuesSubstring, in valueRanges, in originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan);
-				return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
-			}
-			case 4:
-			{
-				// rgba(r,g,b,a)
-				ParseRgbValues (in valuesSubstring, in valueRanges, in originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan);
-				ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
-				if ( !aSpan.IsAllAsciiDigits () ) {
-					throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in aSpan, nameof (A));
-				}
-				return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
-			}
-			default:
-				throw new ColorParseException (in originalString, $"Wrong number of values. Expected 3 or 4 decimal integers. Got {rangeCount}.", in originalString);
-			}
-
-			[Pure]
-			[SkipLocalsInit]
-			static void ParseRgbValues (in ReadOnlySpan<char> valuesString, in Span<Range> valueComponentRanges, in ReadOnlySpan<char> originalString, out ReadOnlySpan<char> rSpan, out ReadOnlySpan<char> gSpan, out ReadOnlySpan<char> bSpan)
-			{
-
-				rSpan = valuesString [valueComponentRanges [0]];
-				if ( !rSpan.IsAllAsciiDigits () ) {
-					throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in rSpan, nameof (R));
-				}
-				gSpan = valuesString [valueComponentRanges [1]];
-				if ( !gSpan.IsAllAsciiDigits () ) {
-					throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in gSpan, nameof (G));
-				}
-				bSpan = valuesString [valueComponentRanges [2]];
-				if ( !bSpan.IsAllAsciiDigits () ) {
-					throw new ColorParseException (in originalString, "Value was not composed entirely of decimal digits.", in bSpan, nameof (B));
-				}
-			}
-		}
-	}
-
-	/// <inheritdoc />
-	[Pure]
-	[SkipLocalsInit]
-	public static Color Parse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider)
-	{
-		return Parse (Encoding.UTF8.GetString (utf8Text), provider);
-	}
-
-	/// <inheritdoc cref="object.ToString" />
-	/// <summary>
-	///   Returns a <see langword="string" /> representation of the current <see cref="Color" /> value, according to the provided
-	///   <paramref name="formatString" /> and optional <paramref name="formatProvider" />.
-	/// </summary>
-	/// <param name="formatString">
-	///   A format string that will be passed to <see cref="string.Format(System.IFormatProvider?,string,object?[])" />.<para/>
-	///   See remarks for parameters passed to that method.
-	/// </param>
-	/// <param name="formatProvider">
-	///   An optional <see cref="IFormatProvider" /> to use when formatting the <see cref="Color" /> using custom format strings not specified for
-	///   this method. Provides this instance as <see cref="Argb" />.
-	///   <br />
-	///   If this parameter is not null, the specified <see cref="IFormatProvider" /> will be used instead of the custom formatting provided by the
-	///   <see cref="Color" /> type.<para/>
-	///   See remarks for defined format strings.
-	/// </param>
-	/// <remarks>
-	///   Pre-defined format strings for this method, if a custom <paramref name="formatProvider" /> is not supplied are: <list type="bullet">
-	///     <listheader>
-	///       <term>Value</term> <description>Result</description>
-	///     </listheader> <item>
-	///       <term>g or null or empty string</term> <description>
-	///         General/default format - Returns a named <see cref="Color" /> if there is a match, or a 24-bit/3-byte/6-hex digit string in
-	///         "#RRGGBB" format.
-	///       </description>
-	///     </item> <item>
-	///       <term>G</term> <description>
-	///         Extended general format - Returns a named <see cref="Color" /> if there is a match, or a 32-bit/4-byte/8-hex digit string in
-	///         "#AARRGGBB" format.
-	///       </description>
-	///     </item> <item>
-	///       <term>d</term> <description>
-	///         Decimal format - Returns a 3-component decimal representation of the <see cref="Color" /> in "rgb(R,G,B)" format.
-	///       </description>
-	///     </item> <item>
-	///       <term>D</term> <description>
-	///         Extended decimal format - Returns a 4-component decimal representation of the <see cref="Color" /> in "rgba(R,G,B,A)" format.
-	///       </description>
-	///     </item>
-	///   </list>
-	///   <para>
-	///     If <paramref name="formatProvider" /> is provided and is a non-null <see cref="ICustomColorFormatter" />, the following behaviors are
-	///     available, for the specified values of <paramref name="formatString" />: <list type="bullet">
-	///       <listheader>
-	///         <term>Value</term> <description>Result</description>
-	///       </listheader> <item>
-	///         <term>null or empty string</term> <description>
-	///           Calls <see cref="ICustomColorFormatter.Format(string?,byte,byte,byte,byte)" /> on the provided <paramref name="formatProvider" />
-	///           with the null string, and <see cref="R" />, <see cref="G" />, <see cref="B" />, and <see cref="A" /> as typed arguments of type
-	///           <see cref="Byte" />.
-	///         </description>
-	///       </item> <item>
-	///         <term>All other values</term> <description>
-	///           Calls <see cref="string.Format{TArg0}" /> with the provided <paramref name="formatProvider" /> and
-	///           <paramref name="formatString" /> (parsed as a <see cref="CompositeFormat" />), with the value of <see cref="Argb" /> as the sole
-	///           <see langword="uint" />-typed argument.
-	///         </description>
-	///       </item>
-	///     </list>
-	///   </para>
-	/// </remarks>
-	[SkipLocalsInit]
-	public string ToString ([StringSyntax (StringSyntaxAttribute.CompositeFormat)] string? formatString, IFormatProvider? formatProvider = null)
-	{
-		return (formatString, formatProvider) switch {
-			// Null or empty string and null formatProvider - Revert to 'g' case behavior
-			(null or { Length: 0 }, null) => ToString (),
-			// Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
-			(null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
-			// Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
-			(null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) => string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
-			// Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
-			(null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) => $"#{R:X2}{G:X2}{B:X2}",
-			// Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
-			({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
-			// g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
-			(['g'], null) => ToString (),
-			// G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
-			(['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
-			// d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
-			(['d'], null) => $"rgb({R:D},{G:D},{B:D})",
-			// D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
-			(['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
-			// All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
-			({ }, _) => string.Format (formatProvider ?? CultureInfo.InvariantCulture, CompositeFormat.Parse (formatString), R, G, B, A),
-			_ => throw new InvalidOperationException ($"Unable to create string from Color with value {Argb}, using format string {formatString}")
-		} ?? throw new InvalidOperationException ($"Unable to create string from Color with value {Argb}, using format string {formatString}");
-	}
-
-	/// <inheritdoc />
-	/// <remarks>
-	///   <para>
-	///     This method should be used only when absolutely necessary, because it <b>always</b> has more overhead than
-	///     <see cref="ToString(string?,System.IFormatProvider?)" />, as this method results in an intermediate allocation of one or more instances
-	///     of <see langword="string" /> and a copy of that string to <paramref name="destination" /> if formatting was successful.
-	///     <br />
-	///     When possible, use <see cref="ToString(string?,System.IFormatProvider?)" />, which attempts to avoid intermediate allocations.
-	///   </para>
-	///   <para>
-	///     This method only returns <see langword="true" /> and with its output written to <paramref name="destination" /> if the formatted
-	///     string, <i>in its entirety</i>, will fit in <paramref name="destination" />. If the resulting formatted string is too large to fit in
-	///     <paramref name="destination" />, the result will be false and <paramref name="destination" /> will be unaltered.
-	///   </para>
-	///   <para>
-	///     The resulting formatted string may be <b>shorter</b> than <paramref name="destination" />. When this method returns
-	///     <see langword="true" />, use <paramref name="charsWritten" /> when handling the value of <paramref name="destination" />.
-	///   </para>
-	/// </remarks>
-	[Pure]
-	[SkipLocalsInit]
-	public bool TryFormat (Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
-	{
-		// TODO: This can probably avoid a string allocation with a little more work
-		try {
-			string formattedString = ToString (format.ToString (), provider);
-			if ( formattedString.Length <= destination.Length ) {
-				formattedString.CopyTo (destination);
-				charsWritten = formattedString.Length;
-				return true;
-			}
-		}
-		catch {
-			destination.Clear ();
-			charsWritten = 0;
-			return false;
-		}
-		destination.Clear ();
-		charsWritten = 0;
-		return false;
-	}
-
-	/// <inheritdoc />
-	/// <remarks>
-	///   Use of this method involves a stack allocation of <paramref name="utf8Destination" />.Length * 2 bytes. Use of the overload taking a char
-	///   span is recommended.
-	/// </remarks>
-	[SkipLocalsInit]
-	public bool TryFormat (Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
-	{
-		Span<char> charDestination = stackalloc char [utf8Destination.Length * 2];
-		if ( TryFormat (charDestination, out int charsWritten, format, provider) ) {
-			Encoding.UTF8.GetBytes (charDestination, utf8Destination);
-			bytesWritten = charsWritten / 2;
-			return true;
-		}
-		utf8Destination.Clear ();
-		bytesWritten = 0;
-		return false;
-	}
-
-	/// <summary>
-	///   Converts the provided <see langword="string" /> to a new <see cref="Color" /> value.
-	/// </summary>
-	/// <param name="text">
-	///   The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
-	///   "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor" /> string values.
-	/// </param>
-	/// <param name="formatProvider">
-	///   Optional <see cref="IFormatProvider" /> to provide formatting services for the input text.
-	///   <br />
-	///   Defaults to <see cref="CultureInfo.InvariantCulture" /> if <see langword="null" />.
-	/// </param>
-	/// <param name="result">
-	///   The parsed value, if successful, or <see langword="default" />(<see cref="Color" />), if unsuccessful.
-	/// </param>
-	/// <returns>A <see langword="bool" /> value indicating whether parsing was successful.</returns>
-	/// <remarks>
-	///   While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
-	/// </remarks>
-	[Pure]
-	[SkipLocalsInit]
-	public static bool TryParse (string? text, IFormatProvider? formatProvider, out Color result)
-	{
-		return TryParse (text.AsSpan (), formatProvider ?? CultureInfo.InvariantCulture, out result);
-	}
-
-	/// <summary>
-	///   Converts the provided <see cref="ReadOnlySpan{T}" /> of <see langword="char" /> to a new <see cref="Color" /> value.
-	/// </summary>
-	/// <param name="text">
-	///   The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
-	///   "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor" /> string values.
-	/// </param>
-	/// <param name="formatProvider">
-	///   If specified and not <see langword="null" />, will be passed to <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)" />.
-	/// </param>
-	/// <param name="color">
-	///   The parsed value, if successful, or <see langword="default" />(<see cref="Color" />), if unsuccessful.
-	/// </param>
-	/// <returns>A <see langword="bool" /> value indicating whether parsing was successful.</returns>
-	/// <remarks>
-	///   While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
-	/// </remarks>
-	[Pure]
-	[SkipLocalsInit]
-	public static bool TryParse (ReadOnlySpan<char> text, IFormatProvider? formatProvider, out Color color)
-	{
-		try {
-			Color c = Parse (text, formatProvider);
-			color = c;
-			return true;
-		}
-		catch ( ColorParseException ) {
-			color = default;
-			return false;
-		}
-	}
-
-	/// <inheritdoc />
-	[Pure]
-	[SkipLocalsInit]
-	public static bool TryParse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out Color result)
-	{
-		return TryParse (Encoding.UTF8.GetString (utf8Text), provider, out result);
-	}
-
-	/// <summary>Converts the color to a string representation.</summary>
-	/// <remarks>
-	///   <para>
-	///     If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.
-	///   </para>
-	///   <para>
-	///     <see cref="A" /> (Alpha channel) is ignored and the returned string will not include it for this overload.
-	///   </para>
-	/// </remarks>
-	/// <returns>The string representation of this value in #RRGGBB format.</returns>
-	[Pure]
-	[SkipLocalsInit]
-	public override string ToString ()
-	{
-		// If Values has an exact match with a named color (in _colorNames), use that.
-		return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
-			? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
-			: // Otherwise return as an RGB hex value.
-			$"#{R:X2}{G:X2}{B:X2}";
-	}
-
-	/// <summary>Converts the provided string to a new <see cref="Color" /> instance.</summary>
-	/// <param name="text">
-	///   The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)", "rgb(r,g,b,a)", "rgba(r,g,b)",
-	///   "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName" /> string values.
-	/// </param>
-	/// <param name="color">The parsed value.</param>
-	/// <returns>A boolean value indicating whether parsing was successful.</returns>
-	/// <remarks>
-	///   While <see cref="Color" /> supports the alpha channel <see cref="A" />, Terminal.Gui does not.
-	/// </remarks>
-	public static bool TryParse (string text, [NotNullWhen (true)] out Color? color)
-	{
-		if ( TryParse (text.AsSpan (), null, out Color c) ) {
-			color = c;
-			return true;
-		}
-		color = null;
-		return false;
-	}
+public readonly partial record struct Color
+{
+    /// <inheritdoc cref="object.ToString"/>
+    /// <summary>
+    ///     Returns a <see langword="string"/> representation of the current <see cref="Color"/> value, according to the
+    ///     provided <paramref name="formatString"/> and optional <paramref name="formatProvider"/>.
+    /// </summary>
+    /// <param name="formatString">
+    ///     A format string that will be passed to
+    ///     <see cref="string.Format(System.IFormatProvider?,string,object?[])"/>.
+    ///     <para/>
+    ///     See remarks for parameters passed to that method.
+    /// </param>
+    /// <param name="formatProvider">
+    ///     An optional <see cref="IFormatProvider"/> to use when formatting the <see cref="Color"/>
+    ///     using custom format strings not specified for this method. Provides this instance as <see cref="Argb"/>. <br/> If
+    ///     this parameter is not null, the specified <see cref="IFormatProvider"/> will be used instead of the custom
+    ///     formatting provided by the <see cref="Color"/> type.
+    ///     <para/>
+    ///     See remarks for defined format strings.
+    /// </param>
+    /// <remarks>
+    ///     Pre-defined format strings for this method, if a custom <paramref name="formatProvider"/> is not supplied are:
+    ///     <list type="bullet">
+    ///         <listheader>
+    ///             <term>Value</term> <description>Result</description>
+    ///         </listheader>
+    ///         <item>
+    ///             <term>g or null or empty string</term>
+    ///             <description>
+    ///                 General/default format - Returns a named <see cref="Color"/> if there is a match, or a
+    ///                 24-bit/3-byte/6-hex digit string in "#RRGGBB" format.
+    ///             </description>
+    ///         </item>
+    ///         <item>
+    ///             <term>G</term>
+    ///             <description>
+    ///                 Extended general format - Returns a named <see cref="Color"/> if there is a match, or a
+    ///                 32-bit/4-byte/8-hex digit string in "#AARRGGBB" format.
+    ///             </description>
+    ///         </item>
+    ///         <item>
+    ///             <term>d</term>
+    ///             <description>
+    ///                 Decimal format - Returns a 3-component decimal representation of the <see cref="Color"/> in
+    ///                 "rgb(R,G,B)" format.
+    ///             </description>
+    ///         </item>
+    ///         <item>
+    ///             <term>D</term>
+    ///             <description>
+    ///                 Extended decimal format - Returns a 4-component decimal representation of the
+    ///                 <see cref="Color"/> in "rgba(R,G,B,A)" format.
+    ///             </description>
+    ///         </item>
+    ///     </list>
+    ///     <para>
+    ///         If <paramref name="formatProvider"/> is provided and is a non-null <see cref="ICustomColorFormatter"/>, the
+    ///         following behaviors are available, for the specified values of <paramref name="formatString"/>:
+    ///         <list type="bullet">
+    ///             <listheader>
+    ///                 <term>Value</term> <description>Result</description>
+    ///             </listheader>
+    ///             <item>
+    ///                 <term>null or empty string</term>
+    ///                 <description>
+    ///                     Calls <see cref="ICustomColorFormatter.Format(string?,byte,byte,byte,byte)"/> on the
+    ///                     provided <paramref name="formatProvider"/> with the null string, and <see cref="R"/>,
+    ///                     <see cref="G"/>, <see cref="B"/>, and <see cref="A"/> as typed arguments of type <see cref="Byte"/>
+    ///                     .
+    ///                 </description>
+    ///             </item>
+    ///             <item>
+    ///                 <term>All other values</term>
+    ///                 <description>
+    ///                     Calls <see cref="string.Format{TArg0}"/> with the provided
+    ///                     <paramref name="formatProvider"/> and <paramref name="formatString"/> (parsed as a
+    ///                     <see cref="CompositeFormat"/>), with the value of <see cref="Argb"/> as the sole
+    ///                     <see langword="uint"/>-typed argument.
+    ///                 </description>
+    ///             </item>
+    ///         </list>
+    ///     </para>
+    /// </remarks>
+    [SkipLocalsInit]
+    public string ToString (
+        [StringSyntax (StringSyntaxAttribute.CompositeFormat)] string? formatString,
+        IFormatProvider? formatProvider = null
+    )
+    {
+        return (formatString, formatProvider) switch
+               {
+                   // Null or empty string and null formatProvider - Revert to 'g' case behavior
+                   (null or { Length: 0 }, null) => ToString (),
+
+                   // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
+                   (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
+
+                   // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+                   (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
+                       string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
+
+                   // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+                   (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
+                       $"#{R:X2}{G:X2}{B:X2}",
+
+                   // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
+                   ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
+
+                   // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
+                   (['g'], null) => ToString (),
+
+                   // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
+                   (['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
+
+                   // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
+                   (['d'], null) => $"rgb({R:D},{G:D},{B:D})",
+
+                   // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
+                   (['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
+
+                   // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
+                   ({ }, _) => string.Format (
+                                              formatProvider ?? CultureInfo.InvariantCulture,
+                                              CompositeFormat.Parse (formatString),
+                                              R,
+                                              G,
+                                              B,
+                                              A
+                                             ),
+                   _ => throw new InvalidOperationException (
+                                                             $"Unable to create string from Color with value {Argb}, using format string {formatString}"
+                                                            )
+               }
+               ?? throw new InvalidOperationException (
+                                                       $"Unable to create string from Color with value {Argb}, using format string {formatString}"
+                                                      );
+    }
+
+    /// <inheritdoc/>
+    /// <remarks>
+    ///     <para>
+    ///         This method should be used only when absolutely necessary, because it <b>always</b> has more overhead than
+    ///         <see cref="ToString(string?,System.IFormatProvider?)"/>, as this method results in an intermediate allocation
+    ///         of one or more instances of <see langword="string"/> and a copy of that string to
+    ///         <paramref name="destination"/> if formatting was successful. <br/> When possible, use
+    ///         <see cref="ToString(string?,System.IFormatProvider?)"/>, which attempts to avoid intermediate allocations.
+    ///     </para>
+    ///     <para>
+    ///         This method only returns <see langword="true"/> and with its output written to <paramref name="destination"/>
+    ///         if the formatted string, <i>in its entirety</i>, will fit in <paramref name="destination"/>. If the resulting
+    ///         formatted string is too large to fit in <paramref name="destination"/>, the result will be false and
+    ///         <paramref name="destination"/> will be unaltered.
+    ///     </para>
+    ///     <para>
+    ///         The resulting formatted string may be <b>shorter</b> than <paramref name="destination"/>. When this method
+    ///         returns <see langword="true"/>, use <paramref name="charsWritten"/> when handling the value of
+    ///         <paramref name="destination"/>.
+    ///     </para>
+    /// </remarks>
+    [Pure]
+    [SkipLocalsInit]
+    public bool TryFormat (
+        Span<char> destination,
+        out int charsWritten,
+        ReadOnlySpan<char> format,
+        IFormatProvider? provider
+    )
+    {
+        // TODO: This can probably avoid a string allocation with a little more work
+        try
+        {
+            string formattedString = ToString (format.ToString (), provider);
+
+            if (formattedString.Length <= destination.Length)
+            {
+                formattedString.CopyTo (destination);
+                charsWritten = formattedString.Length;
+
+                return true;
+            }
+        }
+        catch
+        {
+            destination.Clear ();
+            charsWritten = 0;
+
+            return false;
+        }
+
+        destination.Clear ();
+        charsWritten = 0;
+
+        return false;
+    }
+
+    /// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    /// </param>
+    /// <param name="formatProvider">
+    ///     If specified and not <see langword="null"/>, will be passed to
+    ///     <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)"/>.
+    /// </param>
+    /// <returns>A <see cref="Color"/> value equivalent to <paramref name="text"/>, if parsing was successful.</returns>
+    /// <remarks>While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.</remarks>
+    /// <exception cref="ArgumentNullException">If <paramref name="text"/> is <see langword="null"/>.</exception>
+    /// <exception cref="ArgumentException">
+    ///     If <paramref name="text"/> is an empty string or consists of only whitespace
+    ///     characters.
+    /// </exception>
+    /// <exception cref="ColorParseException">
+    ///     If thrown by
+    ///     <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)"/>.
+    /// </exception>
+    [Pure]
+    [SkipLocalsInit]
+    public static Color Parse (string? text, IFormatProvider? formatProvider = null)
+    {
+        ArgumentException.ThrowIfNullOrWhiteSpace (text, nameof (text));
+
+        if (text is { Length: < 3 } && formatProvider is null)
+        {
+            throw new ColorParseException (
+                                           text,
+                                           reason: "Provided text is too short to be any known color format.",
+                                           badValue: text
+                                          );
+        }
+
+        return Parse (text.AsSpan (), formatProvider ?? CultureInfo.InvariantCulture);
+    }
+
+    /// <summary>
+    ///     Converts the provided <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> to a new <see cref="Color"/>
+    ///     value.
+    /// </summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)",
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    /// </param>
+    /// <param name="formatProvider">
+    ///     Optional <see cref="IFormatProvider"/> to provide parsing services for the input text.
+    ///     <br/> Defaults to <see cref="CultureInfo.InvariantCulture"/> if <see langword="null"/>. <br/> If not null, must
+    ///     implement <see cref="ICustomColorFormatter"/> or will be ignored and <see cref="CultureInfo.InvariantCulture"/>
+    ///     will be used.
+    /// </param>
+    /// <returns>A <see cref="Color"/> value equivalent to <paramref name="text"/>, if parsing was successful.</returns>
+    /// <remarks>While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.</remarks>
+    /// <exception cref="ArgumentException">
+    ///     with an inner <see cref="FormatException"/> if <paramref name="text"/> was unable
+    ///     to be successfully parsed as a <see cref="Color"/>, for any reason.
+    /// </exception>
+    [Pure]
+    [SkipLocalsInit]
+    public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
+    {
+        return text switch
+               {
+                   // Null string or empty span provided
+                   { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
+                                                                                                   in text,
+                                                                                                   "The text provided was null or empty.",
+                                                                                                   in text
+                                                                                                  ),
+
+                   // A valid ICustomColorFormatter was specified and the text wasn't null or empty
+                   { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
+
+                   // Input string is only whitespace
+                   { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
+                                                                                               in text,
+                                                                                               "The text provided consisted of only whitespace characters.",
+                                                                                               in text
+                                                                                              ),
+
+                   // Any string too short to possibly be any supported format.
+                   { Length: > 0 and < 4 } => throw new ColorParseException (
+                                                                             in text,
+                                                                             "Text was too short to be any possible supported format.",
+                                                                             in text
+                                                                            ),
+
+                   // The various hexadecimal cases
+                   ['#', ..] hexString => hexString switch
+                                          {
+                                              // #RGB
+                                              ['#', var rChar, var gChar, var bChar] chars when chars [1..]
+                                                      .IsAllAsciiHexDigits () =>
+                                                  new Color (
+                                                             byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+                                                             byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+                                                             byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
+                                                            ),
+
+                                              // #ARGB
+                                              ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
+                                                      .IsAllAsciiHexDigits () =>
+                                                  new Color (
+                                                             byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+                                                             byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+                                                             byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
+                                                             byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
+                                                            ),
+
+                                              // #RRGGBB
+                                              [
+                                                      '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
+                                                      var b2Char
+                                                  ] chars when chars [1..].IsAllAsciiHexDigits () =>
+                                                  new Color (
+                                                             byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+                                                             byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+                                                             byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
+                                                            ),
+
+                                              // #AARRGGBB
+                                              [
+                                                      '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
+                                                      var g2Char, var b1Char, var b2Char
+                                                  ] chars when chars [1..].IsAllAsciiHexDigits () =>
+                                                  new Color (
+                                                             byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+                                                             byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+                                                             byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
+                                                             byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
+                                                            ),
+                                              _ => throw new ColorParseException (
+                                                                                  in hexString,
+                                                                                  $"Color hex string {hexString} was not in a supported format",
+                                                                                  in hexString
+                                                                                 )
+                                          },
+
+                   // rgb(r,g,b) or rgb(r,g,b,a)
+                   ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
+
+                   // rgba(r,g,b,a) or rgba(r,g,b)
+                   ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
+
+                   // Attempt to parse as a named color from the ColorName enum
+                   { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) =>
+                       new Color (colorName),
+
+                   // Any other input
+                   _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
+               };
+
+        [Pure]
+        [SkipLocalsInit]
+        static Color ParseRgbaFormat (in ReadOnlySpan<char> originalString, in int startIndex)
+        {
+            ReadOnlySpan<char> valuesSubstring = originalString [startIndex..^1];
+            Span<Range> valueRanges = stackalloc Range [4];
+
+            int rangeCount = valuesSubstring.Split (
+                                                    valueRanges,
+                                                    ',',
+                                                    StringSplitOptions.RemoveEmptyEntries
+                                                    | StringSplitOptions.TrimEntries
+                                                   );
+
+            switch (rangeCount)
+            {
+                case 3:
+                {
+                    // rgba(r,g,b)
+                    ParseRgbValues (
+                                    in valuesSubstring,
+                                    in valueRanges,
+                                    in originalString,
+                                    out ReadOnlySpan<char> rSpan,
+                                    out ReadOnlySpan<char> gSpan,
+                                    out ReadOnlySpan<char> bSpan
+                                   );
+
+                    return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
+                }
+                case 4:
+                {
+                    // rgba(r,g,b,a)
+                    ParseRgbValues (
+                                    in valuesSubstring,
+                                    in valueRanges,
+                                    in originalString,
+                                    out ReadOnlySpan<char> rSpan,
+                                    out ReadOnlySpan<char> gSpan,
+                                    out ReadOnlySpan<char> bSpan
+                                   );
+                    ReadOnlySpan<char> aSpan = valuesSubstring [valueRanges [3]];
+
+                    if (!aSpan.IsAllAsciiDigits ())
+                    {
+                        throw new ColorParseException (
+                                                       in originalString,
+                                                       "Value was not composed entirely of decimal digits.",
+                                                       in aSpan,
+                                                       nameof (A)
+                                                      );
+                    }
+
+                    return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
+                }
+                default:
+                    throw new ColorParseException (
+                                                   in originalString,
+                                                   $"Wrong number of values. Expected 3 or 4 decimal integers. Got {rangeCount}.",
+                                                   in originalString
+                                                  );
+            }
+
+            [Pure]
+            [SkipLocalsInit]
+            static void ParseRgbValues (
+                in ReadOnlySpan<char> valuesString,
+                in Span<Range> valueComponentRanges,
+                in ReadOnlySpan<char> originalString,
+                out ReadOnlySpan<char> rSpan,
+                out ReadOnlySpan<char> gSpan,
+                out ReadOnlySpan<char> bSpan
+            )
+            {
+                rSpan = valuesString [valueComponentRanges [0]];
+
+                if (!rSpan.IsAllAsciiDigits ())
+                {
+                    throw new ColorParseException (
+                                                   in originalString,
+                                                   "Value was not composed entirely of decimal digits.",
+                                                   in rSpan,
+                                                   nameof (R)
+                                                  );
+                }
+
+                gSpan = valuesString [valueComponentRanges [1]];
+
+                if (!gSpan.IsAllAsciiDigits ())
+                {
+                    throw new ColorParseException (
+                                                   in originalString,
+                                                   "Value was not composed entirely of decimal digits.",
+                                                   in gSpan,
+                                                   nameof (G)
+                                                  );
+                }
+
+                bSpan = valuesString [valueComponentRanges [2]];
+
+                if (!bSpan.IsAllAsciiDigits ())
+                {
+                    throw new ColorParseException (
+                                                   in originalString,
+                                                   "Value was not composed entirely of decimal digits.",
+                                                   in bSpan,
+                                                   nameof (B)
+                                                  );
+                }
+            }
+        }
+    }
+
+    /// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor (Color)"/> string
+    ///     values.
+    /// </param>
+    /// <param name="formatProvider">
+    ///     Optional <see cref="IFormatProvider"/> to provide formatting services for the input text.
+    ///     <br/> Defaults to <see cref="CultureInfo.InvariantCulture"/> if <see langword="null"/>.
+    /// </param>
+    /// <param name="result">
+    ///     The parsed value, if successful, or <see langword="default"/>(<see cref="Color"/>), if
+    ///     unsuccessful.
+    /// </param>
+    /// <returns>A <see langword="bool"/> value indicating whether parsing was successful.</returns>
+    /// <remarks>While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.</remarks>
+    [Pure]
+    [SkipLocalsInit]
+    public static bool TryParse (string? text, IFormatProvider? formatProvider, out Color result)
+    {
+        return TryParse (
+                         text.AsSpan (),
+                         formatProvider ?? CultureInfo.InvariantCulture,
+                         out result
+                        );
+    }
+
+    /// <summary>
+    ///     Converts the provided <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> to a new <see cref="Color"/>
+    ///     value.
+    /// </summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="GetClosestNamedColor (Color)"/> string
+    ///     values.
+    /// </param>
+    /// <param name="formatProvider">
+    ///     If specified and not <see langword="null"/>, will be passed to
+    ///     <see cref="Parse(System.ReadOnlySpan{char},System.IFormatProvider?)"/>.
+    /// </param>
+    /// <param name="color">
+    ///     The parsed value, if successful, or <see langword="default"/>(<see cref="Color"/>), if
+    ///     unsuccessful.
+    /// </param>
+    /// <returns>A <see langword="bool"/> value indicating whether parsing was successful.</returns>
+    /// <remarks>While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.</remarks>
+    [Pure]
+    [SkipLocalsInit]
+    public static bool TryParse (ReadOnlySpan<char> text, IFormatProvider? formatProvider, out Color color)
+    {
+        try
+        {
+            Color c = Parse (text, formatProvider);
+            color = c;
+
+            return true;
+        }
+        catch (ColorParseException)
+        {
+            color = default (Color);
+
+            return false;
+        }
+    }
+
+    /// <inheritdoc/>
+    /// <remarks>
+    ///     Use of this method involves a stack allocation of <paramref name="utf8Destination"/>.Length * 2 bytes. Use of
+    ///     the overload taking a char span is recommended.
+    /// </remarks>
+    [SkipLocalsInit]
+    public bool TryFormat (
+        Span<byte> utf8Destination,
+        out int bytesWritten,
+        ReadOnlySpan<char> format,
+        IFormatProvider? provider
+    )
+    {
+        Span<char> charDestination = stackalloc char [utf8Destination.Length * 2];
+
+        if (TryFormat (charDestination, out int charsWritten, format, provider))
+        {
+            Encoding.UTF8.GetBytes (charDestination, utf8Destination);
+            bytesWritten = charsWritten / 2;
+
+            return true;
+        }
+
+        utf8Destination.Clear ();
+        bytesWritten = 0;
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    [Pure]
+    [SkipLocalsInit]
+    public static Color Parse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) { return Parse (Encoding.UTF8.GetString (utf8Text), provider); }
+
+    /// <inheritdoc/>
+    [Pure]
+    [SkipLocalsInit]
+    public static bool TryParse (ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out Color result)
+    {
+        return TryParse (Encoding.UTF8.GetString (utf8Text), provider, out result);
+    }
+
+    /// <summary>Converts the color to a string representation.</summary>
+    /// <remarks>
+    ///     <para>If the color is a named color, the name is returned. Otherwise, the color is returned as a hex string.</para>
+    ///     <para><see cref="A"/> (Alpha channel) is ignored and the returned string will not include it for this overload.</para>
+    /// </remarks>
+    /// <returns>The string representation of this value in #RRGGBB format.</returns>
+    [Pure]
+    [SkipLocalsInit]
+    public override string ToString ()
+    {
+        // If Values has an exact match with a named color (in _colorNames), use that.
+        return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
+                   ? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
+                   : // Otherwise return as an RGB hex value.
+                   $"#{R:X2}{G:X2}{B:X2}";
+    }
+
+    /// <summary>Converts the provided string to a new <see cref="Color"/> instance.</summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+    ///     "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="Gui.ColorName"/> string values.
+    /// </param>
+    /// <param name="color">The parsed value.</param>
+    /// <returns>A boolean value indicating whether parsing was successful.</returns>
+    /// <remarks>While <see cref="Color"/> supports the alpha channel <see cref="A"/>, Terminal.Gui does not.</remarks>
+    public static bool TryParse (string text, [NotNullWhen (true)] out Color? color)
+    {
+        if (TryParse (text.AsSpan (), null, out Color c))
+        {
+            color = c;
+
+            return true;
+        }
+
+        color = null;
+
+        return false;
+    }
 }

+ 79 - 72
Terminal.Gui/Drawing/Color.Operators.cs

@@ -4,87 +4,94 @@ using System.Numerics;
 
 namespace Terminal.Gui;
 
-public readonly partial record struct Color {
+public readonly partial record struct Color
+{
+    /// <inheritdoc/>
+    /// <returns>
+    ///     A <see cref="Color"/> <see langword="struct"/> with all values set to <see cref="byte.MaxValue"/>, meaning
+    ///     white.
+    /// </returns>
+    public static Color MaxValue => new (uint.MaxValue);
 
-	/// <inheritdoc />
-	/// <returns>
-	///   A <see cref="Color" /> <see langword="struct" /> with all values set to <see cref="byte.MaxValue" />, meaning white.
-	/// </returns>
-	public static Color MaxValue => new Color (uint.MaxValue);
+    /// <inheritdoc/>
+    /// <returns>A <see cref="Color"/> <see langword="struct"/> with all values set to zero.</returns>
+    /// <remarks>
+    ///     Though this returns a <see cref="Color"/> with <see cref="A"/>, <see cref="R"/>, <see cref="G"/>, and
+    ///     <see cref="B"/> all set to zero, Terminal.Gui will treat it as black, because the alpha channel is not supported.
+    /// </remarks>
+    public static Color MinValue => new (uint.MinValue);
 
-	/// <inheritdoc />
-	/// <returns>A <see cref="Color" /> <see langword="struct" /> with all values set to zero.</returns>
-	/// <remarks>
-	///   Though this returns a <see cref="Color" /> with <see cref="A" />, <see cref="R" />, <see cref="G" />, and <see cref="B" /> all set to
-	///   zero, Terminal.Gui will treat it as black, because the alpha channel is not supported.
-	/// </remarks>
-	public static Color MinValue => new Color (uint.MinValue);
+    /// <inheritdoc/>
+    [Pure]
+    public override int GetHashCode () { return Rgba.GetHashCode (); }
 
-	/// <inheritdoc />
-	[Pure]
-	public override int GetHashCode () => Rgba.GetHashCode ();
+    /// <summary>
+    ///     Implicit conversion from <see cref="Color"/> to <see cref="Vector3"/> via
+    ///     <see cref="Vector3(float,float,float)"/> where ( <see cref="Vector3.X"/>, <see cref="Vector3.Y"/>,
+    ///     <see cref="Vector3.Z"/>) is (R,G,B).
+    /// </summary>
+    /// <remarks>
+    ///     This cast is narrowing and drops the alpha channel.
+    ///     <para/>
+    ///     Use <see cref="implicit operator Vector4(Color)"/> to maintain full value.
+    /// </remarks>
+    [Pure]
+    public static explicit operator Vector3 (Color color) { return new Vector3 (color.R, color.G, color.B); }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="Color" /> to <see cref="Vector3" /> via <see cref="Vector3(float,float,float)" /> where (
-	///   <see cref="Vector3.X" />, <see cref="Vector3.Y" />, <see cref="Vector3.Z" />) is (R,G,B).
-	/// </summary>
-	/// <remarks>
-	///   This cast is narrowing and drops the alpha channel.
-	///   <para />
-	///   Use <see cref="implicit operator Vector4(Color)" /> to maintain full value.
-	/// </remarks>
-	[Pure]
-	public static explicit operator Vector3 (Color color) => new Vector3 (color.R, color.G, color.B);
-	/// <summary>
-	///   Implicit conversion from <see langword="int" /> to <see cref="Color" />, via the <see cref="Color(int)" /> costructor.
-	/// </summary>
-	[Pure]
-	public static implicit operator Color (int rgba) => new Color (rgba);
+    /// <summary>
+    ///     Implicit conversion from <see langword="int"/> to <see cref="Color"/>, via the <see cref="Color(int)"/>
+    ///     costructor.
+    /// </summary>
+    [Pure]
+    public static implicit operator Color (int rgba) { return new Color (rgba); }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="Color" /> to <see langword="int" /> by returning the value of the <see cref="Rgba" /> field.
-	/// </summary>
-	[Pure]
-	public static implicit operator int (Color color) => color.Rgba;
+    /// <summary>
+    ///     Implicit conversion from <see langword="uint"/> to <see cref="Color"/>, via the <see cref="Color(uint)"/>
+    ///     costructor.
+    /// </summary>
+    [Pure]
+    public static implicit operator Color (uint u) { return new Color (u); }
 
-	/// <summary>
-	///   Implicit conversion from <see langword="uint" /> to <see cref="Color" />, via the <see cref="Color(uint)" /> costructor.
-	/// </summary>
-	[Pure]
-	public static implicit operator Color (uint u) => new Color (u);
+    /// <summary>
+    ///     Implicit conversion from <see cref="GetClosestNamedColor (Color)"/> to <see cref="Color"/> via lookup from
+    ///     <see cref="ColorExtensions.ColorNameToColorMap"/>.
+    /// </summary>
+    [Pure]
+    public static implicit operator Color (ColorName colorName) { return ColorExtensions.ColorNameToColorMap [colorName]; }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="Color" /> to <see langword="uint" /> by returning the value of the <see cref="Argb" /> field.
-	/// </summary>
-	[Pure]
-	public static implicit operator uint (Color color) => color.Argb;
+    /// <summary>
+    ///     Implicit conversion from <see cref="Vector4"/> to <see cref="Color"/>, where (<see cref="Vector4.X"/>,
+    ///     <see cref="Vector4.Y"/>, <see cref="Vector4.Z"/>, <see cref="Vector4.W"/>) is (<see cref="A"/>,<see cref="R"/>,
+    ///     <see cref="G"/>,<see cref="B"/>), via <see cref="Color(int,int,int,int)"/>.
+    /// </summary>
+    [Pure]
+    public static implicit operator Color (Vector4 v) { return new Color ((byte)v.X, (byte)v.Y, (byte)v.Z, (byte)v.W); }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="GetClosestNamedColor" /> to <see cref="Color" /> via lookup from
-	///   <see cref="ColorExtensions.ColorNameToColorMap" />.
-	/// </summary>
-	[Pure]
-	public static implicit operator Color (ColorName colorName) => ColorExtensions.ColorNameToColorMap [colorName];
+    /// <summary>
+    ///     Implicit conversion from <see cref="Vector3"/>, where <see cref="Vector3.X"/> = <see cref="R"/>,
+    ///     <see cref="Vector3.Y"/> = <see cref="G"/>, and <see cref="Vector3.Z"/> = <see cref="B"/>.
+    /// </summary>
+    [Pure]
+    public static implicit operator Color (Vector3 v) { return new Color ((byte)v.X, (byte)v.Y, (byte)v.Z); }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="Vector4" /> to <see cref="Color" />, where (<see cref="Vector4.X" />, <see cref="Vector4.Y" />,
-	///   <see cref="Vector4.Z" />, <see cref="Vector4.W" />) is (<see cref="A" />,<see cref="R" />,<see cref="G" />,<see cref="B" />), via
-	///   <see cref="Color(int,int,int,int)" />.
-	/// </summary>
-	[Pure]
-	public static implicit operator Color (Vector4 v) => new Color ((byte)v.X, (byte)v.Y, (byte)v.Z, (byte)v.W);
+    /// <summary>
+    ///     Implicit conversion from <see cref="Color"/> to <see langword="int"/> by returning the value of the
+    ///     <see cref="Rgba"/> field.
+    /// </summary>
+    [Pure]
+    public static implicit operator int (Color color) { return color.Rgba; }
 
-	/// <summary>
-	///   Implicit conversion to <see cref="Vector3" />, where <see cref="Vector3.X" /> = <see cref="R" />, <see cref="Vector3.Y" /> =
-	///   <see cref="G" />, and <see cref="Vector3.Z" /> = <see cref="B" />.
-	/// </summary>
-	[Pure]
-	public static implicit operator Vector4 (Color color) => new Vector4 (color.R, color.G, color.B, color.A);
+    /// <summary>
+    ///     Implicit conversion from <see cref="Color"/> to <see langword="uint"/> by returning the value of the
+    ///     <see cref="Argb"/> field.
+    /// </summary>
+    [Pure]
+    public static implicit operator uint (Color color) { return color.Argb; }
 
-	/// <summary>
-	///   Implicit conversion from <see cref="Vector3" />, where <see cref="Vector3.X" /> = <see cref="R" />, <see cref="Vector3.Y" /> =
-	///   <see cref="G" />, and <see cref="Vector3.Z" /> = <see cref="B" />.
-	/// </summary>
-	[Pure]
-	public static implicit operator Color (Vector3 v) => new Color ((byte)v.X, (byte)v.Y, (byte)v.Z);
+    /// <summary>
+    ///     Implicit conversion to <see cref="Vector3"/>, where <see cref="Vector3.X"/> = <see cref="R"/>,
+    ///     <see cref="Vector3.Y"/> = <see cref="G"/>, and <see cref="Vector3.Z"/> = <see cref="B"/>.
+    /// </summary>
+    [Pure]
+    public static implicit operator Vector4 (Color color) { return new Vector4 (color.R, color.G, color.B, color.A); }
 }

+ 276 - 267
Terminal.Gui/Drawing/Color.cs

@@ -9,274 +9,283 @@ using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-/// <summary>Represents a 24-bit color encoded in ARGB32 format.
-///   <para />
+/// <summary>
+///     Represents a 24-bit color encoded in ARGB32 format.
+///     <para/>
 /// </summary>
-/// <seealso cref="Attribute" />
-/// <seealso cref="ColorExtensions" />
-/// <seealso cref="ColorName" />
+/// <seealso cref="Attribute"/>
+/// <seealso cref="ColorExtensions"/>
+/// <seealso cref="ColorName"/>
 [JsonConverter (typeof (ColorJsonConverter))]
 [StructLayout (LayoutKind.Explicit)]
-public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanParsable<Color>, ISpanFormattable, IUtf8SpanFormattable, IMinMaxValue<Color> {
-
-	/// <summary>The value of the alpha channel component</summary>
-	/// <remarks>
-	///   The alpha channel is not currently supported, so the value of the alpha channel bits will not affect rendering.
-	/// </remarks>
-	[JsonIgnore]
-	[field: FieldOffset (3)]
-	public readonly byte A;
-
-	/// <summary>
-	///   The value of this <see cref="Color" /> as a <see langword="uint" /> in ARGB32 format.
-	/// </summary>
-	/// <remarks>
-	///   The alpha channel is not currently supported, so the value of the alpha channel bits will not affect rendering.
-	/// </remarks>
-	[JsonIgnore]
-	[field: FieldOffset (0)]
-	public readonly uint Argb;
-
-	/// <summary>The value of the blue color component.</summary>
-	[JsonIgnore]
-	[field: FieldOffset (0)]
-	public readonly byte B;
-
-	/// <summary>The value of the green color component.</summary>
-	[JsonIgnore]
-	[field: FieldOffset (1)]
-	public readonly byte G;
-
-	/// <summary>The value of the red color component.</summary>
-	[JsonIgnore]
-	[field: FieldOffset (2)]
-	public readonly byte R;
-
-	/// <summary>
-	///   The value of this <see cref="Color" /> encoded as a signed 32-bit integer in ARGB32 format.
-	/// </summary>
-	[JsonIgnore]
-	[field: FieldOffset (0)]
-	public readonly int Rgba;
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> <see langword="struct" /> using the supplied component values.
-	/// </summary>
-	/// <param name="red">The red 8-bits.</param>
-	/// <param name="green">The green 8-bits.</param>
-	/// <param name="blue">The blue 8-bits.</param>
-	/// <param name="alpha">Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.</param>
-	/// <remarks>Alpha channel is not currently supported by Terminal.Gui.</remarks>
-	/// <exception cref="OverflowException">If the value of any parameter is greater than <see cref="byte.MaxValue" />.</exception>
-	/// <exception cref="ArgumentOutOfRangeException">If the value of any parameter is negative.</exception>
-	public Color (int red = 0, int green = 0, int blue = 0, int alpha = byte.MaxValue)
-	{
-		ArgumentOutOfRangeException.ThrowIfNegative (red, nameof (red));
-		ArgumentOutOfRangeException.ThrowIfNegative (green, nameof (green));
-		ArgumentOutOfRangeException.ThrowIfNegative (blue, nameof (blue));
-		ArgumentOutOfRangeException.ThrowIfNegative (alpha, nameof (alpha));
-
-		A = Convert.ToByte (alpha);
-		R = Convert.ToByte (red);
-		G = Convert.ToByte (green);
-		B = Convert.ToByte (blue);
-	}
-
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> class with an encoded signed 32-bit color value in ARGB32 format.
-	/// </summary>
-	/// <param name="rgba">The encoded 32-bit color value (see <see cref="Rgba" />).</param>
-	/// <remarks>
-	///   The alpha channel is not currently supported, so the value of the alpha channel bits will not affect rendering.
-	/// </remarks>
-	public Color (int rgba)
-	{
-		Rgba = rgba;
-	}
-
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> class with an encoded unsigned 32-bit color value in ARGB32 format.
-	/// </summary>
-	/// <param name="argb">The encoded unsigned 32-bit color value (see <see cref="Argb" />).</param>
-	/// <remarks>
-	///   The alpha channel is not currently supported, so the value of the alpha channel bits will not affect rendering.
-	/// </remarks>
-	public Color (uint argb)
-	{
-		Argb = argb;
-	}
-
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> color from a legacy 16-color named value.
-	/// </summary>
-	/// <param name="colorName">The 16-color value.</param>
-	public Color (in ColorName colorName)
-	{
-		this = ColorExtensions.ColorNameToColorMap [colorName];
-	}
-
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> color from string. See <see cref="TryParse(string, out Color?)" /> for details.
-	/// </summary>
-	/// <param name="colorString"></param>
-	/// <exception cref="ArgumentNullException">If <paramref name="colorString" /> is <see langword="null" />.</exception>
-	/// <exception cref="ArgumentException">
-	///   If <paramref name="colorString" /> is an empty string or consists of only whitespace characters.
-	/// </exception>
-	/// <exception cref="ColorParseException">If thrown by <see cref="Parse(string?,System.IFormatProvider?)" /></exception>
-	public Color (string colorString)
-	{
-		ArgumentException.ThrowIfNullOrWhiteSpace (colorString, nameof (colorString));
-		this = Parse (colorString, CultureInfo.InvariantCulture);
-	}
-
-	/// <summary>
-	///   Initializes a new instance of the <see cref="Color" /> with all channels set to 0.
-	/// </summary>
-	public Color ()
-	{
-		Argb = 0u;
-	}
-
-	/// <summary>
-	///   Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values.
-	/// </summary>
-	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
-	public static Dictionary<ColorName, string> Colors {
-		get =>
-			// Transform _colorToNameMap into a Dictionary<ColorNames,string>
-			ColorExtensions.ColorToNameMap.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
-		set {
-			// Transform Dictionary<ColorNames,string> into _colorToNameMap
-			ColorExtensions.ColorToNameMap = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
-			return;
-
-			static Color GetColorToNameMapKey (KeyValuePair<ColorName, string> kvp) => new Color (kvp.Value);
-			static ColorName GetColorToNameMapValue (KeyValuePair<ColorName, string> kvp) => Enum.TryParse<ColorName> (kvp.Key.ToString (), true, out var colorName) ? colorName : throw new ArgumentException ($"Invalid color name: {kvp.Key}");
-		}
-	}
-
-	/// <summary>
-	///   Gets the <see cref="Color" /> using a legacy 16-color <see cref="ColorName" /> value. <see langword="get" /> will return the closest 16
-	///   color match to the true color when no exact value is found.
-	/// </summary>
-	/// <remarks>
-	///   Get returns the <see cref="GetClosestNamedColor" /> of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
-	/// </remarks>
-	public AnsiColorCode GetAnsiColorCode () => ColorExtensions.ColorNameToAnsiColorMap [GetClosestNamedColor ()];
-
-	/// <summary>
-	///   Gets the <see cref="Color" /> using a legacy 16-color <see cref="Gui.ColorName" /> value. <see langword="get" /> will return the closest
-	///   16 color match to the true color when no exact value is found.
-	/// </summary>
-	/// <remarks>
-	///   Get returns the <see cref="GetClosestNamedColor" /> of the closest 24-bit color value. Set sets the RGB value using a hard-coded map.
-	/// </remarks>
-	public ColorName GetClosestNamedColor () => GetClosestNamedColor (this);
-
-	/// <summary>
-	///   Determines if the closest named <see cref="Color" /> to <see langword="this" /> is the provided <paramref name="namedColor" />.
-	/// </summary>
-	/// <param name="namedColor">
-	///   The <see cref="GetClosestNamedColor" /> to check if this <see cref="Color" /> is closer to than any other configured named color.
-	/// </param>
-	/// <returns>
-	///   <see langword="true" /> if the closest named color is the provided value.
-	///   <br />
-	///   <see langword="false" /> if any other named color is closer to this <see cref="Color" /> than <paramref name="namedColor" />.
-	/// </returns>
-	/// <remarks>
-	///   If <see langword="this" /> is equidistant from two named colors, the result of this method is not guaranteed to be determinate.
-	/// </remarks>
-	[Pure]
-	[MethodImpl (MethodImplOptions.AggressiveInlining)]
-	public bool IsClosestToNamedColor (in ColorName namedColor) => GetClosestNamedColor () == namedColor;
-
-
-	/// <summary>
-	///   Determines if the closest named <see cref="Color" /> to <paramref name="color" />/> is the provided <paramref name="namedColor" />.
-	/// </summary>
-	/// <param name="color">
-	///   The color to test against the <see cref="GetClosestNamedColor" /> value in <paramref name="namedColor" />.
-	/// </param>
-	/// <param name="namedColor">
-	///   The <see cref="GetClosestNamedColor" /> to check if this <see cref="Color" /> is closer to than any other configured named color.
-	/// </param>
-	/// <returns>
-	///   <see langword="true" /> if the closest named color to <paramref name="color" /> is the provided value.
-	///   <br />
-	///   <see langword="false" /> if any other named color is closer to <paramref name="color" /> than <paramref name="namedColor" />.
-	/// </returns>
-	/// <remarks>
-	///   If <paramref name="color" /> is equidistant from two named colors, the result of this method is not guaranteed to be determinate.
-	/// </remarks>
-	[Pure]
-	[MethodImpl (MethodImplOptions.AggressiveInlining)]
-	public static bool IsColorClosestToNamedColor (in Color color, in ColorName namedColor)
-	{
-		return color.IsClosestToNamedColor (in namedColor);
-	}
-
-	/// <summary>Gets the "closest" named color to this <see cref="Color" /> value.</summary>
-	/// <param name="inputColor"></param>
-	/// <remarks>
-	///   Distance is defined here as the Euclidean distance between each color interpreted as a <see cref="Vector3" />.
-	///   <para />
-	///   The order of the values in the passed Vector3 must be
-	/// </remarks>
-	/// <returns></returns>
-	[SkipLocalsInit]
-	internal static ColorName GetClosestNamedColor (Color inputColor) => ColorExtensions.ColorToNameMap.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
-
-	[SkipLocalsInit]
-	static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) => Vector4.Distance (color1, color2);
-
-	#region Legacy Color Names
-	/// <summary>The black color.</summary>
-	public const ColorName Black = ColorName.Black;
-
-	/// <summary>The blue color.</summary>
-	public const ColorName Blue = ColorName.Blue;
-
-	/// <summary>The green color.</summary>
-	public const ColorName Green = ColorName.Green;
-
-	/// <summary>The cyan color.</summary>
-	public const ColorName Cyan = ColorName.Cyan;
-
-	/// <summary>The red color.</summary>
-	public const ColorName Red = ColorName.Red;
-
-	/// <summary>The magenta color.</summary>
-	public const ColorName Magenta = ColorName.Magenta;
-
-	/// <summary>The yellow color.</summary>
-	public const ColorName Yellow = ColorName.Yellow;
-
-	/// <summary>The gray color.</summary>
-	public const ColorName Gray = ColorName.Gray;
-
-	/// <summary>The dark gray color.</summary>
-	public const ColorName DarkGray = ColorName.DarkGray;
-
-	/// <summary>The bright bBlue color.</summary>
-	public const ColorName BrightBlue = ColorName.BrightBlue;
-
-	/// <summary>The bright green color.</summary>
-	public const ColorName BrightGreen = ColorName.BrightGreen;
-
-	/// <summary>The bright cyan color.</summary>
-	public const ColorName BrightCyan = ColorName.BrightCyan;
-
-	/// <summary>The bright red color.</summary>
-	public const ColorName BrightRed = ColorName.BrightRed;
-
-	/// <summary>The bright magenta color.</summary>
-	public const ColorName BrightMagenta = ColorName.BrightMagenta;
-
-	/// <summary>The bright yellow color.</summary>
-	public const ColorName BrightYellow = ColorName.BrightYellow;
-
-	/// <summary>The White color.</summary>
-	public const ColorName White = ColorName.White;
-	#endregion
+public readonly partial record struct Color : ISpanParsable<Color>, IUtf8SpanParsable<Color>, ISpanFormattable,
+                                              IUtf8SpanFormattable, IMinMaxValue<Color>
+{
+    /// <summary>The value of the alpha channel component</summary>
+    /// <remarks>
+    ///     The alpha channel is not currently supported, so the value of the alpha channel bits will not affect
+    ///     rendering.
+    /// </remarks>
+    [JsonIgnore]
+    [field: FieldOffset (3)]
+    public readonly byte A;
+
+    /// <summary>The value of this <see cref="Color"/> as a <see langword="uint"/> in ARGB32 format.</summary>
+    /// <remarks>
+    ///     The alpha channel is not currently supported, so the value of the alpha channel bits will not affect
+    ///     rendering.
+    /// </remarks>
+    [JsonIgnore]
+    [field: FieldOffset (0)]
+    public readonly uint Argb;
+
+    /// <summary>The value of the blue color component.</summary>
+    [JsonIgnore]
+    [field: FieldOffset (0)]
+    public readonly byte B;
+
+    /// <summary>The value of the green color component.</summary>
+    [JsonIgnore]
+    [field: FieldOffset (1)]
+    public readonly byte G;
+
+    /// <summary>The value of the red color component.</summary>
+    [JsonIgnore]
+    [field: FieldOffset (2)]
+    public readonly byte R;
+
+    /// <summary>The value of this <see cref="Color"/> encoded as a signed 32-bit integer in ARGB32 format.</summary>
+    [JsonIgnore]
+    [field: FieldOffset (0)]
+    public readonly int Rgba;
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Color"/> <see langword="struct"/> using the supplied component
+    ///     values.
+    /// </summary>
+    /// <param name="red">The red 8-bits.</param>
+    /// <param name="green">The green 8-bits.</param>
+    /// <param name="blue">The blue 8-bits.</param>
+    /// <param name="alpha">Optional; defaults to 0xFF. The Alpha channel is not supported by Terminal.Gui.</param>
+    /// <remarks>Alpha channel is not currently supported by Terminal.Gui.</remarks>
+    /// <exception cref="OverflowException">If the value of any parameter is greater than <see cref="byte.MaxValue"/>.</exception>
+    /// <exception cref="ArgumentOutOfRangeException">If the value of any parameter is negative.</exception>
+    public Color (int red = 0, int green = 0, int blue = 0, int alpha = byte.MaxValue)
+    {
+        ArgumentOutOfRangeException.ThrowIfNegative (red, nameof (red));
+        ArgumentOutOfRangeException.ThrowIfNegative (green, nameof (green));
+        ArgumentOutOfRangeException.ThrowIfNegative (blue, nameof (blue));
+        ArgumentOutOfRangeException.ThrowIfNegative (alpha, nameof (alpha));
+
+        A = Convert.ToByte (alpha);
+        R = Convert.ToByte (red);
+        G = Convert.ToByte (green);
+        B = Convert.ToByte (blue);
+    }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Color"/> class with an encoded signed 32-bit color value in
+    ///     ARGB32 format.
+    /// </summary>
+    /// <param name="rgba">The encoded 32-bit color value (see <see cref="Rgba"/>).</param>
+    /// <remarks>
+    ///     The alpha channel is not currently supported, so the value of the alpha channel bits will not affect
+    ///     rendering.
+    /// </remarks>
+    public Color (int rgba) { Rgba = rgba; }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Color"/> class with an encoded unsigned 32-bit color value in
+    ///     ARGB32 format.
+    /// </summary>
+    /// <param name="argb">The encoded unsigned 32-bit color value (see <see cref="Argb"/>).</param>
+    /// <remarks>
+    ///     The alpha channel is not currently supported, so the value of the alpha channel bits will not affect
+    ///     rendering.
+    /// </remarks>
+    public Color (uint argb) { Argb = argb; }
+
+    /// <summary>Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color named value.</summary>
+    /// <param name="colorName">The 16-color value.</param>
+    public Color (in ColorName colorName) { this = ColorExtensions.ColorNameToColorMap [colorName]; }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Color"/> color from string. See
+    ///     <see cref="TryParse(string, out Color?)"/> for details.
+    /// </summary>
+    /// <param name="colorString"></param>
+    /// <exception cref="ArgumentNullException">If <paramref name="colorString"/> is <see langword="null"/>.</exception>
+    /// <exception cref="ArgumentException">
+    ///     If <paramref name="colorString"/> is an empty string or consists of only whitespace
+    ///     characters.
+    /// </exception>
+    /// <exception cref="ColorParseException">If thrown by <see cref="Parse(string?,System.IFormatProvider?)"/></exception>
+    public Color (string colorString)
+    {
+        ArgumentException.ThrowIfNullOrWhiteSpace (colorString, nameof (colorString));
+        this = Parse (colorString, CultureInfo.InvariantCulture);
+    }
+
+    /// <summary>Initializes a new instance of the <see cref="Color"/> with all channels set to 0.</summary>
+    public Color () { Argb = 0u; }
+
+    /// <summary>Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values.</summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
+    public static Dictionary<ColorName, string> Colors
+    {
+        get =>
+
+            // Transform _colorToNameMap into a Dictionary<ColorNames,string>
+            ColorExtensions.ColorToNameMap.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
+        set
+        {
+            // Transform Dictionary<ColorNames,string> into _colorToNameMap
+            ColorExtensions.ColorToNameMap = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
+
+            return;
+
+            static Color GetColorToNameMapKey (KeyValuePair<ColorName, string> kvp) { return new Color (kvp.Value); }
+
+            static ColorName GetColorToNameMapValue (KeyValuePair<ColorName, string> kvp)
+            {
+                return Enum.TryParse (kvp.Key.ToString (), true, out ColorName colorName)
+                           ? colorName
+                           : throw new ArgumentException ($"Invalid color name: {kvp.Key}");
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="ColorName"/> value. <see langword="get"/> will
+    ///     return the closest 16 color match to the true color when no exact value is found.
+    /// </summary>
+    /// <remarks>
+    ///     Get returns the <see cref="GetClosestNamedColor (Color)"/> of the closest 24-bit color value. Set sets the RGB
+    ///     value using a hard-coded map.
+    /// </remarks>
+    public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorNameToAnsiColorMap [GetClosestNamedColor ()]; }
+
+    /// <summary>
+    ///     Gets the <see cref="Color"/> using a legacy 16-color <see cref="Gui.ColorName"/> value. <see langword="get"/>
+    ///     will return the closest 16 color match to the true color when no exact value is found.
+    /// </summary>
+    /// <remarks>
+    ///     Get returns the <see cref="GetClosestNamedColor (Color)"/> of the closest 24-bit color value. Set sets the RGB
+    ///     value using a hard-coded map.
+    /// </remarks>
+    public ColorName GetClosestNamedColor () { return GetClosestNamedColor (this); }
+
+    /// <summary>
+    ///     Determines if the closest named <see cref="Color"/> to <see langword="this"/> is the provided
+    ///     <paramref name="namedColor"/>.
+    /// </summary>
+    /// <param name="namedColor">
+    ///     The <see cref="GetClosestNamedColor (Color)"/> to check if this <see cref="Color"/> is closer
+    ///     to than any other configured named color.
+    /// </param>
+    /// <returns>
+    ///     <see langword="true"/> if the closest named color is the provided value. <br/> <see langword="false"/> if any
+    ///     other named color is closer to this <see cref="Color"/> than <paramref name="namedColor"/>.
+    /// </returns>
+    /// <remarks>
+    ///     If <see langword="this"/> is equidistant from two named colors, the result of this method is not guaranteed to
+    ///     be determinate.
+    /// </remarks>
+    [Pure]
+    [MethodImpl (MethodImplOptions.AggressiveInlining)]
+    public bool IsClosestToNamedColor (in ColorName namedColor) { return GetClosestNamedColor () == namedColor; }
+
+    /// <summary>
+    ///     Determines if the closest named <see cref="Color"/> to <paramref name="color"/>/> is the provided
+    ///     <paramref name="namedColor"/>.
+    /// </summary>
+    /// <param name="color">
+    ///     The color to test against the <see cref="GetClosestNamedColor (Color)"/> value in
+    ///     <paramref name="namedColor"/>.
+    /// </param>
+    /// <param name="namedColor">
+    ///     The <see cref="GetClosestNamedColor (Color)"/> to check if this <see cref="Color"/> is closer
+    ///     to than any other configured named color.
+    /// </param>
+    /// <returns>
+    ///     <see langword="true"/> if the closest named color to <paramref name="color"/> is the provided value. <br/>
+    ///     <see langword="false"/> if any other named color is closer to <paramref name="color"/> than
+    ///     <paramref name="namedColor"/>.
+    /// </returns>
+    /// <remarks>
+    ///     If <paramref name="color"/> is equidistant from two named colors, the result of this method is not guaranteed
+    ///     to be determinate.
+    /// </remarks>
+    [Pure]
+    [MethodImpl (MethodImplOptions.AggressiveInlining)]
+    public static bool IsColorClosestToNamedColor (in Color color, in ColorName namedColor) { return color.IsClosestToNamedColor (in namedColor); }
+
+    /// <summary>Gets the "closest" named color to this <see cref="Color"/> value.</summary>
+    /// <param name="inputColor"></param>
+    /// <remarks>
+    ///     Distance is defined here as the Euclidean distance between each color interpreted as a <see cref="Vector3"/>.
+    ///     <para/>
+    ///     The order of the values in the passed Vector3 must be
+    /// </remarks>
+    /// <returns></returns>
+    [SkipLocalsInit]
+    internal static ColorName GetClosestNamedColor (Color inputColor)
+    {
+        return ColorExtensions.ColorToNameMap.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
+    }
+
+    [SkipLocalsInit]
+    private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); }
+
+    #region Legacy Color Names
+
+    /// <summary>The black color.</summary>
+    public const ColorName Black = ColorName.Black;
+
+    /// <summary>The blue color.</summary>
+    public const ColorName Blue = ColorName.Blue;
+
+    /// <summary>The green color.</summary>
+    public const ColorName Green = ColorName.Green;
+
+    /// <summary>The cyan color.</summary>
+    public const ColorName Cyan = ColorName.Cyan;
+
+    /// <summary>The red color.</summary>
+    public const ColorName Red = ColorName.Red;
+
+    /// <summary>The magenta color.</summary>
+    public const ColorName Magenta = ColorName.Magenta;
+
+    /// <summary>The yellow color.</summary>
+    public const ColorName Yellow = ColorName.Yellow;
+
+    /// <summary>The gray color.</summary>
+    public const ColorName Gray = ColorName.Gray;
+
+    /// <summary>The dark gray color.</summary>
+    public const ColorName DarkGray = ColorName.DarkGray;
+
+    /// <summary>The bright bBlue color.</summary>
+    public const ColorName BrightBlue = ColorName.BrightBlue;
+
+    /// <summary>The bright green color.</summary>
+    public const ColorName BrightGreen = ColorName.BrightGreen;
+
+    /// <summary>The bright cyan color.</summary>
+    public const ColorName BrightCyan = ColorName.BrightCyan;
+
+    /// <summary>The bright red color.</summary>
+    public const ColorName BrightRed = ColorName.BrightRed;
+
+    /// <summary>The bright magenta color.</summary>
+    public const ColorName BrightMagenta = ColorName.BrightMagenta;
+
+    /// <summary>The bright yellow color.</summary>
+    public const ColorName BrightYellow = ColorName.BrightYellow;
+
+    /// <summary>The White color.</summary>
+    public const ColorName White = ColorName.White;
+
+    #endregion
 }

+ 69 - 63
Terminal.Gui/Drawing/ColorExtensions.cs

@@ -2,73 +2,79 @@ using System.Collections.Frozen;
 
 namespace Terminal.Gui;
 
-internal static class ColorExtensions {
+internal static class ColorExtensions
+{
+    private static FrozenDictionary<Color, ColorName> colorToNameMap;
 
-	static FrozenDictionary<Color, ColorName> colorToNameMap;
+    static ColorExtensions ()
+    {
+        Dictionary<ColorName, AnsiColorCode> nameToCodeMap = new ()
+        {
+            { ColorName.Black, AnsiColorCode.BLACK },
+            { ColorName.Blue, AnsiColorCode.BLUE },
+            { ColorName.Green, AnsiColorCode.GREEN },
+            { ColorName.Cyan, AnsiColorCode.CYAN },
+            { ColorName.Red, AnsiColorCode.RED },
+            { ColorName.Magenta, AnsiColorCode.MAGENTA },
+            { ColorName.Yellow, AnsiColorCode.YELLOW },
+            { ColorName.Gray, AnsiColorCode.WHITE },
+            { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
+            { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
+            { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
+            { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
+            { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
+            { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
+            { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
+            { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
+        };
+        ColorNameToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
 
-	static ColorExtensions ()
-	{
-		Dictionary<ColorName, AnsiColorCode> nameToCodeMap = new () {
-			{ ColorName.Black, AnsiColorCode.BLACK },
-			{ ColorName.Blue, AnsiColorCode.BLUE },
-			{ ColorName.Green, AnsiColorCode.GREEN },
-			{ ColorName.Cyan, AnsiColorCode.CYAN },
-			{ ColorName.Red, AnsiColorCode.RED },
-			{ ColorName.Magenta, AnsiColorCode.MAGENTA },
-			{ ColorName.Yellow, AnsiColorCode.YELLOW },
-			{ ColorName.Gray, AnsiColorCode.WHITE },
-			{ ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
-			{ ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
-			{ ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
-			{ ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
-			{ ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
-			{ ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
-			{ ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
-			{ ColorName.White, AnsiColorCode.BRIGHT_WHITE }
-		};
-		ColorNameToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
+        ColorToNameMap = new Dictionary<Color, ColorName>
+        {
+            // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
+            // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
+            { new Color (12, 12, 12), ColorName.Black },
+            { new Color (0, 55, 218), ColorName.Blue },
+            { new Color (19, 161, 14), ColorName.Green },
+            { new Color (58, 150, 221), ColorName.Cyan },
+            { new Color (197, 15, 31), ColorName.Red },
+            { new Color (136, 23, 152), ColorName.Magenta },
+            { new Color (128, 64, 32), ColorName.Yellow },
+            { new Color (204, 204, 204), ColorName.Gray },
+            { new Color (118, 118, 118), ColorName.DarkGray },
+            { new Color (59, 120, 255), ColorName.BrightBlue },
+            { new Color (22, 198, 12), ColorName.BrightGreen },
+            { new Color (97, 214, 214), ColorName.BrightCyan },
+            { new Color (231, 72, 86), ColorName.BrightRed },
+            { new Color (180, 0, 158), ColorName.BrightMagenta },
+            { new Color (249, 241, 165), ColorName.BrightYellow },
+            { new Color (242, 242, 242), ColorName.White }
+        }.ToFrozenDictionary ();
+    }
 
-		ColorToNameMap = new Dictionary<Color, ColorName> {
-			// using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
-			// See also: https://en.wikipedia.org/wiki/ANSI_escape_code
-			{ new Color (12, 12, 12), ColorName.Black },
-			{ new Color (0, 55, 218), ColorName.Blue },
-			{ new Color (19, 161, 14), ColorName.Green },
-			{ new Color (58, 150, 221), ColorName.Cyan },
-			{ new Color (197, 15, 31), ColorName.Red },
-			{ new Color (136, 23, 152), ColorName.Magenta },
-			{ new Color (128, 64, 32), ColorName.Yellow },
-			{ new Color (204, 204, 204), ColorName.Gray },
-			{ new Color (118, 118, 118), ColorName.DarkGray },
-			{ new Color (59, 120, 255), ColorName.BrightBlue },
-			{ new Color (22, 198, 12), ColorName.BrightGreen },
-			{ new Color (97, 214, 214), ColorName.BrightCyan },
-			{ new Color (231, 72, 86), ColorName.BrightRed },
-			{ new Color (180, 0, 158), ColorName.BrightMagenta },
-			{ new Color (249, 241, 165), ColorName.BrightYellow },
-			{ new Color (242, 242, 242), ColorName.White }
-		}.ToFrozenDictionary ();
-	}
+    /// <summary>Defines the 16 legacy color names and their corresponding ANSI color codes.</summary>
+    internal static FrozenDictionary<ColorName, AnsiColorCode> ColorNameToAnsiColorMap { get; }
 
-	/// <summary>Defines the 16 legacy color names and their corresponding ANSI color codes.</summary>
-	internal static FrozenDictionary<ColorName, AnsiColorCode> ColorNameToAnsiColorMap { get; }
+    /// <summary>Reverse mapping for <see cref="ColorToNameMap"/>.</summary>
+    internal static FrozenDictionary<ColorName, Color> ColorNameToColorMap { get; private set; }
 
-	/// <summary>
-	///   Gets or sets a <see cref="FrozenDictionary{TKey,TValue}" /> that maps legacy 16-color values to the corresponding
-	///   <see cref="ColorName" />.
-	/// </summary>
-	/// <remarks>
-	///   Setter should be called as infrequently as possible, as <see cref="FrozenDictionary{TKey,TValue}" /> is expensive to create.
-	/// </remarks>
-	internal static FrozenDictionary<Color, ColorName> ColorToNameMap {
-		get => colorToNameMap;
-		set {
-			colorToNameMap = value;
-			//Also be sure to set the reverse mapping
-			ColorNameToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
-		}
-	}
+    /// <summary>
+    ///     Gets or sets a <see cref="FrozenDictionary{TKey,TValue}"/> that maps legacy 16-color values to the
+    ///     corresponding <see cref="ColorName"/>.
+    /// </summary>
+    /// <remarks>
+    ///     Setter should be called as infrequently as possible, as <see cref="FrozenDictionary{TKey,TValue}"/> is
+    ///     expensive to create.
+    /// </remarks>
+    internal static FrozenDictionary<Color, ColorName> ColorToNameMap
+    {
+        get => colorToNameMap;
+        set
+        {
+            colorToNameMap = value;
 
-	/// <summary>Reverse mapping for <see cref="ColorToNameMap" />.</summary>
-	internal static FrozenDictionary<ColorName, Color> ColorNameToColorMap { get; private set; }
+            //Also be sure to set the reverse mapping
+            ColorNameToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
+        }
+    }
 }

+ 57 - 91
Terminal.Gui/Drawing/ColorName.cs

@@ -1,97 +1,63 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Defines the 16 legacy color names and values that can be used to set the
-/// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
+///     Defines the 16 legacy color names and values that can be used to set the foreground and background colors in
+///     Terminal.Gui apps. Used with <see cref="Color"/>.
 /// </summary>
 /// <remarks>
-///         <para>
-///         These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
-///         </para>
-///         <para>
-///         For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be configured
-///         using the
-///         <see cref="Color.Colors"/> property.
-///         </para>
+///     <para>These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.</para>
+///     <para>
+///         For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be
+///         configured using the <see cref="Color.Colors"/> property.
+///     </para>
 /// </remarks>
-public enum ColorName {
-	/// <summary>
-	/// The black color. ANSI escape sequence: <c>\u001b[30m</c>.
-	/// </summary>
-	Black,
-
-	/// <summary>
-	/// The blue color. ANSI escape sequence: <c>\u001b[34m</c>.
-	/// </summary>
-	Blue,
-
-	/// <summary>
-	/// The green color. ANSI escape sequence: <c>\u001b[32m</c>.
-	/// </summary>
-	Green,
-
-	/// <summary>
-	/// The cyan color. ANSI escape sequence: <c>\u001b[36m</c>.
-	/// </summary>
-	Cyan,
-
-	/// <summary>
-	/// The red color. ANSI escape sequence: <c>\u001b[31m</c>.
-	/// </summary>
-	Red,
-
-	/// <summary>
-	/// The magenta color. ANSI escape sequence: <c>\u001b[35m</c>.
-	/// </summary>
-	Magenta,
-
-	/// <summary>
-	/// The yellow color (also known as Brown). ANSI escape sequence: <c>\u001b[33m</c>.
-	/// </summary>
-	Yellow,
-
-	/// <summary>
-	/// The gray color (also known as White). ANSI escape sequence: <c>\u001b[37m</c>.
-	/// </summary>
-	Gray,
-
-	/// <summary>
-	/// The dark gray color (also known as Bright Black). ANSI escape sequence: <c>\u001b[30;1m</c>.
-	/// </summary>
-	DarkGray,
-
-	/// <summary>
-	/// The bright blue color. ANSI escape sequence: <c>\u001b[34;1m</c>.
-	/// </summary>
-	BrightBlue,
-
-	/// <summary>
-	/// The bright green color. ANSI escape sequence: <c>\u001b[32;1m</c>.
-	/// </summary>
-	BrightGreen,
-
-	/// <summary>
-	/// The bright cyan color. ANSI escape sequence: <c>\u001b[36;1m</c>.
-	/// </summary>
-	BrightCyan,
-
-	/// <summary>
-	/// The bright red color. ANSI escape sequence: <c>\u001b[31;1m</c>.
-	/// </summary>
-	BrightRed,
-
-	/// <summary>
-	/// The bright magenta color. ANSI escape sequence: <c>\u001b[35;1m</c>.
-	/// </summary>
-	BrightMagenta,
-
-	/// <summary>
-	/// The bright yellow color. ANSI escape sequence: <c>\u001b[33;1m</c>.
-	/// </summary>
-	BrightYellow,
-
-	/// <summary>
-	/// The White color (also known as Bright White). ANSI escape sequence: <c>\u001b[37;1m</c>.
-	/// </summary>
-	White
-}
+public enum ColorName
+{
+    /// <summary>The black color. ANSI escape sequence: <c>\u001b[30m</c>.</summary>
+    Black,
+
+    /// <summary>The blue color. ANSI escape sequence: <c>\u001b[34m</c>.</summary>
+    Blue,
+
+    /// <summary>The green color. ANSI escape sequence: <c>\u001b[32m</c>.</summary>
+    Green,
+
+    /// <summary>The cyan color. ANSI escape sequence: <c>\u001b[36m</c>.</summary>
+    Cyan,
+
+    /// <summary>The red color. ANSI escape sequence: <c>\u001b[31m</c>.</summary>
+    Red,
+
+    /// <summary>The magenta color. ANSI escape sequence: <c>\u001b[35m</c>.</summary>
+    Magenta,
+
+    /// <summary>The yellow color (also known as Brown). ANSI escape sequence: <c>\u001b[33m</c>.</summary>
+    Yellow,
+
+    /// <summary>The gray color (also known as White). ANSI escape sequence: <c>\u001b[37m</c>.</summary>
+    Gray,
+
+    /// <summary>The dark gray color (also known as Bright Black). ANSI escape sequence: <c>\u001b[30;1m</c>.</summary>
+    DarkGray,
+
+    /// <summary>The bright blue color. ANSI escape sequence: <c>\u001b[34;1m</c>.</summary>
+    BrightBlue,
+
+    /// <summary>The bright green color. ANSI escape sequence: <c>\u001b[32;1m</c>.</summary>
+    BrightGreen,
+
+    /// <summary>The bright cyan color. ANSI escape sequence: <c>\u001b[36;1m</c>.</summary>
+    BrightCyan,
+
+    /// <summary>The bright red color. ANSI escape sequence: <c>\u001b[31;1m</c>.</summary>
+    BrightRed,
+
+    /// <summary>The bright magenta color. ANSI escape sequence: <c>\u001b[35;1m</c>.</summary>
+    BrightMagenta,
+
+    /// <summary>The bright yellow color. ANSI escape sequence: <c>\u001b[33;1m</c>.</summary>
+    BrightYellow,
+
+    /// <summary>The White color (also known as Bright White). ANSI escape sequence: <c>\u001b[37;1m</c>.</summary>
+    White
+}

+ 77 - 76
Terminal.Gui/Drawing/ColorParseException.cs

@@ -3,90 +3,91 @@ using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui;
 
-/// <summary>
-///   An exception thrown when something goes wrong when trying to parse a <see cref="Color" />.
-/// </summary>
-/// <remarks>
-///   Contains additional information to help locate the problem.
-///   <br />
-///   Not intended to be thrown by consumers.
-/// </remarks>
-public sealed class ColorParseException : FormatException {
+/// <summary>An exception thrown when something goes wrong when trying to parse a <see cref="Color"/>.</summary>
+/// <remarks>Contains additional information to help locate the problem. <br/> Not intended to be thrown by consumers.</remarks>
+public sealed class ColorParseException : FormatException
+{
+    internal const string DefaultMessage = "Failed to parse text as Color.";
 
-	internal const string DefaultMessage = "Failed to parse text as Color.";
-	internal ColorParseException (string colorString, string? message, Exception? innerException = null) : base (message ?? DefaultMessage, innerException)
-	{
-		ColorString = colorString;
-	}
+    internal ColorParseException (string colorString, string? message, Exception? innerException = null) :
+        base (message ?? DefaultMessage, innerException)
+    {
+        ColorString = colorString;
+    }
 
-	internal ColorParseException (string colorString, string? message = DefaultMessage) : base (message)
-	{
-		ColorString = colorString;
-	}
+    internal ColorParseException (string colorString, string? message = DefaultMessage) : base (message) { ColorString = colorString; }
 
-	/// <summary>
-	///   Creates a new instance of a <see cref="ColorParseException" /> populated with the provided values.
-	/// </summary>
-	/// <param name="colorString">The text that caused this exception, as a <see langword="string" />.</param>
-	/// <param name="badValue">
-	///   The specific value in <paramref name="colorString" /> that caused this exception.
-	/// </param>
-	/// <param name="badValueName">
-	///   The name of the value (red, green, blue, alpha) that <paramref name="badValue" /> represents.
-	/// </param>
-	/// <param name="reason">The reason that <paramref name="badValue" /> failed to parse.</param>
-	internal ColorParseException (in ReadOnlySpan<char> colorString, string reason, in ReadOnlySpan<char> badValue = default, in ReadOnlySpan<char> badValueName = default) : base (DefaultMessage)
-	{
-		ColorString = colorString.ToString ();
-		BadValue = badValue.ToString ();
-		BadValueName = badValueName.ToString ();
-		Reason = reason;
-	}
+    /// <summary>Creates a new instance of a <see cref="ColorParseException"/> populated with the provided values.</summary>
+    /// <param name="colorString">The text that caused this exception, as a <see langword="string"/>.</param>
+    /// <param name="badValue">The specific value in <paramref name="colorString"/> that caused this exception.</param>
+    /// <param name="badValueName">The name of the value (red, green, blue, alpha) that <paramref name="badValue"/> represents.</param>
+    /// <param name="reason">The reason that <paramref name="badValue"/> failed to parse.</param>
+    internal ColorParseException (
+        in ReadOnlySpan<char> colorString,
+        string reason,
+        in ReadOnlySpan<char> badValue = default,
+        in ReadOnlySpan<char> badValueName = default
+    ) : base (DefaultMessage)
+    {
+        ColorString = colorString.ToString ();
+        BadValue = badValue.ToString ();
+        BadValueName = badValueName.ToString ();
+        Reason = reason;
+    }
 
-	/// <summary>
-	///   Creates a new instance of a <see cref="ColorParseException" /> populated with the provided values.
-	/// </summary>
-	/// <param name="colorString">The text that caused this exception, as a <see langword="string" />.</param>
-	/// <param name="badValue">
-	///   The specific value in <paramref name="colorString" /> that caused this exception.
-	/// </param>
-	/// <param name="badValueName">
-	///   The name of the value (red, green, blue, alpha) that <paramref name="badValue" /> represents.
-	/// </param>
-	/// <param name="reason">The reason that <paramref name="badValue" /> failed to parse.</param>
-	internal ColorParseException (in ReadOnlySpan<char> colorString, string? badValue = null, string? badValueName = null, string? reason = null) : base (DefaultMessage)
-	{
-		ColorString = colorString.ToString ();
-		BadValue = badValue;
-		BadValueName = badValueName;
-		Reason = reason;
-	}
+    /// <summary>Creates a new instance of a <see cref="ColorParseException"/> populated with the provided values.</summary>
+    /// <param name="colorString">The text that caused this exception, as a <see langword="string"/>.</param>
+    /// <param name="badValue">The specific value in <paramref name="colorString"/> that caused this exception.</param>
+    /// <param name="badValueName">The name of the value (red, green, blue, alpha) that <paramref name="badValue"/> represents.</param>
+    /// <param name="reason">The reason that <paramref name="badValue"/> failed to parse.</param>
+    internal ColorParseException (
+        in ReadOnlySpan<char> colorString,
+        string? badValue = null,
+        string? badValueName = null,
+        string? reason = null
+    ) : base (DefaultMessage)
+    {
+        ColorString = colorString.ToString ();
+        BadValue = badValue;
+        BadValueName = badValueName;
+        Reason = reason;
+    }
 
-	/// <summary>Gets the text that failed to parse, as a <see langword="string" /></summary>
-	/// <remarks>
-	///   Is marked <see langword="required" />, so must be set by a constructor or initializer.
-	/// </remarks>
-	public string ColorString { get; }
+    /// <summary>Gets the substring of <see cref="ColorString"/> caused this exception, as a <see langword="string"/></summary>
+    /// <remarks>May be null or empty - only set if known.</remarks>
+    public string? BadValue { get; }
 
-	/// <summary>
-	///   Gets the substring of <see cref="ColorString" /> caused this exception, as a <see langword="string" />
-	/// </summary>
-	/// <remarks>May be null or empty - only set if known.</remarks>
-	public string? BadValue { get; }
+    /// <summary>Gets the name of the color component corresponding to <see cref="BadValue"/>, if known.</summary>
+    /// <remarks>May be null or empty - only set if known.</remarks>
+    public string? BadValueName { get; }
 
-	/// <summary>
-	///   Gets the name of the color component corresponding to <see cref="BadValue" />, if known.
-	/// </summary>
-	/// <remarks>May be null or empty - only set if known.</remarks>
-	public string? BadValueName { get; }
+    /// <summary>Gets the text that failed to parse, as a <see langword="string"/></summary>
+    /// <remarks>Is marked <see langword="required"/>, so must be set by a constructor or initializer.</remarks>
+    public string ColorString { get; }
 
-	/// <summary>Gets the reason that <see cref="BadValue" /> failed to parse, if known.</summary>
-	/// <remarks>May be null or empty - only set if known.</remarks>
-	public string? Reason { get; }
+    /// <summary>Gets the reason that <see cref="BadValue"/> failed to parse, if known.</summary>
+    /// <remarks>May be null or empty - only set if known.</remarks>
+    public string? Reason { get; }
 
-	[DoesNotReturn]
-	internal static void Throw (in ReadOnlySpan<char> colorString, string reason, in ReadOnlySpan<char> badValue = default, in ReadOnlySpan<char> badValueName = default) => throw new ColorParseException (in colorString, reason, in badValue, in badValueName);
+    [DoesNotReturn]
+    internal static void Throw (
+        in ReadOnlySpan<char> colorString,
+        string reason,
+        in ReadOnlySpan<char> badValue = default,
+        in ReadOnlySpan<char> badValueName = default
+    )
+    {
+        throw new ColorParseException (in colorString, reason, in badValue, in badValueName);
+    }
 
-	[DoesNotReturn]
-	internal static void ThrowIfNotAsciiDigits (in ReadOnlySpan<char> valueText, string reason, in ReadOnlySpan<char> badValue = default, in ReadOnlySpan<char> badValueName = default) => throw new ColorParseException (in valueText, reason, in badValue, in badValueName);
+    [DoesNotReturn]
+    internal static void ThrowIfNotAsciiDigits (
+        in ReadOnlySpan<char> valueText,
+        string reason,
+        in ReadOnlySpan<char> badValue = default,
+        in ReadOnlySpan<char> badValueName = default
+    )
+    {
+        throw new ColorParseException (in valueText, reason, in badValue, in badValueName);
+    }
 }

+ 215 - 239
Terminal.Gui/Drawing/ColorScheme.cs

@@ -4,249 +4,225 @@ using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Defines a standard set of <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>.
-/// </summary>
+/// <summary>Defines a standard set of <see cref="Attribute"/>s for common visible elements in a <see cref="View"/>.</summary>
 /// <remarks>
-/// <para>
-/// ColorScheme objects are immutable. Once constructed, the properties cannot be changed.
-/// To change a ColorScheme, create a new one with the desired values,
-/// using the <see cref="ColorScheme(ColorScheme)"/> constructor.
-/// </para>
-/// <para>
-/// See also: <see cref="ColorSchemesConfiguration.ColorSchemes"/>.
-/// </para>
+///     <para>
+///         ColorScheme objects are immutable. Once constructed, the properties cannot be changed. To change a
+///         ColorScheme, create a new one with the desired values, using the <see cref="ColorScheme(ColorScheme)"/>
+///         constructor.
+///     </para>
 /// </remarks>
 [JsonConverter (typeof (ColorSchemeJsonConverter))]
-public class ColorScheme : IEquatable<ColorScheme> {
-	readonly Attribute _disabled;
-	readonly Attribute _focus;
-	readonly Attribute _hotFocus;
-	readonly Attribute _hotNormal;
-	readonly Attribute _normal;
-
-	/// <summary>
-	/// Creates a new instance set to the default colors (see <see cref="Attribute.Default"/>).
-	/// </summary>
-	public ColorScheme () : this (Attribute.Default) { }
-
-	/// <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)
-	{
-		if (scheme is null) {
-			throw new ArgumentNullException (nameof (scheme));
-		}
-		_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 => _normal;
-		init => _normal = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text when the view has the focus.
-	/// </summary>
-	public Attribute Focus {
-		get => _focus;
-		init => _focus = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for text in a non-focused view that indicates a <see cref="View.HotKey"/>.
-	/// </summary>
-	public Attribute HotNormal {
-		get => _hotNormal;
-		init => _hotNormal = value;
-	}
-
-	/// <summary>
-	/// The foreground and background color for for text in a focused view that indicates a <see cref="View.HotKey"/>.
-	/// </summary>
-	public Attribute HotFocus {
-		get => _hotFocus;
-		init => _hotFocus = value;
-	}
-
-	/// <summary>
-	/// The default foreground and background color for text when the view is disabled.
-	/// </summary>
-	public Attribute Disabled {
-		get => _disabled;
-		init => _disabled = value;
-	}
-
-	/// <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) => other is { } &&
-												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>
-	/// 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) => Equals (obj as ColorScheme);
-
-	/// <summary>
-	/// Returns a hashcode for this instance.
-	/// </summary>
-	/// <returns>hashcode for this instance</returns>
-	public override int GetHashCode ()
-	{
-		var 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) => 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) => !(left == right);
-
-	/// <inheritdoc />
-	public override string ToString ( ) => $"Normal: {Normal}; Focus: {Focus}; HotNormal: {HotNormal}; HotFocus: {HotFocus}; Disabled: {Disabled}";
+public class ColorScheme : IEquatable<ColorScheme>
+{
+    private readonly Attribute _disabled;
+    private readonly Attribute _focus;
+    private readonly Attribute _hotFocus;
+    private readonly Attribute _hotNormal;
+    private readonly Attribute _normal;
+
+    /// <summary>Creates a new instance set to the default colors (see <see cref="Attribute.Default"/>).</summary>
+    public ColorScheme () : this (Attribute.Default) { }
+
+    /// <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)
+    {
+        if (scheme is null)
+        {
+            throw new ArgumentNullException (nameof (scheme));
+        }
+
+        _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 default foreground and background color for text when the view is disabled.</summary>
+    public Attribute Disabled
+    {
+        get => _disabled;
+        init => _disabled = value;
+    }
+
+    /// <summary>The foreground and background color for text when the view has the focus.</summary>
+    public Attribute Focus
+    {
+        get => _focus;
+        init => _focus = value;
+    }
+
+    /// <summary>The foreground and background color for for text in a focused view that indicates a <see cref="View.HotKey"/>.</summary>
+    public Attribute HotFocus
+    {
+        get => _hotFocus;
+        init => _hotFocus = value;
+    }
+
+    /// <summary>The foreground and background color for text in a non-focused view that indicates a <see cref="View.HotKey"/>.</summary>
+    public Attribute HotNormal
+    {
+        get => _hotNormal;
+        init => _hotNormal = value;
+    }
+
+    /// <summary>The foreground and background color for text when the view is not focused, hot, or disabled.</summary>
+    public Attribute Normal
+    {
+        get => _normal;
+        init => _normal = value;
+    }
+
+    /// <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 is { }
+               && 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>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>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); }
+
+    /// <inheritdoc/>
+    public override string ToString () { return $"Normal: {Normal}; Focus: {Focus}; HotNormal: {HotNormal}; HotFocus: {HotFocus}; Disabled: {Disabled}"; }
 }
 
 /// <summary>
-/// Holds the <see cref="ColorScheme"/>s that define the <see cref="Attribute"/>s that are used by views to render themselves.
+///     Holds the <see cref="ColorScheme"/>s that define the <see cref="Attribute"/>s that are used by views to render
+///     themselves.
 /// </summary>
-public static class Colors {
-	static Colors () => Reset ();
-	/// <summary>
-	/// Gets a dictionary of defined <see cref="ColorScheme"/> objects.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// The <see cref="ColorSchemes"/> dictionary includes the following keys, by default:
-	/// <list type="table">
-	/// <listheader>
-	///         <term>Built-in Color Scheme</term>
-	///         <description>Description</description>
-	/// </listheader>
-	/// <item>
-	///         <term>
-	///         Base
-	///         </term>
-	///         <description>
-	///         The base color scheme used for most Views.
-	///         </description>
-	/// </item>
-	/// <item>
-	///         <term>
-	///         TopLevel
-	///         </term>
-	///         <description>
-	///         The application Toplevel color scheme; used for the <see cref="Toplevel"/> View.
-	///         </description>
-	/// </item>
-	/// <item>
-	///         <term>
-	///         Dialog
-	///         </term>
-	///         <description>
-	///         The dialog color scheme; used for <see cref="Dialog"/>, <see cref="MessageBox"/>, and other views dialog-like views.
-	///         </description>
-	/// </item> 
-	/// <item>
-	///         <term>
-	///         Menu
-	///         </term>
-	///         <description>
-	///         The menu color scheme; used for <see cref="MenuBar"/>, <see cref="ContextMenu"/>, and <see cref="StatusBar"/>. 
-	///         </description>
-	/// </item>
-	/// <item>
-	///         <term>
-	///         Error
-	///         </term>
-	///         <description>
-	///         The color scheme for showing errors, such as in <see cref="MessageBox.ErrorQuery(string, string, string[])"/>. 
-	///         </description>
-	/// </item>
-	/// </list>
-	/// </para>
-	/// <para>
-	/// Changing the values of an entry in this dictionary will affect all views that use the scheme.
-	/// </para>
-	/// <para>
-	/// <see cref="ConfigurationManager"/> can be used to override the default values for these schemes and add additional schemes.
-	/// See <see cref="ConfigurationManager.Themes"/>.
-	/// </para>
-	/// </remarks>
-	[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
-	[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
-	public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; } // Serialization requires this to have a setter (private set;)
-
-	/// <summary>
-	/// Resets the <see cref="ColorSchemes"/> dictionary to the default values.
-	/// </summary>
-	public static Dictionary<string, ColorScheme> Reset ()
-	{
-		ColorSchemes ??= new Dictionary<string, ColorScheme> (5, CultureInfo.InvariantCulture.CompareInfo.GetStringComparer (CompareOptions.IgnoreCase));
-		ColorSchemes.Clear ();
-		ColorSchemes.Add ("TopLevel", new ColorScheme ());
-		ColorSchemes.Add ("Base", new ColorScheme ());
-		ColorSchemes.Add ("Dialog", new ColorScheme ());
-		ColorSchemes.Add ("Menu", new ColorScheme ());
-		ColorSchemes.Add ("Error", new ColorScheme ());
-		return ColorSchemes;
-	}
-
-	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) => obj.ToLowerInvariant ().GetHashCode ();
-	}
-}
+public static class Colors
+{
+    static Colors () { Reset (); }
+
+    /// <summary>Gets a dictionary of defined <see cref="ColorScheme"/> objects.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         The <see cref="ColorSchemes"/> dictionary includes the following keys, by default:
+    ///         <list type="table">
+    ///             <listheader>
+    ///                 <term>Built-in Color Scheme</term> <description>Description</description>
+    ///             </listheader>
+    ///             <item>
+    ///                 <term>Base</term> <description>The base color scheme used for most Views.</description>
+    ///             </item>
+    ///             <item>
+    ///                 <term>TopLevel</term>
+    ///                 <description>The application Toplevel color scheme; used for the <see cref="Toplevel"/> View.</description>
+    ///             </item>
+    ///             <item>
+    ///                 <term>Dialog</term>
+    ///                 <description>
+    ///                     The dialog color scheme; used for <see cref="Dialog"/>, <see cref="MessageBox"/>, and
+    ///                     other views dialog-like views.
+    ///                 </description>
+    ///             </item>
+    ///             <item>
+    ///                 <term>Menu</term>
+    ///                 <description>
+    ///                     The menu color scheme; used for <see cref="MenuBar"/>, <see cref="ContextMenu"/>, and
+    ///                     <see cref="StatusBar"/>.
+    ///                 </description>
+    ///             </item>
+    ///             <item>
+    ///                 <term>Error</term>
+    ///                 <description>
+    ///                     The color scheme for showing errors, such as in
+    ///                     <see cref="MessageBox.ErrorQuery(string, string, string[])"/>.
+    ///                 </description>
+    ///             </item>
+    ///         </list>
+    ///     </para>
+    ///     <para>Changing the values of an entry in this dictionary will affect all views that use the scheme.</para>
+    ///     <para>
+    ///         <see cref="ConfigurationManager"/> can be used to override the default values for these schemes and add
+    ///         additional schemes. See <see cref="ConfigurationManager.Themes"/>.
+    ///     </para>
+    /// </remarks>
+    [SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
+    [JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
+    public static Dictionary<string, ColorScheme>
+        ColorSchemes { get; private set; } // Serialization requires this to have a setter (private set;)
+
+    /// <summary>Resets the <see cref="ColorSchemes"/> dictionary to the default values.</summary>
+    public static Dictionary<string, ColorScheme> Reset ()
+    {
+        ColorSchemes ??= new Dictionary<string, ColorScheme> (
+                                                              5,
+                                                              CultureInfo.InvariantCulture.CompareInfo
+                                                                         .GetStringComparer (
+                                                                                             CompareOptions.IgnoreCase
+                                                                                            )
+                                                             );
+        ColorSchemes.Clear ();
+        ColorSchemes.Add ("TopLevel", new ColorScheme ());
+        ColorSchemes.Add ("Base", new ColorScheme ());
+        ColorSchemes.Add ("Dialog", new ColorScheme ());
+        ColorSchemes.Add ("Menu", new ColorScheme ());
+        ColorSchemes.Add ("Error", new ColorScheme ());
+
+        return ColorSchemes;
+    }
+
+    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 (); }
+    }
+}

+ 439 - 689
Terminal.Gui/Drawing/Glyphs.cs

@@ -1,690 +1,440 @@
-using static Terminal.Gui.ConfigurationManager;
-using System.Text.Json.Serialization;
-using System.Text;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Defines the standard set of glyphs used to draw checkboxes, lines, borders, etc...
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Access with <see cref="CM.Glyphs"/> (which is a global using alias for <see cref="ConfigurationManager.Glyphs"/>).
-	/// </para>
-	/// <para>
-	/// The default glyphs can be changed via the <see cref="ConfigurationManager"/>. Within a <c>config.json</c> file 
-	/// The Json property name is the property name prefixed with "Glyphs.". 
-	/// </para>
-	/// <para>
-	/// The JSon property can be either a decimal number or a string. The string may be one of:
-	/// - A unicode char (e.g. "☑")
-	/// - A hex value in U+ format (e.g. "U+2611")
-	/// - A hex value in UTF-16 format (e.g. "\\u2611")
-	/// </para>
-	/// </remarks>
-	public class GlyphDefinitions {
-		#region ----------------- Single Glyphs -----------------
-
-		/// <summary>
-		/// Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).
-		/// </summary>
-		public Rune Checked { get; set; } = (Rune)'☑';
-
-		/// <summary>
-		/// Not Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).
-		/// </summary>
-		public Rune UnChecked { get; set; } = (Rune)'☐';
-
-		/// <summary>
-		/// Null Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).
-		/// </summary>
-		public Rune NullChecked { get; set; } = (Rune)'☒';
-
-		/// <summary>
-		/// Selected indicator  (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).
-		/// </summary>
-		public Rune Selected { get; set; } = (Rune)'◉';
-
-		/// <summary>
-		/// Not Selected indicator (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).
-		/// </summary>
-		public Rune UnSelected { get; set; } = (Rune)'○';
-
-		/// <summary>
-		/// Horizontal arrow.
-		/// </summary>
-		public Rune RightArrow { get; set; } = (Rune)'►';
-
-		/// <summary>
-		/// Left arrow.
-		/// </summary>
-		public Rune LeftArrow { get; set; } = (Rune)'◄';
-
-		/// <summary>
-		/// Down arrow.
-		/// </summary>
-		public Rune DownArrow { get; set; } = (Rune)'▼';
-
-		/// <summary>
-		/// Vertical arrow.
-		/// </summary>
-		public Rune UpArrow { get; set; } = (Rune)'▲';
-
-		/// <summary>
-		/// Left default indicator (e.g. for <see cref="Button"/>.
-		/// </summary>
-		public Rune LeftDefaultIndicator { get; set; } = (Rune)'►';
-
-		/// <summary>
-		/// Horizontal default indicator (e.g. for <see cref="Button"/>.
-		/// </summary>
-		public Rune RightDefaultIndicator { get; set; } = (Rune)'◄';
-
-		/// <summary>
-		/// Left Bracket (e.g. for <see cref="Button"/>. Default is (U+005B) - [.
-		/// </summary>
-		public Rune LeftBracket { get; set; } = (Rune)'⟦';
-
-		/// <summary>
-		/// Horizontal Bracket (e.g. for <see cref="Button"/>. Default is (U+005D) - ].
-		/// </summary>
-		public Rune RightBracket { get; set; } = (Rune)'⟧';
-
-		/// <summary>
-		/// Half block meter segment (e.g. for <see cref="ProgressBar"/>).
-		/// </summary>
-		public Rune BlocksMeterSegment { get; set; } = (Rune)'▌';
-
-		/// <summary>
-		/// Continuous block meter segment (e.g. for <see cref="ProgressBar"/>).
-		/// </summary>
-		public Rune ContinuousMeterSegment { get; set; } = (Rune)'█';
-
-		/// <summary>
-		/// Stipple pattern (e.g. for <see cref="ScrollBarView"/>). Default is Light Shade (U+2591) - ░.
-		/// </summary>
-		public Rune Stipple { get; set; } = (Rune)'░';
-
-		/// <summary>
-		/// Diamond (e.g. for <see cref="ScrollBarView"/>. Default is Lozenge (U+25CA) - ◊.
-		/// </summary>
-		public Rune Diamond { get; set; } = (Rune)'◊';
-
-		/// <summary>
-		/// Close. Default is Heavy Ballot X (U+2718) - ✘.
-		/// </summary>
-		public Rune Close { get; set; } = (Rune)'✘';
-
-		/// <summary>
-		/// Minimize. Default is Lower Horizontal Shadowed White Circle (U+274F) - ❏.
-		/// </summary>
-		public Rune Minimize { get; set; } = (Rune)'❏';
-
-		/// <summary>
-		/// Maximize. Default is Upper Horizontal Shadowed White Circle (U+273D) - ✽.
-		/// </summary>
-		public Rune Maximize { get; set; } = (Rune)'✽';
-
-		/// <summary>
-		/// Dot. Default is (U+2219) - ∙.
-		/// </summary>
-		public Rune Dot { get; set; } = (Rune)'∙';
-
-		/// <summary>
-		/// Black Circle . Default is (U+025cf) - ●.
-		/// </summary>
-		public Rune BlackCircle { get; set; } = (Rune)'●'; // Black Circle - ● U+025cf
-
-		/// <summary>
-		/// Expand (e.g. for <see cref="TreeView"/>.
-		/// </summary>
-		public Rune Expand { get; set; } = (Rune)'+';
-
-		/// <summary>
-		/// Expand (e.g. for <see cref="TreeView"/>.
-		/// </summary>
-		public Rune Collapse { get; set; } = (Rune)'-';
-
-		/// <summary>
-		/// Identical To (U+226)
-		/// </summary>
-		public Rune IdenticalTo { get; set; } = (Rune)'≡';
-
-		/// <summary>
-		/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
-		/// </summary>
-		public Rune Apple { get; set; } = "🍎".ToRunes () [0]; // nonBMP
-
-		/// <summary>
-		/// Apple (BMP). Because snek. See Issue #2610.
-		/// </summary>
-		public Rune AppleBMP { get; set; } = (Rune)'❦';
-
-		///// <summary>
-		///// A nonprintable (low surrogate) that should fail to ctor.
-		///// </summary>
-		//public Rune InvalidGlyph { get; set; } = (Rune)'\ud83d';
-
-		#endregion
-
-		/// <summary>
-		/// Folder icon.  Defaults to ꤉ (Kayah Li Digit Nine)
-		/// </summary>
-		public Rune Folder { get; set; } = (Rune)'꤉';
-
-		/// <summary>
-		/// File icon.  Defaults to ☰ (Trigram For Heaven)
-		/// </summary>
-		public Rune File { get; set; } = (Rune)'☰';
-
-		/// <summary>
-		/// Horizontal Ellipsis - … U+2026
-		/// </summary>
-		public Rune HorizontalEllipsis { get; set; } = (Rune)'…';
-
-		/// <summary>
-		/// Vertical Four Dots - ⁞ U+205e
-		/// </summary>
-		public Rune VerticalFourDots { get; set; } = (Rune)'⁞';
-
-		#region ----------------- Lines -----------------
-		/// <summary>
-		/// Box Drawings Horizontal Line - Light (U+2500) - ─
-		/// </summary>
-		public Rune HLine { get; set; } = (Rune)'─';
-
-		/// <summary>
-		/// Box Drawings Vertical Line - Light (U+2502) - │
-		/// </summary>
-		public Rune VLine { get; set; } = (Rune)'│';
-
-		/// <summary>
-		/// Box Drawings Double Horizontal (U+2550) - ═
-		/// </summary>
-		public Rune HLineDbl { get; set; } = (Rune)'═';
-
-		/// <summary>
-		/// Box Drawings Double Vertical (U+2551) - ║
-		/// </summary>
-		public Rune VLineDbl { get; set; } = (Rune)'║';
-
-		/// <summary>
-		/// Box Drawings Heavy Double Dash Horizontal (U+254D) - ╍
-		/// </summary>
-		public Rune HLineHvDa2 { get; set; } = (Rune)'╍';
-
-		/// <summary>
-		/// Box Drawings Heavy Triple Dash Vertical (U+2507) - ┇
-		/// </summary>
-		public Rune VLineHvDa3 { get; set; } = (Rune)'┇';
-
-		/// <summary>
-		/// Box Drawings Heavy Triple Dash Horizontal (U+2505) - ┅
-		/// </summary>
-		public Rune HLineHvDa3 { get; set; } = (Rune)'┅';
-
-		/// <summary>
-		/// Box Drawings Heavy Quadruple Dash Horizontal (U+2509) - ┉
-		/// </summary>
-		public Rune HLineHvDa4 { get; set; } = (Rune)'┉';
-
-		/// <summary>
-		/// Box Drawings Heavy Double Dash Vertical (U+254F) - ╏
-		/// </summary>
-		public Rune VLineHvDa2 { get; set; } = (Rune)'╏';
-
-		/// <summary>
-		/// Box Drawings Heavy Quadruple Dash Vertical (U+250B) - ┋
-		/// </summary>
-		public Rune VLineHvDa4 { get; set; } = (Rune)'┋';
-
-		/// <summary>
-		/// Box Drawings Light Double Dash Horizontal (U+254C) - ╌
-		/// </summary>
-		public Rune HLineDa2 { get; set; } = (Rune)'╌';
-
-		/// <summary>
-		/// Box Drawings Light Triple Dash Vertical (U+2506) - ┆
-		/// </summary>
-		public Rune VLineDa3 { get; set; } = (Rune)'┆';
-
-		/// <summary>
-		/// Box Drawings Light Triple Dash Horizontal (U+2504) - ┄
-		/// </summary>
-		public Rune HLineDa3 { get; set; } = (Rune)'┄';
-
-		/// <summary>
-		/// Box Drawings Light Quadruple Dash Horizontal (U+2508) - ┈
-		/// </summary>
-		public Rune HLineDa4 { get; set; } = (Rune)'┈';
-
-		/// <summary>
-		/// Box Drawings Light Double Dash Vertical (U+254E) - ╎
-		/// </summary>
-		public Rune VLineDa2 { get; set; } = (Rune)'╎';
-
-		/// <summary>
-		/// Box Drawings Light Quadruple Dash Vertical (U+250A) - ┊
-		/// </summary>
-		public Rune VLineDa4 { get; set; } = (Rune)'┊';
-
-		/// <summary>
-		/// Box Drawings Heavy Horizontal (U+2501) - ━
-		/// </summary>
-		public Rune HLineHv { get; set; } = (Rune)'━';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical (U+2503) - ┃
-		/// </summary>
-		public Rune VLineHv { get; set; } = (Rune)'┃';
-
-		/// <summary>
-		/// Box Drawings Light Left (U+2574) - ╴
-		/// </summary>
-		public Rune HalfLeftLine { get; set; } = (Rune)'╴';
-
-		/// <summary>
-		/// Box Drawings Light Vertical (U+2575) - ╵
-		/// </summary>
-		public Rune HalfTopLine { get; set; } = (Rune)'╵';
-
-		/// <summary>
-		/// Box Drawings Light Horizontal (U+2576) - ╶
-		/// </summary>
-		public Rune HalfRightLine { get; set; } = (Rune)'╶';
-
-		/// <summary>
-		/// Box Drawings Light Down (U+2577) - ╷
-		/// </summary>
-		public Rune HalfBottomLine { get; set; } = (Rune)'╷';
-
-		/// <summary>
-		/// Box Drawings Heavy Left (U+2578) - ╸
-		/// </summary>
-		public Rune HalfLeftLineHv { get; set; } = (Rune)'╸';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical (U+2579) - ╹
-		/// </summary>
-		public Rune HalfTopLineHv { get; set; } = (Rune)'╹';
-
-		/// <summary>
-		/// Box Drawings Heavy Horizontal (U+257A) - ╺
-		/// </summary>
-		public Rune HalfRightLineHv { get; set; } = (Rune)'╺';
-
-		/// <summary>
-		/// Box Drawings Light Vertical and Horizontal (U+257B) - ╻
-		/// </summary>
-		public Rune HalfBottomLineLt { get; set; } = (Rune)'╻';
-
-		/// <summary>
-		/// Box Drawings Light Horizontal and Heavy Horizontal (U+257C) - ╼
-		/// </summary>
-		public Rune RightSideLineLtHv { get; set; } = (Rune)'╼';
-
-		/// <summary>
-		/// Box Drawings Light Vertical and Heavy Horizontal (U+257D) - ╽
-		/// </summary>
-		public Rune BottomSideLineLtHv { get; set; } = (Rune)'╽';
-
-		/// <summary>
-		/// Box Drawings Heavy Left and Light Horizontal (U+257E) - ╾
-		/// </summary>
-		public Rune LeftSideLineHvLt { get; set; } = (Rune)'╾';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical and Light Horizontal (U+257F) - ╿
-		/// </summary>
-		public Rune TopSideLineHvLt { get; set; } = (Rune)'╿';
-		#endregion
-
-		#region ----------------- Upper Left Corners -----------------
-		/// <summary>
-		/// Box Drawings Upper Left Corner - Light Vertical and Light Horizontal (U+250C) - ┌
-		/// </summary>
-		public Rune ULCorner { get; set; } = (Rune)'┌';
-
-		/// <summary>
-		/// Box Drawings Upper Left Corner -  Double (U+2554) - ╔
-		/// </summary>
-		public Rune ULCornerDbl { get; set; } = (Rune)'╔';
-
-		/// <summary>
-		/// Box Drawings Upper Left Corner - Light Arc Down and Horizontal (U+256D) - ╭
-		/// </summary>
-		public Rune ULCornerR { get; set; } = (Rune)'╭';
-
-		/// <summary>
-		/// Box Drawings Heavy Down and Horizontal (U+250F) - ┏
-		/// </summary>
-		public Rune ULCornerHv { get; set; } = (Rune)'┏';
-
-		/// <summary>
-		/// Box Drawings Down Heavy and Horizontal Light (U+251E) - ┎
-		/// </summary>
-		public Rune ULCornerHvLt { get; set; } = (Rune)'┎';
-
-		/// <summary>
-		/// Box Drawings Down Light and Horizontal Heavy (U+250D) - ┎
-		/// </summary>
-		public Rune ULCornerLtHv { get; set; } = (Rune)'┍';
-
-		/// <summary>
-		/// Box Drawings Double Down and Single Horizontal (U+2553) - ╓
-		/// </summary>
-		public Rune ULCornerDblSingle { get; set; } = (Rune)'╓';
-
-		/// <summary>
-		/// Box Drawings Single Down and Double Horizontal (U+2552) - ╒
-		/// </summary>
-		public Rune ULCornerSingleDbl { get; set; } = (Rune)'╒';
-		#endregion
-
-		#region ----------------- Lower Left Corners -----------------
-		/// <summary>
-		/// Box Drawings Lower Left Corner - Light Vertical and Light Horizontal (U+2514) - └
-		/// </summary>
-		public Rune LLCorner { get; set; } = (Rune)'└';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical and Horizontal (U+2517) - ┗
-		/// </summary>
-		public Rune LLCornerHv { get; set; } = (Rune)'┗';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical and Horizontal Light (U+2516) - ┖
-		/// </summary>
-		public Rune LLCornerHvLt { get; set; } = (Rune)'┖';
-
-		/// <summary>
-		/// Box Drawings Vertical Light and Horizontal Heavy (U+2511) - ┕
-		/// </summary>
-		public Rune LLCornerLtHv { get; set; } = (Rune)'┕';
-
-		/// <summary>
-		/// Box Drawings Double Vertical and Double Left (U+255A) - ╚
-		/// </summary>
-		public Rune LLCornerDbl { get; set; } = (Rune)'╚';
-
-		/// <summary>
-		/// Box Drawings Single Vertical and Double Left (U+2558) - ╘
-		/// </summary>
-		public Rune LLCornerSingleDbl { get; set; } = (Rune)'╘';
-
-		/// <summary>
-		/// Box Drawings Double Down and Single Left (U+2559) - ╙
-		/// </summary>
-		public Rune LLCornerDblSingle { get; set; } = (Rune)'╙';
-
-		/// <summary>
-		/// Box Drawings Upper Left Corner - Light Arc Down and Left (U+2570) - ╰
-		/// </summary>
-		public Rune LLCornerR { get; set; } = (Rune)'╰';
-
-		#endregion
-
-		#region ----------------- Upper Right Corners -----------------
-		/// <summary>
-		/// Box Drawings Upper Horizontal Corner - Light Vertical and Light Horizontal (U+2510) - ┐
-		/// </summary>
-		public Rune URCorner { get; set; } = (Rune)'┐';
-
-		/// <summary>
-		/// Box Drawings Upper Horizontal Corner - Double Vertical and Double Horizontal (U+2557) - ╗
-		/// </summary>
-		public Rune URCornerDbl { get; set; } = (Rune)'╗';
-
-		/// <summary>
-		/// Box Drawings Upper Horizontal Corner - Light Arc Vertical and Horizontal (U+256E) - ╮
-		/// </summary>
-		public Rune URCornerR { get; set; } = (Rune)'╮';
-
-		/// <summary>
-		/// Box Drawings Heavy Down and Left (U+2513) - ┓
-		/// </summary>
-		public Rune URCornerHv { get; set; } = (Rune)'┓';
-
-		/// <summary>
-		/// Box Drawings Heavy Vertical and Left Down Light (U+2511) - ┑
-		/// </summary>
-		public Rune URCornerHvLt { get; set; } = (Rune)'┑';
-
-		/// <summary>
-		/// Box Drawings Down Light and Horizontal Heavy (U+2514) - ┒
-		/// </summary>
-		public Rune URCornerLtHv { get; set; } = (Rune)'┒';
-
-		/// <summary>
-		/// Box Drawings Double Vertical and Single Left (U+2556) - ╖
-		/// </summary>
-		public Rune URCornerDblSingle { get; set; } = (Rune)'╖';
-
-		/// <summary>
-		/// Box Drawings Single Vertical and Double Left (U+2555) - ╕
-		/// </summary>
-		public Rune URCornerSingleDbl { get; set; } = (Rune)'╕';
-		#endregion
-
-		#region ----------------- Lower Right Corners -----------------
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Light (U+2518) - ┘
-		/// </summary>
-		public Rune LRCorner { get; set; } = (Rune)'┘';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Double (U+255D) - ╝
-		/// </summary>
-		public Rune LRCornerDbl { get; set; } = (Rune)'╝';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Rounded (U+256F) - ╯
-		/// </summary>
-		public Rune LRCornerR { get; set; } = (Rune)'╯';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Heavy (U+251B) - ┛
-		/// </summary>
-		public Rune LRCornerHv { get; set; } = (Rune)'┛';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Double Vertical and Single Horizontal (U+255C) - ╜
-		/// </summary>
-		public Rune LRCornerDblSingle { get; set; } = (Rune)'╜';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Single Vertical and Double Horizontal (U+255B) - ╛
-		/// </summary>
-		public Rune LRCornerSingleDbl { get; set; } = (Rune)'╛';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Light Vertical and Heavy Horizontal (U+2519) - ┙
-		/// </summary>
-		public Rune LRCornerLtHv { get; set; } = (Rune)'┙';
-
-		/// <summary>
-		/// Box Drawings Lower Right Corner - Heavy Vertical and Light Horizontal (U+251A) - ┚
-		/// </summary>
-		public Rune LRCornerHvLt { get; set; } = (Rune)'┚';
-		#endregion
-
-		#region ----------------- Tees -----------------
-		/// <summary>
-		/// Box Drawings Left Tee - Single Vertical and Single Horizontal (U+251C) - ├
-		/// </summary>
-		public Rune LeftTee { get; set; } = (Rune)'├';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Single Vertical and Double Horizontal (U+255E) - ╞
-		/// </summary>
-		public Rune LeftTeeDblH { get; set; } = (Rune)'╞';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Double Vertical and Single Horizontal (U+255F) - ╟
-		/// </summary>
-		public Rune LeftTeeDblV { get; set; } = (Rune)'╟';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Double Vertical and Double Horizontal (U+2560) - ╠
-		/// </summary>
-		public Rune LeftTeeDbl { get; set; } = (Rune)'╠';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Heavy Horizontal and Light Vertical (U+2523) - ┝
-		/// </summary>
-		public Rune LeftTeeHvH { get; set; } = (Rune)'┝';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Light Horizontal and Heavy Vertical (U+252B) - ┠
-		/// </summary>
-		public Rune LeftTeeHvV { get; set; } = (Rune)'┠';
-
-		/// <summary>
-		/// Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣
-		/// </summary>
-		public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣';
-
-		/// <summary>
-		/// Box Drawings Righ Tee - Single Vertical and Single Horizontal (U+2524) - ┤
-		/// </summary>
-		public Rune RightTee { get; set; } = (Rune)'┤';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - ╡
-		/// </summary>
-		public Rune RightTeeDblH { get; set; } = (Rune)'╡';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Double Vertical and Single Horizontal (U+2562) - ╢
-		/// </summary>
-		public Rune RightTeeDblV { get; set; } = (Rune)'╢';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Double Vertical and Double Horizontal (U+2563) - ╣
-		/// </summary>
-		public Rune RightTeeDbl { get; set; } = (Rune)'╣';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Heavy Horizontal and Light Vertical (U+2528) - ┥
-		/// </summary>
-		public Rune RightTeeHvH { get; set; } = (Rune)'┥';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Light Horizontal and Heavy Vertical (U+2530) - ┨
-		/// </summary>
-		public Rune RightTeeHvV { get; set; } = (Rune)'┨';
-
-		/// <summary>
-		/// Box Drawings Right Tee - Heavy Vertical and Heavy Horizontal (U+252C) - ┫
-		/// </summary>
-		public Rune RightTeeHvDblH { get; set; } = (Rune)'┫';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Single Vertical and Single Horizontal (U+252C) - ┬
-		/// </summary>
-		public Rune TopTee { get; set; } = (Rune)'┬';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Single Vertical and Double Horizontal (U+2564) - ╤
-		/// </summary>
-		public Rune TopTeeDblH { get; set; } = (Rune)'╤';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Double Vertical and Single Horizontal  (U+2565) - ╥
-		/// </summary>
-		public Rune TopTeeDblV { get; set; } = (Rune)'╥';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Double Vertical and Double Horizontal (U+2566) - ╦
-		/// </summary>
-		public Rune TopTeeDbl { get; set; } = (Rune)'╦';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Heavy Horizontal and Light Vertical (U+252F) - ┯
-		/// </summary>
-		public Rune TopTeeHvH { get; set; } = (Rune)'┯';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Light Horizontal and Heavy Vertical (U+2537) - ┰
-		/// </summary>
-		public Rune TopTeeHvV { get; set; } = (Rune)'┰';
-
-		/// <summary>
-		/// Box Drawings Top Tee - Heavy Vertical and Heavy Horizontal (U+2533) - ┳
-		/// </summary>
-		public Rune TopTeeHvDblH { get; set; } = (Rune)'┳';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Single Vertical and Single Horizontal (U+2534) - ┴
-		/// </summary>
-		public Rune BottomTee { get; set; } = (Rune)'┴';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Single Vertical and Double Horizontal (U+2567) - ╧
-		/// </summary>
-		public Rune BottomTeeDblH { get; set; } = (Rune)'╧';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Double Vertical and Single Horizontal (U+2568) - ╨
-		/// </summary>
-		public Rune BottomTeeDblV { get; set; } = (Rune)'╨';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Double Vertical and Double Horizontal (U+2569) - ╩
-		/// </summary>
-		public Rune BottomTeeDbl { get; set; } = (Rune)'╩';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Heavy Horizontal and Light Vertical (U+2535) - ┷
-		/// </summary>
-		public Rune BottomTeeHvH { get; set; } = (Rune)'┷';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Light Horizontal and Heavy Vertical (U+253D) - ┸
-		/// </summary>
-		public Rune BottomTeeHvV { get; set; } = (Rune)'┸';
-
-		/// <summary>
-		/// Box Drawings Bottom Tee - Heavy Vertical and Heavy Horizontal (U+2539) - ┻
-		/// </summary>
-		public Rune BottomTeeHvDblH { get; set; } = (Rune)'┻';
-
-		#endregion
-
-		#region ----------------- Crosses -----------------
-		/// <summary>
-		/// Box Drawings Cross - Single Vertical and Single Horizontal (U+253C) - ┼
-		/// </summary>
-		public Rune Cross { get; set; } = (Rune)'┼';
-
-		/// <summary>
-		/// Box Drawings Cross - Single Vertical and Double Horizontal (U+256A) - ╪
-		/// </summary>
-		public Rune CrossDblH { get; set; } = (Rune)'╪';
-
-		/// <summary>
-		/// Box Drawings Cross - Double Vertical and Single Horizontal (U+256B) - ╫
-		/// </summary>
-		public Rune CrossDblV { get; set; } = (Rune)'╫';
-
-		/// <summary>
-		/// Box Drawings Cross - Double Vertical and Double Horizontal (U+256C) - ╬
-		/// </summary>
-		public Rune CrossDbl { get; set; } = (Rune)'╬';
-
-		/// <summary>
-		/// Box Drawings Cross - Heavy Horizontal and Light Vertical (U+253F) - ┿
-		/// </summary>
-		public Rune CrossHvH { get; set; } = (Rune)'┿';
-
-		/// <summary>
-		/// Box Drawings Cross - Light Horizontal and Heavy Vertical (U+2541) - ╂
-		/// </summary>
-		public Rune CrossHvV { get; set; } = (Rune)'╂';
-
-		/// <summary>
-		/// Box Drawings Cross - Heavy Vertical and Heavy Horizontal (U+254B) - ╋
-		/// </summary>
-		public Rune CrossHv { get; set; } = (Rune)'╋';
-		#endregion
-	}
+namespace Terminal.Gui;
+
+/// <summary>Defines the standard set of glyphs used to draw checkboxes, lines, borders, etc...</summary>
+/// <remarks>
+///     <para>
+///         Access with <see cref="CM.Glyphs"/> (which is a global using alias for
+///         <see cref="ConfigurationManager.Glyphs"/>).
+///     </para>
+///     <para>
+///         The default glyphs can be changed via the <see cref="ConfigurationManager"/>. Within a <c>config.json</c>
+///         file The Json property name is the property name prefixed with "Glyphs.".
+///     </para>
+///     <para>
+///         The JSon property can be either a decimal number or a string. The string may be one of: - A unicode char
+///         (e.g. "☑") - A hex value in U+ format (e.g. "U+2611") - A hex value in UTF-16 format (e.g. "\\u2611")
+///     </para>
+/// </remarks>
+public class GlyphDefinitions
+{
+    /// <summary>File icon.  Defaults to ☰ (Trigram For Heaven)</summary>
+    public Rune File { get; set; } = (Rune)'☰';
+
+    /// <summary>Folder icon.  Defaults to ꤉ (Kayah Li Digit Nine)</summary>
+    public Rune Folder { 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 ----------------- Single Glyphs -----------------
+
+    /// <summary>Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).</summary>
+    public Rune Checked { get; set; } = (Rune)'☑';
+
+    /// <summary>Not Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).</summary>
+    public Rune UnChecked { get; set; } = (Rune)'☐';
+
+    /// <summary>Null Checked indicator (e.g. for <see cref="ListView"/> and <see cref="CheckBox"/>).</summary>
+    public Rune NullChecked { get; set; } = (Rune)'☒';
+
+    /// <summary>Selected indicator  (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).</summary>
+    public Rune Selected { get; set; } = (Rune)'◉';
+
+    /// <summary>Not Selected indicator (e.g. for <see cref="ListView"/> and <see cref="RadioGroup"/>).</summary>
+    public Rune UnSelected { get; set; } = (Rune)'○';
+
+    /// <summary>Horizontal arrow.</summary>
+    public Rune RightArrow { get; set; } = (Rune)'►';
+
+    /// <summary>Left arrow.</summary>
+    public Rune LeftArrow { get; set; } = (Rune)'◄';
+
+    /// <summary>Down arrow.</summary>
+    public Rune DownArrow { get; set; } = (Rune)'▼';
+
+    /// <summary>Vertical arrow.</summary>
+    public Rune UpArrow { get; set; } = (Rune)'▲';
+
+    /// <summary>Left default indicator (e.g. for <see cref="Button"/>.</summary>
+    public Rune LeftDefaultIndicator { get; set; } = (Rune)'►';
+
+    /// <summary>Horizontal default indicator (e.g. for <see cref="Button"/>.</summary>
+    public Rune RightDefaultIndicator { get; set; } = (Rune)'◄';
+
+    /// <summary>Left Bracket (e.g. for <see cref="Button"/>. Default is (U+005B) - [.</summary>
+    public Rune LeftBracket { get; set; } = (Rune)'⟦';
+
+    /// <summary>Horizontal Bracket (e.g. for <see cref="Button"/>. Default is (U+005D) - ].</summary>
+    public Rune RightBracket { get; set; } = (Rune)'⟧';
+
+    /// <summary>Half block meter segment (e.g. for <see cref="ProgressBar"/>).</summary>
+    public Rune BlocksMeterSegment { get; set; } = (Rune)'▌';
+
+    /// <summary>Continuous block meter segment (e.g. for <see cref="ProgressBar"/>).</summary>
+    public Rune ContinuousMeterSegment { get; set; } = (Rune)'█';
+
+    /// <summary>Stipple pattern (e.g. for <see cref="ScrollBarView"/>). Default is Light Shade (U+2591) - ░.</summary>
+    public Rune Stipple { get; set; } = (Rune)'░';
+
+    /// <summary>Diamond (e.g. for <see cref="ScrollBarView"/>. Default is Lozenge (U+25CA) - ◊.</summary>
+    public Rune Diamond { get; set; } = (Rune)'◊';
+
+    /// <summary>Close. Default is Heavy Ballot X (U+2718) - ✘.</summary>
+    public Rune Close { get; set; } = (Rune)'✘';
+
+    /// <summary>Minimize. Default is Lower Horizontal Shadowed White Circle (U+274F) - ❏.</summary>
+    public Rune Minimize { get; set; } = (Rune)'❏';
+
+    /// <summary>Maximize. Default is Upper Horizontal Shadowed White Circle (U+273D) - ✽.</summary>
+    public Rune Maximize { get; set; } = (Rune)'✽';
+
+    /// <summary>Dot. Default is (U+2219) - ∙.</summary>
+    public Rune Dot { get; set; } = (Rune)'∙';
+
+    /// <summary>Black Circle . Default is (U+025cf) - ●.</summary>
+    public Rune BlackCircle { get; set; } = (Rune)'●'; // Black Circle - ● U+025cf
+
+    /// <summary>Expand (e.g. for <see cref="TreeView"/>.</summary>
+    public Rune Expand { get; set; } = (Rune)'+';
+
+    /// <summary>Expand (e.g. for <see cref="TreeView"/>.</summary>
+    public Rune Collapse { get; set; } = (Rune)'-';
+
+    /// <summary>Identical To (U+226)</summary>
+    public Rune IdenticalTo { get; set; } = (Rune)'≡';
+
+    /// <summary>Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.</summary>
+    public Rune Apple { get; set; } = "🍎".ToRunes () [0]; // nonBMP
+
+    /// <summary>Apple (BMP). Because snek. See Issue #2610.</summary>
+    public Rune AppleBMP { get; set; } = (Rune)'❦';
+
+    ///// <summary>
+    ///// A nonprintable (low surrogate) that should fail to ctor.
+    ///// </summary>
+    //public Rune InvalidGlyph { get; set; } = (Rune)'\ud83d';
+
+    #endregion
+
+    #region ----------------- Lines -----------------
+
+    /// <summary>Box Drawings Horizontal Line - Light (U+2500) - ─</summary>
+    public Rune HLine { get; set; } = (Rune)'─';
+
+    /// <summary>Box Drawings Vertical Line - Light (U+2502) - │</summary>
+    public Rune VLine { get; set; } = (Rune)'│';
+
+    /// <summary>Box Drawings Double Horizontal (U+2550) - ═</summary>
+    public Rune HLineDbl { get; set; } = (Rune)'═';
+
+    /// <summary>Box Drawings Double Vertical (U+2551) - ║</summary>
+    public Rune VLineDbl { get; set; } = (Rune)'║';
+
+    /// <summary>Box Drawings Heavy Double Dash Horizontal (U+254D) - ╍</summary>
+    public Rune HLineHvDa2 { get; set; } = (Rune)'╍';
+
+    /// <summary>Box Drawings Heavy Triple Dash Vertical (U+2507) - ┇</summary>
+    public Rune VLineHvDa3 { get; set; } = (Rune)'┇';
+
+    /// <summary>Box Drawings Heavy Triple Dash Horizontal (U+2505) - ┅</summary>
+    public Rune HLineHvDa3 { get; set; } = (Rune)'┅';
+
+    /// <summary>Box Drawings Heavy Quadruple Dash Horizontal (U+2509) - ┉</summary>
+    public Rune HLineHvDa4 { get; set; } = (Rune)'┉';
+
+    /// <summary>Box Drawings Heavy Double Dash Vertical (U+254F) - ╏</summary>
+    public Rune VLineHvDa2 { get; set; } = (Rune)'╏';
+
+    /// <summary>Box Drawings Heavy Quadruple Dash Vertical (U+250B) - ┋</summary>
+    public Rune VLineHvDa4 { get; set; } = (Rune)'┋';
+
+    /// <summary>Box Drawings Light Double Dash Horizontal (U+254C) - ╌</summary>
+    public Rune HLineDa2 { get; set; } = (Rune)'╌';
+
+    /// <summary>Box Drawings Light Triple Dash Vertical (U+2506) - ┆</summary>
+    public Rune VLineDa3 { get; set; } = (Rune)'┆';
+
+    /// <summary>Box Drawings Light Triple Dash Horizontal (U+2504) - ┄</summary>
+    public Rune HLineDa3 { get; set; } = (Rune)'┄';
+
+    /// <summary>Box Drawings Light Quadruple Dash Horizontal (U+2508) - ┈</summary>
+    public Rune HLineDa4 { get; set; } = (Rune)'┈';
+
+    /// <summary>Box Drawings Light Double Dash Vertical (U+254E) - ╎</summary>
+    public Rune VLineDa2 { get; set; } = (Rune)'╎';
+
+    /// <summary>Box Drawings Light Quadruple Dash Vertical (U+250A) - ┊</summary>
+    public Rune VLineDa4 { get; set; } = (Rune)'┊';
+
+    /// <summary>Box Drawings Heavy Horizontal (U+2501) - ━</summary>
+    public Rune HLineHv { get; set; } = (Rune)'━';
+
+    /// <summary>Box Drawings Heavy Vertical (U+2503) - ┃</summary>
+    public Rune VLineHv { get; set; } = (Rune)'┃';
+
+    /// <summary>Box Drawings Light Left (U+2574) - ╴</summary>
+    public Rune HalfLeftLine { get; set; } = (Rune)'╴';
+
+    /// <summary>Box Drawings Light Vertical (U+2575) - ╵</summary>
+    public Rune HalfTopLine { get; set; } = (Rune)'╵';
+
+    /// <summary>Box Drawings Light Horizontal (U+2576) - ╶</summary>
+    public Rune HalfRightLine { get; set; } = (Rune)'╶';
+
+    /// <summary>Box Drawings Light Down (U+2577) - ╷</summary>
+    public Rune HalfBottomLine { get; set; } = (Rune)'╷';
+
+    /// <summary>Box Drawings Heavy Left (U+2578) - ╸</summary>
+    public Rune HalfLeftLineHv { get; set; } = (Rune)'╸';
+
+    /// <summary>Box Drawings Heavy Vertical (U+2579) - ╹</summary>
+    public Rune HalfTopLineHv { get; set; } = (Rune)'╹';
+
+    /// <summary>Box Drawings Heavy Horizontal (U+257A) - ╺</summary>
+    public Rune HalfRightLineHv { get; set; } = (Rune)'╺';
+
+    /// <summary>Box Drawings Light Vertical and Horizontal (U+257B) - ╻</summary>
+    public Rune HalfBottomLineLt { get; set; } = (Rune)'╻';
+
+    /// <summary>Box Drawings Light Horizontal and Heavy Horizontal (U+257C) - ╼</summary>
+    public Rune RightSideLineLtHv { get; set; } = (Rune)'╼';
+
+    /// <summary>Box Drawings Light Vertical and Heavy Horizontal (U+257D) - ╽</summary>
+    public Rune BottomSideLineLtHv { get; set; } = (Rune)'╽';
+
+    /// <summary>Box Drawings Heavy Left and Light Horizontal (U+257E) - ╾</summary>
+    public Rune LeftSideLineHvLt { get; set; } = (Rune)'╾';
+
+    /// <summary>Box Drawings Heavy Vertical and Light Horizontal (U+257F) - ╿</summary>
+    public Rune TopSideLineHvLt { get; set; } = (Rune)'╿';
+
+    #endregion
+
+    #region ----------------- Upper Left Corners -----------------
+
+    /// <summary>Box Drawings Upper Left Corner - Light Vertical and Light Horizontal (U+250C) - ┌</summary>
+    public Rune ULCorner { get; set; } = (Rune)'┌';
+
+    /// <summary>Box Drawings Upper Left Corner -  Double (U+2554) - ╔</summary>
+    public Rune ULCornerDbl { get; set; } = (Rune)'╔';
+
+    /// <summary>Box Drawings Upper Left Corner - Light Arc Down and Horizontal (U+256D) - ╭</summary>
+    public Rune ULCornerR { get; set; } = (Rune)'╭';
+
+    /// <summary>Box Drawings Heavy Down and Horizontal (U+250F) - ┏</summary>
+    public Rune ULCornerHv { get; set; } = (Rune)'┏';
+
+    /// <summary>Box Drawings Down Heavy and Horizontal Light (U+251E) - ┎</summary>
+    public Rune ULCornerHvLt { get; set; } = (Rune)'┎';
+
+    /// <summary>Box Drawings Down Light and Horizontal Heavy (U+250D) - ┎</summary>
+    public Rune ULCornerLtHv { get; set; } = (Rune)'┍';
+
+    /// <summary>Box Drawings Double Down and Single Horizontal (U+2553) - ╓</summary>
+    public Rune ULCornerDblSingle { get; set; } = (Rune)'╓';
+
+    /// <summary>Box Drawings Single Down and Double Horizontal (U+2552) - ╒</summary>
+    public Rune ULCornerSingleDbl { get; set; } = (Rune)'╒';
+
+    #endregion
+
+    #region ----------------- Lower Left Corners -----------------
+
+    /// <summary>Box Drawings Lower Left Corner - Light Vertical and Light Horizontal (U+2514) - └</summary>
+    public Rune LLCorner { get; set; } = (Rune)'└';
+
+    /// <summary>Box Drawings Heavy Vertical and Horizontal (U+2517) - ┗</summary>
+    public Rune LLCornerHv { get; set; } = (Rune)'┗';
+
+    /// <summary>Box Drawings Heavy Vertical and Horizontal Light (U+2516) - ┖</summary>
+    public Rune LLCornerHvLt { get; set; } = (Rune)'┖';
+
+    /// <summary>Box Drawings Vertical Light and Horizontal Heavy (U+2511) - ┕</summary>
+    public Rune LLCornerLtHv { get; set; } = (Rune)'┕';
+
+    /// <summary>Box Drawings Double Vertical and Double Left (U+255A) - ╚</summary>
+    public Rune LLCornerDbl { get; set; } = (Rune)'╚';
+
+    /// <summary>Box Drawings Single Vertical and Double Left (U+2558) - ╘</summary>
+    public Rune LLCornerSingleDbl { get; set; } = (Rune)'╘';
+
+    /// <summary>Box Drawings Double Down and Single Left (U+2559) - ╙</summary>
+    public Rune LLCornerDblSingle { get; set; } = (Rune)'╙';
+
+    /// <summary>Box Drawings Upper Left Corner - Light Arc Down and Left (U+2570) - ╰</summary>
+    public Rune LLCornerR { get; set; } = (Rune)'╰';
+
+    #endregion
+
+    #region ----------------- Upper Right Corners -----------------
+
+    /// <summary>Box Drawings Upper Horizontal Corner - Light Vertical and Light Horizontal (U+2510) - ┐</summary>
+    public Rune URCorner { get; set; } = (Rune)'┐';
+
+    /// <summary>Box Drawings Upper Horizontal Corner - Double Vertical and Double Horizontal (U+2557) - ╗</summary>
+    public Rune URCornerDbl { get; set; } = (Rune)'╗';
+
+    /// <summary>Box Drawings Upper Horizontal Corner - Light Arc Vertical and Horizontal (U+256E) - ╮</summary>
+    public Rune URCornerR { get; set; } = (Rune)'╮';
+
+    /// <summary>Box Drawings Heavy Down and Left (U+2513) - ┓</summary>
+    public Rune URCornerHv { get; set; } = (Rune)'┓';
+
+    /// <summary>Box Drawings Heavy Vertical and Left Down Light (U+2511) - ┑</summary>
+    public Rune URCornerHvLt { get; set; } = (Rune)'┑';
+
+    /// <summary>Box Drawings Down Light and Horizontal Heavy (U+2514) - ┒</summary>
+    public Rune URCornerLtHv { get; set; } = (Rune)'┒';
+
+    /// <summary>Box Drawings Double Vertical and Single Left (U+2556) - ╖</summary>
+    public Rune URCornerDblSingle { get; set; } = (Rune)'╖';
+
+    /// <summary>Box Drawings Single Vertical and Double Left (U+2555) - ╕</summary>
+    public Rune URCornerSingleDbl { get; set; } = (Rune)'╕';
+
+    #endregion
+
+    #region ----------------- Lower Right Corners -----------------
+
+    /// <summary>Box Drawings Lower Right Corner - Light (U+2518) - ┘</summary>
+    public Rune LRCorner { get; set; } = (Rune)'┘';
+
+    /// <summary>Box Drawings Lower Right Corner - Double (U+255D) - ╝</summary>
+    public Rune LRCornerDbl { get; set; } = (Rune)'╝';
+
+    /// <summary>Box Drawings Lower Right Corner - Rounded (U+256F) - ╯</summary>
+    public Rune LRCornerR { get; set; } = (Rune)'╯';
+
+    /// <summary>Box Drawings Lower Right Corner - Heavy (U+251B) - ┛</summary>
+    public Rune LRCornerHv { get; set; } = (Rune)'┛';
+
+    /// <summary>Box Drawings Lower Right Corner - Double Vertical and Single Horizontal (U+255C) - ╜</summary>
+    public Rune LRCornerDblSingle { get; set; } = (Rune)'╜';
+
+    /// <summary>Box Drawings Lower Right Corner - Single Vertical and Double Horizontal (U+255B) - ╛</summary>
+    public Rune LRCornerSingleDbl { get; set; } = (Rune)'╛';
+
+    /// <summary>Box Drawings Lower Right Corner - Light Vertical and Heavy Horizontal (U+2519) - ┙</summary>
+    public Rune LRCornerLtHv { get; set; } = (Rune)'┙';
+
+    /// <summary>Box Drawings Lower Right Corner - Heavy Vertical and Light Horizontal (U+251A) - ┚</summary>
+    public Rune LRCornerHvLt { get; set; } = (Rune)'┚';
+
+    #endregion
+
+    #region ----------------- Tees -----------------
+
+    /// <summary>Box Drawings Left Tee - Single Vertical and Single Horizontal (U+251C) - ├</summary>
+    public Rune LeftTee { get; set; } = (Rune)'├';
+
+    /// <summary>Box Drawings Left Tee - Single Vertical and Double Horizontal (U+255E) - ╞</summary>
+    public Rune LeftTeeDblH { get; set; } = (Rune)'╞';
+
+    /// <summary>Box Drawings Left Tee - Double Vertical and Single Horizontal (U+255F) - ╟</summary>
+    public Rune LeftTeeDblV { get; set; } = (Rune)'╟';
+
+    /// <summary>Box Drawings Left Tee - Double Vertical and Double Horizontal (U+2560) - ╠</summary>
+    public Rune LeftTeeDbl { get; set; } = (Rune)'╠';
+
+    /// <summary>Box Drawings Left Tee - Heavy Horizontal and Light Vertical (U+2523) - ┝</summary>
+    public Rune LeftTeeHvH { get; set; } = (Rune)'┝';
+
+    /// <summary>Box Drawings Left Tee - Light Horizontal and Heavy Vertical (U+252B) - ┠</summary>
+    public Rune LeftTeeHvV { get; set; } = (Rune)'┠';
+
+    /// <summary>Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣</summary>
+    public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣';
+
+    /// <summary>Box Drawings Righ Tee - Single Vertical and Single Horizontal (U+2524) - ┤</summary>
+    public Rune RightTee { get; set; } = (Rune)'┤';
+
+    /// <summary>Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - ╡</summary>
+    public Rune RightTeeDblH { get; set; } = (Rune)'╡';
+
+    /// <summary>Box Drawings Right Tee - Double Vertical and Single Horizontal (U+2562) - ╢</summary>
+    public Rune RightTeeDblV { get; set; } = (Rune)'╢';
+
+    /// <summary>Box Drawings Right Tee - Double Vertical and Double Horizontal (U+2563) - ╣</summary>
+    public Rune RightTeeDbl { get; set; } = (Rune)'╣';
+
+    /// <summary>Box Drawings Right Tee - Heavy Horizontal and Light Vertical (U+2528) - ┥</summary>
+    public Rune RightTeeHvH { get; set; } = (Rune)'┥';
+
+    /// <summary>Box Drawings Right Tee - Light Horizontal and Heavy Vertical (U+2530) - ┨</summary>
+    public Rune RightTeeHvV { get; set; } = (Rune)'┨';
+
+    /// <summary>Box Drawings Right Tee - Heavy Vertical and Heavy Horizontal (U+252C) - ┫</summary>
+    public Rune RightTeeHvDblH { get; set; } = (Rune)'┫';
+
+    /// <summary>Box Drawings Top Tee - Single Vertical and Single Horizontal (U+252C) - ┬</summary>
+    public Rune TopTee { get; set; } = (Rune)'┬';
+
+    /// <summary>Box Drawings Top Tee - Single Vertical and Double Horizontal (U+2564) - ╤</summary>
+    public Rune TopTeeDblH { get; set; } = (Rune)'╤';
+
+    /// <summary>Box Drawings Top Tee - Double Vertical and Single Horizontal  (U+2565) - ╥</summary>
+    public Rune TopTeeDblV { get; set; } = (Rune)'╥';
+
+    /// <summary>Box Drawings Top Tee - Double Vertical and Double Horizontal (U+2566) - ╦</summary>
+    public Rune TopTeeDbl { get; set; } = (Rune)'╦';
+
+    /// <summary>Box Drawings Top Tee - Heavy Horizontal and Light Vertical (U+252F) - ┯</summary>
+    public Rune TopTeeHvH { get; set; } = (Rune)'┯';
+
+    /// <summary>Box Drawings Top Tee - Light Horizontal and Heavy Vertical (U+2537) - ┰</summary>
+    public Rune TopTeeHvV { get; set; } = (Rune)'┰';
+
+    /// <summary>Box Drawings Top Tee - Heavy Vertical and Heavy Horizontal (U+2533) - ┳</summary>
+    public Rune TopTeeHvDblH { get; set; } = (Rune)'┳';
+
+    /// <summary>Box Drawings Bottom Tee - Single Vertical and Single Horizontal (U+2534) - ┴</summary>
+    public Rune BottomTee { get; set; } = (Rune)'┴';
+
+    /// <summary>Box Drawings Bottom Tee - Single Vertical and Double Horizontal (U+2567) - ╧</summary>
+    public Rune BottomTeeDblH { get; set; } = (Rune)'╧';
+
+    /// <summary>Box Drawings Bottom Tee - Double Vertical and Single Horizontal (U+2568) - ╨</summary>
+    public Rune BottomTeeDblV { get; set; } = (Rune)'╨';
+
+    /// <summary>Box Drawings Bottom Tee - Double Vertical and Double Horizontal (U+2569) - ╩</summary>
+    public Rune BottomTeeDbl { get; set; } = (Rune)'╩';
+
+    /// <summary>Box Drawings Bottom Tee - Heavy Horizontal and Light Vertical (U+2535) - ┷</summary>
+    public Rune BottomTeeHvH { get; set; } = (Rune)'┷';
+
+    /// <summary>Box Drawings Bottom Tee - Light Horizontal and Heavy Vertical (U+253D) - ┸</summary>
+    public Rune BottomTeeHvV { get; set; } = (Rune)'┸';
+
+    /// <summary>Box Drawings Bottom Tee - Heavy Vertical and Heavy Horizontal (U+2539) - ┻</summary>
+    public Rune BottomTeeHvDblH { get; set; } = (Rune)'┻';
+
+    #endregion
+
+    #region ----------------- Crosses -----------------
+
+    /// <summary>Box Drawings Cross - Single Vertical and Single Horizontal (U+253C) - ┼</summary>
+    public Rune Cross { get; set; } = (Rune)'┼';
+
+    /// <summary>Box Drawings Cross - Single Vertical and Double Horizontal (U+256A) - ╪</summary>
+    public Rune CrossDblH { get; set; } = (Rune)'╪';
+
+    /// <summary>Box Drawings Cross - Double Vertical and Single Horizontal (U+256B) - ╫</summary>
+    public Rune CrossDblV { get; set; } = (Rune)'╫';
+
+    /// <summary>Box Drawings Cross - Double Vertical and Double Horizontal (U+256C) - ╬</summary>
+    public Rune CrossDbl { get; set; } = (Rune)'╬';
+
+    /// <summary>Box Drawings Cross - Heavy Horizontal and Light Vertical (U+253F) - ┿</summary>
+    public Rune CrossHvH { get; set; } = (Rune)'┿';
+
+    /// <summary>Box Drawings Cross - Light Horizontal and Heavy Vertical (U+2541) - ╂</summary>
+    public Rune CrossHvV { get; set; } = (Rune)'╂';
+
+    /// <summary>Box Drawings Cross - Heavy Vertical and Heavy Horizontal (U+254B) - ╋</summary>
+    public Rune CrossHv { get; set; } = (Rune)'╋';
+
+    #endregion
 }

+ 22 - 24
Terminal.Gui/Drawing/ICustomColorFormatter.cs

@@ -1,29 +1,27 @@
 #nullable enable
 namespace Terminal.Gui;
 
-/// <summary>
-///   An interface to support custom formatting and parsing of <see cref="Color" /> values.
-/// </summary>
-public interface ICustomColorFormatter : IFormatProvider, ICustomFormatter {
+/// <summary>An interface to support custom formatting and parsing of <see cref="Color"/> values.</summary>
+public interface ICustomColorFormatter : IFormatProvider, ICustomFormatter
+{
+    /// <summary>
+    ///     A method that returns a <see langword="string"/> based on the <paramref name="formatString"/> specified and
+    ///     the byte parameters <paramref name="r"/>, <paramref name="g"/>, <paramref name="b"/>, and <paramref name="a"/>,
+    ///     which are provided by <see cref="Color"/>
+    /// </summary>
+    /// <param name="formatString"></param>
+    /// <param name="r"></param>
+    /// <param name="g"></param>
+    /// <param name="b"></param>
+    /// <param name="a"></param>
+    /// <returns></returns>
+    string Format (string? formatString, byte r, byte g, byte b, byte a);
 
-	/// <summary>
-	///   A method that returns a <see langword="string" /> based on the <paramref name="formatString" /> specified and the byte parameters
-	///   <paramref name="r" />, <paramref name="g" />, <paramref name="b" />, and <paramref name="a" />, which are provided by
-	///   <see cref="Color" />
-	/// </summary>
-	/// <param name="formatString"></param>
-	/// <param name="r"></param>
-	/// <param name="g"></param>
-	/// <param name="b"></param>
-	/// <param name="a"></param>
-	/// <returns></returns>
-	string Format (string? formatString, byte r, byte g, byte b, byte a);
-	/// <summary>
-	///   A method that returns a <see cref="Color" /> value based on the <paramref name="text" /> specified.
-	/// </summary>
-	/// <param name="text">
-	///   A string or other <see cref="ReadOnlySpan{T}" /> of <see langword="char" /> to parse as a <see cref="Color" />.
-	/// </param>
-	/// <returns>A <see cref="Color" /> value equivalent to <paramref name="text" />.</returns>
-	Color Parse (ReadOnlySpan<char> text);
+    /// <summary>A method that returns a <see cref="Color"/> value based on the <paramref name="text"/> specified.</summary>
+    /// <param name="text">
+    ///     A string or other <see cref="ReadOnlySpan{T}"/> of <see langword="char"/> to parse as a
+    ///     <see cref="Color"/>.
+    /// </param>
+    /// <returns>A <see cref="Color"/> value equivalent to <paramref name="text"/>.</returns>
+    Color Parse (ReadOnlySpan<char> text);
 }

+ 957 - 835
Terminal.Gui/Drawing/LineCanvas.cs

@@ -1,837 +1,959 @@
 #nullable enable
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Defines the style of lines for a <see cref="LineCanvas"/>.
-	/// </summary>
-	public enum LineStyle {
-		/// <summary>
-		/// No border is drawn.
-		/// </summary>
-		None,
-		/// <summary>
-		/// The border is drawn using thin line CM.Glyphs.
-		/// </summary>
-		Single,
-		/// <summary>
-		/// The border is drawn using thin line glyphs with dashed (double and triple) straight lines.
-		/// </summary>
-		Dashed,
-		/// <summary>
-		/// The border is drawn using thin line glyphs with short dashed (triple and quadruple) straight lines.
-		/// </summary>
-		Dotted,
-		/// <summary>
-		/// The border is drawn using thin double line CM.Glyphs.
-		/// </summary>
-		Double,
-		/// <summary>
-		/// The border is drawn using heavy line CM.Glyphs.
-		/// </summary>
-		Heavy,
-		/// <summary>
-		/// The border is drawn using heavy line glyphs with dashed (double and triple) straight lines.
-		/// </summary>
-		HeavyDashed,
-		/// <summary>
-		/// The border is drawn using heavy line glyphs with short dashed (triple and quadruple) straight lines.
-		/// </summary>
-		HeavyDotted,
-		/// <summary>
-		/// The border is drawn using thin line glyphs with rounded corners.
-		/// </summary>
-		Rounded,
-		/// <summary>
-		/// The border is drawn using thin line glyphs with rounded corners and dashed (double and triple) straight lines.
-		/// </summary>
-		RoundedDashed,
-		/// <summary>
-		/// The border is drawn using thin line glyphs with rounded corners and short dashed (triple and quadruple) straight lines.
-		/// </summary>
-		RoundedDotted,
-		// TODO: Support Ruler
-		///// <summary> 
-		///// The border is drawn as a diagnostic ruler ("|123456789...").
-		///// </summary>
-		//Ruler
-	}
-
-	/// <summary>
-	/// Facilitates box drawing and line intersection detection
-	/// and rendering.  Does not support diagonal lines.
-	/// </summary>
-	public class LineCanvas : IDisposable {
-		/// <summary>
-		/// Creates a new instance.
-		/// </summary>
-		public LineCanvas()
-		{
-			// TODO: Refactor ConfigurationManager to not use an event handler for this.
-			// Instead, have it call a method on any class appropriately attributed
-			// to update the cached values. See Issue #2871
-			ConfigurationManager.Applied += ConfigurationManager_Applied;
-		}
-
-		/// <summary>
-		/// Creates a new instance with the given <paramref name="lines"/>.
-		/// </summary>
-		/// <param name="lines">Initial lines for the canvas.</param>
-		public LineCanvas (IEnumerable<StraightLine> lines) : this()
-		{
-			_lines = lines.ToList();
-		}
-
-		private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e)
-		{
-			foreach (var irr in runeResolvers) {
-				irr.Value.SetGlyphs ();
-			}
-		}
-
-		private List<StraightLine> _lines = new List<StraightLine> ();
-
-		/// <summary>
-		/// Gets the lines in the canvas.
-		/// </summary>
-		public IReadOnlyCollection<StraightLine> Lines { get { return _lines.AsReadOnly(); } }
-
-		Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new Dictionary<IntersectionRuneType, IntersectionRuneResolver> {
-			{IntersectionRuneType.ULCorner,new ULIntersectionRuneResolver()},
-			{IntersectionRuneType.URCorner,new URIntersectionRuneResolver()},
-			{IntersectionRuneType.LLCorner,new LLIntersectionRuneResolver()},
-			{IntersectionRuneType.LRCorner,new LRIntersectionRuneResolver()},
-
-			{IntersectionRuneType.TopTee,new TopTeeIntersectionRuneResolver()},
-			{IntersectionRuneType.LeftTee,new LeftTeeIntersectionRuneResolver()},
-			{IntersectionRuneType.RightTee,new RightTeeIntersectionRuneResolver()},
-			{IntersectionRuneType.BottomTee,new BottomTeeIntersectionRuneResolver()},
-
-			{IntersectionRuneType.Cross,new CrossIntersectionRuneResolver()},
-			// TODO: Add other resolvers
-		};
-
-		/// <summary>
-		/// <para>
-		/// Adds a new <paramref name="length"/> long line to the canvas starting at <paramref name="start"/>.
-		/// </para>
-		/// <para>
-		/// Use positive <paramref name="length"/> for the line to extend Right and negative for Left
-		/// when <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>.
-		/// </para>
-		/// <para>
-		/// Use positive <paramref name="length"/> for the line to extend Down and negative for Up
-		/// when <see cref="Orientation"/> is <see cref="Orientation.Vertical"/>.
-		/// </para>
-		/// </summary>
-		/// <param name="start">Starting point.</param>
-		/// <param name="length">The length of line. 0 for an intersection (cross or T). Positive for Down/Right. Negative for Up/Left.</param>
-		/// <param name="orientation">The direction of the line.</param>
-		/// <param name="style">The style of line to use</param>
-		/// <param name="attribute"></param>
-		public void AddLine (Point start, int length, Orientation orientation, LineStyle style, Attribute? attribute = default)
-		{
-			_cachedBounds = Rect.Empty;
-			_lines.Add (new StraightLine (start, length, orientation, style, attribute));
-		}
-
-		/// <summary>
-		/// Adds a new line to the canvas
-		/// </summary>
-		/// <param name="line"></param>
-		public void AddLine (StraightLine line)
-		{
-			_cachedBounds = Rect.Empty;
-			_lines.Add (line);
-		}
-
-		/// <summary>
-		/// Removes the last line added to the canvas
-		/// </summary>
-		/// <returns></returns>
-		public StraightLine RemoveLastLine()
-		{
-			var l = _lines.LastOrDefault ();
-			if(l != null) {
-				_lines.Remove(l);
-			}
-
-			return l!;
-		}
-
-		/// <summary>
-		/// Clears all lines from the LineCanvas.
-		/// </summary>
-		public void Clear ()
-		{
-			_cachedBounds = Rect.Empty;
-			_lines.Clear ();
-		}
-
-		/// <summary>
-		/// Clears any cached states from the canvas
-		/// Call this method if you make changes to lines
-		/// that have already been added.
-		/// </summary>
-		public void ClearCache ()
-		{
-			_cachedBounds = Rect.Empty;
-		}
-		private Rect _cachedBounds;
-
-		/// <summary>
-		/// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the 
-		/// line that is furthest left/top and Size is defined by the line that extends the furthest
-		/// right/bottom.
-		/// </summary>
-		public Rect Bounds {
-			get {
-				if (_cachedBounds.IsEmpty) {
-					if (_lines.Count == 0) {
-						return _cachedBounds;
-					}
-
-					Rect bounds = _lines [0].Bounds;
-
-					for (var i = 1; i < _lines.Count; i++) {
-						var line = _lines [i];
-						var lineBounds = line.Bounds;
-						bounds = Rect.Union (bounds, lineBounds);
-					}
-
-					if (bounds.Width == 0) {
-						bounds.Width = 1;
-					}
-
-					if (bounds.Height == 0) {
-						bounds.Height = 1;
-					}
-					_cachedBounds = new Rect (bounds.X, bounds.Y, bounds.Width, bounds.Height);
-				}
-
-				return _cachedBounds;
-			}
-		}
-
-		// TODO: Unless there's an obvious use case for this API we should delete it in favor of the
-		// simpler version that doensn't take an area.
-		/// <summary>
-		/// Evaluates the lines that have been added to the canvas and returns a map containing
-		/// the glyphs and their locations. The glyphs are the characters that should be rendered
-		/// so that all lines connect up with the appropriate intersection symbols. 
-		/// </summary>
-		/// <param name="inArea">A rectangle to constrain the search by.</param>
-		/// <returns>A map of the points within the canvas that intersect with <paramref name="inArea"/>.</returns>
-		public Dictionary<Point, Rune> GetMap (Rect inArea)
-		{
-			var map = new Dictionary<Point, Rune> ();
-
-			// walk through each pixel of the bitmap
-			for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++) {
-				for (int x = inArea.X; x < inArea.X + inArea.Width; x++) {
-
-					var intersects = _lines
-						.Select (l => l.Intersects (x, y))
-						.Where (i => i != null)
-						.ToArray ();
-
-					var rune = GetRuneForIntersects (Application.Driver, intersects);
-
-					if (rune != null) {
-						map.Add (new Point (x, y), rune.Value);
-					}
-				}
-			}
-
-			return map;
-		}
-
-		/// <summary>
-		/// Evaluates the lines that have been added to the canvas and returns a map containing
-		/// the glyphs and their locations. The glyphs are the characters that should be rendered
-		/// so that all lines connect up with the appropriate intersection symbols. 
-		/// </summary>
-		/// <returns>A map of all the points within the canvas.</returns>
-		public Dictionary<Point, Cell> GetCellMap ()
-		{
-			var map = new Dictionary<Point, Cell> ();
-
-			// walk through each pixel of the bitmap
-			for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++) {
-				for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++) {
-
-					var intersects = _lines
-						.Select (l => l.Intersects (x, y))
-						.Where (i => i != null)
-						.ToArray ();
-
-					var cell = GetCellForIntersects (Application.Driver, intersects);
-
-					if (cell != null) {
-						map.Add (new Point (x, y), cell);
-					}
-				}
-			}
-
-			return map;
-		}
-
-		/// <summary>
-		/// Evaluates the lines that have been added to the canvas and returns a map containing
-		/// the glyphs and their locations. The glyphs are the characters that should be rendered
-		/// so that all lines connect up with the appropriate intersection symbols. 
-		/// </summary>
-		/// <returns>A map of all the points within the canvas.</returns>
-		public Dictionary<Point, Rune> GetMap () => GetMap (Bounds);
-
-		/// <summary>
-		/// Returns the contents of the line canvas rendered to a string. The string
-		/// will include all columns and rows, even if <see cref="Bounds"/> has negative coordinates. 
-		/// For example, if the canvas contains a single line that starts at (-1,-1) with a length of 2, the
-		/// rendered string will have a length of 2.
-		/// </summary>
-		/// <returns>The canvas rendered to a string.</returns>
-		public override string ToString ()
-		{
-			if (Bounds.IsEmpty) {
-				return string.Empty;
-			}
-
-			// Generate the rune map for the entire canvas
-			var runeMap = GetMap ();
-
-			// Create the rune canvas
-			Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width];
-
-			// Copy the rune map to the canvas, adjusting for any negative coordinates
-			foreach (var kvp in runeMap) {
-				int x = kvp.Key.X - Bounds.X;
-				int y = kvp.Key.Y - Bounds.Y;
-				canvas [y, x] = kvp.Value;
-			}
-
-			// Convert the canvas to a string
-			StringBuilder sb = new StringBuilder ();
-			for (int y = 0; y < canvas.GetLength (0); y++) {
-				for (int x = 0; x < canvas.GetLength (1); x++) {
-					Rune r = canvas [y, x];
-					sb.Append (r.Value == 0 ? ' ' : r.ToString ());
-				}
-				if (y < canvas.GetLength (0) - 1) {
-					sb.AppendLine ();
-				}
-			}
-
-			return sb.ToString ();
-		}
-
-		private abstract class IntersectionRuneResolver {
-			internal Rune _round;
-			internal Rune _doubleH;
-			internal Rune _doubleV;
-			internal Rune _doubleBoth;
-			internal Rune _thickH;
-			internal Rune _thickV;
-			internal Rune _thickBoth;
-			internal Rune _normal;
-
-			public IntersectionRuneResolver()
-			{
-				SetGlyphs ();
-			}
-			
-			/// <summary>
-			/// Sets the glyphs used. Call this method after construction and any time 
-			/// ConfigurationManager has updated the settings.
-			/// </summary>
-			public abstract void SetGlyphs ();
-
-			public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
-			{
-				var useRounded = intersects.Any (i => i?.Line.Length != 0 && (
-					i?.Line.Style == LineStyle.Rounded || i?.Line.Style == LineStyle.RoundedDashed || i?.Line.Style == LineStyle.RoundedDotted));
-
-				// Note that there aren't any glyphs for intersections of double lines with heavy lines
-
-				bool doubleHorizontal = intersects.Any (l => l?.Line.Orientation == Orientation.Horizontal && l.Line.Style == LineStyle.Double);
-				bool doubleVertical = intersects.Any (l => l?.Line.Orientation == Orientation.Vertical && l.Line.Style == LineStyle.Double);
-
-				bool thickHorizontal = intersects.Any (l => l?.Line.Orientation == Orientation.Horizontal && (
-					l.Line.Style == LineStyle.Heavy || l.Line.Style == LineStyle.HeavyDashed || l.Line.Style == LineStyle.HeavyDotted));
-				bool thickVertical = intersects.Any (l => l?.Line.Orientation == Orientation.Vertical && (
-					l.Line.Style == LineStyle.Heavy || l.Line.Style == LineStyle.HeavyDashed || l.Line.Style == LineStyle.HeavyDotted));
-
-				if (doubleHorizontal) {
-					return doubleVertical ? _doubleBoth : _doubleH;
-				}
-				if (doubleVertical) {
-					return _doubleV;
-				}
-
-				if (thickHorizontal) {
-					return thickVertical ? _thickBoth : _thickH;
-				}
-				if (thickVertical) {
-					return _thickV;
-				}
-
-				return useRounded ? _round : _normal;
-			}
-		}
-
-		private class ULIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.ULCornerR;
-				_doubleH = CM.Glyphs.ULCornerSingleDbl;
-				_doubleV = CM.Glyphs.ULCornerDblSingle;
-				_doubleBoth = CM.Glyphs.ULCornerDbl;
-				_thickH = CM.Glyphs.ULCornerLtHv;
-				_thickV = CM.Glyphs.ULCornerHvLt;
-				_thickBoth = CM.Glyphs.ULCornerHv;
-				_normal = CM.Glyphs.ULCorner;
-			}
-		}
-		private class URIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.URCornerR;
-				_doubleH = CM.Glyphs.URCornerSingleDbl;
-				_doubleV = CM.Glyphs.URCornerDblSingle;
-				_doubleBoth = CM.Glyphs.URCornerDbl;
-				_thickH = CM.Glyphs.URCornerHvLt;
-				_thickV = CM.Glyphs.URCornerLtHv;
-				_thickBoth = CM.Glyphs.URCornerHv;
-				_normal = CM.Glyphs.URCorner;
-			}
-		}
-		private class LLIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.LLCornerR;
-				_doubleH = CM.Glyphs.LLCornerSingleDbl;
-				_doubleV = CM.Glyphs.LLCornerDblSingle;
-				_doubleBoth = CM.Glyphs.LLCornerDbl;
-				_thickH = CM.Glyphs.LLCornerLtHv;
-				_thickV = CM.Glyphs.LLCornerHvLt;
-				_thickBoth = CM.Glyphs.LLCornerHv;
-				_normal = CM.Glyphs.LLCorner;
-			}
-
-		}
-		private class LRIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.LRCornerR;
-				_doubleH = CM.Glyphs.LRCornerSingleDbl;
-				_doubleV = CM.Glyphs.LRCornerDblSingle;
-				_doubleBoth = CM.Glyphs.LRCornerDbl;
-				_thickH = CM.Glyphs.LRCornerLtHv;
-				_thickV = CM.Glyphs.LRCornerHvLt;
-				_thickBoth = CM.Glyphs.LRCornerHv;
-				_normal = CM.Glyphs.LRCorner;
-			}
-		}
-
-		private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.TopTee;
-				_doubleH = CM.Glyphs.TopTeeDblH;
-				_doubleV = CM.Glyphs.TopTeeDblV;
-				_doubleBoth = CM.Glyphs.TopTeeDbl;
-				_thickH = CM.Glyphs.TopTeeHvH;
-				_thickV = CM.Glyphs.TopTeeHvV;
-				_thickBoth = CM.Glyphs.TopTeeHvDblH;
-				_normal = CM.Glyphs.TopTee;
-			}
-		}
-		private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.LeftTee;
-				_doubleH = CM.Glyphs.LeftTeeDblH;
-				_doubleV = CM.Glyphs.LeftTeeDblV;
-				_doubleBoth = CM.Glyphs.LeftTeeDbl;
-				_thickH = CM.Glyphs.LeftTeeHvH;
-				_thickV = CM.Glyphs.LeftTeeHvV;
-				_thickBoth = CM.Glyphs.LeftTeeHvDblH;
-				_normal = CM.Glyphs.LeftTee;
-			}
-		}
-		private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.RightTee;
-				_doubleH = CM.Glyphs.RightTeeDblH;
-				_doubleV = CM.Glyphs.RightTeeDblV;
-				_doubleBoth = CM.Glyphs.RightTeeDbl;
-				_thickH = CM.Glyphs.RightTeeHvH;
-				_thickV = CM.Glyphs.RightTeeHvV;
-				_thickBoth = CM.Glyphs.RightTeeHvDblH;
-				_normal = CM.Glyphs.RightTee;
-			}
-		}
-		private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.BottomTee;
-				_doubleH = CM.Glyphs.BottomTeeDblH;
-				_doubleV = CM.Glyphs.BottomTeeDblV;
-				_doubleBoth = CM.Glyphs.BottomTeeDbl;
-				_thickH = CM.Glyphs.BottomTeeHvH;
-				_thickV = CM.Glyphs.BottomTeeHvV;
-				_thickBoth = CM.Glyphs.BottomTeeHvDblH;
-				_normal = CM.Glyphs.BottomTee;
-			}
-		}
-		private class CrossIntersectionRuneResolver : IntersectionRuneResolver {
-			public override void SetGlyphs ()
-			{
-				_round = CM.Glyphs.Cross;
-				_doubleH = CM.Glyphs.CrossDblH;
-				_doubleV = CM.Glyphs.CrossDblV;
-				_doubleBoth = CM.Glyphs.CrossDbl;
-				_thickH = CM.Glyphs.CrossHvH;
-				_thickV = CM.Glyphs.CrossHvV;
-				_thickBoth = CM.Glyphs.CrossHv;
-				_normal = CM.Glyphs.Cross;
-			}
-		}
-
-		private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
-		{
-			if (!intersects.Any ()) {
-				return null;
-			}
-
-			var runeType = GetRuneTypeForIntersects (intersects);
-
-			if (runeResolvers.TryGetValue (runeType, out var resolver)) {
-				return resolver.GetRuneForIntersects (driver, intersects);
-			}
-
-			// TODO: Remove these once we have all of the below ported to IntersectionRuneResolvers
-			var useDouble = intersects.Any (i => i?.Line.Style == LineStyle.Double);
-			var useDashed = intersects.Any (i => i?.Line.Style == LineStyle.Dashed || i?.Line.Style == LineStyle.RoundedDashed);
-			var useDotted = intersects.Any (i => i?.Line.Style == LineStyle.Dotted || i?.Line.Style == LineStyle.RoundedDotted);
-			// horiz and vert lines same as Single for Rounded
-			var useThick = intersects.Any (i => i?.Line.Style == LineStyle.Heavy);
-			var useThickDashed = intersects.Any (i => i?.Line.Style == LineStyle.HeavyDashed);
-			var useThickDotted = intersects.Any (i => i?.Line.Style == LineStyle.HeavyDotted);
-			// TODO: Support ruler
-			//var useRuler = intersects.Any (i => i.Line.Style == LineStyle.Ruler && i.Line.Length != 0);
-
-			// TODO: maybe make these resolvers too for simplicity?
-			switch (runeType) {
-			case IntersectionRuneType.None:
-				return null;
-			case IntersectionRuneType.Dot:
-				return (Rune)CM.Glyphs.Dot;
-			case IntersectionRuneType.HLine:
-				if (useDouble) {
-					return CM.Glyphs.HLineDbl;
-				}
-				if (useDashed) {
-					return CM.Glyphs.HLineDa2;
-				}
-				if (useDotted) {
-					return CM.Glyphs.HLineDa3;
-				}
-				return useThick ? CM.Glyphs.HLineHv : (useThickDashed ? CM.Glyphs.HLineHvDa2 : (useThickDotted ? CM.Glyphs.HLineHvDa3 : CM.Glyphs.HLine));
-			case IntersectionRuneType.VLine:
-				if (useDouble) {
-					return CM.Glyphs.VLineDbl;
-				}
-				if (useDashed) {
-					return CM.Glyphs.VLineDa3;
-				}
-				if (useDotted) {
-					return CM.Glyphs.VLineDa4;
-				}
-				return useThick ? CM.Glyphs.VLineHv : (useThickDashed ? CM.Glyphs.VLineHvDa3 : (useThickDotted ? CM.Glyphs.VLineHvDa4 : CM.Glyphs.VLine));
-
-			default: throw new Exception ("Could not find resolver or switch case for " + nameof (runeType) + ":" + runeType);
-			}
-		}
-
-		private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) =>intersects [0]!.Line.Attribute;
-
-		private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
-		{
-			if (!intersects.Any ()) {
-				return null;
-			}
-
-			var cell = new Cell ();
-			var rune = GetRuneForIntersects (driver, intersects);
-			if (rune.HasValue) {
-				cell.Rune = rune.Value;
-			}
-			cell.Attribute = GetAttributeForIntersects (intersects);
-			return cell;
-		}
-
-		private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition? [] intersects)
-		{
-			var set = new HashSet<IntersectionType> (intersects.Select (i => i!.Type));
-
-			#region Cross Conditions
-			if (Has (set,
-				IntersectionType.PassOverHorizontal,
-				IntersectionType.PassOverVertical
-				)) {
-				return IntersectionRuneType.Cross;
-			}
-
-			if (Has (set,
-				IntersectionType.PassOverVertical,
-				IntersectionType.StartLeft,
-				IntersectionType.StartRight
-				)) {
-				return IntersectionRuneType.Cross;
-			}
-
-			if (Has (set,
-				IntersectionType.PassOverHorizontal,
-				IntersectionType.StartUp,
-				IntersectionType.StartDown
-				)) {
-				return IntersectionRuneType.Cross;
-			}
-
-			if (Has (set,
-				IntersectionType.StartLeft,
-				IntersectionType.StartRight,
-				IntersectionType.StartUp,
-				IntersectionType.StartDown)) {
-				return IntersectionRuneType.Cross;
-			}
-			#endregion
-
-			#region Corner Conditions
-			if (Exactly (set,
-				IntersectionType.StartRight,
-				IntersectionType.StartDown)) {
-				return IntersectionRuneType.ULCorner;
-			}
-
-			if (Exactly (set,
-				IntersectionType.StartLeft,
-				IntersectionType.StartDown)) {
-				return IntersectionRuneType.URCorner;
-			}
-
-			if (Exactly (set,
-				IntersectionType.StartUp,
-				IntersectionType.StartLeft)) {
-				return IntersectionRuneType.LRCorner;
-			}
-
-			if (Exactly (set,
-				IntersectionType.StartUp,
-				IntersectionType.StartRight)) {
-				return IntersectionRuneType.LLCorner;
-			}
-			#endregion Corner Conditions
-
-			#region T Conditions
-			if (Has (set,
-				IntersectionType.PassOverHorizontal,
-				IntersectionType.StartDown)) {
-				return IntersectionRuneType.TopTee;
-			}
-			if (Has (set,
-				IntersectionType.StartRight,
-				IntersectionType.StartLeft,
-				IntersectionType.StartDown)) {
-				return IntersectionRuneType.TopTee;
-			}
-
-			if (Has (set,
-				IntersectionType.PassOverHorizontal,
-				IntersectionType.StartUp)) {
-				return IntersectionRuneType.BottomTee;
-			}
-			if (Has (set,
-				IntersectionType.StartRight,
-				IntersectionType.StartLeft,
-				IntersectionType.StartUp)) {
-				return IntersectionRuneType.BottomTee;
-			}
-
-			if (Has (set,
-				IntersectionType.PassOverVertical,
-				IntersectionType.StartRight)) {
-				return IntersectionRuneType.LeftTee;
-			}
-			if (Has (set,
-				IntersectionType.StartRight,
-				IntersectionType.StartDown,
-				IntersectionType.StartUp)) {
-				return IntersectionRuneType.LeftTee;
-			}
-
-			if (Has (set,
-				IntersectionType.PassOverVertical,
-				IntersectionType.StartLeft)) {
-				return IntersectionRuneType.RightTee;
-			}
-			if (Has (set,
-				IntersectionType.StartLeft,
-				IntersectionType.StartDown,
-				IntersectionType.StartUp)) {
-				return IntersectionRuneType.RightTee;
-			}
-			#endregion
-
-			if (All (intersects, Orientation.Horizontal)) {
-				return IntersectionRuneType.HLine;
-			}
-
-			if (All (intersects, Orientation.Vertical)) {
-				return IntersectionRuneType.VLine;
-			}
-
-			return IntersectionRuneType.Dot;
-		}
-
-		private bool All (IntersectionDefinition? [] intersects, Orientation orientation)
-		{
-			return intersects.All (i => i!.Line.Orientation == orientation);
-		}
-
-		/// <summary>
-		/// Returns true if the <paramref name="intersects"/> collection has all the <paramref name="types"/>
-		/// specified (i.e. AND).
-		/// </summary>
-		/// <param name="intersects"></param>
-		/// <param name="types"></param>
-		/// <returns></returns>
-		private bool Has (HashSet<IntersectionType> intersects, params IntersectionType [] types)
-		{
-			return types.All (t => intersects.Contains (t));
-		}
-
-		/// <summary>
-		/// Returns true if all requested <paramref name="types"/> appear in <paramref name="intersects"/>
-		/// and there are no additional <see cref="IntersectionRuneType"/>
-		/// </summary>
-		/// <param name="intersects"></param>
-		/// <param name="types"></param>
-		/// <returns></returns>
-		private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types)
-		{
-			return intersects.SetEquals (types);
-		}
-
-		/// <summary>
-		/// Merges one line canvas into this one.
-		/// </summary>
-		/// <param name="lineCanvas"></param>
-		public void Merge (LineCanvas lineCanvas)
-		{
-			foreach (var line in lineCanvas._lines) {
-				AddLine (line);
-			}
-		}
-		
-		/// <inheritdoc />
-		public void Dispose ()
-		{
-			ConfigurationManager.Applied -= ConfigurationManager_Applied;
-		}
-	}
-	internal class IntersectionDefinition {
-		/// <summary>
-		/// The point at which the intersection happens
-		/// </summary>
-		internal Point Point { get; }
-
-		/// <summary>
-		/// Defines how <see cref="Line"/> position relates
-		/// to <see cref="Point"/>.
-		/// </summary>
-		internal IntersectionType Type { get; }
-
-		/// <summary>
-		/// The line that intersects <see cref="Point"/>
-		/// </summary>
-		internal StraightLine Line { get; }
-
-		internal IntersectionDefinition (Point point, IntersectionType type, StraightLine line)
-		{
-			Point = point;
-			Type = type;
-			Line = line;
-		}
-	}
-
-	/// <summary>
-	/// The type of Rune that we will use before considering
-	/// double width, curved borders etc
-	/// </summary>
-	internal enum IntersectionRuneType {
-		None,
-		Dot,
-		ULCorner,
-		URCorner,
-		LLCorner,
-		LRCorner,
-		TopTee,
-		BottomTee,
-		RightTee,
-		LeftTee,
-		Cross,
-		HLine,
-		VLine,
-	}
-
-	internal enum IntersectionType {
-		/// <summary>
-		/// There is no intersection
-		/// </summary>
-		None,
-
-		/// <summary>
-		///  A line passes directly over this point traveling along
-		///  the horizontal axis
-		/// </summary>
-		PassOverHorizontal,
-
-		/// <summary>
-		///  A line passes directly over this point traveling along
-		///  the vertical axis
-		/// </summary>
-		PassOverVertical,
-
-		/// <summary>
-		/// A line starts at this point and is traveling up
-		/// </summary>
-		StartUp,
-
-		/// <summary>
-		/// A line starts at this point and is traveling right
-		/// </summary>
-		StartRight,
-
-		/// <summary>
-		/// A line starts at this point and is traveling down
-		/// </summary>
-		StartDown,
-
-		/// <summary>
-		/// A line starts at this point and is traveling left
-		/// </summary>
-		StartLeft,
-
-		/// <summary>
-		/// A line exists at this point who has 0 length
-		/// </summary>
-		Dot
-	}
+namespace Terminal.Gui;
+
+/// <summary>Defines the style of lines for a <see cref="LineCanvas"/>.</summary>
+public enum LineStyle
+{
+    /// <summary>No border is drawn.</summary>
+    None,
+
+    /// <summary>The border is drawn using thin line CM.Glyphs.</summary>
+    Single,
+
+    /// <summary>The border is drawn using thin line glyphs with dashed (double and triple) straight lines.</summary>
+    Dashed,
+
+    /// <summary>The border is drawn using thin line glyphs with short dashed (triple and quadruple) straight lines.</summary>
+    Dotted,
+
+    /// <summary>The border is drawn using thin double line CM.Glyphs.</summary>
+    Double,
+
+    /// <summary>The border is drawn using heavy line CM.Glyphs.</summary>
+    Heavy,
+
+    /// <summary>The border is drawn using heavy line glyphs with dashed (double and triple) straight lines.</summary>
+    HeavyDashed,
+
+    /// <summary>The border is drawn using heavy line glyphs with short dashed (triple and quadruple) straight lines.</summary>
+    HeavyDotted,
+
+    /// <summary>The border is drawn using thin line glyphs with rounded corners.</summary>
+    Rounded,
+
+    /// <summary>The border is drawn using thin line glyphs with rounded corners and dashed (double and triple) straight lines.</summary>
+    RoundedDashed,
+
+    /// <summary>
+    ///     The border is drawn using thin line glyphs with rounded corners and short dashed (triple and quadruple)
+    ///     straight lines.
+    /// </summary>
+    RoundedDotted
+
+    // TODO: Support Ruler
+    ///// <summary> 
+    ///// The border is drawn as a diagnostic ruler ("|123456789...").
+    ///// </summary>
+    //Ruler
+}
+
+/// <summary>Facilitates box drawing and line intersection detection and rendering.  Does not support diagonal lines.</summary>
+public class LineCanvas : IDisposable
+{
+    private readonly List<StraightLine> _lines = new ();
+
+    private readonly Dictionary<IntersectionRuneType, IntersectionRuneResolver> runeResolvers = new ()
+    {
+        {
+            IntersectionRuneType.ULCorner,
+            new ULIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.URCorner,
+            new URIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LLCorner,
+            new LLIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LRCorner,
+            new LRIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.TopTee,
+            new TopTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.LeftTee,
+            new LeftTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.RightTee,
+            new RightTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.BottomTee,
+            new BottomTeeIntersectionRuneResolver ()
+        },
+        {
+            IntersectionRuneType.Cross,
+            new CrossIntersectionRuneResolver ()
+        }
+
+        // TODO: Add other resolvers
+    };
+
+    private Rect _cachedBounds;
+
+    /// <summary>Creates a new instance.</summary>
+    public LineCanvas ()
+    {
+        // TODO: Refactor ConfigurationManager to not use an event handler for this.
+        // Instead, have it call a method on any class appropriately attributed
+        // to update the cached values. See Issue #2871
+        Applied += ConfigurationManager_Applied;
+    }
+
+    /// <summary>Creates a new instance with the given <paramref name="lines"/>.</summary>
+    /// <param name="lines">Initial lines for the canvas.</param>
+    public LineCanvas (IEnumerable<StraightLine> lines) : this () { _lines = lines.ToList (); }
+
+    /// <summary>
+    ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
+    ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
+    /// </summary>
+    public Rect Bounds
+    {
+        get
+        {
+            if (_cachedBounds.IsEmpty)
+            {
+                if (_lines.Count == 0)
+                {
+                    return _cachedBounds;
+                }
+
+                Rect bounds = _lines [0].Bounds;
+
+                for (var i = 1; i < _lines.Count; i++)
+                {
+                    StraightLine line = _lines [i];
+                    Rect lineBounds = line.Bounds;
+                    bounds = Rect.Union (bounds, lineBounds);
+                }
+
+                if (bounds.Width == 0)
+                {
+                    bounds.Width = 1;
+                }
+
+                if (bounds.Height == 0)
+                {
+                    bounds.Height = 1;
+                }
+
+                _cachedBounds = new Rect (bounds.X, bounds.Y, bounds.Width, bounds.Height);
+            }
+
+            return _cachedBounds;
+        }
+    }
+
+    /// <summary>Gets the lines in the canvas.</summary>
+    public IReadOnlyCollection<StraightLine> Lines => _lines.AsReadOnly ();
+
+    /// <inheritdoc/>
+    public void Dispose () { Applied -= ConfigurationManager_Applied; }
+
+    /// <summary>
+    ///     <para>Adds a new <paramref name="length"/> long line to the canvas starting at <paramref name="start"/>.</para>
+    ///     <para>
+    ///         Use positive <paramref name="length"/> for the line to extend Right and negative for Left when
+    ///         <see cref="Orientation"/> is <see cref="Orientation.Horizontal"/>.
+    ///     </para>
+    ///     <para>
+    ///         Use positive <paramref name="length"/> for the line to extend Down and negative for Up when
+    ///         <see cref="Orientation"/> is <see cref="Orientation.Vertical"/>.
+    ///     </para>
+    /// </summary>
+    /// <param name="start">Starting point.</param>
+    /// <param name="length">
+    ///     The length of line. 0 for an intersection (cross or T). Positive for Down/Right. Negative for
+    ///     Up/Left.
+    /// </param>
+    /// <param name="orientation">The direction of the line.</param>
+    /// <param name="style">The style of line to use</param>
+    /// <param name="attribute"></param>
+    public void AddLine (
+        Point start,
+        int length,
+        Orientation orientation,
+        LineStyle style,
+        Attribute? attribute = default
+    )
+    {
+        _cachedBounds = Rect.Empty;
+        _lines.Add (new StraightLine (start, length, orientation, style, attribute));
+    }
+
+    /// <summary>Adds a new line to the canvas</summary>
+    /// <param name="line"></param>
+    public void AddLine (StraightLine line)
+    {
+        _cachedBounds = Rect.Empty;
+        _lines.Add (line);
+    }
+
+    /// <summary>Clears all lines from the LineCanvas.</summary>
+    public void Clear ()
+    {
+        _cachedBounds = Rect.Empty;
+        _lines.Clear ();
+    }
+
+    /// <summary>
+    ///     Clears any cached states from the canvas Call this method if you make changes to lines that have already been
+    ///     added.
+    /// </summary>
+    public void ClearCache () { _cachedBounds = Rect.Empty; }
+
+    /// <summary>
+    ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
+    ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
+    ///     intersection symbols.
+    /// </summary>
+    /// <returns>A map of all the points within the canvas.</returns>
+    public Dictionary<Point, Cell> GetCellMap ()
+    {
+        Dictionary<Point, Cell> map = new ();
+
+        // walk through each pixel of the bitmap
+        for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++)
+        {
+            for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++)
+            {
+                IntersectionDefinition? [] intersects = _lines
+                                                        .Select (l => l.Intersects (x, y))
+                                                        .Where (i => i != null)
+                                                        .ToArray ();
+
+                Cell? cell = GetCellForIntersects (Application.Driver, intersects);
+
+                if (cell != null)
+                {
+                    map.Add (new Point (x, y), cell);
+                }
+            }
+        }
+
+        return map;
+    }
+
+    // TODO: Unless there's an obvious use case for this API we should delete it in favor of the
+    // simpler version that doensn't take an area.
+    /// <summary>
+    ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
+    ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
+    ///     intersection symbols.
+    /// </summary>
+    /// <param name="inArea">A rectangle to constrain the search by.</param>
+    /// <returns>A map of the points within the canvas that intersect with <paramref name="inArea"/>.</returns>
+    public Dictionary<Point, Rune> GetMap (Rect inArea)
+    {
+        Dictionary<Point, Rune> map = new ();
+
+        // walk through each pixel of the bitmap
+        for (int y = inArea.Y; y < inArea.Y + inArea.Height; y++)
+        {
+            for (int x = inArea.X; x < inArea.X + inArea.Width; x++)
+            {
+                IntersectionDefinition? [] intersects = _lines
+                                                        .Select (l => l.Intersects (x, y))
+                                                        .Where (i => i != null)
+                                                        .ToArray ();
+
+                Rune? rune = GetRuneForIntersects (Application.Driver, intersects);
+
+                if (rune != null)
+                {
+                    map.Add (new Point (x, y), rune.Value);
+                }
+            }
+        }
+
+        return map;
+    }
+
+    /// <summary>
+    ///     Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their
+    ///     locations. The glyphs are the characters that should be rendered so that all lines connect up with the appropriate
+    ///     intersection symbols.
+    /// </summary>
+    /// <returns>A map of all the points within the canvas.</returns>
+    public Dictionary<Point, Rune> GetMap () { return GetMap (Bounds); }
+
+    /// <summary>Merges one line canvas into this one.</summary>
+    /// <param name="lineCanvas"></param>
+    public void Merge (LineCanvas lineCanvas)
+    {
+        foreach (StraightLine line in lineCanvas._lines)
+        {
+            AddLine (line);
+        }
+    }
+
+    /// <summary>Removes the last line added to the canvas</summary>
+    /// <returns></returns>
+    public StraightLine RemoveLastLine ()
+    {
+        StraightLine? l = _lines.LastOrDefault ();
+
+        if (l != null)
+        {
+            _lines.Remove (l);
+        }
+
+        return l!;
+    }
+
+    /// <summary>
+    ///     Returns the contents of the line canvas rendered to a string. The string will include all columns and rows,
+    ///     even if <see cref="Bounds"/> has negative coordinates. For example, if the canvas contains a single line that
+    ///     starts at (-1,-1) with a length of 2, the rendered string will have a length of 2.
+    /// </summary>
+    /// <returns>The canvas rendered to a string.</returns>
+    public override string ToString ()
+    {
+        if (Bounds.IsEmpty)
+        {
+            return string.Empty;
+        }
+
+        // Generate the rune map for the entire canvas
+        Dictionary<Point, Rune> runeMap = GetMap ();
+
+        // Create the rune canvas
+        Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width];
+
+        // Copy the rune map to the canvas, adjusting for any negative coordinates
+        foreach (KeyValuePair<Point, Rune> kvp in runeMap)
+        {
+            int x = kvp.Key.X - Bounds.X;
+            int y = kvp.Key.Y - Bounds.Y;
+            canvas [y, x] = kvp.Value;
+        }
+
+        // Convert the canvas to a string
+        var sb = new StringBuilder ();
+
+        for (var y = 0; y < canvas.GetLength (0); y++)
+        {
+            for (var x = 0; x < canvas.GetLength (1); x++)
+            {
+                Rune r = canvas [y, x];
+                sb.Append (r.Value == 0 ? ' ' : r.ToString ());
+            }
+
+            if (y < canvas.GetLength (0) - 1)
+            {
+                sb.AppendLine ();
+            }
+        }
+
+        return sb.ToString ();
+    }
+
+    private bool All (IntersectionDefinition? [] intersects, Orientation orientation) { return intersects.All (i => i!.Line.Orientation == orientation); }
+
+    private void ConfigurationManager_Applied (object? sender, ConfigurationManagerEventArgs e)
+    {
+        foreach (KeyValuePair<IntersectionRuneType, IntersectionRuneResolver> irr in runeResolvers)
+        {
+            irr.Value.SetGlyphs ();
+        }
+    }
+
+    /// <summary>
+    ///     Returns true if all requested <paramref name="types"/> appear in <paramref name="intersects"/> and there are
+    ///     no additional <see cref="IntersectionRuneType"/>
+    /// </summary>
+    /// <param name="intersects"></param>
+    /// <param name="types"></param>
+    /// <returns></returns>
+    private bool Exactly (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return intersects.SetEquals (types); }
+
+    private Attribute? GetAttributeForIntersects (IntersectionDefinition? [] intersects) { return intersects [0]!.Line.Attribute; }
+
+    private Cell? GetCellForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    {
+        if (!intersects.Any ())
+        {
+            return null;
+        }
+
+        var cell = new Cell ();
+        Rune? rune = GetRuneForIntersects (driver, intersects);
+
+        if (rune.HasValue)
+        {
+            cell.Rune = rune.Value;
+        }
+
+        cell.Attribute = GetAttributeForIntersects (intersects);
+
+        return cell;
+    }
+
+    private Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+    {
+        if (!intersects.Any ())
+        {
+            return null;
+        }
+
+        IntersectionRuneType runeType = GetRuneTypeForIntersects (intersects);
+
+        if (runeResolvers.TryGetValue (runeType, out IntersectionRuneResolver? resolver))
+        {
+            return resolver.GetRuneForIntersects (driver, intersects);
+        }
+
+        // TODO: Remove these once we have all of the below ported to IntersectionRuneResolvers
+        bool useDouble = intersects.Any (i => i?.Line.Style == LineStyle.Double);
+
+        bool useDashed = intersects.Any (
+                                         i => i?.Line.Style == LineStyle.Dashed
+                                              || i?.Line.Style == LineStyle.RoundedDashed
+                                        );
+
+        bool useDotted = intersects.Any (
+                                         i => i?.Line.Style == LineStyle.Dotted
+                                              || i?.Line.Style == LineStyle.RoundedDotted
+                                        );
+
+        // horiz and vert lines same as Single for Rounded
+        bool useThick = intersects.Any (i => i?.Line.Style == LineStyle.Heavy);
+        bool useThickDashed = intersects.Any (i => i?.Line.Style == LineStyle.HeavyDashed);
+        bool useThickDotted = intersects.Any (i => i?.Line.Style == LineStyle.HeavyDotted);
+
+        // TODO: Support ruler
+        //var useRuler = intersects.Any (i => i.Line.Style == LineStyle.Ruler && i.Line.Length != 0);
+
+        // TODO: maybe make these resolvers too for simplicity?
+        switch (runeType)
+        {
+            case IntersectionRuneType.None:
+                return null;
+            case IntersectionRuneType.Dot:
+                return Glyphs.Dot;
+            case IntersectionRuneType.HLine:
+                if (useDouble)
+                {
+                    return Glyphs.HLineDbl;
+                }
+
+                if (useDashed)
+                {
+                    return Glyphs.HLineDa2;
+                }
+
+                if (useDotted)
+                {
+                    return Glyphs.HLineDa3;
+                }
+
+                return useThick ? Glyphs.HLineHv :
+                       useThickDashed ? Glyphs.HLineHvDa2 :
+                       useThickDotted ? Glyphs.HLineHvDa3 : Glyphs.HLine;
+            case IntersectionRuneType.VLine:
+                if (useDouble)
+                {
+                    return Glyphs.VLineDbl;
+                }
+
+                if (useDashed)
+                {
+                    return Glyphs.VLineDa3;
+                }
+
+                if (useDotted)
+                {
+                    return Glyphs.VLineDa4;
+                }
+
+                return useThick ? Glyphs.VLineHv :
+                       useThickDashed ? Glyphs.VLineHvDa3 :
+                       useThickDotted ? Glyphs.VLineHvDa4 : Glyphs.VLine;
+
+            default:
+                throw new Exception (
+                                     "Could not find resolver or switch case for "
+                                     + nameof (runeType)
+                                     + ":"
+                                     + runeType
+                                    );
+        }
+    }
+
+    private IntersectionRuneType GetRuneTypeForIntersects (IntersectionDefinition? [] intersects)
+    {
+        HashSet<IntersectionType> set = new (intersects.Select (i => i!.Type));
+
+        #region Cross Conditions
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverHorizontal,
+                 IntersectionType.PassOverVertical
+                ))
+        {
+            return IntersectionRuneType.Cross;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverVertical,
+                 IntersectionType.StartLeft,
+                 IntersectionType.StartRight
+                ))
+        {
+            return IntersectionRuneType.Cross;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverHorizontal,
+                 IntersectionType.StartUp,
+                 IntersectionType.StartDown
+                ))
+        {
+            return IntersectionRuneType.Cross;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.StartLeft,
+                 IntersectionType.StartRight,
+                 IntersectionType.StartUp,
+                 IntersectionType.StartDown
+                ))
+        {
+            return IntersectionRuneType.Cross;
+        }
+
+        #endregion
+
+        #region Corner Conditions
+
+        if (Exactly (
+                     set,
+                     IntersectionType.StartRight,
+                     IntersectionType.StartDown
+                    ))
+        {
+            return IntersectionRuneType.ULCorner;
+        }
+
+        if (Exactly (
+                     set,
+                     IntersectionType.StartLeft,
+                     IntersectionType.StartDown
+                    ))
+        {
+            return IntersectionRuneType.URCorner;
+        }
+
+        if (Exactly (
+                     set,
+                     IntersectionType.StartUp,
+                     IntersectionType.StartLeft
+                    ))
+        {
+            return IntersectionRuneType.LRCorner;
+        }
+
+        if (Exactly (
+                     set,
+                     IntersectionType.StartUp,
+                     IntersectionType.StartRight
+                    ))
+        {
+            return IntersectionRuneType.LLCorner;
+        }
+
+        #endregion Corner Conditions
+
+        #region T Conditions
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverHorizontal,
+                 IntersectionType.StartDown
+                ))
+        {
+            return IntersectionRuneType.TopTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.StartRight,
+                 IntersectionType.StartLeft,
+                 IntersectionType.StartDown
+                ))
+        {
+            return IntersectionRuneType.TopTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverHorizontal,
+                 IntersectionType.StartUp
+                ))
+        {
+            return IntersectionRuneType.BottomTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.StartRight,
+                 IntersectionType.StartLeft,
+                 IntersectionType.StartUp
+                ))
+        {
+            return IntersectionRuneType.BottomTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverVertical,
+                 IntersectionType.StartRight
+                ))
+        {
+            return IntersectionRuneType.LeftTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.StartRight,
+                 IntersectionType.StartDown,
+                 IntersectionType.StartUp
+                ))
+        {
+            return IntersectionRuneType.LeftTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.PassOverVertical,
+                 IntersectionType.StartLeft
+                ))
+        {
+            return IntersectionRuneType.RightTee;
+        }
+
+        if (Has (
+                 set,
+                 IntersectionType.StartLeft,
+                 IntersectionType.StartDown,
+                 IntersectionType.StartUp
+                ))
+        {
+            return IntersectionRuneType.RightTee;
+        }
+
+        #endregion
+
+        if (All (intersects, Orientation.Horizontal))
+        {
+            return IntersectionRuneType.HLine;
+        }
+
+        if (All (intersects, Orientation.Vertical))
+        {
+            return IntersectionRuneType.VLine;
+        }
+
+        return IntersectionRuneType.Dot;
+    }
+
+    /// <summary>
+    ///     Returns true if the <paramref name="intersects"/> collection has all the <paramref name="types"/> specified
+    ///     (i.e. AND).
+    /// </summary>
+    /// <param name="intersects"></param>
+    /// <param name="types"></param>
+    /// <returns></returns>
+    private bool Has (HashSet<IntersectionType> intersects, params IntersectionType [] types) { return types.All (t => intersects.Contains (t)); }
+
+    private class BottomTeeIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.BottomTee;
+            _doubleH = Glyphs.BottomTeeDblH;
+            _doubleV = Glyphs.BottomTeeDblV;
+            _doubleBoth = Glyphs.BottomTeeDbl;
+            _thickH = Glyphs.BottomTeeHvH;
+            _thickV = Glyphs.BottomTeeHvV;
+            _thickBoth = Glyphs.BottomTeeHvDblH;
+            _normal = Glyphs.BottomTee;
+        }
+    }
+
+    private class CrossIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.Cross;
+            _doubleH = Glyphs.CrossDblH;
+            _doubleV = Glyphs.CrossDblV;
+            _doubleBoth = Glyphs.CrossDbl;
+            _thickH = Glyphs.CrossHvH;
+            _thickV = Glyphs.CrossHvV;
+            _thickBoth = Glyphs.CrossHv;
+            _normal = Glyphs.Cross;
+        }
+    }
+
+    private abstract class IntersectionRuneResolver
+    {
+        internal Rune _doubleBoth;
+        internal Rune _doubleH;
+        internal Rune _doubleV;
+        internal Rune _normal;
+        internal Rune _round;
+        internal Rune _thickBoth;
+        internal Rune _thickH;
+        internal Rune _thickV;
+        public IntersectionRuneResolver () { SetGlyphs (); }
+
+        public Rune? GetRuneForIntersects (ConsoleDriver driver, IntersectionDefinition? [] intersects)
+        {
+            bool useRounded = intersects.Any (
+                                              i => i?.Line.Length != 0
+                                                   && (
+                                                          i?.Line.Style == LineStyle.Rounded
+                                                          || i?.Line.Style
+                                                          == LineStyle.RoundedDashed
+                                                          || i?.Line.Style
+                                                          == LineStyle.RoundedDotted)
+                                             );
+
+            // Note that there aren't any glyphs for intersections of double lines with heavy lines
+
+            bool doubleHorizontal = intersects.Any (
+                                                    l => l?.Line.Orientation == Orientation.Horizontal
+                                                         && l.Line.Style == LineStyle.Double
+                                                   );
+
+            bool doubleVertical = intersects.Any (
+                                                  l => l?.Line.Orientation == Orientation.Vertical
+                                                       && l.Line.Style == LineStyle.Double
+                                                 );
+
+            bool thickHorizontal = intersects.Any (
+                                                   l => l?.Line.Orientation == Orientation.Horizontal
+                                                        && (
+                                                               l.Line.Style == LineStyle.Heavy
+                                                               || l.Line.Style == LineStyle.HeavyDashed
+                                                               || l.Line.Style == LineStyle.HeavyDotted)
+                                                  );
+
+            bool thickVertical = intersects.Any (
+                                                 l => l?.Line.Orientation == Orientation.Vertical
+                                                      && (
+                                                             l.Line.Style == LineStyle.Heavy
+                                                             || l.Line.Style == LineStyle.HeavyDashed
+                                                             || l.Line.Style == LineStyle.HeavyDotted)
+                                                );
+
+            if (doubleHorizontal)
+            {
+                return doubleVertical ? _doubleBoth : _doubleH;
+            }
+
+            if (doubleVertical)
+            {
+                return _doubleV;
+            }
+
+            if (thickHorizontal)
+            {
+                return thickVertical ? _thickBoth : _thickH;
+            }
+
+            if (thickVertical)
+            {
+                return _thickV;
+            }
+
+            return useRounded ? _round : _normal;
+        }
+
+        /// <summary>
+        ///     Sets the glyphs used. Call this method after construction and any time ConfigurationManager has updated the
+        ///     settings.
+        /// </summary>
+        public abstract void SetGlyphs ();
+    }
+
+    private class LeftTeeIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.LeftTee;
+            _doubleH = Glyphs.LeftTeeDblH;
+            _doubleV = Glyphs.LeftTeeDblV;
+            _doubleBoth = Glyphs.LeftTeeDbl;
+            _thickH = Glyphs.LeftTeeHvH;
+            _thickV = Glyphs.LeftTeeHvV;
+            _thickBoth = Glyphs.LeftTeeHvDblH;
+            _normal = Glyphs.LeftTee;
+        }
+    }
+
+    private class LLIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.LLCornerR;
+            _doubleH = Glyphs.LLCornerSingleDbl;
+            _doubleV = Glyphs.LLCornerDblSingle;
+            _doubleBoth = Glyphs.LLCornerDbl;
+            _thickH = Glyphs.LLCornerLtHv;
+            _thickV = Glyphs.LLCornerHvLt;
+            _thickBoth = Glyphs.LLCornerHv;
+            _normal = Glyphs.LLCorner;
+        }
+    }
+
+    private class LRIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.LRCornerR;
+            _doubleH = Glyphs.LRCornerSingleDbl;
+            _doubleV = Glyphs.LRCornerDblSingle;
+            _doubleBoth = Glyphs.LRCornerDbl;
+            _thickH = Glyphs.LRCornerLtHv;
+            _thickV = Glyphs.LRCornerHvLt;
+            _thickBoth = Glyphs.LRCornerHv;
+            _normal = Glyphs.LRCorner;
+        }
+    }
+
+    private class RightTeeIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.RightTee;
+            _doubleH = Glyphs.RightTeeDblH;
+            _doubleV = Glyphs.RightTeeDblV;
+            _doubleBoth = Glyphs.RightTeeDbl;
+            _thickH = Glyphs.RightTeeHvH;
+            _thickV = Glyphs.RightTeeHvV;
+            _thickBoth = Glyphs.RightTeeHvDblH;
+            _normal = Glyphs.RightTee;
+        }
+    }
+
+    private class TopTeeIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.TopTee;
+            _doubleH = Glyphs.TopTeeDblH;
+            _doubleV = Glyphs.TopTeeDblV;
+            _doubleBoth = Glyphs.TopTeeDbl;
+            _thickH = Glyphs.TopTeeHvH;
+            _thickV = Glyphs.TopTeeHvV;
+            _thickBoth = Glyphs.TopTeeHvDblH;
+            _normal = Glyphs.TopTee;
+        }
+    }
+
+    private class ULIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.ULCornerR;
+            _doubleH = Glyphs.ULCornerSingleDbl;
+            _doubleV = Glyphs.ULCornerDblSingle;
+            _doubleBoth = Glyphs.ULCornerDbl;
+            _thickH = Glyphs.ULCornerLtHv;
+            _thickV = Glyphs.ULCornerHvLt;
+            _thickBoth = Glyphs.ULCornerHv;
+            _normal = Glyphs.ULCorner;
+        }
+    }
+
+    private class URIntersectionRuneResolver : IntersectionRuneResolver
+    {
+        public override void SetGlyphs ()
+        {
+            _round = Glyphs.URCornerR;
+            _doubleH = Glyphs.URCornerSingleDbl;
+            _doubleV = Glyphs.URCornerDblSingle;
+            _doubleBoth = Glyphs.URCornerDbl;
+            _thickH = Glyphs.URCornerHvLt;
+            _thickV = Glyphs.URCornerLtHv;
+            _thickBoth = Glyphs.URCornerHv;
+            _normal = Glyphs.URCorner;
+        }
+    }
+}
+
+internal class IntersectionDefinition
+{
+    internal IntersectionDefinition (Point point, IntersectionType type, StraightLine line)
+    {
+        Point = point;
+        Type = type;
+        Line = line;
+    }
+
+    /// <summary>The line that intersects <see cref="Point"/></summary>
+    internal StraightLine Line { get; }
+
+    /// <summary>The point at which the intersection happens</summary>
+    internal Point Point { get; }
+
+    /// <summary>Defines how <see cref="Line"/> position relates to <see cref="Point"/>.</summary>
+    internal IntersectionType Type { get; }
+}
+
+/// <summary>The type of Rune that we will use before considering double width, curved borders etc</summary>
+internal enum IntersectionRuneType
+{
+    None,
+    Dot,
+    ULCorner,
+    URCorner,
+    LLCorner,
+    LRCorner,
+    TopTee,
+    BottomTee,
+    RightTee,
+    LeftTee,
+    Cross,
+    HLine,
+    VLine
+}
+
+internal enum IntersectionType
+{
+    /// <summary>There is no intersection</summary>
+    None,
+
+    /// <summary>A line passes directly over this point traveling along the horizontal axis</summary>
+    PassOverHorizontal,
+
+    /// <summary>A line passes directly over this point traveling along the vertical axis</summary>
+    PassOverVertical,
+
+    /// <summary>A line starts at this point and is traveling up</summary>
+    StartUp,
+
+    /// <summary>A line starts at this point and is traveling right</summary>
+    StartRight,
+
+    /// <summary>A line starts at this point and is traveling down</summary>
+    StartDown,
+
+    /// <summary>A line starts at this point and is traveling left</summary>
+    StartLeft,
+
+    /// <summary>A line exists at this point who has 0 length</summary>
+    Dot
 }

+ 58 - 65
Terminal.Gui/Drawing/Ruler.cs

@@ -1,65 +1,58 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Text;
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Draws a ruler on the screen.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// </para>
-	/// </remarks>
-	public class Ruler {
-
-		/// <summary>
-		/// Gets or sets whether the ruler is drawn horizontally or vertically. The default is horizontally.
-		/// </summary>
-		public Orientation Orientation { get; set; }
-
-		/// <summary>
-		/// Gets or sets the length of the ruler. The default is 0.
-		/// </summary>
-		public int Length { get; set; }
-
-		/// <summary>
-		/// Gets or sets the foreground and background color to use.
-		/// </summary>
-		public Attribute Attribute { get; set; } = new Attribute ();
-
-		string _hTemplate { get; } = "|123456789";
-		string _vTemplate { get; } = "-123456789";
-
-		/// <summary>
-		/// Draws the <see cref="Ruler"/>. 
-		/// </summary>
-		/// <param name="location">The location to start drawing the ruler, in screen-relative coordinates.</param>
-		/// <param name="start">The start value of the ruler.</param>
-		public void Draw (Point location, int start = 0)
-		{
-			if (start < 0) {
-				throw new ArgumentException ("start must be greater than or equal to 0");
-			}
-
-			if (Length < 1) {
-				return;
-			}
-
-			if (Orientation == Orientation.Horizontal) {
-				var hrule = _hTemplate.Repeat ((int)Math.Ceiling ((double)Length + 2 / (double)_hTemplate.Length)) [start..(Length + start)];
-				// Top
-				Application.Driver.Move (location.X, location.Y);
-				Application.Driver.AddStr (hrule);
-
-			} else {
-				var vrule = _vTemplate.Repeat ((int)Math.Ceiling ((double)(Length + 2) / (double)_vTemplate.Length)) [start..(Length + start)];
-				for (var r = location.Y; r < location.Y + Length; r++) {
-					Application.Driver.Move (location.X, r);
-					Application.Driver.AddRune ((Rune)vrule [r - location.Y]);
-				}
-			}
-		}
-	}
-}
+namespace Terminal.Gui;
+
+/// <summary>Draws a ruler on the screen.</summary>
+/// <remarks>
+///     <para></para>
+/// </remarks>
+public class Ruler
+{
+    /// <summary>Gets or sets the foreground and background color to use.</summary>
+    public Attribute Attribute { get; set; } = new ();
+
+    /// <summary>Gets or sets the length of the ruler. The default is 0.</summary>
+    public int Length { get; set; }
+
+    /// <summary>Gets or sets whether the ruler is drawn horizontally or vertically. The default is horizontally.</summary>
+    public Orientation Orientation { get; set; }
+
+    private string _hTemplate { get; } = "|123456789";
+    private string _vTemplate { get; } = "-123456789";
+
+    /// <summary>Draws the <see cref="Ruler"/>.</summary>
+    /// <param name="location">The location to start drawing the ruler, in screen-relative coordinates.</param>
+    /// <param name="start">The start value of the ruler.</param>
+    public void Draw (Point location, int start = 0)
+    {
+        if (start < 0)
+        {
+            throw new ArgumentException ("start must be greater than or equal to 0");
+        }
+
+        if (Length < 1)
+        {
+            return;
+        }
+
+        if (Orientation == Orientation.Horizontal)
+        {
+            string hrule =
+                _hTemplate.Repeat ((int)Math.Ceiling (Length + 2 / (double)_hTemplate.Length)) [start..(Length + start)];
+
+            // Top
+            Application.Driver.Move (location.X, location.Y);
+            Application.Driver.AddStr (hrule);
+        }
+        else
+        {
+            string vrule =
+                _vTemplate.Repeat ((int)Math.Ceiling ((Length + 2) / (double)_vTemplate.Length))
+                    [start..(Length + start)];
+
+            for (int r = location.Y; r < location.Y + Length; r++)
+            {
+                Application.Driver.Move (location.X, r);
+                Application.Driver.AddRune ((Rune)vrule [r - location.Y]);
+            }
+        }
+    }
+}

+ 193 - 196
Terminal.Gui/Drawing/StraightLine.cs

@@ -1,198 +1,195 @@
-using System;
-
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 #nullable enable
-	// TODO: Add events that notify when StraightLine changes to enable dynamic layout
-	/// <summary>
-	/// A line between two points on a horizontal or vertical <see cref="Orientation"/>
-	/// and a given style/color.
-	/// </summary>
-	public class StraightLine {
-
-		/// <summary>
-		/// Gets or sets where the line begins.
-		/// </summary>
-		public Point Start { get; set; }
-
-		/// <summary>
-		/// Gets or sets the length of the line.
-		/// </summary>
-		public int Length { get; set; }
-
-		/// <summary>
-		/// Gets or sets the orientation (horizontal or vertical) of the line.
-		/// </summary>
-		public Orientation Orientation { get; set; }
-
-		/// <summary>
-		/// Gets or sets the line style of the line (e.g. dotted, double).
-		/// </summary>
-		public LineStyle Style { get; set; }
-
-		/// <summary>
-		/// Gets or sets the color of the line.
-		/// </summary>
-		public Attribute? Attribute { get; set; }
-
-		/// <summary>
-		/// Creates a new instance of the <see cref="StraightLine"/> class.
-		/// </summary>
-		/// <param name="start"></param>
-		/// <param name="length"></param>
-		/// <param name="orientation"></param>
-		/// <param name="style"></param>
-		/// <param name="attribute"></param>
-		public StraightLine (Point start, int length, Orientation orientation, LineStyle style, Attribute? attribute = default)
-		{
-			this.Start = start;
-			this.Length = length;
-			this.Orientation = orientation;
-			this.Style = style;
-			this.Attribute = attribute;
-		}
-
-		internal IntersectionDefinition? Intersects (int x, int y)
-		{
-			switch (Orientation) {
-			case Orientation.Horizontal: return IntersectsHorizontally (x, y);
-			case Orientation.Vertical: return IntersectsVertically (x, y);
-			default: throw new ArgumentOutOfRangeException (nameof (Orientation));
-			}
-
-		}
-
-		private IntersectionDefinition? IntersectsHorizontally (int x, int y)
-		{
-			if (Start.Y != y) {
-				return null;
-			} else {
-				if (StartsAt (x, y)) {
-
-					return new IntersectionDefinition (
-						Start,
-						GetTypeByLength (IntersectionType.StartLeft, IntersectionType.PassOverHorizontal, IntersectionType.StartRight),
-						this
-						);
-
-				}
-
-				if (EndsAt (x, y)) {
-
-					return new IntersectionDefinition (
-						Start,
-						Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft,
-						this
-						);
-
-				} else {
-					var xmin = Math.Min (Start.X, Start.X + Length);
-					var xmax = Math.Max (Start.X, Start.X + Length);
-
-					if (xmin < x && xmax > x) {
-						return new IntersectionDefinition (
-						new Point (x, y),
-						IntersectionType.PassOverHorizontal,
-						this
-						);
-					}
-				}
-
-				return null;
-			}
-		}
-
-		private IntersectionDefinition? IntersectsVertically (int x, int y)
-		{
-			if (Start.X != x) {
-				return null;
-			} else {
-				if (StartsAt (x, y)) {
-
-					return new IntersectionDefinition (
-						Start,
-						GetTypeByLength (IntersectionType.StartUp, IntersectionType.PassOverVertical, IntersectionType.StartDown),
-						this
-						);
-
-				}
-
-				if (EndsAt (x, y)) {
-
-					return new IntersectionDefinition (
-						Start,
-						Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp,
-						this
-						);
-
-				} else {
-					var ymin = Math.Min (Start.Y, Start.Y + Length);
-					var ymax = Math.Max (Start.Y, Start.Y + Length);
-
-					if (ymin < y && ymax > y) {
-						return new IntersectionDefinition (
-						new Point (x, y),
-						IntersectionType.PassOverVertical,
-						this
-						);
-					}
-				}
-
-				return null;
-			}
-		}
-
-		private IntersectionType GetTypeByLength (IntersectionType typeWhenNegative, IntersectionType typeWhenZero, IntersectionType typeWhenPositive)
-		{
-			if (Length == 0) {
-				return typeWhenZero;
-			}
-
-			return Length < 0 ? typeWhenNegative : typeWhenPositive;
-		}
-
-		private bool EndsAt (int x, int y)
-		{
-			var sub = (Length == 0) ? 0 : (Length > 0) ? 1 : -1;
-			if (Orientation == Orientation.Horizontal) {
-				return Start.X + Length - sub == x && Start.Y == y;
-			}
-
-			return Start.X == x && Start.Y + Length - sub == y;
-		}
-
-		private bool StartsAt (int x, int y)
-		{
-			return Start.X == x && Start.Y == y;
-		}
-
-		/// <summary>
-		/// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the 
-		/// line that is furthest left/top and Size is defined by the line that extends the furthest
-		/// right/bottom.
-		/// </summary>
-		internal Rect Bounds {
-			get {
-
-				// 0 and 1/-1 Length means a size (width or height) of 1
-				var size = Math.Max (1, Math.Abs (Length));
-
-				// How much to offset x or y to get the start of the line
-				var offset = Math.Abs (Length < 0 ? Length + 1 : 0);
-				var x = Start.X - (Orientation == Orientation.Horizontal ? offset : 0);
-				var y = Start.Y - (Orientation == Orientation.Vertical ? offset : 0);
-				var width = Orientation == Orientation.Horizontal ? size : 1;
-				var height = Orientation == Orientation.Vertical ? size : 1;
-
-				return new Rect (x, y, width, height);
-			}
-		}
-
-		/// <summary>
-		/// Formats the Line as a string in (Start.X,Start.Y,Length,Orientation) notation.
-		/// </summary>
-		public override string ToString ()
-		{
-			return $"({Start.X},{Start.Y},{Length},{Orientation})";
-		}
-	}
+
+// TODO: Add events that notify when StraightLine changes to enable dynamic layout
+/// <summary>A line between two points on a horizontal or vertical <see cref="Orientation"/> and a given style/color.</summary>
+public class StraightLine
+{
+    /// <summary>Creates a new instance of the <see cref="StraightLine"/> class.</summary>
+    /// <param name="start"></param>
+    /// <param name="length"></param>
+    /// <param name="orientation"></param>
+    /// <param name="style"></param>
+    /// <param name="attribute"></param>
+    public StraightLine (
+        Point start,
+        int length,
+        Orientation orientation,
+        LineStyle style,
+        Attribute? attribute = default
+    )
+    {
+        Start = start;
+        Length = length;
+        Orientation = orientation;
+        Style = style;
+        Attribute = attribute;
+    }
+
+    /// <summary>Gets or sets the color of the line.</summary>
+    public Attribute? Attribute { get; set; }
+
+    /// <summary>Gets or sets the length of the line.</summary>
+    public int Length { get; set; }
+
+    /// <summary>Gets or sets the orientation (horizontal or vertical) of the line.</summary>
+    public Orientation Orientation { get; set; }
+
+    /// <summary>Gets or sets where the line begins.</summary>
+    public Point Start { get; set; }
+
+    /// <summary>Gets or sets the line style of the line (e.g. dotted, double).</summary>
+    public LineStyle Style { get; set; }
+
+    /// <summary>
+    ///     Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is
+    ///     furthest left/top and Size is defined by the line that extends the furthest right/bottom.
+    /// </summary>
+    internal Rect Bounds
+    {
+        get
+        {
+            // 0 and 1/-1 Length means a size (width or height) of 1
+            int size = Math.Max (1, Math.Abs (Length));
+
+            // How much to offset x or y to get the start of the line
+            int offset = Math.Abs (Length < 0 ? Length + 1 : 0);
+            int x = Start.X - (Orientation == Orientation.Horizontal ? offset : 0);
+            int y = Start.Y - (Orientation == Orientation.Vertical ? offset : 0);
+            int width = Orientation == Orientation.Horizontal ? size : 1;
+            int height = Orientation == Orientation.Vertical ? size : 1;
+
+            return new Rect (x, y, width, height);
+        }
+    }
+
+    /// <summary>Formats the Line as a string in (Start.X,Start.Y,Length,Orientation) notation.</summary>
+    public override string ToString () { return $"({Start.X},{Start.Y},{Length},{Orientation})"; }
+
+    internal IntersectionDefinition? Intersects (int x, int y)
+    {
+        switch (Orientation)
+        {
+            case Orientation.Horizontal: return IntersectsHorizontally (x, y);
+            case Orientation.Vertical: return IntersectsVertically (x, y);
+            default: throw new ArgumentOutOfRangeException (nameof (Orientation));
+        }
+    }
+
+    private bool EndsAt (int x, int y)
+    {
+        int sub = Length == 0 ? 0 :
+                  Length > 0 ? 1 : -1;
+
+        if (Orientation == Orientation.Horizontal)
+        {
+            return Start.X + Length - sub == x && Start.Y == y;
+        }
+
+        return Start.X == x && Start.Y + Length - sub == y;
+    }
+
+    private IntersectionType GetTypeByLength (
+        IntersectionType typeWhenNegative,
+        IntersectionType typeWhenZero,
+        IntersectionType typeWhenPositive
+    )
+    {
+        if (Length == 0)
+        {
+            return typeWhenZero;
+        }
+
+        return Length < 0 ? typeWhenNegative : typeWhenPositive;
+    }
+
+    private IntersectionDefinition? IntersectsHorizontally (int x, int y)
+    {
+        if (Start.Y != y)
+        {
+            return null;
+        }
+
+        if (StartsAt (x, y))
+        {
+            return new IntersectionDefinition (
+                                               Start,
+                                               GetTypeByLength (
+                                                                IntersectionType.StartLeft,
+                                                                IntersectionType.PassOverHorizontal,
+                                                                IntersectionType.StartRight
+                                                               ),
+                                               this
+                                              );
+        }
+
+        if (EndsAt (x, y))
+        {
+            return new IntersectionDefinition (
+                                               Start,
+                                               Length < 0 ? IntersectionType.StartRight : IntersectionType.StartLeft,
+                                               this
+                                              );
+        }
+
+        int xmin = Math.Min (Start.X, Start.X + Length);
+        int xmax = Math.Max (Start.X, Start.X + Length);
+
+        if (xmin < x && xmax > x)
+        {
+            return new IntersectionDefinition (
+                                               new Point (x, y),
+                                               IntersectionType.PassOverHorizontal,
+                                               this
+                                              );
+        }
+
+        return null;
+    }
+
+    private IntersectionDefinition? IntersectsVertically (int x, int y)
+    {
+        if (Start.X != x)
+        {
+            return null;
+        }
+
+        if (StartsAt (x, y))
+        {
+            return new IntersectionDefinition (
+                                               Start,
+                                               GetTypeByLength (
+                                                                IntersectionType.StartUp,
+                                                                IntersectionType.PassOverVertical,
+                                                                IntersectionType.StartDown
+                                                               ),
+                                               this
+                                              );
+        }
+
+        if (EndsAt (x, y))
+        {
+            return new IntersectionDefinition (
+                                               Start,
+                                               Length < 0 ? IntersectionType.StartDown : IntersectionType.StartUp,
+                                               this
+                                              );
+        }
+
+        int ymin = Math.Min (Start.Y, Start.Y + Length);
+        int ymax = Math.Max (Start.Y, Start.Y + Length);
+
+        if (ymin < y && ymax > y)
+        {
+            return new IntersectionDefinition (
+                                               new Point (x, y),
+                                               IntersectionType.PassOverVertical,
+                                               this
+                                              );
+        }
+
+        return null;
+    }
+
+    private bool StartsAt (int x, int y) { return Start.X == x && Start.Y == y; }
 }

+ 235 - 212
Terminal.Gui/Drawing/StraightLineExtensions.cs

@@ -1,213 +1,236 @@
-using System;
-using System.Collections.Generic;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Extension methods for <see cref="StraightLine"/> (including collections).
-	/// </summary>
-	public static class StraightLineExtensions {
-		/// <summary>
-		/// Splits or removes all lines in the <paramref name="collection"/> such that none cover the given
-		/// exclusion area.
-		/// </summary>
-		/// <param name="collection">Lines to adjust</param>
-		/// <param name="start">First point to remove from collection</param>
-		/// <param name="length">The number of sequential points to exclude</param>
-		/// <param name="orientation">Orientation of the exclusion line</param>
-		/// <returns></returns>
-		public static IEnumerable<StraightLine> Exclude (this IEnumerable<StraightLine> collection, Point start, int length, Orientation orientation)
-		{
-			var toReturn = new List<StraightLine> ();
-			if (length == 0) {
-				return collection;
-			}
-
-			foreach (var l in collection) {
-
-				if(l.Length == 0) {
-					toReturn.Add (l);
-					continue;
-				}
-
-				// lines are parallel.  For any straight line one axis (x or y) is constant
-				// e.g. Horizontal lines have constant y
-				int econstPoint = orientation == Orientation.Horizontal ? start.Y : start.X;
-				int lconstPoint = l.Orientation == Orientation.Horizontal ? l.Start.Y : l.Start.X;
-
-				// For the varying axis what is the max/mins
-				// i.e. points on horizontal lines vary by x, vertical lines vary by y
-				int eDiffMin = GetLineStartOnDiffAxis (start, length, orientation);
-				int eDiffMax = GetLineEndOnDiffAxis (start, length, orientation);
-				int lDiffMin = GetLineStartOnDiffAxis (l.Start, l.Length, l.Orientation);
-				int lDiffMax = GetLineEndOnDiffAxis (l.Start, l.Length, l.Orientation);
-
-				// line is parallel to exclusion
-				if (l.Orientation == orientation) {
-
-					// Do the parallel lines share constant plane
-					if (econstPoint != lconstPoint) {
-
-						// No, so no way they overlap
-						toReturn.Add (l);
-					} else {
-
-						
-
-						if (lDiffMax < eDiffMin) {
-							// Line ends before exclusion starts
-							toReturn.Add (l);
-						} else if (lDiffMin > eDiffMax) {
-							// Line starts after exclusion ends
-							toReturn.Add (l);
-						} else {
-							//lines overlap!
-
-							// Is there a bit we can keep on the left?
-							if (lDiffMin < eDiffMin) {
-								// Create line up to exclusion point
-								int from = lDiffMin;
-								int len = eDiffMin - lDiffMin;
-
-								if (len > 0) {
-									toReturn.Add (CreateLineFromDiff (l, from, len));
-								}
-							}
-
-							// Is there a bit we can keep on the right?
-							if (lDiffMax > eDiffMax) {
-								// Create line up to exclusion point
-								int from = eDiffMax + 1;
-								int len = lDiffMax - eDiffMax;
-
-								if (len > 0) {
-
-									// A single line with length 1 and -1 are the same (fills only the single cell)
-									// They differ only in how they join to other lines (i.e. to create corners)
-									// Using negative for the later half of the line ensures line joins in a way
-									// consistent with its pre-snipped state.
-									if (len == 1) {
-										len = -1;
-									}
-										
-									toReturn.Add (CreateLineFromDiff (l, from, len));
-								}
-							}
-						}
-					}
-
-				} else {
-					// line is perpendicular to exclusion
-
-					// Does the constant plane of the exclusion appear within the differing plane of the line?
-					if(econstPoint >= lDiffMin && econstPoint <= lDiffMax) {
-						// Yes, e.g. Vertical exclusion's x is within xmin/xmax of the horizontal line
-
-						// Vice versa must also be true
-						// for example there is no intersection if the vertical exclusion line does not
-						// stretch down far enough to reach the line
-						if(lconstPoint >= eDiffMin && lconstPoint <= eDiffMax) {
-
-							// Perpendicular intersection occurs here
-							var intersection = l.Orientation == Orientation.Horizontal ?
-								new Point (econstPoint,lconstPoint) :
-								new Point (lconstPoint,econstPoint);
-
-							// To snip out this single point we will use a recursive call
-							// snipping 1 length along the orientation of l (i.e. parallel)
-							toReturn.AddRange (new [] { l }.Exclude (intersection, 1, l.Orientation));
-						}
-						else {
-							// No intersection
-							toReturn.Add (l);
-						}
-
-					}
-					else {
-						// Lines do not intersect
-						toReturn.Add (l);
-					}
-
-				}
-			}
-
-			return toReturn;
-		}
-
-		/// <summary>
-		/// <para>Calculates the single digit point where a line starts on the differing axis
-		/// i.e. the minimum (controlling for negative lengths).</para>
-		/// <para>
-		/// For lines with <see cref="Orientation.Horizontal"/> this is an x coordinate.
-		/// For lines that are <see cref="Orientation.Vertical"/> this is a y coordinate.
-		/// </para>
-		/// </summary>
-		/// <param name="start">Where the line starts</param>
-		/// <param name="length">Length of the line</param>
-		/// <param name="orientation">Orientation of the line</param>
-		/// <returns>The minimum x or y (whichever is differing) point on the line, controlling for negative lengths. </returns>
-		private static int GetLineStartOnDiffAxis (Point start, int length, Orientation orientation)
-		{
-			if(length == 0) {
-				throw new ArgumentException ("0 length lines are not supported", nameof (length));
-			}
-
-			var sub = length > 0 ? 1 : -1;
-
-			if (orientation == Orientation.Vertical) {
-				// Points on line differ by y
-				return Math.Min (start.Y + length - sub, start.Y);
-			}
-
-			// Points on line differ by x
-			return Math.Min (start.X + length - sub, start.X);
-		}
-
-		/// <summary>
-		/// <para>Calculates the single digit point where a line ends on the differing axis
-		/// i.e. the maximum (controlling for negative lengths).</para>
-		/// <para>
-		/// For lines with <see cref="Orientation.Horizontal"/> this is an x coordinate.
-		/// For lines that are <see cref="Orientation.Vertical"/> this is a y coordinate.
-		/// </para>
-		/// </summary>
-		/// <param name="start">Where the line starts</param>
-		/// <param name="length">Length of the line</param>
-		/// <param name="orientation">Orientation of the line</param>
-		/// <returns>The maximum x or y (whichever is differing) point on the line, controlling for negative lengths. </returns>
-		private static int GetLineEndOnDiffAxis (Point start, int length, Orientation orientation)
-		{
-			if (length == 0) {
-				throw new ArgumentException ("0 length lines are not supported", nameof (length));
-			}
-
-			var sub = length > 0 ? 1 : -1;
-
-			if (orientation == Orientation.Vertical) {
-				// Points on line differ by y
-				return Math.Max (start.Y + length - sub, start.Y);
-			}
-
-			// Points on line differ by x
-			return Math.Max (start.X + length - sub, start.X);
-		}
-
-		/// <summary>
-		/// Creates a new line which is part of <paramref name="l"/> from the point on the varying
-		/// axis <paramref name="from"/> to <paramref name="length"/>.  Horizontal lines have points that
-		/// vary by x while vertical lines have points that vary by y
-		/// </summary>
-		/// <param name="l">Line to create sub part from</param>
-		/// <param name="from">Point on varying axis to start at</param>
-		/// <param name="length">Length of line to return</param>
-		/// <returns>The new line</returns>
-		private static StraightLine CreateLineFromDiff (StraightLine l, int from, int length)
-		{
-			var start = new Point (
-				l.Orientation == Orientation.Horizontal ? from : l.Start.X,
-				l.Orientation == Orientation.Horizontal ? l.Start.Y : from);
-
-			return new StraightLine (start, length, l.Orientation, l.Style, l.Attribute);
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>Extension methods for <see cref="StraightLine"/> (including collections).</summary>
+public static class StraightLineExtensions
+{
+    /// <summary>
+    ///     Splits or removes all lines in the <paramref name="collection"/> such that none cover the given exclusion
+    ///     area.
+    /// </summary>
+    /// <param name="collection">Lines to adjust</param>
+    /// <param name="start">First point to remove from collection</param>
+    /// <param name="length">The number of sequential points to exclude</param>
+    /// <param name="orientation">Orientation of the exclusion line</param>
+    /// <returns></returns>
+    public static IEnumerable<StraightLine> Exclude (
+        this IEnumerable<StraightLine> collection,
+        Point start,
+        int length,
+        Orientation orientation
+    )
+    {
+        List<StraightLine> toReturn = new ();
+
+        if (length == 0)
+        {
+            return collection;
+        }
+
+        foreach (StraightLine l in collection)
+        {
+            if (l.Length == 0)
+            {
+                toReturn.Add (l);
+
+                continue;
+            }
+
+            // lines are parallel.  For any straight line one axis (x or y) is constant
+            // e.g. Horizontal lines have constant y
+            int econstPoint = orientation == Orientation.Horizontal ? start.Y : start.X;
+            int lconstPoint = l.Orientation == Orientation.Horizontal ? l.Start.Y : l.Start.X;
+
+            // For the varying axis what is the max/mins
+            // i.e. points on horizontal lines vary by x, vertical lines vary by y
+            int eDiffMin = GetLineStartOnDiffAxis (start, length, orientation);
+            int eDiffMax = GetLineEndOnDiffAxis (start, length, orientation);
+            int lDiffMin = GetLineStartOnDiffAxis (l.Start, l.Length, l.Orientation);
+            int lDiffMax = GetLineEndOnDiffAxis (l.Start, l.Length, l.Orientation);
+
+            // line is parallel to exclusion
+            if (l.Orientation == orientation)
+            {
+                // Do the parallel lines share constant plane
+                if (econstPoint != lconstPoint)
+                {
+                    // No, so no way they overlap
+                    toReturn.Add (l);
+                }
+                else
+                {
+                    if (lDiffMax < eDiffMin)
+                    {
+                        // Line ends before exclusion starts
+                        toReturn.Add (l);
+                    }
+                    else if (lDiffMin > eDiffMax)
+                    {
+                        // Line starts after exclusion ends
+                        toReturn.Add (l);
+                    }
+                    else
+                    {
+                        //lines overlap!
+
+                        // Is there a bit we can keep on the left?
+                        if (lDiffMin < eDiffMin)
+                        {
+                            // Create line up to exclusion point
+                            int from = lDiffMin;
+                            int len = eDiffMin - lDiffMin;
+
+                            if (len > 0)
+                            {
+                                toReturn.Add (CreateLineFromDiff (l, from, len));
+                            }
+                        }
+
+                        // Is there a bit we can keep on the right?
+                        if (lDiffMax > eDiffMax)
+                        {
+                            // Create line up to exclusion point
+                            int from = eDiffMax + 1;
+                            int len = lDiffMax - eDiffMax;
+
+                            if (len > 0)
+                            {
+                                // A single line with length 1 and -1 are the same (fills only the single cell)
+                                // They differ only in how they join to other lines (i.e. to create corners)
+                                // Using negative for the later half of the line ensures line joins in a way
+                                // consistent with its pre-snipped state.
+                                if (len == 1)
+                                {
+                                    len = -1;
+                                }
+
+                                toReturn.Add (CreateLineFromDiff (l, from, len));
+                            }
+                        }
+                    }
+                }
+            }
+            else
+            {
+                // line is perpendicular to exclusion
+
+                // Does the constant plane of the exclusion appear within the differing plane of the line?
+                if (econstPoint >= lDiffMin && econstPoint <= lDiffMax)
+                {
+                    // Yes, e.g. Vertical exclusion's x is within xmin/xmax of the horizontal line
+
+                    // Vice versa must also be true
+                    // for example there is no intersection if the vertical exclusion line does not
+                    // stretch down far enough to reach the line
+                    if (lconstPoint >= eDiffMin && lconstPoint <= eDiffMax)
+                    {
+                        // Perpendicular intersection occurs here
+                        Point intersection = l.Orientation == Orientation.Horizontal
+                                                 ? new Point (econstPoint, lconstPoint)
+                                                 : new Point (lconstPoint, econstPoint);
+
+                        // To snip out this single point we will use a recursive call
+                        // snipping 1 length along the orientation of l (i.e. parallel)
+                        toReturn.AddRange (new [] { l }.Exclude (intersection, 1, l.Orientation));
+                    }
+                    else
+                    {
+                        // No intersection
+                        toReturn.Add (l);
+                    }
+                }
+                else
+                {
+                    // Lines do not intersect
+                    toReturn.Add (l);
+                }
+            }
+        }
+
+        return toReturn;
+    }
+
+    /// <summary>
+    ///     Creates a new line which is part of <paramref name="l"/> from the point on the varying axis
+    ///     <paramref name="from"/> to <paramref name="length"/>.  Horizontal lines have points that vary by x while vertical
+    ///     lines have points that vary by y
+    /// </summary>
+    /// <param name="l">Line to create sub part from</param>
+    /// <param name="from">Point on varying axis to start at</param>
+    /// <param name="length">Length of line to return</param>
+    /// <returns>The new line</returns>
+    private static StraightLine CreateLineFromDiff (StraightLine l, int from, int length)
+    {
+        var start = new Point (
+                               l.Orientation == Orientation.Horizontal ? from : l.Start.X,
+                               l.Orientation == Orientation.Horizontal ? l.Start.Y : from
+                              );
+
+        return new StraightLine (start, length, l.Orientation, l.Style, l.Attribute);
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Calculates the single digit point where a line ends on the differing axis i.e. the maximum (controlling for
+    ///         negative lengths).
+    ///     </para>
+    ///     <para>
+    ///         For lines with <see cref="Orientation.Horizontal"/> this is an x coordinate. For lines that are
+    ///         <see cref="Orientation.Vertical"/> this is a y coordinate.
+    ///     </para>
+    /// </summary>
+    /// <param name="start">Where the line starts</param>
+    /// <param name="length">Length of the line</param>
+    /// <param name="orientation">Orientation of the line</param>
+    /// <returns>The maximum x or y (whichever is differing) point on the line, controlling for negative lengths. </returns>
+    private static int GetLineEndOnDiffAxis (Point start, int length, Orientation orientation)
+    {
+        if (length == 0)
+        {
+            throw new ArgumentException ("0 length lines are not supported", nameof (length));
+        }
+
+        int sub = length > 0 ? 1 : -1;
+
+        if (orientation == Orientation.Vertical)
+        {
+            // Points on line differ by y
+            return Math.Max (start.Y + length - sub, start.Y);
+        }
+
+        // Points on line differ by x
+        return Math.Max (start.X + length - sub, start.X);
+    }
+
+    /// <summary>
+    ///     <para>
+    ///         Calculates the single digit point where a line starts on the differing axis i.e. the minimum (controlling for
+    ///         negative lengths).
+    ///     </para>
+    ///     <para>
+    ///         For lines with <see cref="Orientation.Horizontal"/> this is an x coordinate. For lines that are
+    ///         <see cref="Orientation.Vertical"/> this is a y coordinate.
+    ///     </para>
+    /// </summary>
+    /// <param name="start">Where the line starts</param>
+    /// <param name="length">Length of the line</param>
+    /// <param name="orientation">Orientation of the line</param>
+    /// <returns>The minimum x or y (whichever is differing) point on the line, controlling for negative lengths. </returns>
+    private static int GetLineStartOnDiffAxis (Point start, int length, Orientation orientation)
+    {
+        if (length == 0)
+        {
+            throw new ArgumentException ("0 length lines are not supported", nameof (length));
+        }
+
+        int sub = length > 0 ? 1 : -1;
+
+        if (orientation == Orientation.Vertical)
+        {
+            // Points on line differ by y
+            return Math.Min (start.Y + length - sub, start.Y);
+        }
+
+        // Points on line differ by x
+        return Math.Min (start.X + length - sub, start.X);
+    }
 }

+ 298 - 298
Terminal.Gui/Drawing/Thickness.cs

@@ -1,298 +1,298 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json.Serialization;
-using Terminal.Gui;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Describes the thickness of a frame around a rectangle. Four <see cref="int"/> values describe
-	///  the <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides
-	///  of the rectangle, respectively.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Use the helper API (<see cref="GetInside(Rect)"/> to get the rectangle describing the insides of the frame,
-	/// with the thickness widths subtracted.
-	/// </para>
-	/// <para>
-	/// Use the helper API (<see cref="Draw(Rect, string)"/> to draw the frame with the specified thickness.
-	/// </para>
-	/// </remarks>
-	public class Thickness : IEquatable<Thickness> {
-		private int validate (int width)
-		{
-			if (width < 0) {
-				throw new ArgumentException ("Thickness widths cannot be negative.");
-			}
-			return width;
-		}
-
-		/// <summary>
-		/// Gets or sets the width of the left side of the rectangle.
-		/// </summary>
-		[JsonInclude]
-		public int Left;
-
-		/// <summary>
-		/// Gets or sets the width of the upper side of the rectangle.
-		/// </summary>
-		[JsonInclude]
-		public int Top;
-
-		/// <summary>
-		/// Gets or sets the width of the right side of the rectangle.
-		/// </summary>
-		[JsonInclude]
-		public int Right;
-
-		/// <summary>
-		/// Gets or sets the width of the lower side of the rectangle.
-		/// </summary>
-		[JsonInclude]
-		public int Bottom;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Thickness"/> class with all widths
-		/// set to 0.
-		/// </summary>
-		public Thickness () { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.
-		/// </summary>
-		/// <param name="width"></param>
-		public Thickness (int width) : this (width, width, width, width) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Thickness"/> class that has specific
-		///  widths applied to each side of the rectangle.
-		/// </summary>
-		/// <param name="left"></param>
-		/// <param name="top"></param>
-		/// <param name="right"></param>
-		/// <param name="bottom"></param>
-		public Thickness (int left, int top, int right, int bottom)
-		{
-			Left = left;
-			Top = top;
-			Right = right;
-			Bottom = bottom;
-		}
-
-		/// <summary>
-		/// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom sides of the rectangle to half the specified value.
-		/// </summary>
-		public int Vertical {
-			get {
-				return Top + Bottom;
-			}
-			set {
-				Top = Bottom = value / 2;
-			}
-		}
-
-		/// <summary>
-		/// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides of the rectangle to half the specified value.
-		/// </summary>
-		public int Horizontal {
-			get {
-				return Left + Right;
-			}
-			set {
-				Left = Right = value / 2;
-			}
-		}
-
-		/// <summary>
-		/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside of
-		/// the rectangle described by <see cref="GetInside(Rect)"/>.
-		/// </summary>
-		/// <param name="outside">Describes the location and size of the rectangle that contains the thickness.</param>
-		/// <param name="x">The x coord to check.</param>
-		/// <param name="y">The y coord to check.</param>
-		/// <returns><see langword="true"/> if the specified coordinate is within the thickness; <see langword="false"/> otherwise.</returns>
-		public bool Contains (Rect outside, int x, int y)
-		{
-			var inside = GetInside (outside);
-			return outside.Contains (x, y) && !inside.Contains (x, y);
-		}
-
-		/// <summary>
-		/// Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/>
-		/// with the thickness widths subtracted. The height and width of the returned rectangle will
-		/// never be less than 0.
-		/// </summary>
-		/// <remarks>If a thickness width is negative, the inside rectangle will be larger than <paramref name="rect"/>. e.g.
-		/// a <c>Thickness (-1, -1, -1, -1) will result in a rectangle skewed -1 in the X and Y directions and 
-		/// with a Size increased by 1.</c></remarks>
-		/// <param name="rect">The source rectangle</param>
-		/// <returns></returns>
-		public Rect GetInside (Rect rect)
-		{
-			var x = rect.X + Left;
-			var y = rect.Y + Top;
-			var width = Math.Max (0, rect.Size.Width - Horizontal);
-			var height = Math.Max (0, rect.Size.Height - Vertical);
-			return new Rect (new Point (x, y), new Size (width, height));
-		}
-
-		/// <summary>
-		/// Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.
-		/// </summary>
-		/// <remarks>
-		/// If <see cref="ConsoleDriver.DiagnosticFlags"/> is set to <see cref="ConsoleDriver.DiagnosticFlags.FramePadding"/> then
-		/// 'T', 'L', 'R', and 'B' glyphs will be used instead of space. If <see cref="ConsoleDriver.DiagnosticFlags"/>
-		/// is set to <see cref="ConsoleDriver.DiagnosticFlags.FrameRuler"/> then a ruler will be drawn on the outer edge of the
-		/// Thickness.
-		/// </remarks>
-		/// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in 
-		/// screen coordinates.</param>
-		/// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
-		/// <returns>The inner rectangle remaining to be drawn.</returns>
-		public Rect Draw (Rect rect, string label = null)
-		{
-			if (rect.Size.Width < 1 || rect.Size.Height < 1) {
-				return Rect.Empty;
-			}
-
-			Rune clearChar = (Rune)' ';
-			Rune leftChar = clearChar;
-			Rune rightChar = clearChar;
-			Rune topChar = clearChar;
-			Rune bottomChar = clearChar;
-
-			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) {
-				leftChar = (Rune)'L';
-				rightChar = (Rune)'R';
-				topChar = (Rune)'T';
-				bottomChar = (Rune)'B';
-				if (!string.IsNullOrEmpty (label)) {
-					leftChar = rightChar = bottomChar = topChar = (Rune)label [0];
-				}
-			}
-
-			// Draw the Top side
-			if (Top > 0) {
-				Application.Driver.FillRect (new Rect (rect.X, rect.Y, rect.Width, Math.Min (rect.Height, Top)), topChar);
-			}
-
-			// Draw the Left side
-			if (Left > 0) {
-				Application.Driver.FillRect (new Rect (rect.X, rect.Y, Math.Min (rect.Width, Left), rect.Height), leftChar);
-			}
-
-			// Draw the Right side			
-			if (Right > 0) {
-				Application.Driver.FillRect (new Rect (Math.Max (0, rect.X + rect.Width - Right), rect.Y, Math.Min (rect.Width, Right), rect.Height), rightChar);
-			}
-
-			// Draw the Bottom side
-			if (Bottom > 0) {
-				Application.Driver.FillRect (new Rect (rect.X, rect.Y + Math.Max (0, rect.Height - Bottom), rect.Width, Bottom), bottomChar);
-			}
-
-			// TODO: This should be moved to LineCanvas as a new LineStyle.Ruler
-			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) {
-				// Top
-				var hruler = new Ruler () { Length = rect.Width, Orientation = Orientation.Horizontal };
-				if (Top > 0) {
-					hruler.Draw (new Point (rect.X, rect.Y));
-				}
-
-				//Left
-				var vruler = new Ruler () { Length = rect.Height - 2, Orientation = Orientation.Vertical };
-				if (Left > 0) {
-					vruler.Draw (new Point (rect.X, rect.Y + 1), 1);
-				}
-
-				// Bottom
-				if (Bottom > 0) {
-					hruler.Draw (new Point (rect.X, rect.Y + rect.Height - 1));
-				}
-
-				// Right
-				if (Right > 0) {
-					vruler.Draw (new Point (rect.X + rect.Width - 1, rect.Y + 1), 1);
-				}
-			}
-
-			if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) {
-				// Draw the diagnostics label on the bottom
-				var tf = new TextFormatter () {
-					Text = label == null ? string.Empty : $"{label} {this}",
-					Alignment = TextAlignment.Centered,
-					VerticalAlignment = VerticalTextAlignment.Bottom
-				};
-				tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false);
-			}
-
-			return GetInside (rect);
-
-		}
-
-		// TODO: add operator overloads
-		/// <summary>
-		/// Gets an empty thickness.
-		/// </summary>
-		public static Thickness Empty => new Thickness (0);
-
-		/// <summary>Determines whether the specified object is equal to the current object.</summary>
-		/// <param name="obj">The object to compare with the current object.</param>
-		/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
-		public override bool Equals (object obj)
-		{
-			//Check for null and compare run-time types.
-			if ((obj == null) || !this.GetType ().Equals (obj.GetType ())) {
-				return false;
-			} else {
-				return Equals ((Thickness)obj);
-			}
-		}
-
-		/// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
-		/// <returns>The thickness widths as a string.</returns>
-		public override string ToString ()
-		{
-			return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})";
-		}
-
-		// IEquitable
-		/// <summary>
-		/// Indicates whether the current object is equal to another object of the same type.
-		/// </summary>
-		/// <param name="other"></param>
-		/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
-		public bool Equals (Thickness other)
-		{
-			return other is not null &&
-			       Left == other.Left &&
-			       Right == other.Right &&
-			       Top == other.Top &&
-			       Bottom == other.Bottom;
-		}
-
-		/// <inheritdoc/>
-		public override int GetHashCode ()
-		{
-			int hashCode = 1380952125;
-			hashCode = hashCode * -1521134295 + Left.GetHashCode ();
-			hashCode = hashCode * -1521134295 + Right.GetHashCode ();
-			hashCode = hashCode * -1521134295 + Top.GetHashCode ();
-			hashCode = hashCode * -1521134295 + Bottom.GetHashCode ();
-			return hashCode;
-		}
-
-		/// <inheritdoc/>
-		public static bool operator == (Thickness left, Thickness right)
-		{
-			return EqualityComparer<Thickness>.Default.Equals (left, right);
-		}
-
-		/// <inheritdoc/>
-		public static bool operator != (Thickness left, Thickness right)
-		{
-			return !(left == right);
-		}
-	}
-}
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Describes the thickness of a frame around a rectangle. Four <see cref="int"/> values describe the
+///     <see cref="Left"/>, <see cref="Top"/>, <see cref="Right"/>, and <see cref="Bottom"/> sides of the rectangle,
+///     respectively.
+/// </summary>
+/// <remarks>
+///     <para>
+///         Use the helper API (<see cref="GetInside(Rect)"/> to get the rectangle describing the insides of the frame,
+///         with the thickness widths subtracted.
+///     </para>
+///     <para>Use the helper API (<see cref="Draw(Rect, string)"/> to draw the frame with the specified thickness.</para>
+/// </remarks>
+public class Thickness : IEquatable<Thickness>
+{
+    /// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
+    [JsonInclude]
+    public int Bottom;
+
+    /// <summary>Gets or sets the width of the left side of the rectangle.</summary>
+    [JsonInclude]
+    public int Left;
+
+    /// <summary>Gets or sets the width of the right side of the rectangle.</summary>
+    [JsonInclude]
+    public int Right;
+
+    /// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
+    [JsonInclude]
+    public int Top;
+
+    /// <summary>Initializes a new instance of the <see cref="Thickness"/> class with all widths set to 0.</summary>
+    public Thickness () { }
+
+    /// <summary>Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.</summary>
+    /// <param name="width"></param>
+    public Thickness (int width) : this (width, width, width, width) { }
+
+    /// <summary>
+    ///     Initializes a new instance of the <see cref="Thickness"/> class that has specific widths applied to each side
+    ///     of the rectangle.
+    /// </summary>
+    /// <param name="left"></param>
+    /// <param name="top"></param>
+    /// <param name="right"></param>
+    /// <param name="bottom"></param>
+    public Thickness (int left, int top, int right, int bottom)
+    {
+        Left = left;
+        Top = top;
+        Right = right;
+        Bottom = bottom;
+    }
+
+    // TODO: add operator overloads
+    /// <summary>Gets an empty thickness.</summary>
+    public static Thickness Empty => new (0);
+
+    /// <summary>
+    ///     Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
+    ///     of the rectangle to half the specified value.
+    /// </summary>
+    public int Horizontal
+    {
+        get => Left + Right;
+        set => Left = Right = value / 2;
+    }
+
+    /// <summary>
+    ///     Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom
+    ///     sides of the rectangle to half the specified value.
+    /// </summary>
+    public int Vertical
+    {
+        get => Top + Bottom;
+        set => Top = Bottom = value / 2;
+    }
+
+    // IEquitable
+    /// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
+    /// <param name="other"></param>
+    /// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
+    public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; }
+
+    /// <summary>
+    ///     Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside of
+    ///     the rectangle described by <see cref="GetInside(Rect)"/>.
+    /// </summary>
+    /// <param name="outside">Describes the location and size of the rectangle that contains the thickness.</param>
+    /// <param name="x">The x coord to check.</param>
+    /// <param name="y">The y coord to check.</param>
+    /// <returns><see langword="true"/> if the specified coordinate is within the thickness; <see langword="false"/> otherwise.</returns>
+    public bool Contains (Rect outside, int x, int y)
+    {
+        Rect inside = GetInside (outside);
+
+        return outside.Contains (x, y) && !inside.Contains (x, y);
+    }
+
+    /// <summary>Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.</summary>
+    /// <remarks>
+    ///     If <see cref="ConsoleDriver.DiagnosticFlags"/> is set to
+    ///     <see cref="ConsoleDriver.DiagnosticFlags.FramePadding"/> then 'T', 'L', 'R', and 'B' glyphs will be used instead of
+    ///     space. If <see cref="ConsoleDriver.DiagnosticFlags"/> is set to
+    ///     <see cref="ConsoleDriver.DiagnosticFlags.FrameRuler"/> then a ruler will be drawn on the outer edge of the
+    ///     Thickness.
+    /// </remarks>
+    /// <param name="rect">The location and size of the rectangle that bounds the thickness rectangle, in screen coordinates.</param>
+    /// <param name="label">The diagnostics label to draw on the bottom of the <see cref="Bottom"/>.</param>
+    /// <returns>The inner rectangle remaining to be drawn.</returns>
+    public Rect Draw (Rect rect, string label = null)
+    {
+        if (rect.Size.Width < 1 || rect.Size.Height < 1)
+        {
+            return Rect.Empty;
+        }
+
+        var clearChar = (Rune)' ';
+        Rune leftChar = clearChar;
+        Rune rightChar = clearChar;
+        Rune topChar = clearChar;
+        Rune bottomChar = clearChar;
+
+        if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding)
+            == ConsoleDriver.DiagnosticFlags.FramePadding)
+        {
+            leftChar = (Rune)'L';
+            rightChar = (Rune)'R';
+            topChar = (Rune)'T';
+            bottomChar = (Rune)'B';
+
+            if (!string.IsNullOrEmpty (label))
+            {
+                leftChar = rightChar = bottomChar = topChar = (Rune)label [0];
+            }
+        }
+
+        // Draw the Top side
+        if (Top > 0)
+        {
+            Application.Driver.FillRect (new Rect (rect.X, rect.Y, rect.Width, Math.Min (rect.Height, Top)), topChar);
+        }
+
+        // Draw the Left side
+        if (Left > 0)
+        {
+            Application.Driver.FillRect (new Rect (rect.X, rect.Y, Math.Min (rect.Width, Left), rect.Height), leftChar);
+        }
+
+        // Draw the Right side			
+        if (Right > 0)
+        {
+            Application.Driver.FillRect (
+                                         new Rect (
+                                                   Math.Max (0, rect.X + rect.Width - Right),
+                                                   rect.Y,
+                                                   Math.Min (rect.Width, Right),
+                                                   rect.Height
+                                                  ),
+                                         rightChar
+                                        );
+        }
+
+        // Draw the Bottom side
+        if (Bottom > 0)
+        {
+            Application.Driver.FillRect (
+                                         new Rect (
+                                                   rect.X,
+                                                   rect.Y + Math.Max (0, rect.Height - Bottom),
+                                                   rect.Width,
+                                                   Bottom
+                                                  ),
+                                         bottomChar
+                                        );
+        }
+
+        // TODO: This should be moved to LineCanvas as a new LineStyle.Ruler
+        if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler)
+            == ConsoleDriver.DiagnosticFlags.FrameRuler)
+        {
+            // Top
+            var hruler = new Ruler { Length = rect.Width, Orientation = Orientation.Horizontal };
+
+            if (Top > 0)
+            {
+                hruler.Draw (new Point (rect.X, rect.Y));
+            }
+
+            //Left
+            var vruler = new Ruler { Length = rect.Height - 2, Orientation = Orientation.Vertical };
+
+            if (Left > 0)
+            {
+                vruler.Draw (new Point (rect.X, rect.Y + 1), 1);
+            }
+
+            // Bottom
+            if (Bottom > 0)
+            {
+                hruler.Draw (new Point (rect.X, rect.Y + rect.Height - 1));
+            }
+
+            // Right
+            if (Right > 0)
+            {
+                vruler.Draw (new Point (rect.X + rect.Width - 1, rect.Y + 1), 1);
+            }
+        }
+
+        if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding)
+            == ConsoleDriver.DiagnosticFlags.FramePadding)
+        {
+            // Draw the diagnostics label on the bottom
+            var tf = new TextFormatter
+            {
+                Text = label == null ? string.Empty : $"{label} {this}",
+                Alignment = TextAlignment.Centered,
+                VerticalAlignment = VerticalTextAlignment.Bottom
+            };
+            tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false);
+        }
+
+        return GetInside (rect);
+    }
+
+    /// <summary>Determines whether the specified object is equal to the current object.</summary>
+    /// <param name="obj">The object to compare with the current object.</param>
+    /// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
+    public override bool Equals (object obj)
+    {
+        //Check for null and compare run-time types.
+        if (obj == null || !GetType ().Equals (obj.GetType ()))
+        {
+            return false;
+        }
+
+        return Equals ((Thickness)obj);
+    }
+
+    /// <inheritdoc/>
+    public override int GetHashCode ()
+    {
+        var hashCode = 1380952125;
+        hashCode = hashCode * -1521134295 + Left.GetHashCode ();
+        hashCode = hashCode * -1521134295 + Right.GetHashCode ();
+        hashCode = hashCode * -1521134295 + Top.GetHashCode ();
+        hashCode = hashCode * -1521134295 + Bottom.GetHashCode ();
+
+        return hashCode;
+    }
+
+    /// <summary>
+    ///     Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/> with the
+    ///     thickness widths subtracted. The height and width of the returned rectangle will never be less than 0.
+    /// </summary>
+    /// <remarks>
+    ///     If a thickness width is negative, the inside rectangle will be larger than <paramref name="rect"/>. e.g. a
+    ///     <c>
+    ///         Thickness (-1, -1, -1, -1) will result in a rectangle skewed -1 in the X and Y directions and with a Size
+    ///         increased by 1.
+    ///     </c>
+    /// </remarks>
+    /// <param name="rect">The source rectangle</param>
+    /// <returns></returns>
+    public Rect GetInside (Rect rect)
+    {
+        int x = rect.X + Left;
+        int y = rect.Y + Top;
+        int width = Math.Max (0, rect.Size.Width - Horizontal);
+        int height = Math.Max (0, rect.Size.Height - Vertical);
+
+        return new Rect (new Point (x, y), new Size (width, height));
+    }
+
+    /// <inheritdoc/>
+    public static bool operator == (Thickness left, Thickness right) { return EqualityComparer<Thickness>.Default.Equals (left, right); }
+
+    /// <inheritdoc/>
+    public static bool operator != (Thickness left, Thickness right) { return !(left == right); }
+
+    /// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
+    /// <returns>The thickness widths as a string.</returns>
+    public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; }
+
+    private int validate (int width)
+    {
+        if (width < 0)
+        {
+            throw new ArgumentException ("Thickness widths cannot be negative.");
+        }
+
+        return width;
+    }
+}

+ 12 - 24
Terminal.Gui/Drawing/ThicknessEventArgs.cs

@@ -1,28 +1,16 @@
-using System;
+#nullable enable
 
-#nullable enable
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event arguments for the <see cref="Thickness"/> events.
-	/// </summary>
-	public class ThicknessEventArgs : EventArgs {
+/// <summary>Event arguments for the <see cref="Thickness"/> events.</summary>
+public class ThicknessEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="ThicknessEventArgs"/></summary>
+    public ThicknessEventArgs () { }
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ThicknessEventArgs"/>
-		/// </summary>
-		public ThicknessEventArgs ()
-		{
-		}
+    /// <summary>The previous Thickness.</summary>
+    public Thickness PreviousThickness { get; set; } = Thickness.Empty;
 
-		/// <summary>
-		/// The new Thickness.
-		/// </summary>
-		public Thickness Thickness { get; set; } = Thickness.Empty;
-
-		/// <summary>
-		/// The previous Thickness.
-		/// </summary>
-		public Thickness PreviousThickness { get; set; } = Thickness.Empty;
-	}
-}
+    /// <summary>The new Thickness.</summary>
+    public Thickness Thickness { get; set; } = Thickness.Empty;
+}

+ 93 - 116
Terminal.Gui/FileServices/AllowedType.cs

@@ -1,116 +1,93 @@
-using System;
-using System.CodeDom;
-using System.Data;
-using System.IO;
-using System.Linq;
-using Terminal.Gui.Resources;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Interface for <see cref="FileDialog"/> restrictions on which file type(s) the
-	/// user is allowed to select/enter.
-	/// </summary>
-	public interface IAllowedType
-	{
-		/// <summary>
-		/// Returns true if the file at <paramref name="path"/> is compatible with this
-		/// allow option.  Note that the file may not exist (e.g. in the case of saving).
-		/// </summary>
-		/// <param name="path"></param>
-		/// <returns></returns>
-		bool IsAllowed (string path);
-	}
-
-	/// <summary>
-	/// <see cref="IAllowedType"/> that allows selection of any types (*.*).
-	/// </summary>
-	public class AllowedTypeAny : IAllowedType {
-
-		/// <inheritdoc/>
-		public bool IsAllowed (string path)
-		{
-			return true;
-		}
-
-		/// <summary>
-		/// Returns a string representation of this <see cref="AllowedTypeAny"/>.
-		/// </summary>
-		/// <returns></returns>
-		public override string ToString ()
-		{
-			return Strings.fdAnyFiles + "(*.*)";
-		}
-	}
-
-	/// <summary>
-	/// Describes a requirement on what <see cref="FileInfo"/> can be selected.
-	/// This can be combined with other <see cref="IAllowedType"/> in a <see cref="FileDialog"/>
-	/// to for example show only .csv files but let user change to open any if they want.
-	/// </summary>
-	public class AllowedType : IAllowedType {
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="AllowedType"/> class.
-		/// </summary>
-		/// <param name="description">The human readable text to display.</param>
-		/// <param name="extensions">Extension(s) to match e.g. .csv.</param>
-		public AllowedType (string description, params string [] extensions)
-		{
-			if (extensions.Length == 0) {
-				throw new ArgumentException ("You must supply at least one extension");
-			}
-
-			this.Description = description;
-			this.Extensions = extensions;
-		}
-
-		/// <summary>
-		/// Gets or Sets the human readable description for the file type
-		/// e.g. "Comma Separated Values".
-		/// </summary>
-		public string Description { get; set; }
-
-		/// <summary>
-		/// Gets or Sets the permitted file extension(s) (e.g. ".csv").
-		/// </summary>
-		public string [] Extensions { get; set; }
-
-		/// <summary>
-		/// Returns <see cref="Description"/> plus all <see cref="Extensions"/> separated by semicolons.
-		/// </summary>
-		public override string ToString ()
-		{
-			const int maxLength = 30;
-
-			var desc = $"{this.Description} ({string.Join (";", this.Extensions.Select (e => '*' + e).ToArray ())})";
-
-			if(desc.Length > maxLength) {
-				return desc.Substring (0, maxLength-2) + "…";
-			}
-			return desc;
-		}
-
-		/// <inheritdoc/>
-		public bool IsAllowed(string path)
-		{
-			if(string.IsNullOrWhiteSpace(path)) {
-				return false;
-			}
-
-			var extension = Path.GetExtension (path);
-
-			if(this.Extensions.Any(e=>path.EndsWith(e, StringComparison.InvariantCultureIgnoreCase))) {
-				return true;
-			}
-
-			// There is a requirement to have a particular extension and we have none
-			if (string.IsNullOrEmpty (extension)) {
-				return false;
-			}
-
-			return this.Extensions.Any (e => e.Equals (extension, StringComparison.InvariantCultureIgnoreCase));
-		}
-	}
-	
-}
+using Terminal.Gui.Resources;
+
+namespace Terminal.Gui;
+
+/// <summary>Interface for <see cref="FileDialog"/> restrictions on which file type(s) the user is allowed to select/enter.</summary>
+public interface IAllowedType
+{
+    /// <summary>
+    ///     Returns true if the file at <paramref name="path"/> is compatible with this allow option.  Note that the file
+    ///     may not exist (e.g. in the case of saving).
+    /// </summary>
+    /// <param name="path"></param>
+    /// <returns></returns>
+    bool IsAllowed (string path);
+}
+
+/// <summary><see cref="IAllowedType"/> that allows selection of any types (*.*).</summary>
+public class AllowedTypeAny : IAllowedType
+{
+    /// <inheritdoc/>
+    public bool IsAllowed (string path) { return true; }
+
+    /// <summary>Returns a string representation of this <see cref="AllowedTypeAny"/>.</summary>
+    /// <returns></returns>
+    public override string ToString () { return Strings.fdAnyFiles + "(*.*)"; }
+}
+
+/// <summary>
+///     Describes a requirement on what <see cref="FileInfo"/> can be selected. This can be combined with other
+///     <see cref="IAllowedType"/> in a <see cref="FileDialog"/> to for example show only .csv files but let user change to
+///     open any if they want.
+/// </summary>
+public class AllowedType : IAllowedType
+{
+    /// <summary>Initializes a new instance of the <see cref="AllowedType"/> class.</summary>
+    /// <param name="description">The human readable text to display.</param>
+    /// <param name="extensions">Extension(s) to match e.g. .csv.</param>
+    public AllowedType (string description, params string [] extensions)
+    {
+        if (extensions.Length == 0)
+        {
+            throw new ArgumentException ("You must supply at least one extension");
+        }
+
+        Description = description;
+        Extensions = extensions;
+    }
+
+    /// <summary>Gets or Sets the human readable description for the file type e.g. "Comma Separated Values".</summary>
+    public string Description { get; set; }
+
+    /// <summary>Gets or Sets the permitted file extension(s) (e.g. ".csv").</summary>
+    public string [] Extensions { get; set; }
+
+    /// <inheritdoc/>
+    public bool IsAllowed (string path)
+    {
+        if (string.IsNullOrWhiteSpace (path))
+        {
+            return false;
+        }
+
+        string extension = Path.GetExtension (path);
+
+        if (Extensions.Any (e => path.EndsWith (e, StringComparison.InvariantCultureIgnoreCase)))
+        {
+            return true;
+        }
+
+        // There is a requirement to have a particular extension and we have none
+        if (string.IsNullOrEmpty (extension))
+        {
+            return false;
+        }
+
+        return Extensions.Any (e => e.Equals (extension, StringComparison.InvariantCultureIgnoreCase));
+    }
+
+    /// <summary>Returns <see cref="Description"/> plus all <see cref="Extensions"/> separated by semicolons.</summary>
+    public override string ToString ()
+    {
+        const int maxLength = 30;
+
+        var desc = $"{Description} ({string.Join (";", Extensions.Select (e => '*' + e).ToArray ())})";
+
+        if (desc.Length > maxLength)
+        {
+            return desc.Substring (0, maxLength - 2) + "…";
+        }
+
+        return desc;
+    }
+}

+ 167 - 137
Terminal.Gui/FileServices/DefaultFileOperations.cs

@@ -1,140 +1,170 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
 using System.IO.Abstractions;
-using System.Linq;
 using Terminal.Gui.Resources;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Default file operation handlers using modal dialogs.
-	/// </summary>
-	public class DefaultFileOperations : IFileOperations {
-
-		/// <inheritdoc/>
-		public bool Delete (IEnumerable<IFileSystemInfo> toDelete)
-		{
-			// Default implementation does not allow deleting multiple files
-			if (toDelete.Count () != 1) {
-				return false;
-			}
-			var d = toDelete.Single ();
-			var adjective = d.Name;
-
-			int result = MessageBox.Query (
-				string.Format (Strings.fdDeleteTitle, adjective),
-				string.Format (Strings.fdDeleteBody, adjective),
-				Strings.btnYes, Strings.btnNo);
-
-			try {
-				if (result == 0) {
-					if (d is IFileInfo) {
-						d.Delete ();
-					} else {
-						((IDirectoryInfo)d).Delete (true);
-					}
-
-					return true;
-				}
-			} catch (Exception ex) {
-				MessageBox.ErrorQuery (Strings.fdDeleteFailedTitle, ex.Message, Strings.btnOk);
-			}
-
-			return false;
-		}
-
-		private bool Prompt (string title, string defaultText, out string result)
-		{
-
-			bool confirm = false;
-			var btnOk = new Button (Strings.btnOk) {
-				IsDefault = true,
-			};
-			btnOk.Clicked += (s, e) => {
-				confirm = true;
-				Application.RequestStop ();
-			};
-			var btnCancel = new Button (Strings.btnCancel);
-			btnCancel.Clicked += (s, e) => {
-				confirm = false;
-				Application.RequestStop ();
-			};
-
-			var lbl = new Label (Strings.fdRenamePrompt);
-			var tf = new TextField (defaultText) {
-				X = Pos.Right (lbl),
-				Width = Dim.Fill (),
-			};
-			tf.SelectAll ();
-
-			var dlg = new Dialog () {
-				Title = title,
-				Width = Dim.Percent (50),
-				Height = 4
-			};
-			dlg.Add (lbl);
-			dlg.Add (tf);
-
-			// Add buttons last so tab order is friendly
-			// and TextField gets focus
-			dlg.AddButton (btnOk);
-			dlg.AddButton (btnCancel);
-
-			Application.Run (dlg);
-
-			result = tf.Text;
-
-			return confirm;
-		}
-
-		/// <inheritdoc/>
-		public IFileSystemInfo Rename (IFileSystem fileSystem, IFileSystemInfo toRename)
-		{
-			// Don't allow renaming C: or D: or / (on linux) etc
-			if (toRename is IDirectoryInfo dir && dir.Parent == null) {
-				return null;
-			}
-
-			if (Prompt (Strings.fdRenameTitle, toRename.Name, out var newName)) {
-				if (!string.IsNullOrWhiteSpace (newName)) {
-					try {
-						if (toRename is IFileInfo f) {
-
-							var newLocation = fileSystem.FileInfo.New (Path.Combine (f.Directory.FullName, newName));
-							f.MoveTo (newLocation.FullName);
-							return newLocation;
-
-						} else {
-							var d = (IDirectoryInfo)toRename;
-
-							var newLocation = fileSystem.DirectoryInfo.New (Path.Combine (d.Parent.FullName, newName));
-							d.MoveTo (newLocation.FullName);
-							return newLocation;
-						}
-					} catch (Exception ex) {
-						MessageBox.ErrorQuery (Strings.fdRenameFailedTitle, ex.Message, "Ok");
-					}
-				}
-			}
-
-			return null;
-		}
-
-		/// <inheritdoc/>
-		public IFileSystemInfo New (IFileSystem fileSystem, IDirectoryInfo inDirectory)
-		{
-			if (Prompt (Strings.fdNewTitle, "", out var named)) {
-				if (!string.IsNullOrWhiteSpace (named)) {
-					try {
-						var newDir = fileSystem.DirectoryInfo.New (Path.Combine (inDirectory.FullName, named));
-						newDir.Create ();
-						return newDir;
-					} catch (Exception ex) {
-						MessageBox.ErrorQuery (Strings.fdNewFailed, ex.Message, "Ok");
-					}
-				}
-			}
-			return null;
-		}
-	}
-}
+namespace Terminal.Gui;
+
+/// <summary>Default file operation handlers using modal dialogs.</summary>
+public class DefaultFileOperations : IFileOperations
+{
+    /// <inheritdoc/>
+    public bool Delete (IEnumerable<IFileSystemInfo> toDelete)
+    {
+        // Default implementation does not allow deleting multiple files
+        if (toDelete.Count () != 1)
+        {
+            return false;
+        }
+
+        IFileSystemInfo d = toDelete.Single ();
+        string adjective = d.Name;
+
+        int result = MessageBox.Query (
+                                       string.Format (Strings.fdDeleteTitle, adjective),
+                                       string.Format (Strings.fdDeleteBody, adjective),
+                                       Strings.btnYes,
+                                       Strings.btnNo
+                                      );
+
+        try
+        {
+            if (result == 0)
+            {
+                if (d is IFileInfo)
+                {
+                    d.Delete ();
+                }
+                else
+                {
+                    ((IDirectoryInfo)d).Delete (true);
+                }
+
+                return true;
+            }
+        }
+        catch (Exception ex)
+        {
+            MessageBox.ErrorQuery (Strings.fdDeleteFailedTitle, ex.Message, Strings.btnOk);
+        }
+
+        return false;
+    }
+
+    /// <inheritdoc/>
+    public IFileSystemInfo Rename (IFileSystem fileSystem, IFileSystemInfo toRename)
+    {
+        // Don't allow renaming C: or D: or / (on linux) etc
+        if (toRename is IDirectoryInfo dir && dir.Parent == null)
+        {
+            return null;
+        }
+
+        if (Prompt (Strings.fdRenameTitle, toRename.Name, out string newName))
+        {
+            if (!string.IsNullOrWhiteSpace (newName))
+            {
+                try
+                {
+                    if (toRename is IFileInfo f)
+                    {
+                        IFileInfo newLocation =
+                            fileSystem.FileInfo.New (
+                                                     Path.Combine (
+                                                                   f.Directory.FullName,
+                                                                   newName
+                                                                  )
+                                                    );
+                        f.MoveTo (newLocation.FullName);
+
+                        return newLocation;
+                    }
+                    else
+                    {
+                        var d = (IDirectoryInfo)toRename;
+
+                        IDirectoryInfo newLocation =
+                            fileSystem.DirectoryInfo.New (
+                                                          Path.Combine (
+                                                                        d.Parent.FullName,
+                                                                        newName
+                                                                       )
+                                                         );
+                        d.MoveTo (newLocation.FullName);
+
+                        return newLocation;
+                    }
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.ErrorQuery (Strings.fdRenameFailedTitle, ex.Message, "Ok");
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /// <inheritdoc/>
+    public IFileSystemInfo New (IFileSystem fileSystem, IDirectoryInfo inDirectory)
+    {
+        if (Prompt (Strings.fdNewTitle, "", out string named))
+        {
+            if (!string.IsNullOrWhiteSpace (named))
+            {
+                try
+                {
+                    IDirectoryInfo newDir =
+                        fileSystem.DirectoryInfo.New (
+                                                      Path.Combine (inDirectory.FullName, named)
+                                                     );
+                    newDir.Create ();
+
+                    return newDir;
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.ErrorQuery (Strings.fdNewFailed, ex.Message, "Ok");
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private bool Prompt (string title, string defaultText, out string result)
+    {
+        var confirm = false;
+        var btnOk = new Button { IsDefault = true, Text = Strings.btnOk };
+
+        btnOk.Clicked += (s, e) =>
+                         {
+                             confirm = true;
+                             Application.RequestStop ();
+                         };
+        var btnCancel = new Button { Text = Strings.btnCancel };
+
+        btnCancel.Clicked += (s, e) =>
+                             {
+                                 confirm = false;
+                                 Application.RequestStop ();
+                             };
+
+        var lbl = new Label { Text = Strings.fdRenamePrompt };
+        var tf = new TextField { X = Pos.Right (lbl), Width = Dim.Fill (), Text = defaultText };
+        tf.SelectAll ();
+
+        var dlg = new Dialog { Title = title, Width = Dim.Percent (50), Height = 4 };
+        dlg.Add (lbl);
+        dlg.Add (tf);
+
+        // Add buttons last so tab order is friendly
+        // and TextField gets focus
+        dlg.AddButton (btnOk);
+        dlg.AddButton (btnCancel);
+
+        Application.Run (dlg);
+
+        result = tf.Text;
+
+        return confirm;
+    }
+}

+ 19 - 24
Terminal.Gui/FileServices/DefaultSearchMatcher.cs

@@ -1,29 +1,24 @@
-using System;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
+using System.IO.Abstractions;
 
-namespace Terminal.Gui {
-	class DefaultSearchMatcher : ISearchMatcher {
-		string [] terms;
+namespace Terminal.Gui;
 
-		public void Initialize (string terms)
-		{
-			this.terms = terms.Split (new string [] { " " }, StringSplitOptions.RemoveEmptyEntries);
-		}
+internal class DefaultSearchMatcher : ISearchMatcher
+{
+    private string [] terms;
+    public void Initialize (string terms) { this.terms = terms.Split (new [] { " " }, StringSplitOptions.RemoveEmptyEntries); }
 
-		public bool IsMatch (IFileSystemInfo f)
-		{
-			//Contains overload with StringComparison is not available in (net472) or (netstandard2.0)
-			//return f.Name.Contains (terms, StringComparison.OrdinalIgnoreCase);
+    public bool IsMatch (IFileSystemInfo f)
+    {
+        //Contains overload with StringComparison is not available in (net472) or (netstandard2.0)
+        //return f.Name.Contains (terms, StringComparison.OrdinalIgnoreCase);
 
-			return
-				// At least one term must match the file name only e.g. "my" in "myfile.csv"
-				terms.Any (t => f.Name.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0)
-				&&
-				// All terms must exist in full path e.g. "dos my" can match "c:\documents\myfile.csv"
-				terms.All (t => f.FullName.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0);
-		}
-	}
+        return
 
-}
+            // At least one term must match the file name only e.g. "my" in "myfile.csv"
+            terms.Any (t => f.Name.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0)
+            &&
+
+            // All terms must exist in full path e.g. "dos my" can match "c:\documents\myfile.csv"
+            terms.All (t => f.FullName.IndexOf (t, StringComparison.OrdinalIgnoreCase) >= 0);
+    }
+}

+ 97 - 111
Terminal.Gui/FileServices/FileDialogHistory.cs

@@ -1,111 +1,97 @@
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-
-namespace Terminal.Gui {
-
-	internal class FileDialogHistory {
-		private Stack<FileDialogState> back = new Stack<FileDialogState> ();
-		private Stack<FileDialogState> forward = new Stack<FileDialogState> ();
-		private FileDialog dlg;
-
-		public FileDialogHistory (FileDialog dlg)
-		{
-			this.dlg = dlg;
-		}
-
-		public bool Back ()
-		{
-
-			IDirectoryInfo goTo = null;
-			FileSystemInfoStats restoreSelection = null;
-			string restorePath = null;
-
-			if (this.CanBack ()) {
-
-				var backTo = this.back.Pop ();
-				goTo = backTo.Directory;
-				restoreSelection = backTo.Selected;
-				restorePath = backTo.Path;
-
-			} else if (this.CanUp ()) {
-				goTo = this.dlg.State?.Directory.Parent;
-			}
-
-			// nowhere to go
-			if (goTo == null) {
-				return false;
-			}
-
-			this.forward.Push (this.dlg.State);
-			this.dlg.PushState (goTo, false, true, false, restorePath);
-
-
-			if (restoreSelection != null) {
-				this.dlg.RestoreSelection (restoreSelection.FileSystemInfo);
-			}
-
-			return true;
-		}
-
-		internal bool CanBack ()
-		{
-			return this.back.Count > 0;
-		}
-
-		internal bool Forward ()
-		{
-			if (this.forward.Count > 0) {
-
-				this.dlg.PushState (this.forward.Pop ().Directory, true, true, false);
-				return true;
-			}
-
-			return false;
-		}
-
-		internal bool Up ()
-		{
-			var parent = this.dlg.State?.Directory.Parent;
-			if (parent != null) {
-
-				this.back.Push (new FileDialogState (parent, this.dlg));
-				this.dlg.PushState (parent, false);
-				return true;
-			}
-
-			return false;
-		}
-
-		internal bool CanUp ()
-		{
-			return this.dlg.State?.Directory.Parent != null;
-		}
-
-		internal void Push (FileDialogState state, bool clearForward)
-		{
-			if (state == null) {
-				return;
-			}
-
-			// if changing to a new directory push onto the Back history
-			if (this.back.Count == 0 || this.back.Peek ().Directory.FullName != state.Directory.FullName) {
-
-				this.back.Push (state);
-				if (clearForward) {
-					this.ClearForward ();
-				}
-			}
-		}
-
-		internal bool CanForward ()
-		{
-			return this.forward.Count > 0;
-		}
-
-		internal void ClearForward ()
-		{
-			this.forward.Clear ();
-		}
-	}
-}
+using System.IO.Abstractions;
+
+namespace Terminal.Gui;
+
+internal class FileDialogHistory
+{
+    private readonly Stack<FileDialogState> back = new ();
+    private readonly FileDialog dlg;
+    private readonly Stack<FileDialogState> forward = new ();
+    public FileDialogHistory (FileDialog dlg) { this.dlg = dlg; }
+
+    public bool Back ()
+    {
+        IDirectoryInfo goTo = null;
+        FileSystemInfoStats restoreSelection = null;
+        string restorePath = null;
+
+        if (CanBack ())
+        {
+            FileDialogState backTo = back.Pop ();
+            goTo = backTo.Directory;
+            restoreSelection = backTo.Selected;
+            restorePath = backTo.Path;
+        }
+        else if (CanUp ())
+        {
+            goTo = dlg.State?.Directory.Parent;
+        }
+
+        // nowhere to go
+        if (goTo == null)
+        {
+            return false;
+        }
+
+        forward.Push (dlg.State);
+        dlg.PushState (goTo, false, true, false, restorePath);
+
+        if (restoreSelection != null)
+        {
+            dlg.RestoreSelection (restoreSelection.FileSystemInfo);
+        }
+
+        return true;
+    }
+
+    internal bool CanBack () { return back.Count > 0; }
+    internal bool CanForward () { return forward.Count > 0; }
+    internal bool CanUp () { return dlg.State?.Directory.Parent != null; }
+    internal void ClearForward () { forward.Clear (); }
+
+    internal bool Forward ()
+    {
+        if (forward.Count > 0)
+        {
+            dlg.PushState (forward.Pop ().Directory, true, true, false);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    internal void Push (FileDialogState state, bool clearForward)
+    {
+        if (state == null)
+        {
+            return;
+        }
+
+        // if changing to a new directory push onto the Back history
+        if (back.Count == 0 || back.Peek ().Directory.FullName != state.Directory.FullName)
+        {
+            back.Push (state);
+
+            if (clearForward)
+            {
+                ClearForward ();
+            }
+        }
+    }
+
+    internal bool Up ()
+    {
+        IDirectoryInfo parent = dlg.State?.Directory.Parent;
+
+        if (parent != null)
+        {
+            back.Push (new FileDialogState (parent, dlg));
+            dlg.PushState (parent, false);
+
+            return true;
+        }
+
+        return false;
+    }
+}

+ 90 - 84
Terminal.Gui/FileServices/FileDialogState.cs

@@ -1,84 +1,90 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
-
-namespace Terminal.Gui {
-
-	internal class FileDialogState {
-
-		public FileSystemInfoStats Selected { get; set; }
-		protected readonly FileDialog Parent;
-
-		/// <summary>
-		/// Gets what was entered in the path text box of the dialog
-		/// when the state was active.
-		/// </summary>
-		public string Path { get; }
-		
-		public FileDialogState (IDirectoryInfo dir, FileDialog parent)
-		{
-			this.Directory = dir;
-			Parent = parent;
-			Path = parent.Path;
-
-			this.RefreshChildren ();
-		}
-
-		public IDirectoryInfo Directory { get; }
-
-		public FileSystemInfoStats [] Children { get; internal set; }
-
-		internal virtual void RefreshChildren ()
-		{
-			var dir = this.Directory;
-			Children = GetChildren (dir).ToArray ();
-		}
-
-		protected virtual IEnumerable<FileSystemInfoStats> GetChildren (IDirectoryInfo dir)
-		{
-			try {
-
-				List<FileSystemInfoStats> children;
-
-				// if directories only
-				if (Parent.OpenMode == OpenMode.Directory) {
-					children = dir.GetDirectories ().Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)).ToList ();
-				} else {
-					children = dir.GetFileSystemInfos ().Select (e => new FileSystemInfoStats (e, Parent.Style.Culture)).ToList ();
-				}
-
-				// if only allowing specific file types
-				if (Parent.AllowedTypes.Any () && Parent.OpenMode == OpenMode.File) {
-
-					children = children.Where (
-						c => c.IsDir ||
-						(c.FileSystemInfo is IFileInfo f && Parent.IsCompatibleWithAllowedExtensions (f)))
-						.ToList ();
-				}
-
-				// if theres a UI filter in place too
-				if (Parent.CurrentFilter != null) {
-					children = children.Where (MatchesApiFilter).ToList ();
-				}
-
-				// allow navigating up as '..'
-				if (dir.Parent != null) {
-					children.Add (new FileSystemInfoStats (dir.Parent, Parent.Style.Culture) { IsParent = true });
-				}
-
-				return children;
-			} catch (Exception) {
-				// Access permissions Exceptions, Dir not exists etc
-				return Enumerable.Empty<FileSystemInfoStats> ();
-			}
-		}
-
-		protected bool MatchesApiFilter (FileSystemInfoStats arg)
-		{
-			return arg.IsDir ||
-			(arg.FileSystemInfo is IFileInfo f && Parent.CurrentFilter.IsAllowed (f.FullName));
-		}
-	}
-}
+using System.IO.Abstractions;
+
+namespace Terminal.Gui;
+
+internal class FileDialogState
+{
+    protected readonly FileDialog Parent;
+
+    public FileDialogState (IDirectoryInfo dir, FileDialog parent)
+    {
+        Directory = dir;
+        Parent = parent;
+        Path = parent.Path;
+
+        RefreshChildren ();
+    }
+
+    public FileSystemInfoStats [] Children { get; internal set; }
+    public IDirectoryInfo Directory { get; }
+
+    /// <summary>Gets what was entered in the path text box of the dialog when the state was active.</summary>
+    public string Path { get; }
+
+    public FileSystemInfoStats Selected { get; set; }
+
+    protected virtual IEnumerable<FileSystemInfoStats> GetChildren (IDirectoryInfo dir)
+    {
+        try
+        {
+            List<FileSystemInfoStats> children;
+
+            // if directories only
+            if (Parent.OpenMode == OpenMode.Directory)
+            {
+                children = dir.GetDirectories ()
+                              .Select (e => new FileSystemInfoStats (e, Parent.Style.Culture))
+                              .ToList ();
+            }
+            else
+            {
+                children = dir.GetFileSystemInfos ()
+                              .Select (e => new FileSystemInfoStats (e, Parent.Style.Culture))
+                              .ToList ();
+            }
+
+            // if only allowing specific file types
+            if (Parent.AllowedTypes.Any () && Parent.OpenMode == OpenMode.File)
+            {
+                children = children.Where (
+                                           c => c.IsDir
+                                                || (c.FileSystemInfo is IFileInfo f
+                                                    && Parent.IsCompatibleWithAllowedExtensions (f))
+                                          )
+                                   .ToList ();
+            }
+
+            // if theres a UI filter in place too
+            if (Parent.CurrentFilter != null)
+            {
+                children = children.Where (MatchesApiFilter).ToList ();
+            }
+
+            // allow navigating up as '..'
+            if (dir.Parent != null)
+            {
+                children.Add (new FileSystemInfoStats (dir.Parent, Parent.Style.Culture) { IsParent = true });
+            }
+
+            return children;
+        }
+        catch (Exception)
+        {
+            // Access permissions Exceptions, Dir not exists etc
+            return Enumerable.Empty<FileSystemInfoStats> ();
+        }
+    }
+
+    protected bool MatchesApiFilter (FileSystemInfoStats arg)
+    {
+        return arg.IsDir
+               || (arg.FileSystemInfo is IFileInfo f
+                   && Parent.CurrentFilter.IsAllowed (f.FullName));
+    }
+
+    internal virtual void RefreshChildren ()
+    {
+        IDirectoryInfo dir = Directory;
+        Children = GetChildren (dir).ToArray ();
+    }
+}

+ 198 - 228
Terminal.Gui/FileServices/FileDialogStyle.cs

@@ -1,231 +1,201 @@
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Globalization;
-using System.IO;
+using System.Globalization;
 using System.IO.Abstractions;
-using System.Linq;
 using Terminal.Gui.Resources;
 using static System.Environment;
-using static Terminal.Gui.ConfigurationManager;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Stores style settings for <see cref="FileDialog"/>.
-	/// </summary>
-	public class FileDialogStyle {
-		readonly IFileSystem _fileSystem;
-
-		/// <summary>
-		/// Gets or sets the default value to use for <see cref="UseColors"/>.
-		/// This can be populated from .tui config files via <see cref="ConfigurationManager"/>
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static bool DefaultUseColors { get; set; }
-
-		/// <summary>
-		/// Gets or sets the default value to use for <see cref="UseUnicodeCharacters"/>.
-		/// This can be populated from .tui config files via <see cref="ConfigurationManager"/>
-		/// </summary>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static bool DefaultUseUnicodeCharacters { get; set; }
-
-		/// <summary>
-		/// Gets or Sets a value indicating whether different colors
-		/// should be used for different file types/directories.  Defaults
-		/// to false.
-		/// </summary>
-		public bool UseColors { get; set; } = DefaultUseColors;
-
-		/// <summary>
-		/// Gets or sets the class responsible for determining which symbol
-		/// to use to represent files and directories.
-		/// </summary>
-		public FileSystemIconProvider IconProvider { get; set; } = new FileSystemIconProvider ();
-
-		/// <summary>
-		///	Gets or sets the class thatis responsible for determining which color
-		/// to use to represent files and directories when <see cref="UseColors"/> is
-		/// <see langword="true"/>.
-		/// </summary>
-		public FileSystemColorProvider ColorProvider { get; set; } = new FileSystemColorProvider ();
-
-		/// <summary>
-		/// Gets or sets the culture to use (e.g. for number formatting).
-		/// Defaults to <see cref="CultureInfo.CurrentUICulture"/>.
-		/// </summary>
-		public CultureInfo Culture { get; set; } = CultureInfo.CurrentUICulture;
-
-		/// <summary>
-		/// Gets or sets the header text displayed in the Filename column of the files table.
-		/// </summary>
-		public string FilenameColumnName { get; set; } = Strings.fdFilename;
-
-		/// <summary>
-		/// Gets or sets the header text displayed in the Size column of the files table.
-		/// </summary>
-		public string SizeColumnName { get; set; } = Strings.fdSize;
-
-		/// <summary>
-		/// Gets or sets the header text displayed in the Modified column of the files table.
-		/// </summary>
-		public string ModifiedColumnName { get; set; } = Strings.fdModified;
-
-		/// <summary>
-		/// Gets or sets the header text displayed in the Type column of the files table.
-		/// </summary>
-		public string TypeColumnName { get; set; } = Strings.fdType;
-
-		/// <summary>
-		/// Gets or sets the text displayed in the 'Search' text box when user has not supplied any input yet.
-		/// </summary>
-		public string SearchCaption { get; set; } = Strings.fdSearchCaption;
-
-		/// <summary>
-		/// Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet.
-		/// </summary>
-		public string PathCaption { get; set; } = Strings.fdPathCaption;
-
-		/// <summary>
-		/// Gets or sets the text on the 'Ok' button.  Typically you may want to change this to
-		/// "Open" or "Save" etc.
-		/// </summary>
-		public string OkButtonText { get; set; } = Strings.btnOk;
-
-		/// <summary>
-		/// Gets or sets the text on the 'Cancel' button.
-		/// </summary>
-		public string CancelButtonText { get; set; } = Strings.btnCancel;
-
-		/// <summary>
-		/// Gets or sets whether to flip the order of the Ok and Cancel buttons. Defaults
-		/// to false (Ok button then Cancel button). Set to true to show Cancel button on
-		/// left then Ok button instead.
-		/// </summary>
-		public bool FlipOkCancelButtonLayoutOrder { get; set; }
-
-		/// <summary>
-		/// Gets or sets error message when user attempts to select a file type that is not one of <see cref="FileDialog.AllowedTypes"/>
-		/// </summary>
-		public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback;
-
-		/// <summary>
-		/// Gets or sets error message when user selects a directory that does not exist and
-		/// <see cref="OpenMode"/> is <see cref="OpenMode.Directory"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
-		/// </summary>
-		public string DirectoryMustExistFeedback { get; set; } = Strings.fdDirectoryMustExistFeedback;
-
-		/// <summary>
-		/// Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.Directory"/>
-		/// and user enters the name of an existing file (File system cannot have a folder with the same name as a file).
-		/// </summary>
-		public string FileAlreadyExistsFeedback { get; set; } = Strings.fdFileAlreadyExistsFeedback;
-
-		/// <summary>
-		/// Gets or sets error message when user selects a file that does not exist and
-		/// <see cref="OpenMode"/> is <see cref="OpenMode.File"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
-		/// </summary>
-		public string FileMustExistFeedback { get; set; } = Strings.fdFileMustExistFeedback;
-
-		/// <summary>
-		/// Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.File"/>
-		/// and user enters the name of an existing directory (File system cannot have a folder with the same name as a file).
-		/// </summary>
-		public string DirectoryAlreadyExistsFeedback { get; set; } = Strings.fdDirectoryAlreadyExistsFeedback;
-
-		/// <summary>
-		/// Gets or sets error message when user selects a file/dir that does not exist and
-		/// <see cref="OpenMode"/> is <see cref="OpenMode.Mixed"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
-		/// </summary>
-		public string FileOrDirectoryMustExistFeedback { get; set; } = Strings.fdFileOrDirectoryMustExistFeedback;
-
-		/// <summary>
-		/// Gets the style settings for the table of files (in currently selected directory).
-		/// </summary>
-		public TableStyle TableStyle { get; internal set; }
-
-		/// <summary>
-		/// Gets the style settings for the collapse-able directory/places tree
-		/// </summary>
-		public TreeStyle TreeStyle { get; internal set; }
-
-		/// <summary>
-		/// Gets or Sets the method for getting the root tree objects that are displayed in
-		/// the collapse-able tree in the <see cref="FileDialog"/>.  Defaults to all accessible
-		/// <see cref="System.Environment.GetLogicalDrives"/> and unique
-		/// <see cref="Environment.SpecialFolder"/>.
-		/// </summary>
-		/// <remarks>Must be configured before showing the dialog.</remarks>
-		public Func<Dictionary<IDirectoryInfo, string>> TreeRootGetter { get; set; }
-
-		/// <summary>
-		/// Gets or sets whether to use advanced unicode characters which might not be installed
-		/// on all users computers.
-		/// </summary>
-		public bool UseUnicodeCharacters { get; set; } = DefaultUseUnicodeCharacters;
-
-		/// <summary>
-		/// Gets or sets the format to use for date/times in the Modified column.
-		/// Defaults to <see cref="DateTimeFormatInfo.SortableDateTimePattern"/> 
-		/// of the <see cref="CultureInfo.CurrentCulture"/>
-		/// </summary>
-		public string DateFormat { get; set; }
-
-		/// <summary>
-		/// Creates a new instance of the <see cref="FileDialogStyle"/> class.
-		/// </summary>
-		public FileDialogStyle (IFileSystem fileSystem)
-		{
-			_fileSystem = fileSystem;
-			TreeRootGetter = DefaultTreeRootGetter;
-
-			DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern;
-		}
-
-
-		private Dictionary<IDirectoryInfo, string> DefaultTreeRootGetter ()
-		{
-			var roots = new Dictionary<IDirectoryInfo, string> ();
-			try {
-				foreach (var d in Environment.GetLogicalDrives ()) {
-
-					var dir = _fileSystem.DirectoryInfo.New (d);
-
-					if (!roots.ContainsKey (dir)) {
-						roots.Add (dir, d);
-					}
-				}
-
-			} catch (Exception) {
-				// Cannot get the system disks thats fine
-			}
-
-			try {
-				foreach (var special in Enum.GetValues (typeof (Environment.SpecialFolder)).Cast<SpecialFolder> ()) {
-					try {
-						var path = Environment.GetFolderPath (special);
-
-						if (string.IsNullOrWhiteSpace (path)) {
-							continue;
-						}
-
-						var dir = _fileSystem.DirectoryInfo.New (path);
-
-						if (!roots.ContainsKey (dir) && dir.Exists) {
-							roots.Add (dir, special.ToString ());
-						}
-					} catch (Exception) {
-						// Special file exists but contents are unreadable (permissions?)
-						// skip it anyway
-					}
-				}
-			} catch (Exception) {
-				// Cannot get the special files for this OS oh well
-			}
-
-			return roots;
-		}
-	}
-
-}
+
+namespace Terminal.Gui;
+
+/// <summary>Stores style settings for <see cref="FileDialog"/>.</summary>
+public class FileDialogStyle
+{
+    private readonly IFileSystem _fileSystem;
+
+    /// <summary>Creates a new instance of the <see cref="FileDialogStyle"/> class.</summary>
+    public FileDialogStyle (IFileSystem fileSystem)
+    {
+        _fileSystem = fileSystem;
+        TreeRootGetter = DefaultTreeRootGetter;
+
+        DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.SortableDateTimePattern;
+    }
+
+    /// <summary>Gets or sets the text on the 'Cancel' button.</summary>
+    public string CancelButtonText { get; set; } = Strings.btnCancel;
+
+    /// <summary>
+    ///     Gets or sets the class thatis responsible for determining which color to use to represent files and
+    ///     directories when <see cref="UseColors"/> is <see langword="true"/>.
+    /// </summary>
+    public FileSystemColorProvider ColorProvider { get; set; } = new ();
+
+    /// <summary>
+    ///     Gets or sets the culture to use (e.g. for number formatting). Defaults to
+    ///     <see cref="CultureInfo.CurrentUICulture"/>.
+    /// </summary>
+    public CultureInfo Culture { get; set; } = CultureInfo.CurrentUICulture;
+
+    /// <summary>
+    ///     Gets or sets the format to use for date/times in the Modified column. Defaults to
+    ///     <see cref="DateTimeFormatInfo.SortableDateTimePattern"/> of the <see cref="CultureInfo.CurrentCulture"/>
+    /// </summary>
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the default value to use for <see cref="UseColors"/>. This can be populated from .tui config
+    ///     files via <see cref="ConfigurationManager"/>
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool DefaultUseColors { get; set; }
+
+    /// <summary>
+    ///     Gets or sets the default value to use for <see cref="UseUnicodeCharacters"/>. This can be populated from .tui
+    ///     config files via <see cref="ConfigurationManager"/>
+    /// </summary>
+    [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+    public static bool DefaultUseUnicodeCharacters { get; set; }
+
+    /// <summary>
+    ///     Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.File"/> and user enters the
+    ///     name of an existing directory (File system cannot have a folder with the same name as a file).
+    /// </summary>
+    public string DirectoryAlreadyExistsFeedback { get; set; } = Strings.fdDirectoryAlreadyExistsFeedback;
+
+    /// <summary>
+    ///     Gets or sets error message when user selects a directory that does not exist and <see cref="OpenMode"/> is
+    ///     <see cref="OpenMode.Directory"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
+    /// </summary>
+    public string DirectoryMustExistFeedback { get; set; } = Strings.fdDirectoryMustExistFeedback;
+
+    /// <summary>
+    ///     Gets or sets error message when user <see cref="OpenMode"/> is <see cref="OpenMode.Directory"/> and user
+    ///     enters the name of an existing file (File system cannot have a folder with the same name as a file).
+    /// </summary>
+    public string FileAlreadyExistsFeedback { get; set; } = Strings.fdFileAlreadyExistsFeedback;
+
+    /// <summary>
+    ///     Gets or sets error message when user selects a file that does not exist and <see cref="OpenMode"/> is
+    ///     <see cref="OpenMode.File"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
+    /// </summary>
+    public string FileMustExistFeedback { get; set; } = Strings.fdFileMustExistFeedback;
+
+    /// <summary>Gets or sets the header text displayed in the Filename column of the files table.</summary>
+    public string FilenameColumnName { get; set; } = Strings.fdFilename;
+
+    /// <summary>
+    ///     Gets or sets error message when user selects a file/dir that does not exist and <see cref="OpenMode"/> is
+    ///     <see cref="OpenMode.Mixed"/> and <see cref="FileDialog.MustExist"/> is <see langword="true"/>.
+    /// </summary>
+    public string FileOrDirectoryMustExistFeedback { get; set; } = Strings.fdFileOrDirectoryMustExistFeedback;
+
+    /// <summary>
+    ///     Gets or sets whether to flip the order of the Ok and Cancel buttons. Defaults to false (Ok button then Cancel
+    ///     button). Set to true to show Cancel button on left then Ok button instead.
+    /// </summary>
+    public bool FlipOkCancelButtonLayoutOrder { get; set; }
+
+    /// <summary>Gets or sets the class responsible for determining which symbol to use to represent files and directories.</summary>
+    public FileSystemIconProvider IconProvider { get; set; } = new ();
+
+    /// <summary>Gets or sets the header text displayed in the Modified column of the files table.</summary>
+    public string ModifiedColumnName { get; set; } = Strings.fdModified;
+
+    /// <summary>Gets or sets the text on the 'Ok' button.  Typically you may want to change this to "Open" or "Save" etc.</summary>
+    public string OkButtonText { get; set; } = Strings.btnOk;
+
+    /// <summary>Gets or sets the text displayed in the 'Path' text box when user has not supplied any input yet.</summary>
+    public string PathCaption { get; set; } = Strings.fdPathCaption;
+
+    /// <summary>Gets or sets the text displayed in the 'Search' text box when user has not supplied any input yet.</summary>
+    public string SearchCaption { get; set; } = Strings.fdSearchCaption;
+
+    /// <summary>Gets or sets the header text displayed in the Size column of the files table.</summary>
+    public string SizeColumnName { get; set; } = Strings.fdSize;
+
+    /// <summary>Gets the style settings for the table of files (in currently selected directory).</summary>
+    public TableStyle TableStyle { get; internal set; }
+
+    /// <summary>
+    ///     Gets or Sets the method for getting the root tree objects that are displayed in the collapse-able tree in the
+    ///     <see cref="FileDialog"/>.  Defaults to all accessible <see cref="System.Environment.GetLogicalDrives"/> and unique
+    ///     <see cref="Environment.SpecialFolder"/>.
+    /// </summary>
+    /// <remarks>Must be configured before showing the dialog.</remarks>
+    public Func<Dictionary<IDirectoryInfo, string>> TreeRootGetter { get; set; }
+
+    /// <summary>Gets the style settings for the collapse-able directory/places tree</summary>
+    public TreeStyle TreeStyle { get; internal set; }
+
+    /// <summary>Gets or sets the header text displayed in the Type column of the files table.</summary>
+    public string TypeColumnName { get; set; } = Strings.fdType;
+
+    /// <summary>
+    ///     Gets or Sets a value indicating whether different colors should be used for different file types/directories.
+    ///     Defaults to false.
+    /// </summary>
+    public bool UseColors { get; set; } = DefaultUseColors;
+
+    /// <summary>Gets or sets whether to use advanced unicode characters which might not be installed on all users computers.</summary>
+    public bool UseUnicodeCharacters { get; set; } = DefaultUseUnicodeCharacters;
+
+    /// <summary>
+    ///     Gets or sets error message when user attempts to select a file type that is not one of
+    ///     <see cref="FileDialog.AllowedTypes"/>
+    /// </summary>
+    public string WrongFileTypeFeedback { get; set; } = Strings.fdWrongFileTypeFeedback;
+
+    private Dictionary<IDirectoryInfo, string> DefaultTreeRootGetter ()
+    {
+        Dictionary<IDirectoryInfo, string> roots = new ();
+
+        try
+        {
+            foreach (string d in GetLogicalDrives ())
+            {
+                IDirectoryInfo dir = _fileSystem.DirectoryInfo.New (d);
+
+                if (!roots.ContainsKey (dir))
+                {
+                    roots.Add (dir, d);
+                }
+            }
+        }
+        catch (Exception)
+        {
+            // Cannot get the system disks thats fine
+        }
+
+        try
+        {
+            foreach (SpecialFolder special in Enum.GetValues (typeof (SpecialFolder)).Cast<SpecialFolder> ())
+            {
+                try
+                {
+                    string path = GetFolderPath (special);
+
+                    if (string.IsNullOrWhiteSpace (path))
+                    {
+                        continue;
+                    }
+
+                    IDirectoryInfo dir = _fileSystem.DirectoryInfo.New (path);
+
+                    if (!roots.ContainsKey (dir) && dir.Exists)
+                    {
+                        roots.Add (dir, special.ToString ());
+                    }
+                }
+                catch (Exception)
+                {
+                    // Special file exists but contents are unreadable (permissions?)
+                    // skip it anyway
+                }
+            }
+        }
+        catch (Exception)
+        {
+            // Cannot get the special files for this OS oh well
+        }
+
+        return roots;
+    }
+}

+ 107 - 103
Terminal.Gui/FileServices/FileSystemInfoStats.cs

@@ -1,106 +1,110 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
+using System.Globalization;
 using System.IO.Abstractions;
-using System.Linq;
 using Terminal.Gui.Resources;
 
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Wrapper for <see cref="FileSystemInfo"/> that contains additional information
-	/// (e.g. <see cref="IsParent"/>) and helper methods.
-	/// </summary>
-	internal class FileSystemInfoStats {
-
-		/* ---- Colors used by the ls command line tool ----
-		 *
-		* Blue: Directory
-		* Green: Executable or recognized data file
-		* Cyan (Sky Blue): Symbolic link file
-		* Yellow with black background: Device
-		* Magenta (Pink): Graphic image file
-		* Red: Archive file
-		* Red with black background: Broken link
-		*/
-
-		private const long ByteConversion = 1024;
-
-		private static readonly string [] SizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
-		private static readonly List<string> ImageExtensions = new List<string> { ".JPG", ".JPEG", ".JPE", ".BMP", ".GIF", ".PNG" };
-		private static readonly List<string> ExecutableExtensions = new List<string> { ".EXE", ".BAT" };
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileSystemInfoStats"/> class.
-		/// </summary>
-		/// <param name="fsi">The directory of path to wrap.</param>
-		/// <param name="culture"></param>
-		public FileSystemInfoStats (IFileSystemInfo fsi, CultureInfo culture)
-		{
-			this.FileSystemInfo = fsi;
-			this.LastWriteTime = fsi.LastWriteTime;
-
-			if (fsi is IFileInfo fi) {
-				this.MachineReadableLength = fi.Length;
-				this.HumanReadableLength = GetHumanReadableFileSize (this.MachineReadableLength, culture);
-				this.Type = fi.Extension;
-			} else {
-				this.HumanReadableLength = string.Empty;
-				this.Type = $"<{Strings.fdDirectory}>";
-				this.IsDir = true;
-			}
-		}
-
-		/// <summary>
-		/// Gets the wrapped <see cref="FileSystemInfo"/> (directory or file).
-		/// </summary>
-		public IFileSystemInfo FileSystemInfo { get; }
-		public string HumanReadableLength { get; }
-		public long MachineReadableLength { get; }
-		public DateTime? LastWriteTime { get; }
-		public string Type { get; }
-
-		/// <summary>
-		/// Gets or Sets a value indicating whether this instance represents
-		/// the parent of the current state (i.e. "..").
-		/// </summary>
-		public bool IsParent { get; internal set; }
-		public string Name => this.IsParent ? ".." : this.FileSystemInfo.Name;
-
-		public bool IsDir { get; }
-
-		public bool IsImage ()
-		{
-			return this.FileSystemInfo is IFileSystemInfo f &&
-				ImageExtensions.Contains (
-					f.Extension,
-					StringComparer.InvariantCultureIgnoreCase);
-		}
-
-		public bool IsExecutable ()
-		{
-			// TODO: handle linux executable status
-			return this.FileSystemInfo is IFileSystemInfo f &&
-				ExecutableExtensions.Contains (
-					f.Extension,
-					StringComparer.InvariantCultureIgnoreCase);
-		}
-
-		private static string GetHumanReadableFileSize (long value, CultureInfo culture)
-		{
-
-			if (value < 0) {
-				return "-" + GetHumanReadableFileSize (-value, culture);
-			}
-
-			if (value == 0) {
-				return "0.0 B";
-			}
-
-			int mag = (int)Math.Log (value, ByteConversion);
-			double adjustedSize = value / Math.Pow (1000, mag);
-			return string.Format (culture.NumberFormat,"{0:n2} {1}", adjustedSize, SizeSuffixes [mag]);
-		}
-	}
-}
+namespace Terminal.Gui;
+
+/// <summary>
+///     Wrapper for <see cref="FileSystemInfo"/> that contains additional information (e.g. <see cref="IsParent"/>)
+///     and helper methods.
+/// </summary>
+internal class FileSystemInfoStats
+{
+    /* ---- Colors used by the ls command line tool ----
+     *
+     * Blue: Directory
+     * Green: Executable or recognized data file
+     * Cyan (Sky Blue): Symbolic link file
+     * Yellow with black background: Device
+     * Magenta (Pink): Graphic image file
+     * Red: Archive file
+     * Red with black background: Broken link
+     */
+    private const long ByteConversion = 1024;
+    private static readonly List<string> ExecutableExtensions = new () { ".EXE", ".BAT" };
+
+    private static readonly List<string> ImageExtensions = new ()
+    {
+        ".JPG",
+        ".JPEG",
+        ".JPE",
+        ".BMP",
+        ".GIF",
+        ".PNG"
+    };
+
+    private static readonly string [] SizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
+
+    /// <summary>Initializes a new instance of the <see cref="FileSystemInfoStats"/> class.</summary>
+    /// <param name="fsi">The directory of path to wrap.</param>
+    /// <param name="culture"></param>
+    public FileSystemInfoStats (IFileSystemInfo fsi, CultureInfo culture)
+    {
+        FileSystemInfo = fsi;
+        LastWriteTime = fsi.LastWriteTime;
+
+        if (fsi is IFileInfo fi)
+        {
+            MachineReadableLength = fi.Length;
+            HumanReadableLength = GetHumanReadableFileSize (MachineReadableLength, culture);
+            Type = fi.Extension;
+        }
+        else
+        {
+            HumanReadableLength = string.Empty;
+            Type = $"<{Strings.fdDirectory}>";
+            IsDir = true;
+        }
+    }
+
+    /// <summary>Gets the wrapped <see cref="FileSystemInfo"/> (directory or file).</summary>
+    public IFileSystemInfo FileSystemInfo { get; }
+
+    public string HumanReadableLength { get; }
+    public bool IsDir { get; }
+
+    /// <summary>Gets or Sets a value indicating whether this instance represents the parent of the current state (i.e. "..").</summary>
+    public bool IsParent { get; internal set; }
+
+    public DateTime? LastWriteTime { get; }
+    public long MachineReadableLength { get; }
+    public string Name => IsParent ? ".." : FileSystemInfo.Name;
+    public string Type { get; }
+
+    public bool IsExecutable ()
+    {
+        // TODO: handle linux executable status
+        return FileSystemInfo is IFileSystemInfo f
+               && ExecutableExtensions.Contains (
+                                                 f.Extension,
+                                                 StringComparer.InvariantCultureIgnoreCase
+                                                );
+    }
+
+    public bool IsImage ()
+    {
+        return FileSystemInfo is IFileSystemInfo f
+               && ImageExtensions.Contains (
+                                            f.Extension,
+                                            StringComparer.InvariantCultureIgnoreCase
+                                           );
+    }
+
+    private static string GetHumanReadableFileSize (long value, CultureInfo culture)
+    {
+        if (value < 0)
+        {
+            return "-" + GetHumanReadableFileSize (-value, culture);
+        }
+
+        if (value == 0)
+        {
+            return "0.0 B";
+        }
+
+        var mag = (int)Math.Log (value, ByteConversion);
+        double adjustedSize = value / Math.Pow (1000, mag);
+
+        return string.Format (culture.NumberFormat, "{0:n2} {1}", adjustedSize, SizeSuffixes [mag]);
+    }
+}

+ 60 - 80
Terminal.Gui/FileServices/FileSystemTreeBuilder.cs

@@ -1,80 +1,60 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Abstractions;
-using System.Linq;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// TreeView builder for creating file system based trees.
-	/// </summary>
-	public class FileSystemTreeBuilder : ITreeBuilder<IFileSystemInfo>, IComparer<IFileSystemInfo> {
-
-
-		/// <summary>
-		/// Creates a new instance of the <see cref="FileSystemTreeBuilder"/> class.
-		/// </summary>
-		public FileSystemTreeBuilder ()
-		{
-			Sorter = this;
-		}
-
-		/// <inheritdoc/>
-		public bool SupportsCanExpand => true;
-
-		/// <summary>
-		/// Gets or sets a flag indicating whether to show files as leaf elements
-		/// in the tree. Defaults to true.
-		/// </summary>
-		public bool IncludeFiles { get; } = true;
-
-		/// <summary>
-		/// Gets or sets the order of directory children.  Defaults to <see langword="this"/>.
-		/// </summary>
-		public IComparer<IFileSystemInfo> Sorter { get; set; }
-
-		/// <inheritdoc/>
-		public bool CanExpand (IFileSystemInfo toExpand)
-		{
-			return this.TryGetChildren (toExpand).Any ();
-		}
-
-		/// <inheritdoc/>
-		public IEnumerable<IFileSystemInfo> GetChildren (IFileSystemInfo forObject)
-		{
-			return this.TryGetChildren (forObject).OrderBy(k=>k,Sorter);
-		}
-
-		private IEnumerable<IFileSystemInfo> TryGetChildren (IFileSystemInfo entry)
-		{
-			if (entry is IFileInfo) {
-				return Enumerable.Empty<IFileSystemInfo> ();
-			}
-
-			var dir = (IDirectoryInfo)entry;
-
-			try {
-				return dir.GetFileSystemInfos ().Where (e => IncludeFiles || e is IDirectoryInfo);
-
-			} catch (Exception) {
-
-				return Enumerable.Empty<IFileSystemInfo> ();
-			}
-		}
-
-		/// <inheritdoc/>
-		public int Compare (IFileSystemInfo x, IFileSystemInfo y)
-		{
-			if (x is IDirectoryInfo && y is not IDirectoryInfo) {
-				return -1;
-			}
-
-			if (x is not IDirectoryInfo && y is IDirectoryInfo) {
-				return 1;
-			}
-
-			return x.Name.CompareTo (y.Name);
-		}
-	}
-}
+using System.IO.Abstractions;
+
+namespace Terminal.Gui;
+
+/// <summary>TreeView builder for creating file system based trees.</summary>
+public class FileSystemTreeBuilder : ITreeBuilder<IFileSystemInfo>, IComparer<IFileSystemInfo>
+{
+    /// <summary>Creates a new instance of the <see cref="FileSystemTreeBuilder"/> class.</summary>
+    public FileSystemTreeBuilder () { Sorter = this; }
+
+    /// <summary>Gets or sets a flag indicating whether to show files as leaf elements in the tree. Defaults to true.</summary>
+    public bool IncludeFiles { get; } = true;
+
+    /// <summary>Gets or sets the order of directory children.  Defaults to <see langword="this"/>.</summary>
+    public IComparer<IFileSystemInfo> Sorter { get; set; }
+
+    /// <inheritdoc/>
+    public int Compare (IFileSystemInfo x, IFileSystemInfo y)
+    {
+        if (x is IDirectoryInfo && y is not IDirectoryInfo)
+        {
+            return -1;
+        }
+
+        if (x is not IDirectoryInfo && y is IDirectoryInfo)
+        {
+            return 1;
+        }
+
+        return x.Name.CompareTo (y.Name);
+    }
+
+    /// <inheritdoc/>
+    public bool SupportsCanExpand => true;
+
+    /// <inheritdoc/>
+    public bool CanExpand (IFileSystemInfo toExpand) { return TryGetChildren (toExpand).Any (); }
+
+    /// <inheritdoc/>
+    public IEnumerable<IFileSystemInfo> GetChildren (IFileSystemInfo forObject) { return TryGetChildren (forObject).OrderBy (k => k, Sorter); }
+
+    private IEnumerable<IFileSystemInfo> TryGetChildren (IFileSystemInfo entry)
+    {
+        if (entry is IFileInfo)
+        {
+            return Enumerable.Empty<IFileSystemInfo> ();
+        }
+
+        var dir = (IDirectoryInfo)entry;
+
+        try
+        {
+            return dir.GetFileSystemInfos ().Where (e => IncludeFiles || e is IDirectoryInfo);
+        }
+        catch (Exception)
+        {
+            return Enumerable.Empty<IFileSystemInfo> ();
+        }
+    }
+}

+ 18 - 27
Terminal.Gui/FileServices/FilesSelectedEventArgs.cs

@@ -1,30 +1,21 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event args for the <see cref="FileDialog.FilesSelected"/> event
-	/// </summary>
-	public class FilesSelectedEventArgs : EventArgs {
-		/// <summary>
-		/// Set to true if you want to prevent the selection
-		/// going ahead (this will leave the <see cref="FileDialog"/>
-		/// still showing).
-		/// </summary>
-		public bool Cancel { get; set; }
+/// <summary>Event args for the <see cref="FileDialog.FilesSelected"/> event</summary>
+public class FilesSelectedEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="FilesSelectedEventArgs"/></summary>
+    /// <param name="dialog"></param>
+    public FilesSelectedEventArgs (FileDialog dialog) { Dialog = dialog; }
 
-		/// <summary>
-		/// The dialog where the choice is being made.  Use <see cref="FileDialog.Path"/>
-		/// and/or <see cref="FileDialog.MultiSelected"/> to evaluate the users choice.
-		/// </summary>
-		public FileDialog Dialog { get; }
+    /// <summary>
+    ///     Set to true if you want to prevent the selection going ahead (this will leave the <see cref="FileDialog"/>
+    ///     still showing).
+    /// </summary>
+    public bool Cancel { get; set; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="FilesSelectedEventArgs"/>
-		/// </summary>
-		/// <param name="dialog"></param>
-		public FilesSelectedEventArgs (FileDialog dialog)
-		{
-			Dialog = dialog;
-		}
-	}
-}
+    /// <summary>
+    ///     The dialog where the choice is being made.  Use <see cref="FileDialog.Path"/> and/or
+    ///     <see cref="FileDialog.MultiSelected"/> to evaluate the users choice.
+    /// </summary>
+    public FileDialog Dialog { get; }
+}

+ 32 - 41
Terminal.Gui/FileServices/IFileOperations.cs

@@ -1,45 +1,36 @@
-using System.Collections.Generic;
-using System.IO;
 using System.IO.Abstractions;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Interface for defining how to handle file/directory 
-	/// deletion, rename and newing attempts in <see cref="FileDialog"/>.
-	/// </summary>
-	public interface IFileOperations {
-		/// <summary>
-		/// Specifies how to handle file/directory deletion attempts
-		/// in <see cref="FileDialog"/>.
-		/// </summary>
-		/// <param name="toDelete"></param>
-		/// <returns><see langword="true"/> if operation was completed or 
-		/// <see langword="false"/> if cancelled</returns>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		bool Delete (IEnumerable<IFileSystemInfo> toDelete);
+namespace Terminal.Gui;
 
-		/// <summary>
-		/// Specifies how to handle file/directory rename attempts
-		/// in <see cref="FileDialog"/>.
-		/// </summary>
-		/// <param name="fileSystem"></param>
-		/// <param name="toRename"></param>
-		/// <returns>The new name for the file or null if cancelled</returns>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		IFileSystemInfo Rename (IFileSystem fileSystem, IFileSystemInfo toRename);
+/// <summary>
+///     Interface for defining how to handle file/directory deletion, rename and newing attempts in
+///     <see cref="FileDialog"/>.
+/// </summary>
+public interface IFileOperations
+{
+    /// <summary>Specifies how to handle file/directory deletion attempts in <see cref="FileDialog"/>.</summary>
+    /// <param name="toDelete"></param>
+    /// <returns><see langword="true"/> if operation was completed or <see langword="false"/> if cancelled</returns>
+    /// <remarks>
+    ///     Ensure you use a try/catch block with appropriate error handling (e.g. showing a <see cref="MessageBox"/>
+    /// </remarks>
+    bool Delete (IEnumerable<IFileSystemInfo> toDelete);
 
-		/// <summary>
-		/// Specifies how to handle 'new directory' operation
-		/// in <see cref="FileDialog"/>.
-		/// </summary>
-		/// <param name="fileSystem"></param>
-		/// <param name="inDirectory">The parent directory in which the new
-		/// directory should be created</param>
-		/// <returns>The newly created directory or null if cancelled.</returns>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		IFileSystemInfo New (IFileSystem fileSystem, IDirectoryInfo inDirectory);
-	}
-}
+    /// <summary>Specifies how to handle 'new directory' operation in <see cref="FileDialog"/>.</summary>
+    /// <param name="fileSystem"></param>
+    /// <param name="inDirectory">The parent directory in which the new directory should be created</param>
+    /// <returns>The newly created directory or null if cancelled.</returns>
+    /// <remarks>
+    ///     Ensure you use a try/catch block with appropriate error handling (e.g. showing a <see cref="MessageBox"/>
+    /// </remarks>
+    IFileSystemInfo New (IFileSystem fileSystem, IDirectoryInfo inDirectory);
+
+    /// <summary>Specifies how to handle file/directory rename attempts in <see cref="FileDialog"/>.</summary>
+    /// <param name="fileSystem"></param>
+    /// <param name="toRename"></param>
+    /// <returns>The new name for the file or null if cancelled</returns>
+    /// <remarks>
+    ///     Ensure you use a try/catch block with appropriate error handling (e.g. showing a <see cref="MessageBox"/>
+    /// </remarks>
+    IFileSystemInfo Rename (IFileSystem fileSystem, IFileSystemInfo toRename);
+}

+ 10 - 20
Terminal.Gui/FileServices/ISearchMatcher.cs

@@ -1,23 +1,13 @@
-using System.IO;
-using System.IO.Abstractions;
+using System.IO.Abstractions;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
-	/// <summary>
-	/// Defines whether a given file/directory matches a set of
-	/// search terms.
-	/// </summary>
-	public interface ISearchMatcher {
-		/// <summary>
-		/// Called once for each new search. Defines the string
-		/// the user has provided as search terms.
-		/// </summary>
-		void Initialize (string terms);
+/// <summary>Defines whether a given file/directory matches a set of search terms.</summary>
+public interface ISearchMatcher
+{
+    /// <summary>Called once for each new search. Defines the string the user has provided as search terms.</summary>
+    void Initialize (string terms);
 
-		/// <summary>
-		/// Return true if <paramref name="f"/> is a match to the
-		/// last provided search terms
-		/// </summary>
-		bool IsMatch (IFileSystemInfo f);
-	}
-}
+    /// <summary>Return true if <paramref name="f"/> is a match to the last provided search terms</summary>
+    bool IsMatch (IFileSystemInfo f);
+}

+ 266 - 414
Terminal.Gui/Input/Command.cs

@@ -3,417 +3,269 @@
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Actions which can be performed by the application or bound to keys in a <see cref="View"/> control.
-/// </summary>
-public enum Command {
-	/// <summary>
-	/// The default command. For <see cref="View"/> this focuses the view.
-	/// </summary>
-	Default,
-
-	/// <summary>
-	/// Moves down one item (cell, line, etc...).
-	/// </summary>
-	LineDown,
-
-	/// <summary>
-	/// Extends the selection down one (cell, line, etc...).
-	/// </summary>
-	LineDownExtend,
-
-	/// <summary>
-	/// Moves down to the last child node of the branch that holds the current selection.
-	/// </summary>
-	LineDownToLastBranch,
-
-	/// <summary>
-	/// Scrolls down one (cell, line, etc...) (without changing the selection).
-	/// </summary>
-	ScrollDown,
-
-	// --------------------------------------------------------------------
-
-	/// <summary>
-	/// Moves up one (cell, line, etc...).
-	/// </summary>
-	LineUp,
-
-	/// <summary>
-	/// Extends the selection up one item (cell, line, etc...).
-	/// </summary>
-	LineUpExtend,
-
-	/// <summary>
-	/// Moves up to the first child node of the branch that holds the current selection.
-	/// </summary>
-	LineUpToFirstBranch,
-
-	/// <summary>
-	/// Scrolls up one item (cell, line, etc...) (without changing the selection).
-	/// </summary>
-	ScrollUp,
-
-	/// <summary>
-	/// Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single character, cell, item etc.
-	/// </summary>
-	Left,
-
-	/// <summary>
-	/// Scrolls one item (cell, character, etc...) to the left
-	/// </summary>
-	ScrollLeft,
-
-	/// <summary>
-	/// Extends the selection left one by the minimum increment supported by the view e.g. single character, cell, item etc.
-	/// </summary>
-	LeftExtend,
-
-	/// <summary>
-	/// Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
-	/// </summary>
-	Right,
-
-	/// <summary>
-	/// Scrolls one item (cell, character, etc...) to the right.
-	/// </summary>
-	ScrollRight,
-
-	/// <summary>
-	/// Extends the selection right one by the minimum increment supported by the view e.g. single character, cell, item etc.
-	/// </summary>
-	RightExtend,
-
-	/// <summary>
-	/// Moves the caret to the start of the previous word.
-	/// </summary>
-	WordLeft,
-
-	/// <summary>
-	/// Extends the selection to the start of the previous word.
-	/// </summary>
-	WordLeftExtend,
-
-	/// <summary>
-	/// Moves the caret to the start of the next word.
-	/// </summary>
-	WordRight,
-
-	/// <summary>
-	/// Extends the selection to the start of the next word.
-	/// </summary>
-	WordRightExtend,
-
-	/// <summary>
-	/// Cuts to the clipboard the characters from the current position to the end of the line.
-	/// </summary>
-	CutToEndLine,
-
-	/// <summary>
-	/// Cuts to the clipboard the characters from the current position to the start of the line.
-	/// </summary>
-	CutToStartLine,
-
-	/// <summary>
-	/// Deletes the characters forwards.
-	/// </summary>
-	KillWordForwards,
-
-	/// <summary>
-	/// Deletes the characters backwards.
-	/// </summary>
-	KillWordBackwards,
-
-	/// <summary>
-	/// Toggles overwrite mode such that newly typed text overwrites the text that is
-	/// already there (typically associated with the Insert key).
-	/// </summary>
-	ToggleOverwrite,
-
-	/// <summary>
-	/// Enables overwrite mode such that newly typed text overwrites the text that is
-	/// already there (typically associated with the Insert key).
-	/// </summary>
-	EnableOverwrite,
-
-	/// <summary>
-	/// Disables overwrite mode (<see cref="EnableOverwrite"/>)
-	/// </summary>
-	DisableOverwrite,
-
-	/// <summary>
-	/// Move one page down.
-	/// </summary>
-	PageDown,
-
-	/// <summary>
-	/// Move one page page extending the selection to cover revealed objects/characters.
-	/// </summary>
-	PageDownExtend,
-
-	/// <summary>
-	/// Move one page up.
-	/// </summary>
-	PageUp,
-
-	/// <summary>
-	/// Move one page up extending the selection to cover revealed objects/characters.
-	/// </summary>
-	PageUpExtend,
-
-	/// <summary>
-	/// Moves to the top/home.
-	/// </summary>
-	TopHome,
-
-	/// <summary>
-	/// Extends the selection to the top/home.
-	/// </summary>
-	TopHomeExtend,
-
-	/// <summary>
-	/// Moves to the bottom/end.
-	/// </summary>
-	BottomEnd,
-
-	/// <summary>
-	/// Extends the selection to the bottom/end.
-	/// </summary>
-	BottomEndExtend,
-
-	/// <summary>
-	/// Open the selected item.
-	/// </summary>
-	OpenSelectedItem,
-
-	/// <summary>
-	/// Toggle the checked state.
-	/// </summary>
-	ToggleChecked,
-
-	/// <summary>
-	/// Accepts the current state (e.g. selection, button press etc).
-	/// </summary>
-	Accept,
-
-	/// <summary>
-	/// Toggles the Expanded or collapsed state of a a list or item (with subitems).
-	/// </summary>
-	ToggleExpandCollapse,
-
-	/// <summary>
-	/// Expands a list or item (with subitems).
-	/// </summary>
-	Expand,
-
-	/// <summary>
-	/// Recursively Expands all child items and their child items (if any).
-	/// </summary>
-	ExpandAll,
-
-	/// <summary>
-	/// Collapses a list or item (with subitems).
-	/// </summary>
-	Collapse,
-
-	/// <summary>
-	/// Recursively collapses a list items of their children (if any).
-	/// </summary>
-	CollapseAll,
-
-	/// <summary>
-	/// Cancels an action or any temporary states on the control e.g. expanding
-	/// a combo list.
-	/// </summary>
-	Cancel,
-
-	/// <summary>
-	/// Unix emulation.
-	/// </summary>
-	UnixEmulation,
-
-	/// <summary>
-	/// Deletes the character on the right.
-	/// </summary>
-	DeleteCharRight,
-
-	/// <summary>
-	/// Deletes the character on the left.
-	/// </summary>
-	DeleteCharLeft,
-
-	/// <summary>
-	/// Selects all objects.
-	/// </summary>
-	SelectAll,
-
-	/// <summary>
-	/// Deletes all objects.
-	/// </summary>
-	DeleteAll,
-
-	/// <summary>
-	/// Moves the cursor to the start of line.
-	/// </summary>
-	StartOfLine,
-
-	/// <summary>
-	/// Extends the selection to the start of line.
-	/// </summary>
-	StartOfLineExtend,
-
-	/// <summary>
-	/// Moves the cursor to the end of line.
-	/// </summary>
-	EndOfLine,
-
-	/// <summary>
-	/// Extends the selection to the end of line.
-	/// </summary>
-	EndOfLineExtend,
-
-	/// <summary>
-	/// Moves the cursor to the top of page.
-	/// </summary>
-	StartOfPage,
-
-	/// <summary>
-	/// Moves the cursor to the bottom of page.
-	/// </summary>
-	EndOfPage,
-
-	/// <summary>
-	/// Moves to the left page.
-	/// </summary>
-	PageLeft,
-
-	/// <summary>
-	/// Moves to the right page.
-	/// </summary>
-	PageRight,
-
-	/// <summary>
-	/// Moves to the left begin.
-	/// </summary>
-	LeftHome,
-
-	/// <summary>
-	/// Extends the selection to the left begin.
-	/// </summary>
-	LeftHomeExtend,
-
-	/// <summary>
-	/// Moves to the right end.
-	/// </summary>
-	RightEnd,
-
-	/// <summary>
-	/// Extends the selection to the right end.
-	/// </summary>
-	RightEndExtend,
-
-	/// <summary>
-	/// Undo changes.
-	/// </summary>
-	Undo,
-
-	/// <summary>
-	/// Redo changes.
-	/// </summary>
-	Redo,
-
-	/// <summary>
-	/// Copies the current selection.
-	/// </summary>
-	Copy,
-
-	/// <summary>
-	/// Cuts the current selection.
-	/// </summary>
-	Cut,
-
-	/// <summary>
-	/// Pastes the current selection.
-	/// </summary>
-	Paste,
-
-	/// <summary>
-	/// Quit a <see cref="Toplevel"/>.
-	/// </summary>
-	QuitToplevel,
-
-	/// <summary>
-	/// Suspend a application (Only implemented in <see cref="CursesDriver"/>).
-	/// </summary>
-	Suspend,
-
-	/// <summary>
-	/// Moves focus to the next view.
-	/// </summary>
-	NextView,
-
-	/// <summary>
-	/// Moves focuss to the previous view.
-	/// </summary>
-	PreviousView,
-
-	/// <summary>
-	/// Moves focus to the next view or Toplevel (case of Overlapped).
-	/// </summary>
-	NextViewOrTop,
-
-	/// <summary>
-	/// Moves focus to the next previous or Toplevel (case of Overlapped).
-	/// </summary>
-	PreviousViewOrTop,
-
-	/// <summary>
-	/// Refresh.
-	/// </summary>
-	Refresh,
-
-	/// <summary>
-	/// Toggles the selection.
-	/// </summary>
-	ToggleExtend,
-
-	/// <summary>
-	/// Inserts a new item.
-	/// </summary>
-	NewLine,
-
-	/// <summary>
-	/// Tabs to the next item.
-	/// </summary>
-	Tab,
-
-	/// <summary>
-	/// Tabs back to the previous item.
-	/// </summary>
-	BackTab,
-
-	/// <summary>
-	/// Saves the current document.
-	/// </summary>
-	Save,
-
-	/// <summary>
-	/// Saves the current document with a new name.
-	/// </summary>
-	SaveAs,
-
-	/// <summary>
-	/// Creates a new document.
-	/// </summary>
-	New,
-
-	/// <summary>
-	/// Moves selection to an item (e.g. highlighting a different menu item) without necessarily accepting it.
-	/// </summary>
-	Select,
-
-	/// <summary>
-	/// Shows context about the item (e.g. a context menu).
-	/// </summary>
-	ShowContextMenu
-}
+/// <summary>Actions which can be performed by the application or bound to keys in a <see cref="View"/> control.</summary>
+public enum Command
+{
+    /// <summary>The default command. For <see cref="View"/> this focuses the view.</summary>
+    Default,
+
+    /// <summary>Moves down one item (cell, line, etc...).</summary>
+    LineDown,
+
+    /// <summary>Extends the selection down one (cell, line, etc...).</summary>
+    LineDownExtend,
+
+    /// <summary>Moves down to the last child node of the branch that holds the current selection.</summary>
+    LineDownToLastBranch,
+
+    /// <summary>Scrolls down one (cell, line, etc...) (without changing the selection).</summary>
+    ScrollDown,
+
+    // --------------------------------------------------------------------
+
+    /// <summary>Moves up one (cell, line, etc...).</summary>
+    LineUp,
+
+    /// <summary>Extends the selection up one item (cell, line, etc...).</summary>
+    LineUpExtend,
+
+    /// <summary>Moves up to the first child node of the branch that holds the current selection.</summary>
+    LineUpToFirstBranch,
+
+    /// <summary>Scrolls up one item (cell, line, etc...) (without changing the selection).</summary>
+    ScrollUp,
+
+    /// <summary>
+    ///     Moves the selection left one by the minimum increment supported by the <see cref="View"/> e.g. single
+    ///     character, cell, item etc.
+    /// </summary>
+    Left,
+
+    /// <summary>Scrolls one item (cell, character, etc...) to the left</summary>
+    ScrollLeft,
+
+    /// <summary>
+    ///     Extends the selection left one by the minimum increment supported by the view e.g. single character, cell,
+    ///     item etc.
+    /// </summary>
+    LeftExtend,
+
+    /// <summary>
+    ///     Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item
+    ///     etc.
+    /// </summary>
+    Right,
+
+    /// <summary>Scrolls one item (cell, character, etc...) to the right.</summary>
+    ScrollRight,
+
+    /// <summary>
+    ///     Extends the selection right one by the minimum increment supported by the view e.g. single character, cell,
+    ///     item etc.
+    /// </summary>
+    RightExtend,
+
+    /// <summary>Moves the caret to the start of the previous word.</summary>
+    WordLeft,
+
+    /// <summary>Extends the selection to the start of the previous word.</summary>
+    WordLeftExtend,
+
+    /// <summary>Moves the caret to the start of the next word.</summary>
+    WordRight,
+
+    /// <summary>Extends the selection to the start of the next word.</summary>
+    WordRightExtend,
+
+    /// <summary>Cuts to the clipboard the characters from the current position to the end of the line.</summary>
+    CutToEndLine,
+
+    /// <summary>Cuts to the clipboard the characters from the current position to the start of the line.</summary>
+    CutToStartLine,
+
+    /// <summary>Deletes the characters forwards.</summary>
+    KillWordForwards,
+
+    /// <summary>Deletes the characters backwards.</summary>
+    KillWordBackwards,
+
+    /// <summary>
+    ///     Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
+    ///     associated with the Insert key).
+    /// </summary>
+    ToggleOverwrite,
+
+    /// <summary>
+    ///     Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
+    ///     associated with the Insert key).
+    /// </summary>
+    EnableOverwrite,
+
+    /// <summary>Disables overwrite mode (<see cref="EnableOverwrite"/>)</summary>
+    DisableOverwrite,
+
+    /// <summary>Move one page down.</summary>
+    PageDown,
+
+    /// <summary>Move one page page extending the selection to cover revealed objects/characters.</summary>
+    PageDownExtend,
+
+    /// <summary>Move one page up.</summary>
+    PageUp,
+
+    /// <summary>Move one page up extending the selection to cover revealed objects/characters.</summary>
+    PageUpExtend,
+
+    /// <summary>Moves to the top/home.</summary>
+    TopHome,
+
+    /// <summary>Extends the selection to the top/home.</summary>
+    TopHomeExtend,
+
+    /// <summary>Moves to the bottom/end.</summary>
+    BottomEnd,
+
+    /// <summary>Extends the selection to the bottom/end.</summary>
+    BottomEndExtend,
+
+    /// <summary>Open the selected item.</summary>
+    OpenSelectedItem,
+
+    /// <summary>Toggle the checked state.</summary>
+    ToggleChecked,
+
+    /// <summary>Accepts the current state (e.g. selection, button press etc).</summary>
+    Accept,
+
+    /// <summary>Toggles the Expanded or collapsed state of a a list or item (with subitems).</summary>
+    ToggleExpandCollapse,
+
+    /// <summary>Expands a list or item (with subitems).</summary>
+    Expand,
+
+    /// <summary>Recursively Expands all child items and their child items (if any).</summary>
+    ExpandAll,
+
+    /// <summary>Collapses a list or item (with subitems).</summary>
+    Collapse,
+
+    /// <summary>Recursively collapses a list items of their children (if any).</summary>
+    CollapseAll,
+
+    /// <summary>Cancels an action or any temporary states on the control e.g. expanding a combo list.</summary>
+    Cancel,
+
+    /// <summary>Unix emulation.</summary>
+    UnixEmulation,
+
+    /// <summary>Deletes the character on the right.</summary>
+    DeleteCharRight,
+
+    /// <summary>Deletes the character on the left.</summary>
+    DeleteCharLeft,
+
+    /// <summary>Selects all objects.</summary>
+    SelectAll,
+
+    /// <summary>Deletes all objects.</summary>
+    DeleteAll,
+
+    /// <summary>Moves the cursor to the start of line.</summary>
+    StartOfLine,
+
+    /// <summary>Extends the selection to the start of line.</summary>
+    StartOfLineExtend,
+
+    /// <summary>Moves the cursor to the end of line.</summary>
+    EndOfLine,
+
+    /// <summary>Extends the selection to the end of line.</summary>
+    EndOfLineExtend,
+
+    /// <summary>Moves the cursor to the top of page.</summary>
+    StartOfPage,
+
+    /// <summary>Moves the cursor to the bottom of page.</summary>
+    EndOfPage,
+
+    /// <summary>Moves to the left page.</summary>
+    PageLeft,
+
+    /// <summary>Moves to the right page.</summary>
+    PageRight,
+
+    /// <summary>Moves to the left begin.</summary>
+    LeftHome,
+
+    /// <summary>Extends the selection to the left begin.</summary>
+    LeftHomeExtend,
+
+    /// <summary>Moves to the right end.</summary>
+    RightEnd,
+
+    /// <summary>Extends the selection to the right end.</summary>
+    RightEndExtend,
+
+    /// <summary>Undo changes.</summary>
+    Undo,
+
+    /// <summary>Redo changes.</summary>
+    Redo,
+
+    /// <summary>Copies the current selection.</summary>
+    Copy,
+
+    /// <summary>Cuts the current selection.</summary>
+    Cut,
+
+    /// <summary>Pastes the current selection.</summary>
+    Paste,
+
+    /// <summary>Quit a <see cref="Toplevel"/>.</summary>
+    QuitToplevel,
+
+    /// <summary>Suspend a application (Only implemented in <see cref="CursesDriver"/>).</summary>
+    Suspend,
+
+    /// <summary>Moves focus to the next view.</summary>
+    NextView,
+
+    /// <summary>Moves focuss to the previous view.</summary>
+    PreviousView,
+
+    /// <summary>Moves focus to the next view or Toplevel (case of Overlapped).</summary>
+    NextViewOrTop,
+
+    /// <summary>Moves focus to the next previous or Toplevel (case of Overlapped).</summary>
+    PreviousViewOrTop,
+
+    /// <summary>Refresh.</summary>
+    Refresh,
+
+    /// <summary>Toggles the selection.</summary>
+    ToggleExtend,
+
+    /// <summary>Inserts a new item.</summary>
+    NewLine,
+
+    /// <summary>Tabs to the next item.</summary>
+    Tab,
+
+    /// <summary>Tabs back to the previous item.</summary>
+    BackTab,
+
+    /// <summary>Saves the current document.</summary>
+    Save,
+
+    /// <summary>Saves the current document with a new name.</summary>
+    SaveAs,
+
+    /// <summary>Creates a new document.</summary>
+    New,
+
+    /// <summary>Moves selection to an item (e.g. highlighting a different menu item) without necessarily accepting it.</summary>
+    Select,
+
+    /// <summary>Shows context about the item (e.g. a context menu).</summary>
+    ShowContextMenu
+}

+ 14 - 25
Terminal.Gui/Input/GrabMouseEventArgs.cs

@@ -1,29 +1,18 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Args <see cref="Application.GrabMouse"/> related events.
-	/// </summary>
-	public class GrabMouseEventArgs : EventArgs {
+/// <summary>Args <see cref="Application.GrabMouse"/> related events.</summary>
+public class GrabMouseEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="GrabMouseEventArgs"/> class.</summary>
+    /// <param name="view">The view that the event is about.</param>
+    public GrabMouseEventArgs (View view) { View = view; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="GrabMouseEventArgs"/> class.
-		/// </summary>
-		/// <param name="view">The view that the event is about.</param>
-		public GrabMouseEventArgs (View view)
-		{
-			View = view;
-		}
+    /// <summary>
+    ///     Flag that allows the cancellation of the event. If set to <see langword="true"/> in the event handler, the
+    ///     event will be canceled.
+    /// </summary>
+    public bool Cancel { get; set; }
 
-		/// <summary>
-		/// Gets the view that grabbed the mouse.
-		/// </summary>
-		public View View { get; }
-
-		/// <summary>
-		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
-		/// event handler, the event will be canceled.
-		/// </summary>
-		public bool Cancel { get; set; }
-	}
+    /// <summary>Gets the view that grabbed the mouse.</summary>
+    public View View { get; }
 }

+ 926 - 1044
Terminal.Gui/Input/Key.cs

@@ -1,1052 +1,934 @@
-using System;
-using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-using System.Text.Json.Serialization;
-using static System.Runtime.CompilerServices.RuntimeHelpers;
+using System.Globalization;
 
 namespace Terminal.Gui;
 
 /// <summary>
-/// Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and raising keyboard events.
+///     Provides an abstraction for common keyboard operations and state. Used for processing keyboard input and
+///     raising keyboard events.
 /// </summary>
 /// <remarks>
-/// <para>
-/// This class provides a high-level abstraction with helper methods and properties for common keyboard operations. Use this class
-/// instead of the <see cref="Terminal.Gui.KeyCode"/> enumeration for keyboard input whenever possible.
-/// </para>
-/// <para>
-/// 
-/// </para>
-/// <para>
-/// The default value for <see cref="Key"/> is <see cref="KeyCode.Null"/> and can be tested using <see cref="Key.Empty"/>.
-/// </para>
-/// <para>
-/// <list type="table">
-///	<listheader>
-///	<term>Concept</term><description>Definition</description>
-///	</listheader>
-///	<item>
-///	<term>Testing Shift State</term>
-///	<description>
-///	The <c>Is</c> properties (<see cref="IsShift"/>,<see cref="IsCtrl"/>, <see cref="IsAlt"/>) test for shift state; whether the key press was modified by a shift key.
-///	</description>
-///	</item>
-///	<item>
-/// 	<term>Adding Shift State</term>
-///	<description>
-///	The <c>With</c> properties (<see cref="WithShift"/>,<see cref="WithCtrl"/>, <see cref="WithAlt"/>) return a copy of the Key with the shift modifier applied. This
-///     is useful for specifying a key that requires a shift modifier (e.g. <c>var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;</c>.
-///	</description>
-///	</item>
-///	<item>
-/// 	<term>Removing Shift State</term>
-///	<description>
-///	The <c>No</c> properties (<see cref="NoShift"/>,<see cref="NoCtrl"/>, <see cref="NoAlt"/>) return a copy of the Key with the shift modifier removed. This
-///     is useful for specifying a key that does not require a shift modifier (e.g. <c>var ControlDelete = ControlAltDelete.NoCtrl;</c>.
-///	</description>
-///	</item>
-///	<item>
-///	<term>Encoding of A..Z</term>
-///	<description>
-///	Lowercase alpha keys are encoded (in <see cref="Key.KeyCode"/>) as values between 65 and 90 corresponding to
-///	the un-shifted A to Z keys on a keyboard. Properties are provided for these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
-///	Even though the encoded values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-///	</description>
-///	</item>
-///	<item>
-///     <term>Persistence as strings</term>
-///	<description>
-///     Keys are persisted as <c>"[Modifiers]+[Key]</c>. For example <c>new Key(Key.Delete).WithAlt.WithDel</c> is persisted as <c>"Ctrl+Alt+Delete"</c>. See <see cref="ToString()"/>
-///     and <see cref="TryParse(string, out Terminal.Gui.Key)"/> for more information.
-///	</description>
-///	</item>
-/// </list>
-/// </para>
+///     <para>
+///         This class provides a high-level abstraction with helper methods and properties for common keyboard
+///         operations. Use this class instead of the <see cref="Terminal.Gui.KeyCode"/> enumeration for keyboard input
+///         whenever possible.
+///     </para>
+///     <para></para>
+///     <para>
+///         The default value for <see cref="Key"/> is <see cref="KeyCode.Null"/> and can be tested using
+///         <see cref="Key.Empty"/>.
+///     </para>
+///     <para>
+///         <list type="table">
+///             <listheader>
+///                 <term>Concept</term><description>Definition</description>
+///             </listheader>
+///             <item>
+///                 <term>Testing Shift State</term>
+///                 <description>
+///                     The <c>Is</c> properties (<see cref="IsShift"/>,<see cref="IsCtrl"/>, <see cref="IsAlt"/>)
+///                     test for shift state; whether the key press was modified by a shift key.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>Adding Shift State</term>
+///                 <description>
+///                     The <c>With</c> properties (<see cref="WithShift"/>,<see cref="WithCtrl"/>,
+///                     <see cref="WithAlt"/>) return a copy of the Key with the shift modifier applied. This is useful for
+///                     specifying a key that requires a shift modifier (e.g.
+///                     <c>var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;</c>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>Removing Shift State</term>
+///                 <description>
+///                     The <c>No</c> properties (<see cref="NoShift"/>,<see cref="NoCtrl"/>, <see cref="NoAlt"/>)
+///                     return a copy of the Key with the shift modifier removed. This is useful for specifying a key that
+///                     does not require a shift modifier (e.g. <c>var ControlDelete = ControlAltDelete.NoCtrl;</c>.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>Encoding of A..Z</term>
+///                 <description>
+///                     Lowercase alpha keys are encoded (in <see cref="Key.KeyCode"/>) as values between 65 and
+///                     90 corresponding to the un-shifted A to Z keys on a keyboard. Properties are provided for these
+///                     (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the encoded values are the same
+///                     as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted
+///                     characters.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>Persistence as strings</term>
+///                 <description>
+///                     Keys are persisted as <c>"[Modifiers]+[Key]</c>. For example
+///                     <c>new Key(Key.Delete).WithAlt.WithDel</c> is persisted as <c>"Ctrl+Alt+Delete"</c>. See
+///                     <see cref="ToString()"/> and <see cref="TryParse(string, out Terminal.Gui.Key)"/> for more
+///                     information.
+///                 </description>
+///             </item>
+///         </list>
+///     </para>
 /// </remarks>
-public class Key : EventArgs, IEquatable<Key> {
-	/// <summary>
-	/// Constructs a new <see cref="Key"/>
-	/// </summary>
-	public Key () : this (KeyCode.Null) { }
-
-	/// <summary>
-	/// Constructs a new <see cref="Key"/> from the provided Key value
-	/// </summary>
-	/// <param name="k">The key</param>
-	public Key (KeyCode k) => KeyCode = k;
-
-	/// <summary>
-	/// Constructs a new <see cref="Key"/> from a char.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// The key codes for the A..Z keys are encoded as values between 65 and 90 (<see cref="KeyCode.A"/> through <see cref="KeyCode.Z"/>).
-	/// While these are the same as the ASCII values for uppercase characters, they represent *keys*, not characters.
-	/// Therefore, this constructor will store 'A'..'Z' as <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/> with the
-	/// <see cref="KeyCode.ShiftMask"/> set and will
-	/// store `a`..`z` as <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/>.
-	/// </para>
-	/// </remarks>
-	/// <param name="ch"></param>
-	public Key (char ch)
-	{
-		switch (ch) {
-		case >= 'A' and <= 'Z':
-			// Upper case A..Z mean "Shift-char" so we need to add Shift
-			KeyCode = (KeyCode)ch | KeyCode.ShiftMask;
-			break;
-		case >= 'a' and <= 'z':
-			// Lower case a..z mean no shift, so we need to store as Key.A...Key.Z
-			KeyCode = (KeyCode)(ch - 32);
-			return;
-		default:
-			KeyCode = (KeyCode)ch;
-			break;
-		}
-	}
-
-	/// <summary>
-	/// Constructs a new Key from a string describing the key.
-	/// See <see cref="TryParse(string, out Terminal.Gui.Key)"/> for information
-	/// on the format of the string.
-	/// </summary>
-	/// <param name="str">The string describing the key.</param>
-	public Key (string str)
-	{
-		var result = Key.TryParse (str, out Key key);
-		if (!result) {
-			throw new ArgumentException (@$"Invalid key string: {str}", nameof (str));
-		}
-		KeyCode = key.KeyCode;
-	}
-
-	/// <summary>
-	/// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
-	/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
-	/// </summary>
-	public bool Handled { get; set; } = false;
-
-	/// <summary>
-	/// The encoded key value. 
-	/// </summary>
-	/// <para>
-	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and
-	/// 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
-	/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
-	/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-	/// </para>
-	/// <remarks>
-	/// This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.
-	/// </remarks>
-	public KeyCode KeyCode { get; init; }
-
-	/// <summary>
-	/// Enables passing the key binding scope with the event. Default is <see cref="KeyBindingScope.Focused"/>.
-	/// </summary>
-	public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused;
-
-	/// <summary>
-	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. Useful
-	/// for determining if a key represents is a printable character.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
-	/// </para>
-	/// <para>
-	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
-	/// <see cref="KeyCode.ShiftMask"/> is set.
-	/// </para>
-	/// <para>
-	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
-	/// </para>
-	/// </remarks>
-	public Rune AsRune => ToRune (KeyCode);
-
-	/// <summary>
-	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful
-	/// for determining if a key represents is a printable character.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
-	/// </para>
-	/// <para>
-	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
-	/// <see cref="KeyCode.ShiftMask"/> is set.
-	/// </para>
-	/// <para>
-	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
-	/// </para>
-	/// </remarks>
-	/// <param name="key"></param>
-	/// <returns>The key converted to a Rune. <see langword="default"/> if conversion is not possible.</returns>
-	public static Rune ToRune (KeyCode key)
-	{
-		if (key is KeyCode.Null or KeyCode.SpecialMask || key.HasFlag (KeyCode.CtrlMask) || key.HasFlag (KeyCode.AltMask)) {
-			return default;
-		}
-
-		// Extract the base key code
-		var baseKey = key;
-		if (baseKey.HasFlag(KeyCode.ShiftMask)) {
-			baseKey &= ~KeyCode.ShiftMask;
-		}
-
-		switch (baseKey) {
-		case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
-			return new Rune ((uint)(baseKey + 32));
-		case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
-			return new Rune ((uint)baseKey);
-		case > KeyCode.Null and < KeyCode.A:
-			return new Rune ((uint)baseKey);
-		}
-
-		if (Enum.IsDefined (typeof (KeyCode), baseKey)) {
-			return default;
-		}
-
-		return new Rune ((uint)baseKey);
-	}
-
-	/// <summary>
-	/// Gets a value indicating whether the Shift key was pressed.
-	/// </summary>
-	/// <value><see langword="true"/> if is shift; otherwise, <see langword="false"/>.</value>
-	public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0;
-
-	/// <summary>
-	/// Gets a value indicating whether the Alt key was pressed (real or synthesized)
-	/// </summary>
-	/// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
-	public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0;
-
-	/// <summary>
-	/// Gets a value indicating whether the Ctrl key was pressed.
-	/// </summary>
-	/// <value><see langword="true"/> if is ctrl; otherwise, <see langword="false"/>.</value>
-	public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
-
-	/// <summary>
-	/// Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
-	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
-	/// special cased.
-	/// </summary>
-	/// <remarks>
-	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
-	/// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
-	/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-	/// </remarks>
-	public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
-
-	/// <summary>
-	/// Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
-	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
-	/// special cased.
-	/// </summary>
-	/// <remarks>
-	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
-	/// the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g. <see cref="Key.A"/>, <see cref="Key.B"/>, etc.).
-	/// Even though the values are the same as the ASCII values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-	/// </remarks>
-	public static bool GetIsKeyCodeAtoZ (KeyCode keyCode)
-	{
-		if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0) {
-			return false;
-		}
-
-		if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z) {
-			return true;
-		}
-
-		return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z;
-	}
-
-	/// <summary>
-	/// Indicates whether the <see cref="Key"/> is valid or not. Invalid keys are <see cref="Key.Empty"/>,
-	/// and keys with only shift modifiers.
-	/// </summary>
-	public bool IsValid => this != Empty && (NoAlt.NoShift.NoCtrl != Empty);
-
-	/// <summary>
-	/// Helper for specifying a shifted <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// </code>
-	/// </summary>
-	public Key WithShift => new (KeyCode | KeyCode.ShiftMask);
-
-	/// <summary>
-	/// Helper for removing a shift modifier from a <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// var AltDelete = ControlAltDelete.NoCtrl;
-	/// </code>
-	/// </summary>
-	public Key NoShift => new (KeyCode & ~KeyCode.ShiftMask);
-
-	/// <summary>
-	/// Helper for specifying a shifted <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// </code>
-	/// </summary>
-	public Key WithCtrl => new (KeyCode | KeyCode.CtrlMask);
-
-	/// <summary>
-	/// Helper for removing a shift modifier from a <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// var AltDelete = ControlAltDelete.NoCtrl;
-	/// </code>
-	/// </summary>
-	public Key NoCtrl => new (KeyCode & ~KeyCode.CtrlMask);
-
-	/// <summary>
-	/// Helper for specifying a shifted <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// </code>
-	/// </summary>
-	public Key WithAlt => new (KeyCode | KeyCode.AltMask);
-
-	/// <summary>
-	/// Helper for removing a shift modifier from a <see cref="Key"/>.
-	/// <code>
-	/// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
-	/// var AltDelete = ControlAltDelete.NoCtrl;
-	/// </code>
-	/// </summary>
-	public Key NoAlt => new (KeyCode & ~KeyCode.AltMask);
-
-	#region Operators
-	/// <summary>
-	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties
-	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
-	/// </summary>
-	/// <remarks>
-	/// Uses <see cref="AsRune"/>.
-	/// </remarks>
-	/// <param name="kea"></param>
-	public static explicit operator Rune (Key kea) => kea.AsRune;
-
-	// BUGBUG: (Tig) I do not think this cast operator is really needed. 
-	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties
-	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
-	/// </summary>
-	/// <param name="kea"></param>
-	public static explicit operator uint (Key kea) => (uint)kea.KeyCode;
-
-	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties
-	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
-	/// </summary>
-	/// <param name="key"></param>
-	public static explicit operator KeyCode (Key key) => key.KeyCode;
-
-	/// <summary>
-	/// Cast <see cref="KeyCode"/> to a <see cref="Key"/>. 
-	/// </summary>
-	/// <param name="keyCode"></param>
-	public static implicit operator Key (KeyCode keyCode) => new Key (keyCode);
-
-	/// <summary>
-	/// Cast <see langword="char"/> to a <see cref="Key"/>. 
-	/// </summary>
-	/// <remarks>
-	/// See <see cref="Key(char)"/> for more information.
-	/// </remarks>
-	/// <param name="ch"></param>
-	public static implicit operator Key (char ch) => new Key (ch);
-
-	/// <summary>
-	/// Cast <see langword="string"/> to a <see cref="Key"/>. 
-	/// </summary>
-	/// <remarks>
-	/// See <see cref="Key(string)"/> for more information.
-	/// </remarks>
-	/// <param name="str"></param>
-	public static implicit operator Key (string str) => new Key (str);
-
-	/// <summary>
-	/// Cast a <see cref="Key"/> to a <see langword="string"/>. 
-	/// </summary>
-	/// <remarks>
-	/// See <see cref="Key(string)"/> for more information.
-	/// </remarks>
-	/// <param name="key"></param>
-	public static implicit operator string (Key key) => key.ToString ();
-
-	/// <inheritdoc/>
-	public override bool Equals (object obj) => obj is Key k && k.KeyCode == KeyCode;
-
-	bool IEquatable<Key>.Equals (Key other) => Equals ((object)other);
-
-	/// <inheritdoc/>
-	public override int GetHashCode () => (int)KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for equality.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for not equality.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator != (Key a, Key b) => a?.KeyCode != b?.KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for less-than.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator < (Key a, Key b) => a?.KeyCode < b?.KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for greater-than.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator > (Key a, Key b) => a?.KeyCode > b?.KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for greater-than-or-equal-to.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator <= (Key a, Key b) => a?.KeyCode <= b?.KeyCode;
-
-	/// <summary>
-	/// Compares two <see cref="Key"/>s for greater-than-or-equal-to.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <param name="b"></param>
-	/// <returns></returns>
-	public static bool operator >= (Key a, Key b) => a?.KeyCode >= b?.KeyCode;
-	#endregion Operators
-
-	#region String conversion
-	/// <summary>
-	/// Pretty prints the KeyEvent
-	/// </summary>
-	/// <returns></returns>
-	public override string ToString () => ToString (KeyCode, (Rune)'+');
-
-	static string GetKeyString (KeyCode key)
-	{
-		if (key is KeyCode.Null or KeyCode.SpecialMask) {
-			return string.Empty;
-		}
-		// Extract the base key (removing modifier flags)
-		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
-
-		if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) {
-			return ((Rune)(uint)(key + 32)).ToString ();
-		}
-
-		if (key is > KeyCode.Space and < KeyCode.A) {
-			return ((Rune)(uint)key).ToString ();
-		}
-
-		string keyName = Enum.GetName (typeof (KeyCode), key);
-		return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
-	}
-
-
-	/// <summary>
-	/// Formats a <see cref="KeyCode"/> as a string using the default separator of '+'
-	/// </summary>
-	/// <param name="key">The key to format.</param>
-	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
-	public static string ToString (KeyCode key) => ToString (key, (Rune)'+');
-
-	/// <summary>
-	/// Formats a <see cref="KeyCode"/> as a string.
-	/// </summary>
-	/// <param name="key">The key to format.</param>
-	/// <param name="separator">The character to use as a separator between modifier keys and and the key itself.</param>
-	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
-	public static string ToString (KeyCode key, Rune separator)
-	{
-		if (key is KeyCode.Null) {
-			// Same as Key.IsValid
-			return @"Null";
-		}
-
-		var sb = new StringBuilder ();
-
-		// Extract the base key (removing modifier flags)
-		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
-
-		// Extract and handle modifiers
-		bool hasModifiers = false;
-		if ((key & KeyCode.CtrlMask) != 0) {
-			sb.Append ($"Ctrl{separator}");
-			hasModifiers = true;
-		}
-		if ((key & KeyCode.AltMask) != 0) {
-			sb.Append ($"Alt{separator}");
-			hasModifiers = true;
-		}
-		if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key)) {
-			sb.Append ($"Shift{separator}");
-			hasModifiers = true;
-		}
-
-		// Handle special cases and modifiers on their own
-		if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers)) {
-			if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
-				sb.Append (baseKey & ~KeyCode.Space);
-			} else {
-				// Append the actual key name
-				sb.Append (GetKeyString (baseKey));
-			}
-		}
-
-		return TrimEndSeparator (sb.ToString (), separator);
-	}
-
-	static string TrimEndSeparator (string input, Rune separator)
-	{
-		// Trim the trailing separator (+). Unless there are two separators at the end.
-		// "+" (don't trim)
-		// "Ctrl+" (trim)
-		// "Ctrl++" (trim)
-
-		if (input.Length > 1 && new Rune(input [^1]) == separator && new Rune(input [^2]) != separator) {
-			return input [..^1];
-		}
-		return input;
-	}
-
-	static readonly Dictionary<string, KeyCode> _modifierDict = new (comparer: StringComparer.InvariantCultureIgnoreCase) {
-		{ "Shift", KeyCode.ShiftMask },
-		{ "Ctrl", KeyCode.CtrlMask },
-		{ "Alt", KeyCode.AltMask }
-	};
-
-	/// <summary>
-	/// Converts the provided string to a new <see cref="Key"/> instance.
-	/// </summary>
-	/// <param name="text">The text to analyze. Formats supported are
-	/// "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X", "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
-	/// </param>
-	/// <param name="key">The parsed value.</param>
-	/// <returns>A boolean value indicating whether parsing was successful.</returns>
-	/// <remarks>
-	/// </remarks>
-	public static bool TryParse (string text, [NotNullWhen (true)] out Key key)
-	{
-		if (string.IsNullOrEmpty (text)) {
-			key = new Key (KeyCode.Null);
-			return true;
-		}
-
-		key = null;
-
-		// Split the string into parts
-		string [] parts = text.Split ('+', '-');
-
-		if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty)) {
-			return false;
-		}
-
-		// if it's just a shift key
-		if (parts.Length == 1) {
-			switch (parts [0]) {
-			case "Ctrl":
-				key = new Key (KeyCode.CtrlMask);
-				return true;
-			case "Alt":
-				key = new Key (KeyCode.AltMask);
-				return true;
-			case "Shift":
-				key = new Key (KeyCode.ShiftMask);
-				return true;
-			}
-		}
-
-		var modifiers = KeyCode.Null;
-		for (int index = 0; index < parts.Length; index++) {
-			if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out var modifier)) {
-				modifiers |= modifier;
-				parts [index] = string.Empty; // eat it
-			}
-		}
-
-		// we now have the modifiers
-
-		string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty);
-		var parsedKeyCode = KeyCode.Null;
-		int parsedInt = 0;
-		if (partNotFound.Length == 1) {
-			var keyCode = (KeyCode)partNotFound [0];
-			// if it's a single digit int, treat it as such
-			if (int.TryParse (partNotFound,
-				System.Globalization.NumberStyles.Integer,
-				System.Globalization.CultureInfo.InvariantCulture,
-				out parsedInt)) {
-				keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt);
-			} else if (Enum.TryParse (partNotFound, false, out parsedKeyCode)) {
-				if (parsedKeyCode != KeyCode.Null) {
-					if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
-						key = new Key (parsedKeyCode | KeyCode.ShiftMask);
-						return true;
-					}
-					key = new Key ((KeyCode)parsedKeyCode | modifiers);
-					return true;
-				}
-			}
-			key = new Key (keyCode | modifiers);
-			return true;
-		}
-
-		if (Enum.TryParse (partNotFound, true, out parsedKeyCode)) {
-			if (parsedKeyCode != KeyCode.Null) {
-				if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
-					key = new Key (parsedKeyCode | KeyCode.ShiftMask);
-					return true;
-				}
-				key = new Key (parsedKeyCode | modifiers);
-				return true;
-			}
-		}
-
-		// if it's a number int, treat it as a unicode value
-		if (int.TryParse (partNotFound,
-			System.Globalization.NumberStyles.Number,
-			System.Globalization.CultureInfo.InvariantCulture, out parsedInt)) {
-			if (!Rune.IsValid (parsedInt)) {
-				return false;
-			}
-			if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0) {
-				key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
-				return true;
-			}
-			key = new Key ((KeyCode)parsedInt);
-			return true;
-		}
-
-		if (!Enum.TryParse (partNotFound, true, out parsedKeyCode)) {
-			return false;
-		}
-
-		if (GetIsKeyCodeAtoZ (parsedKeyCode)) {
-			key = new Key (parsedKeyCode | modifiers & ~KeyCode.Space);
-			return true;
-		}
-
-		return false;
-	}
-	#endregion
-
-
-	#region Standard Key Definitions
-	/// <summary>
-	/// An uninitialized The <see cref="Key"/> object.
-	/// </summary>
-	public new static Key Empty => new ();
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Backspace key.
-	/// </summary>
-	public static Key Backspace => new (KeyCode.Backspace);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the tab key (forwards tab key).
-	/// </summary>
-	public static Key Tab => new (KeyCode.Tab);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the return key.
-	/// </summary>
-	public static Key Enter => new (KeyCode.Enter);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the clear key.
-	/// </summary>
-	public static Key Clear => new (KeyCode.Clear);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Escape key.
-	/// </summary>
-	public static Key Esc => new (KeyCode.Esc);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Space bar key.
-	/// </summary>
-	public static Key Space => new (KeyCode.Space);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 0 key.
-	/// </summary>
-	public static Key D0 => new (KeyCode.D0);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 1 key.
-	/// </summary>
-	public static Key D1 => new (KeyCode.D1);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 2 key.
-	/// </summary>
-	public static Key D2 => new (KeyCode.D2);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 3 key.
-	/// </summary>
-	public static Key D3 => new (KeyCode.D3);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 4 key.
-	/// </summary>
-	public static Key D4 => new (KeyCode.D4);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 5 key.
-	/// </summary>
-	public static Key D5 => new (KeyCode.D5);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 6 key.
-	/// </summary>
-	public static Key D6 => new (KeyCode.D6);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 7 key.
-	/// </summary>
-	public static Key D7 => new (KeyCode.D7);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 8 key.
-	/// </summary>
-	public static Key D8 => new (KeyCode.D8);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for 9 key.
-	/// </summary>
-	public static Key D9 => new (KeyCode.D9);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.
-	/// </summary>
-	public static Key A => new (KeyCode.A);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.
-	/// </summary>
-	public static Key B => new (KeyCode.B);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.
-	/// </summary>
-	public static Key C => new (KeyCode.C);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.
-	/// </summary>
-	public static Key D => new (KeyCode.D);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.
-	/// </summary>
-	public static Key E => new (KeyCode.E);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.
-	/// </summary>
-	public static Key F => new (KeyCode.F);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.
-	/// </summary>
-	public static Key G => new (KeyCode.G);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.
-	/// </summary>
-	public static Key H => new (KeyCode.H);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.
-	/// </summary>
-	public static Key I => new (KeyCode.I);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.
-	/// </summary>
-	public static Key J => new (KeyCode.J);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.
-	/// </summary>
-	public static Key K => new (KeyCode.K);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.
-	/// </summary>
-	public static Key L => new (KeyCode.L);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.
-	/// </summary>
-	public static Key M => new (KeyCode.M);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.
-	/// </summary>
-	public static Key N => new (KeyCode.N);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.
-	/// </summary>
-	public static Key O => new (KeyCode.O);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.
-	/// </summary>
-	public static Key P => new (KeyCode.P);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.
-	/// </summary>
-	public static Key Q => new (KeyCode.Q);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.
-	/// </summary>
-	public static Key R => new (KeyCode.R);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.
-	/// </summary>
-	public static Key S => new (KeyCode.S);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.
-	/// </summary>
-	public static Key T => new (KeyCode.T);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.
-	/// </summary>
-	public static Key U => new (KeyCode.U);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.
-	/// </summary>
-	public static Key V => new (KeyCode.V);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.
-	/// </summary>
-	public static Key W => new (KeyCode.W);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.
-	/// </summary>
-	public static Key X => new (KeyCode.X);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.
-	/// </summary>
-	public static Key Y => new (KeyCode.Y);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.
-	/// </summary>
-	public static Key Z => new (KeyCode.Z);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Delete key.
-	/// </summary>
-	public static Key Delete => new (KeyCode.Delete);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Cursor up key.
-	/// </summary>
-	public static Key CursorUp => new (KeyCode.CursorUp);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Cursor down key.
-	/// </summary>
-	public static Key CursorDown => new (KeyCode.CursorDown);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Cursor left key.
-	/// </summary>
-	public static Key CursorLeft => new (KeyCode.CursorLeft);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Cursor right key.
-	/// </summary>
-	public static Key CursorRight => new (KeyCode.CursorRight);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Page Up key.
-	/// </summary>
-	public static Key PageUp => new (KeyCode.PageUp);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Page Down key.
-	/// </summary>
-	public static Key PageDown => new (KeyCode.PageDown);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Home key.
-	/// </summary>
-	public static Key Home => new (KeyCode.Home);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for End key.
-	/// </summary>
-	public static Key End => new (KeyCode.End);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Insert Character key.
-	/// </summary>
-	public static Key InsertChar => new (KeyCode.Insert);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Delete Character key.
-	/// </summary>
-	public static Key DeleteChar => new (KeyCode.Delete);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for Print Screen key.
-	/// </summary>
-	public static Key PrintScreen => new (KeyCode.PrintScreen);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F1 key.
-	/// </summary>
-	public static Key F1 => new (KeyCode.F1);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F2 key.
-	/// </summary>
-	public static Key F2 => new (KeyCode.F2);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F3 key.
-	/// </summary>
-	public static Key F3 => new (KeyCode.F3);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F4 key.
-	/// </summary>
-	public static Key F4 => new (KeyCode.F4);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F5 key.
-	/// </summary>
-	public static Key F5 => new (KeyCode.F5);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F6 key.
-	/// </summary>
-	public static Key F6 => new (KeyCode.F6);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F7 key.
-	/// </summary>
-	public static Key F7 => new (KeyCode.F7);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F8 key.
-	/// </summary>
-	public static Key F8 => new (KeyCode.F8);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F9 key.
-	/// </summary>
-	public static Key F9 => new (KeyCode.F9);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F10 key.
-	/// </summary>
-	public static Key F10 => new (KeyCode.F10);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F11 key.
-	/// </summary>
-	public static Key F11 => new (KeyCode.F11);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F12 key.
-	/// </summary>
-	public static Key F12 => new (KeyCode.F12);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F13 key.
-	/// </summary>
-	public static Key F13 => new (KeyCode.F13);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F14 key.
-	/// </summary>
-	public static Key F14 => new (KeyCode.F14);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F15 key.
-	/// </summary>
-	public static Key F15 => new (KeyCode.F15);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F16 key.
-	/// </summary>
-	public static Key F16 => new (KeyCode.F16);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F17 key.
-	/// </summary>
-	public static Key F17 => new (KeyCode.F17);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F18 key.
-	/// </summary>
-	public static Key F18 => new (KeyCode.F18);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F19 key.
-	/// </summary>
-	public static Key F19 => new (KeyCode.F19);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F20 key.
-	/// </summary>
-	public static Key F20 => new (KeyCode.F20);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F21 key.
-	/// </summary>
-	public static Key F21 => new (KeyCode.F21);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F22 key.
-	/// </summary>
-	public static Key F22 => new (KeyCode.F22);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F23 key.
-	/// </summary>
-	public static Key F23 => new (KeyCode.F23);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for F24 key.
-	/// </summary>
-	public static Key F24 => new (KeyCode.F24);
-	#endregion
-}
+public class Key : EventArgs, IEquatable<Key>
+{
+    /// <summary>Constructs a new <see cref="Key"/></summary>
+    public Key () : this (KeyCode.Null) { }
+
+    /// <summary>Constructs a new <see cref="Key"/> from the provided Key value</summary>
+    /// <param name="k">The key</param>
+    public Key (KeyCode k) { KeyCode = k; }
+
+    /// <summary>Constructs a new <see cref="Key"/> from a char.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         The key codes for the A..Z keys are encoded as values between 65 and 90 (<see cref="KeyCode.A"/> through
+    ///         <see cref="KeyCode.Z"/>). While these are the same as the ASCII values for uppercase characters, they represent
+    ///         *keys*, not characters. Therefore, this constructor will store 'A'..'Z' as <see cref="KeyCode.A"/>..
+    ///         <see cref="KeyCode.Z"/> with the <see cref="KeyCode.ShiftMask"/> set and will store `a`..`z` as
+    ///         <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="ch"></param>
+    public Key (char ch)
+    {
+        switch (ch)
+        {
+            case >= 'A' and <= 'Z':
+                // Upper case A..Z mean "Shift-char" so we need to add Shift
+                KeyCode = (KeyCode)ch | KeyCode.ShiftMask;
+
+                break;
+            case >= 'a' and <= 'z':
+                // Lower case a..z mean no shift, so we need to store as Key.A...Key.Z
+                KeyCode = (KeyCode)(ch - 32);
+
+                return;
+            default:
+                KeyCode = (KeyCode)ch;
+
+                break;
+        }
+    }
+
+    /// <summary>
+    ///     Constructs a new Key from a string describing the key. See
+    ///     <see cref="TryParse(string, out Terminal.Gui.Key)"/> for information on the format of the string.
+    /// </summary>
+    /// <param name="str">The string describing the key.</param>
+    public Key (string str)
+    {
+        bool result = TryParse (str, out Key key);
+
+        if (!result)
+        {
+            throw new ArgumentException (@$"Invalid key string: {str}", nameof (str));
+        }
+
+        KeyCode = key.KeyCode;
+    }
+
+    /// <summary>
+    ///     The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
+    ///     Useful for determining if a key represents is a printable character.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
+    ///     <para>
+    ///         If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+    ///         <see cref="KeyCode.ShiftMask"/> is set.
+    ///     </para>
+    ///     <para>
+    ///         If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
+    ///         <see langword="default"/>.
+    ///     </para>
+    /// </remarks>
+    public Rune AsRune => ToRune (KeyCode);
+
+    /// <summary>
+    ///     Indicates if the current Key event has already been processed and the driver should stop notifying any other
+    ///     event subscriber. Its important to set this value to true specially when updating any View's layout from inside the
+    ///     subscriber method.
+    /// </summary>
+    public bool Handled { get; set; } = false;
+
+    /// <summary>Gets a value indicating whether the Alt key was pressed (real or synthesized)</summary>
+    /// <value><see langword="true"/> if is alternate; otherwise, <see langword="false"/>.</value>
+    public bool IsAlt => (KeyCode & KeyCode.AltMask) != 0;
+
+    /// <summary>Gets a value indicating whether the Ctrl key was pressed.</summary>
+    /// <value><see langword="true"/> if is ctrl; otherwise, <see langword="false"/>.</value>
+    public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
+
+    /// <summary>
+    ///     Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to
+    ///     <see cref="KeyCode.Z"/>, regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is
+    ///     based on these keys which are special cased.
+    /// </summary>
+    /// <remarks>
+    ///     IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
+    ///     corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
+    ///     <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
+    ///     uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+    /// </remarks>
+    public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
+
+    /// <summary>Gets a value indicating whether the Shift key was pressed.</summary>
+    /// <value><see langword="true"/> if is shift; otherwise, <see langword="false"/>.</value>
+    public bool IsShift => (KeyCode & KeyCode.ShiftMask) != 0;
+
+    /// <summary>
+    ///     Indicates whether the <see cref="Key"/> is valid or not. Invalid keys are <see cref="Key.Empty"/>, and keys
+    ///     with only shift modifiers.
+    /// </summary>
+    public bool IsValid => this != Empty && NoAlt.NoShift.NoCtrl != Empty;
+
+    /// <summary>The encoded key value.</summary>
+    /// <para>
+    ///     IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and 90
+    ///     corresponding to the un-shifted A to Z keys on a keyboard. Enum values are provided for these (e.g.
+    ///     <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII values
+    ///     for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+    /// </para>
+    /// <remarks>This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.</remarks>
+    public KeyCode KeyCode { get; init; }
+
+    /// <summary>
+    ///     Helper for removing a shift modifier from a <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// var AltDelete = ControlAltDelete.NoCtrl;
+    /// </code>
+    /// </summary>
+    public Key NoAlt => new (KeyCode & ~KeyCode.AltMask);
+
+    /// <summary>
+    ///     Helper for removing a shift modifier from a <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// var AltDelete = ControlAltDelete.NoCtrl;
+    /// </code>
+    /// </summary>
+    public Key NoCtrl => new (KeyCode & ~KeyCode.CtrlMask);
+
+    /// <summary>
+    ///     Helper for removing a shift modifier from a <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// var AltDelete = ControlAltDelete.NoCtrl;
+    /// </code>
+    /// </summary>
+    public Key NoShift => new (KeyCode & ~KeyCode.ShiftMask);
+
+    /// <summary>Enables passing the key binding scope with the event. Default is <see cref="KeyBindingScope.Focused"/>.</summary>
+    public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused;
+
+    /// <summary>
+    ///     Helper for specifying a shifted <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// </code>
+    /// </summary>
+    public Key WithAlt => new (KeyCode | KeyCode.AltMask);
+
+    /// <summary>
+    ///     Helper for specifying a shifted <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// </code>
+    /// </summary>
+    public Key WithCtrl => new (KeyCode | KeyCode.CtrlMask);
+
+    /// <summary>
+    ///     Helper for specifying a shifted <see cref="Key"/>.
+    ///     <code>
+    /// var ControlAltDelete = new Key(Key.Delete).WithAlt.WithDel;
+    /// </code>
+    /// </summary>
+    public Key WithShift => new (KeyCode | KeyCode.ShiftMask);
+
+    /// <summary>
+    ///     Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
+    ///     regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which
+    ///     are special cased.
+    /// </summary>
+    /// <remarks>
+    ///     IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90
+    ///     corresponding to the un-shifted A to Z keys on a keyboard. Helper properties are provided these (e.g.
+    ///     <see cref="Key.A"/>, <see cref="Key.B"/>, etc.). Even though the values are the same as the ASCII values for
+    ///     uppercase characters, these enum values represent *lowercase*, un-shifted characters.
+    /// </remarks>
+    public static bool GetIsKeyCodeAtoZ (KeyCode keyCode)
+    {
+        if ((keyCode & KeyCode.AltMask) != 0 || (keyCode & KeyCode.CtrlMask) != 0)
+        {
+            return false;
+        }
+
+        if ((keyCode & ~KeyCode.Space & ~KeyCode.ShiftMask) is >= KeyCode.A and <= KeyCode.Z)
+        {
+            return true;
+        }
+
+        return (keyCode & KeyCode.CharMask) is >= KeyCode.A and <= KeyCode.Z;
+    }
+
+    /// <summary>
+    ///     Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful for determining if a key represents is a
+    ///     printable character.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Keys with Ctrl or Alt modifiers will return <see langword="default"/>.</para>
+    ///     <para>
+    ///         If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+    ///         <see cref="KeyCode.ShiftMask"/> is set.
+    ///     </para>
+    ///     <para>
+    ///         If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be
+    ///         <see langword="default"/>.
+    ///     </para>
+    /// </remarks>
+    /// <param name="key"></param>
+    /// <returns>The key converted to a Rune. <see langword="default"/> if conversion is not possible.</returns>
+    public static Rune ToRune (KeyCode key)
+    {
+        if (key is KeyCode.Null or KeyCode.SpecialMask
+            || key.HasFlag (KeyCode.CtrlMask)
+            || key.HasFlag (KeyCode.AltMask))
+        {
+            return default (Rune);
+        }
+
+        // Extract the base key code
+        KeyCode baseKey = key;
+
+        if (baseKey.HasFlag (KeyCode.ShiftMask))
+        {
+            baseKey &= ~KeyCode.ShiftMask;
+        }
+
+        switch (baseKey)
+        {
+            case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
+                return new Rune ((uint)(baseKey + 32));
+            case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
+                return new Rune ((uint)baseKey);
+            case > KeyCode.Null and < KeyCode.A:
+                return new Rune ((uint)baseKey);
+        }
+
+        if (Enum.IsDefined (typeof (KeyCode), baseKey))
+        {
+            return default (Rune);
+        }
+
+        return new Rune ((uint)baseKey);
+    }
+
+    #region Operators
+
+    /// <summary>
+    ///     Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties such
+    ///     as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
+    /// </summary>
+    /// <remarks>Uses <see cref="AsRune"/>.</remarks>
+    /// <param name="kea"></param>
+    public static explicit operator Rune (Key kea) { return kea.AsRune; }
+
+    // BUGBUG: (Tig) I do not think this cast operator is really needed. 
+    /// <summary>
+    ///     Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties such
+    ///     as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
+    /// </summary>
+    /// <param name="kea"></param>
+    public static explicit operator uint (Key kea) { return (uint)kea.KeyCode; }
+
+    /// <summary>
+    ///     Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties such
+    ///     as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
+    /// </summary>
+    /// <param name="key"></param>
+    public static explicit operator KeyCode (Key key) { return key.KeyCode; }
+
+    /// <summary>Cast <see cref="KeyCode"/> to a <see cref="Key"/>.</summary>
+    /// <param name="keyCode"></param>
+    public static implicit operator Key (KeyCode keyCode) { return new Key (keyCode); }
+
+    /// <summary>Cast <see langword="char"/> to a <see cref="Key"/>.</summary>
+    /// <remarks>See <see cref="Key(char)"/> for more information.</remarks>
+    /// <param name="ch"></param>
+    public static implicit operator Key (char ch) { return new Key (ch); }
+
+    /// <summary>Cast <see langword="string"/> to a <see cref="Key"/>.</summary>
+    /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
+    /// <param name="str"></param>
+    public static implicit operator Key (string str) { return new Key (str); }
+
+    /// <summary>Cast a <see cref="Key"/> to a <see langword="string"/>.</summary>
+    /// <remarks>See <see cref="Key(string)"/> for more information.</remarks>
+    /// <param name="key"></param>
+    public static implicit operator string (Key key) { return key.ToString (); }
+
+    /// <inheritdoc/>
+    public override bool Equals (object obj) { return obj is Key k && k.KeyCode == KeyCode; }
+
+    bool IEquatable<Key>.Equals (Key other) { return Equals (other); }
+
+    /// <inheritdoc/>
+    public override int GetHashCode () { return (int)KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for equality.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator == (Key a, Key b) { return a?.KeyCode == b?.KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for not equality.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator != (Key a, Key b) { return a?.KeyCode != b?.KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for less-than.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator < (Key a, Key b) { return a?.KeyCode < b?.KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for greater-than.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator > (Key a, Key b) { return a?.KeyCode > b?.KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator <= (Key a, Key b) { return a?.KeyCode <= b?.KeyCode; }
+
+    /// <summary>Compares two <see cref="Key"/>s for greater-than-or-equal-to.</summary>
+    /// <param name="a"></param>
+    /// <param name="b"></param>
+    /// <returns></returns>
+    public static bool operator >= (Key a, Key b) { return a?.KeyCode >= b?.KeyCode; }
+
+    #endregion Operators
+
+    #region String conversion
+
+    /// <summary>Pretty prints the KeyEvent</summary>
+    /// <returns></returns>
+    public override string ToString () { return ToString (KeyCode, (Rune)'+'); }
+
+    private static string GetKeyString (KeyCode key)
+    {
+        if (key is KeyCode.Null or KeyCode.SpecialMask)
+        {
+            return string.Empty;
+        }
+
+        // Extract the base key (removing modifier flags)
+        KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+
+        if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z)
+        {
+            return ((Rune)(uint)(key + 32)).ToString ();
+        }
+
+        if (key is > KeyCode.Space and < KeyCode.A)
+        {
+            return ((Rune)(uint)key).ToString ();
+        }
+
+        string keyName = Enum.GetName (typeof (KeyCode), key);
+
+        return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
+    }
+
+    /// <summary>Formats a <see cref="KeyCode"/> as a string using the default separator of '+'</summary>
+    /// <param name="key">The key to format.</param>
+    /// <returns>
+    ///     The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
+    ///     name will be returned.
+    /// </returns>
+    public static string ToString (KeyCode key) { return ToString (key, (Rune)'+'); }
+
+    /// <summary>Formats a <see cref="KeyCode"/> as a string.</summary>
+    /// <param name="key">The key to format.</param>
+    /// <param name="separator">The character to use as a separator between modifier keys and and the key itself.</param>
+    /// <returns>
+    ///     The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key
+    ///     name will be returned.
+    /// </returns>
+    public static string ToString (KeyCode key, Rune separator)
+    {
+        if (key is KeyCode.Null)
+        {
+            // Same as Key.IsValid
+            return @"Null";
+        }
+
+        var sb = new StringBuilder ();
+
+        // Extract the base key (removing modifier flags)
+        KeyCode baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+
+        // Extract and handle modifiers
+        var hasModifiers = false;
+
+        if ((key & KeyCode.CtrlMask) != 0)
+        {
+            sb.Append ($"Ctrl{separator}");
+            hasModifiers = true;
+        }
+
+        if ((key & KeyCode.AltMask) != 0)
+        {
+            sb.Append ($"Alt{separator}");
+            hasModifiers = true;
+        }
+
+        if ((key & KeyCode.ShiftMask) != 0 && !GetIsKeyCodeAtoZ (key))
+        {
+            sb.Append ($"Shift{separator}");
+            hasModifiers = true;
+        }
+
+        // Handle special cases and modifiers on their own
+        if (key != KeyCode.SpecialMask && (baseKey != KeyCode.Null || hasModifiers))
+        {
+            if ((key & KeyCode.SpecialMask) != 0 && (baseKey & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z)
+            {
+                sb.Append (baseKey & ~KeyCode.Space);
+            }
+            else
+            {
+                // Append the actual key name
+                sb.Append (GetKeyString (baseKey));
+            }
+        }
+
+        return TrimEndSeparator (sb.ToString (), separator);
+    }
+
+    private static string TrimEndSeparator (string input, Rune separator)
+    {
+        // Trim the trailing separator (+). Unless there are two separators at the end.
+        // "+" (don't trim)
+        // "Ctrl+" (trim)
+        // "Ctrl++" (trim)
+
+        if (input.Length > 1 && new Rune (input [^1]) == separator && new Rune (input [^2]) != separator)
+        {
+            return input [..^1];
+        }
+
+        return input;
+    }
+
+    private static readonly Dictionary<string, KeyCode> _modifierDict =
+        new (StringComparer.InvariantCultureIgnoreCase)
+        {
+            { "Shift", KeyCode.ShiftMask }, { "Ctrl", KeyCode.CtrlMask }, { "Alt", KeyCode.AltMask }
+        };
+
+    /// <summary>Converts the provided string to a new <see cref="Key"/> instance.</summary>
+    /// <param name="text">
+    ///     The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
+    ///     "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
+    /// </param>
+    /// <param name="key">The parsed value.</param>
+    /// <returns>A boolean value indicating whether parsing was successful.</returns>
+    /// <remarks></remarks>
+    public static bool TryParse (string text, [NotNullWhen (true)] out Key key)
+    {
+        if (string.IsNullOrEmpty (text))
+        {
+            key = new Key (KeyCode.Null);
+
+            return true;
+        }
+
+        key = null;
+
+        // Split the string into parts
+        string [] parts = text.Split ('+', '-');
+
+        if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
+        {
+            return false;
+        }
+
+        // if it's just a shift key
+        if (parts.Length == 1)
+        {
+            switch (parts [0])
+            {
+                case "Ctrl":
+                    key = new Key (KeyCode.CtrlMask);
+
+                    return true;
+                case "Alt":
+                    key = new Key (KeyCode.AltMask);
+
+                    return true;
+                case "Shift":
+                    key = new Key (KeyCode.ShiftMask);
+
+                    return true;
+            }
+        }
+
+        var modifiers = KeyCode.Null;
+
+        for (var index = 0; index < parts.Length; index++)
+        {
+            if (_modifierDict.TryGetValue (parts [index].ToLowerInvariant (), out KeyCode modifier))
+            {
+                modifiers |= modifier;
+                parts [index] = string.Empty; // eat it
+            }
+        }
+
+        // we now have the modifiers
+
+        string partNotFound = parts.FirstOrDefault (p => !string.IsNullOrEmpty (p), string.Empty);
+        var parsedKeyCode = KeyCode.Null;
+        var parsedInt = 0;
+
+        if (partNotFound.Length == 1)
+        {
+            var keyCode = (KeyCode)partNotFound [0];
+
+            // if it's a single digit int, treat it as such
+            if (int.TryParse (
+                              partNotFound,
+                              NumberStyles.Integer,
+                              CultureInfo.InvariantCulture,
+                              out parsedInt
+                             ))
+            {
+                keyCode = (KeyCode)((int)KeyCode.D0 + parsedInt);
+            }
+            else if (Enum.TryParse (partNotFound, false, out parsedKeyCode))
+            {
+                if (parsedKeyCode != KeyCode.Null)
+                {
+                    if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
+                    {
+                        key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+
+                        return true;
+                    }
+
+                    key = new Key (parsedKeyCode | modifiers);
+
+                    return true;
+                }
+            }
+
+            key = new Key (keyCode | modifiers);
+
+            return true;
+        }
+
+        if (Enum.TryParse (partNotFound, true, out parsedKeyCode))
+        {
+            if (parsedKeyCode != KeyCode.Null)
+            {
+                if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
+                {
+                    key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+
+                    return true;
+                }
+
+                key = new Key (parsedKeyCode | modifiers);
+
+                return true;
+            }
+        }
+
+        // if it's a number int, treat it as a unicode value
+        if (int.TryParse (
+                          partNotFound,
+                          NumberStyles.Number,
+                          CultureInfo.InvariantCulture,
+                          out parsedInt
+                         ))
+        {
+            if (!Rune.IsValid (parsedInt))
+            {
+                return false;
+            }
+
+            if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
+            {
+                key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
+
+                return true;
+            }
+
+            key = new Key ((KeyCode)parsedInt);
+
+            return true;
+        }
+
+        if (!Enum.TryParse (partNotFound, true, out parsedKeyCode))
+        {
+            return false;
+        }
+
+        if (GetIsKeyCodeAtoZ (parsedKeyCode))
+        {
+            key = new Key (parsedKeyCode | (modifiers & ~KeyCode.Space));
+
+            return true;
+        }
+
+        return false;
+    }
+
+    #endregion
+
+    #region Standard Key Definitions
+
+    /// <summary>An uninitialized The <see cref="Key"/> object.</summary>
+    public new static Key Empty => new ();
+
+    /// <summary>The <see cref="Key"/> object for the Backspace key.</summary>
+    public static Key Backspace => new (KeyCode.Backspace);
+
+    /// <summary>The <see cref="Key"/> object for the tab key (forwards tab key).</summary>
+    public static Key Tab => new (KeyCode.Tab);
+
+    /// <summary>The <see cref="Key"/> object for the return key.</summary>
+    public static Key Enter => new (KeyCode.Enter);
+
+    /// <summary>The <see cref="Key"/> object for the clear key.</summary>
+    public static Key Clear => new (KeyCode.Clear);
+
+    /// <summary>The <see cref="Key"/> object for the Escape key.</summary>
+    public static Key Esc => new (KeyCode.Esc);
+
+    /// <summary>The <see cref="Key"/> object for the Space bar key.</summary>
+    public static Key Space => new (KeyCode.Space);
+
+    /// <summary>The <see cref="Key"/> object for 0 key.</summary>
+    public static Key D0 => new (KeyCode.D0);
+
+    /// <summary>The <see cref="Key"/> object for 1 key.</summary>
+    public static Key D1 => new (KeyCode.D1);
+
+    /// <summary>The <see cref="Key"/> object for 2 key.</summary>
+    public static Key D2 => new (KeyCode.D2);
+
+    /// <summary>The <see cref="Key"/> object for 3 key.</summary>
+    public static Key D3 => new (KeyCode.D3);
+
+    /// <summary>The <see cref="Key"/> object for 4 key.</summary>
+    public static Key D4 => new (KeyCode.D4);
+
+    /// <summary>The <see cref="Key"/> object for 5 key.</summary>
+    public static Key D5 => new (KeyCode.D5);
+
+    /// <summary>The <see cref="Key"/> object for 6 key.</summary>
+    public static Key D6 => new (KeyCode.D6);
+
+    /// <summary>The <see cref="Key"/> object for 7 key.</summary>
+    public static Key D7 => new (KeyCode.D7);
+
+    /// <summary>The <see cref="Key"/> object for 8 key.</summary>
+    public static Key D8 => new (KeyCode.D8);
+
+    /// <summary>The <see cref="Key"/> object for 9 key.</summary>
+    public static Key D9 => new (KeyCode.D9);
+
+    /// <summary>The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.</summary>
+    public static Key A => new (KeyCode.A);
+
+    /// <summary>The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.</summary>
+    public static Key B => new (KeyCode.B);
+
+    /// <summary>The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.</summary>
+    public static Key C => new (KeyCode.C);
+
+    /// <summary>The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.</summary>
+    public static Key D => new (KeyCode.D);
+
+    /// <summary>The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.</summary>
+    public static Key E => new (KeyCode.E);
+
+    /// <summary>The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.</summary>
+    public static Key F => new (KeyCode.F);
+
+    /// <summary>The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.</summary>
+    public static Key G => new (KeyCode.G);
+
+    /// <summary>The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.</summary>
+    public static Key H => new (KeyCode.H);
+
+    /// <summary>The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.</summary>
+    public static Key I => new (KeyCode.I);
+
+    /// <summary>The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.</summary>
+    public static Key J => new (KeyCode.J);
+
+    /// <summary>The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.</summary>
+    public static Key K => new (KeyCode.K);
+
+    /// <summary>The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.</summary>
+    public static Key L => new (KeyCode.L);
+
+    /// <summary>The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.</summary>
+    public static Key M => new (KeyCode.M);
+
+    /// <summary>The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.</summary>
+    public static Key N => new (KeyCode.N);
+
+    /// <summary>The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.</summary>
+    public static Key O => new (KeyCode.O);
+
+    /// <summary>The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.</summary>
+    public static Key P => new (KeyCode.P);
+
+    /// <summary>The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.</summary>
+    public static Key Q => new (KeyCode.Q);
+
+    /// <summary>The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.</summary>
+    public static Key R => new (KeyCode.R);
+
+    /// <summary>The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.</summary>
+    public static Key S => new (KeyCode.S);
+
+    /// <summary>The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.</summary>
+    public static Key T => new (KeyCode.T);
+
+    /// <summary>The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.</summary>
+    public static Key U => new (KeyCode.U);
+
+    /// <summary>The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.</summary>
+    public static Key V => new (KeyCode.V);
+
+    /// <summary>The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.</summary>
+    public static Key W => new (KeyCode.W);
+
+    /// <summary>The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.</summary>
+    public static Key X => new (KeyCode.X);
+
+    /// <summary>The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.</summary>
+    public static Key Y => new (KeyCode.Y);
+
+    /// <summary>The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.</summary>
+    public static Key Z => new (KeyCode.Z);
+
+    /// <summary>The <see cref="Key"/> object for the Delete key.</summary>
+    public static Key Delete => new (KeyCode.Delete);
+
+    /// <summary>The <see cref="Key"/> object for the Cursor up key.</summary>
+    public static Key CursorUp => new (KeyCode.CursorUp);
+
+    /// <summary>The <see cref="Key"/> object for Cursor down key.</summary>
+    public static Key CursorDown => new (KeyCode.CursorDown);
+
+    /// <summary>The <see cref="Key"/> object for Cursor left key.</summary>
+    public static Key CursorLeft => new (KeyCode.CursorLeft);
+
+    /// <summary>The <see cref="Key"/> object for Cursor right key.</summary>
+    public static Key CursorRight => new (KeyCode.CursorRight);
+
+    /// <summary>The <see cref="Key"/> object for Page Up key.</summary>
+    public static Key PageUp => new (KeyCode.PageUp);
+
+    /// <summary>The <see cref="Key"/> object for Page Down key.</summary>
+    public static Key PageDown => new (KeyCode.PageDown);
+
+    /// <summary>The <see cref="Key"/> object for Home key.</summary>
+    public static Key Home => new (KeyCode.Home);
+
+    /// <summary>The <see cref="Key"/> object for End key.</summary>
+    public static Key End => new (KeyCode.End);
+
+    /// <summary>The <see cref="Key"/> object for Insert Character key.</summary>
+    public static Key InsertChar => new (KeyCode.Insert);
+
+    /// <summary>The <see cref="Key"/> object for Delete Character key.</summary>
+    public static Key DeleteChar => new (KeyCode.Delete);
+
+    /// <summary>The <see cref="Key"/> object for Print Screen key.</summary>
+    public static Key PrintScreen => new (KeyCode.PrintScreen);
+
+    /// <summary>The <see cref="Key"/> object for F1 key.</summary>
+    public static Key F1 => new (KeyCode.F1);
+
+    /// <summary>The <see cref="Key"/> object for F2 key.</summary>
+    public static Key F2 => new (KeyCode.F2);
+
+    /// <summary>The <see cref="Key"/> object for F3 key.</summary>
+    public static Key F3 => new (KeyCode.F3);
+
+    /// <summary>The <see cref="Key"/> object for F4 key.</summary>
+    public static Key F4 => new (KeyCode.F4);
+
+    /// <summary>The <see cref="Key"/> object for F5 key.</summary>
+    public static Key F5 => new (KeyCode.F5);
+
+    /// <summary>The <see cref="Key"/> object for F6 key.</summary>
+    public static Key F6 => new (KeyCode.F6);
+
+    /// <summary>The <see cref="Key"/> object for F7 key.</summary>
+    public static Key F7 => new (KeyCode.F7);
+
+    /// <summary>The <see cref="Key"/> object for F8 key.</summary>
+    public static Key F8 => new (KeyCode.F8);
+
+    /// <summary>The <see cref="Key"/> object for F9 key.</summary>
+    public static Key F9 => new (KeyCode.F9);
+
+    /// <summary>The <see cref="Key"/> object for F10 key.</summary>
+    public static Key F10 => new (KeyCode.F10);
+
+    /// <summary>The <see cref="Key"/> object for F11 key.</summary>
+    public static Key F11 => new (KeyCode.F11);
+
+    /// <summary>The <see cref="Key"/> object for F12 key.</summary>
+    public static Key F12 => new (KeyCode.F12);
+
+    /// <summary>The <see cref="Key"/> object for F13 key.</summary>
+    public static Key F13 => new (KeyCode.F13);
+
+    /// <summary>The <see cref="Key"/> object for F14 key.</summary>
+    public static Key F14 => new (KeyCode.F14);
+
+    /// <summary>The <see cref="Key"/> object for F15 key.</summary>
+    public static Key F15 => new (KeyCode.F15);
+
+    /// <summary>The <see cref="Key"/> object for F16 key.</summary>
+    public static Key F16 => new (KeyCode.F16);
+
+    /// <summary>The <see cref="Key"/> object for F17 key.</summary>
+    public static Key F17 => new (KeyCode.F17);
+
+    /// <summary>The <see cref="Key"/> object for F18 key.</summary>
+    public static Key F18 => new (KeyCode.F18);
+
+    /// <summary>The <see cref="Key"/> object for F19 key.</summary>
+    public static Key F19 => new (KeyCode.F19);
+
+    /// <summary>The <see cref="Key"/> object for F20 key.</summary>
+    public static Key F20 => new (KeyCode.F20);
+
+    /// <summary>The <see cref="Key"/> object for F21 key.</summary>
+    public static Key F21 => new (KeyCode.F21);
+
+    /// <summary>The <see cref="Key"/> object for F22 key.</summary>
+    public static Key F22 => new (KeyCode.F22);
+
+    /// <summary>The <see cref="Key"/> object for F23 key.</summary>
+    public static Key F23 => new (KeyCode.F23);
+
+    /// <summary>The <see cref="Key"/> object for F24 key.</summary>
+    public static Key F24 => new (KeyCode.F24);
+
+    #endregion
+}

+ 223 - 253
Terminal.Gui/Input/KeyBinding.cs

@@ -1,286 +1,256 @@
 // These classes use a key binding system based on the design implemented in Scintilla.Net which is an
 // MIT licensed open source project https://github.com/jacobslusser/ScintillaNET/blob/master/src/ScintillaNET/Command.cs
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Defines the scope of a <see cref="Command"/> that has been bound to a key with <see cref="KeyBindings.Add(Key, Terminal.Gui.Command[])"/>.
+///     Defines the scope of a <see cref="Command"/> that has been bound to a key with
+///     <see cref="KeyBindings.Add(Key, Terminal.Gui.Command[])"/>.
 /// </summary>
 /// <remarks>
-/// <para>
-/// Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.
-/// </para>
+///     <para>Key bindings are scoped to the most-focused view (<see cref="Focused"/>) by default.</para>
 /// </remarks>
-public enum KeyBindingScope {
-	/// <summary>
-	/// The key binding is scoped to just the view that has focus.
-	/// </summary>
-	Focused = 0,
+public enum KeyBindingScope
+{
+    /// <summary>The key binding is scoped to just the view that has focus.</summary>
+    Focused = 0,
 
-	/// <summary>
-	/// The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as long as the
-	/// SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
-	/// <remarks>
-	/// <para>
-	/// Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even when not focused.
-	/// </para>
-	/// <para>
-	/// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews.
-	/// </para>
-	/// </remarks>
-	/// </summary>
-	HotKey,
+    /// <summary>
+    ///     The key binding is scoped to the View's SuperView and will be triggered even when the View does not have focus, as
+    ///     long as the SuperView does have focus. This is typically used for <see cref="View.HotKey"/>s.
+    ///     <remarks>
+    ///         <para>
+    ///             Use for Views such as MenuBar and StatusBar which provide commands (shortcuts etc...) that trigger even
+    ///             when not focused.
+    ///         </para>
+    ///         <para>
+    ///             HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+    ///             any of its subviews.
+    ///         </para>
+    ///     </remarks>
+    /// </summary>
+    HotKey,
 
-	/// <summary>
-	/// The key binding will be triggered regardless of which view has focus. This is typically used for global commands.
-	/// </summary>
-	/// <remarks>
-	/// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or any of its subviews,
-	/// and if the key down event was not bound to a <see cref="View.HotKey"/>.
-	/// </remarks>
-	Application
+    /// <summary>
+    ///     The key binding will be triggered regardless of which view has focus. This is typically used for global
+    ///     commands.
+    /// </summary>
+    /// <remarks>
+    ///     Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+    ///     any of its subviews, and if the key down event was not bound to a <see cref="View.HotKey"/>.
+    /// </remarks>
+    Application
 }
 
-/// <summary>
-/// Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.
-/// </summary>
-public class KeyBinding {
-	/// <summary>
-	/// Initializes a new instance.
-	/// </summary>
-	/// <param name="commands"></param>
-	/// <param name="scope"></param>
-	public KeyBinding (Command [] commands, KeyBindingScope scope)
-	{
-		Commands = commands;
-		Scope = scope;
-	}
+/// <summary>Provides a collection of <see cref="Command"/> objects that are scoped to <see cref="KeyBindingScope"/>.</summary>
+public class KeyBinding
+{
+    /// <summary>Initializes a new instance.</summary>
+    /// <param name="commands"></param>
+    /// <param name="scope"></param>
+    public KeyBinding (Command [] commands, KeyBindingScope scope)
+    {
+        Commands = commands;
+        Scope = scope;
+    }
 
-	/// <summary>
-	/// The actions which can be performed by the application or bound to keys in a <see cref="View"/> control.
-	/// </summary>
-	public Command [] Commands { get; set; }
+    /// <summary>The actions which can be performed by the application or bound to keys in a <see cref="View"/> control.</summary>
+    public Command [] Commands { get; set; }
 
-	/// <summary>
-	/// The scope of the <see cref="Commands"/> bound to a key.
-	/// </summary>
-	public KeyBindingScope Scope { get; set; }
+    /// <summary>The scope of the <see cref="Commands"/> bound to a key.</summary>
+    public KeyBindingScope Scope { get; set; }
 }
 
-/// <summary>
-/// A class that provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
-/// </summary>
-public class KeyBindings {
-	/// <summary>
-	/// The collection of <see cref="KeyBinding"/> objects.
-	/// </summary>
-	public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
+/// <summary>A class that provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.</summary>
+public class KeyBindings
+{
+    /// <summary>The collection of <see cref="KeyBinding"/> objects.</summary>
+    public Dictionary<Key, KeyBinding> Bindings { get; } = new ();
 
-	/// <summary>
-	/// Adds a <see cref="KeyBinding"/> to the collection.
-	/// </summary>
-	/// <param name="key"></param>
-	/// <param name="binding"></param>
-	public void Add (Key key, KeyBinding binding) => Bindings.Add (key, binding);
+    /// <summary>Adds a <see cref="KeyBinding"/> to the collection.</summary>
+    /// <param name="key"></param>
+    /// <param name="binding"></param>
+    public void Add (Key key, KeyBinding binding) { Bindings.Add (key, binding); }
 
-	/// <summary>
-	/// Removes a <see cref="KeyBinding"/> from the collection.
-	/// </summary>
-	/// <param name="key"></param>
-	public void Remove (Key key) => Bindings.Remove (key);
+    /// <summary>
+    ///     <para>Adds a new key combination that will trigger the commands in <paramref name="commands"/>.</para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="scope">The scope for the command.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, KeyBindingScope scope, params Command [] commands)
+    {
+        if (commands.Length == 0)
+        {
+            throw new ArgumentException (@"At least one command must be specified", nameof (commands));
+        }
 
-	/// <summary>
-	/// Removes all <see cref="KeyBinding"/> objects from the collection.
-	/// </summary>
-	public void Clear () => Bindings.Clear ();
+        if (key == null || !key.IsValid)
+        {
+            //throw new ArgumentException ("Invalid Key", nameof (commands));
+            return;
+        }
 
-	/// <summary>
-	/// <para>
-	/// Adds a new key combination that will trigger the commands in <paramref name="commands"/>.
-	/// </para>
-	/// <para>
-	/// If the key is already bound to a different array of <see cref="Command"/>s it will be
-	/// rebound <paramref name="commands"/>.</para>
-	/// </summary>
-	/// <remarks>
-	/// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature
-	/// cannot be used to switch focus to another view and perform multiple commands there).
-	/// </remarks>
-	/// <param name="key">
-	/// The key to check.
-	/// </param>
-	/// <param name="scope">The scope for the command.</param>
-	/// <param name="commands">The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed.
-	/// When multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike
-	/// will be consumed if any took effect.</param>
-	public void Add (Key key, KeyBindingScope scope, params Command [] commands)
-	{
-		if (commands.Length == 0) {
-			throw new ArgumentException (@"At least one command must be specified", nameof (commands));
-		}
+        if (TryGet (key, out KeyBinding _))
+        {
+            Bindings [key] = new KeyBinding (commands, scope);
+        }
+        else
+        {
+            Bindings.Add (key, new KeyBinding (commands, scope));
+        }
+    }
 
-		if (key == null || !key.IsValid) {
-			//throw new ArgumentException ("Invalid Key", nameof (commands));
-			return;
-		}
+    /// <summary>
+    ///     <para>
+    ///         Adds a new key combination that will trigger the commands in <paramref name="commands"/> (if supported by the
+    ///         View - see <see cref="View.GetSupportedCommands"/>).
+    ///     </para>
+    ///     <para>
+    ///         This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/> for
+    ///         <see cref="KeyBindingScope.Focused"/> scoped commands.
+    ///     </para>
+    ///     <para>
+    ///         If the key is already bound to a different array of <see cref="Command"/>s it will be rebound
+    ///         <paramref name="commands"/>.
+    ///     </para>
+    /// </summary>
+    /// <remarks>
+    ///     Commands are only ever applied to the current <see cref="View"/> (i.e. this feature cannot be used to switch
+    ///     focus to another view and perform multiple commands there).
+    /// </remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="commands">
+    ///     The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed. When
+    ///     multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike will be
+    ///     consumed if any took effect.
+    /// </param>
+    public void Add (Key key, params Command [] commands) { Add (key, KeyBindingScope.Focused, commands); }
 
-		if (TryGet (key, out var _)) {
-			Bindings [key] = new KeyBinding (commands, scope);
-		} else {
-			Bindings.Add (key, new KeyBinding (commands, scope));
-		}
-	}
+    /// <summary>Removes all <see cref="KeyBinding"/> objects from the collection.</summary>
+    public void Clear () { Bindings.Clear (); }
 
-	/// <summary>
-	/// <para>
-	/// Adds a new key combination that will trigger the commands in <paramref name="commands"/>
-	/// (if supported by the View - see <see cref="View.GetSupportedCommands"/>).
-	/// </para>
-	/// <para>
-	/// This is a helper function for <see cref="Add(Key,KeyBindingScope,Terminal.Gui.Command[])"/>
-	/// for <see cref="KeyBindingScope.Focused"/> scoped commands.
-	/// </para>
-	/// <para>
-	/// If the key is already bound to a different array of <see cref="Command"/>s it will be
-	/// rebound <paramref name="commands"/>.</para>
-	/// </summary>
-	/// <remarks>
-	/// Commands are only ever applied to the current <see cref="View"/> (i.e. this feature
-	/// cannot be used to switch focus to another view and perform multiple commands there).
-	/// </remarks>
-	/// <param name="key">
-	/// The key to check.
-	/// </param>
-	/// <param name="commands">The command to invoked on the <see cref="View"/> when <paramref name="key"/> is pressed.
-	/// When multiple commands are provided,they will be applied in sequence. The bound <paramref name="key"/> strike
-	/// will be consumed if any took effect.</param>
-	public void Add (Key key, params Command [] commands) => Add (key, KeyBindingScope.Focused, commands);
+    /// <summary>
+    ///     Removes all key bindings that trigger the given command set. Views can have multiple different keys bound to
+    ///     the same command sets and this method will clear all of them.
+    /// </summary>
+    /// <param name="command"></param>
+    public void Clear (params Command [] command)
+    {
+        foreach (KeyValuePair<Key, KeyBinding> kvp in Bindings.Where (kvp => kvp.Value.Commands.SequenceEqual (command))
+                                                              .ToArray ())
+        {
+            Bindings.Remove (kvp.Key);
+        }
+    }
 
-	/// <summary>
-	/// Replaces a key combination already bound to a set of <see cref="Command"/>s.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	/// <param name="fromKey">The key to be replaced.</param>
-	/// <param name="toKey">The new key to be used.</param>
-	public void Replace (Key fromKey, Key toKey)
-	{
-		if (!TryGet (fromKey, out var _)) {
-			return;
-		}
-		var value = Bindings [fromKey];
-		Bindings.Remove (fromKey);
-		Bindings [toKey] = value;
-	}
+    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
+    /// <param name="key"></param>
+    /// <returns></returns>
+    public KeyBinding Get (Key key) { return TryGet (key, out KeyBinding binding) ? binding : null; }
 
-	/// <summary>
-	/// Gets the commands bound with the specified Key.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	/// <param name="key">
-	/// The key to check.
-	/// </param>
-	/// <param name="binding">
-	/// When this method returns, contains the commands bound with the specified Key, if the Key is found;
-	/// otherwise, null. This parameter is passed uninitialized.
-	/// </param>
-	/// <returns>
-	/// <see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.
-	/// </returns>
-	public bool TryGet (Key key, out KeyBinding binding)
-	{
-		if (key.IsValid) {
-			return Bindings.TryGetValue (key, out binding);
-		}
-		binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
-		return false;
-	}
+    /// <summary>Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.</summary>
+    /// <param name="key"></param>
+    /// <param name="scope"></param>
+    /// <returns></returns>
+    public KeyBinding Get (Key key, KeyBindingScope scope) { return TryGet (key, scope, out KeyBinding binding) ? binding : null; }
 
-	/// <summary>
-	/// Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.
-	/// </summary>
-	/// <param name="key"></param>
-	/// <returns></returns>
-	public KeyBinding Get (Key key) => TryGet (key, out var binding) ? binding : null;
+    /// <summary>Gets the array of <see cref="Command"/>s bound to <paramref name="key"/> if it exists.</summary>
+    /// <param name="key">The key to check.</param>
+    /// <returns>
+    ///     The array of <see cref="Command"/>s if <paramref name="key"/> is bound. An empty <see cref="Command"/> array
+    ///     if not.
+    /// </returns>
+    public Command [] GetCommands (Key key)
+    {
+        if (TryGet (key, out KeyBinding bindings))
+        {
+            return bindings.Commands;
+        }
 
-	/// <summary>
-	/// Gets the commands bound with the specified Key that are scoped to a particular scope.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	/// <param name="key">
-	/// The key to check.
-	/// </param>
-	/// <param name="scope">the scope to filter on</param>
-	/// <param name="binding">
-	/// When this method returns, contains the commands bound with the specified Key, if the Key is found;
-	/// otherwise, null. This parameter is passed uninitialized.
-	/// </param>
-	/// <returns>
-	/// <see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.
-	/// </returns>
-	public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
-	{
-		if (key.IsValid && Bindings.TryGetValue (key, out binding)) {
-			if (binding.Scope == scope) {
-				return true;
-			}
-		}
-		binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
-		return false;
-	}
+        return Array.Empty<Command> ();
+    }
 
-	/// <summary>
-	/// Gets the <see cref="KeyBinding"/> for the specified <see cref="Key"/>.
-	/// </summary>
-	/// <param name="key"></param>
-	/// <param name="scope"></param>
-	/// <returns></returns>
-	public KeyBinding Get (Key key, KeyBindingScope scope) => TryGet (key, scope, out var binding) ? binding : null;
+    /// <summary>Gets the Key used by a set of commands.</summary>
+    /// <remarks></remarks>
+    /// <param name="commands">The set of commands to search.</param>
+    /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
+    /// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
+    public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
 
-	/// <summary>
-	/// Gets the array of <see cref="Command"/>s bound to <paramref name="key"/> if it exists.
-	/// </summary>
-	/// <param name="key">
-	/// The key to check.
-	/// </param>
-	/// <returns>The array of <see cref="Command"/>s if <paramref name="key"/> is bound. An empty <see cref="Command"/> array if not.</returns>
-	public Command [] GetCommands (Key key)
-	{
-		if (TryGet (key, out var bindings)) {
-			return bindings.Commands;
-		}
-		return Array.Empty<Command> ();
-	}
+    /// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
+    /// <param name="key"></param>
+    public void Remove (Key key) { Bindings.Remove (key); }
 
-	/// <summary>
-	/// Removes all key bindings that trigger the given command set. Views can have multiple different
-	/// keys bound to the same command sets and this method will clear all of them.
-	/// </summary>
-	/// <param name="command"></param>
-	public void Clear (params Command [] command)
-	{
-		foreach (var kvp in Bindings.Where (kvp => kvp.Value.Commands.SequenceEqual (command)).ToArray ()) {
-			Bindings.Remove (kvp.Key);
-		}
-	}
+    /// <summary>Replaces a key combination already bound to a set of <see cref="Command"/>s.</summary>
+    /// <remarks></remarks>
+    /// <param name="fromKey">The key to be replaced.</param>
+    /// <param name="toKey">The new key to be used.</param>
+    public void Replace (Key fromKey, Key toKey)
+    {
+        if (!TryGet (fromKey, out KeyBinding _))
+        {
+            return;
+        }
 
-	/// <summary>
-	/// Gets the Key used by a set of commands.
-	/// </summary>
-	/// <remarks>
-	/// </remarks>
-	/// <param name="commands">The set of commands to search.</param>
-	/// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
-	/// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
-	public Key GetKeyFromCommands (params Command [] commands)
-	{
-		return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key;
-	}
-}
+        KeyBinding value = Bindings [fromKey];
+        Bindings.Remove (fromKey);
+        Bindings [toKey] = value;
+    }
 
+    /// <summary>Gets the commands bound with the specified Key.</summary>
+    /// <remarks></remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="binding">
+    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
+    ///     found; otherwise, null. This parameter is passed uninitialized.
+    /// </param>
+    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
+    public bool TryGet (Key key, out KeyBinding binding)
+    {
+        if (key.IsValid)
+        {
+            return Bindings.TryGetValue (key, out binding);
+        }
+
+        binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
+
+        return false;
+    }
+
+    /// <summary>Gets the commands bound with the specified Key that are scoped to a particular scope.</summary>
+    /// <remarks></remarks>
+    /// <param name="key">The key to check.</param>
+    /// <param name="scope">the scope to filter on</param>
+    /// <param name="binding">
+    ///     When this method returns, contains the commands bound with the specified Key, if the Key is
+    ///     found; otherwise, null. This parameter is passed uninitialized.
+    /// </param>
+    /// <returns><see langword="true"/> if the Key is bound; otherwise <see langword="false"/>.</returns>
+    public bool TryGet (Key key, KeyBindingScope scope, out KeyBinding binding)
+    {
+        if (key.IsValid && Bindings.TryGetValue (key, out binding))
+        {
+            if (binding.Scope == scope)
+            {
+                return true;
+            }
+        }
+
+        binding = new KeyBinding (Array.Empty<Command> (), KeyBindingScope.Focused);
+
+        return false;
+    }
+}

+ 17 - 27
Terminal.Gui/Input/KeyChangedEventArgs.cs

@@ -1,33 +1,23 @@
-using System;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
-/// Event args for when a <see cref="Key"/> is changed from
-/// one value to a new value (e.g. in <see cref="View.HotKeyChanged"/>)
+///     Event args for when a <see cref="Key"/> is changed from one value to a new value (e.g. in
+///     <see cref="View.HotKeyChanged"/>)
 /// </summary>
-public class KeyChangedEventArgs : EventArgs {
-
-	/// <summary>
-	/// Gets the old <see cref="Key"/> that was set before the event.
-	/// Use <see cref="Key.Empty"/> to check for empty.
-	/// </summary>
-	public Key OldKey { get; }
+public class KeyChangedEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="KeyChangedEventArgs"/> class</summary>
+    /// <param name="oldKey"></param>
+    /// <param name="newKey"></param>
+    public KeyChangedEventArgs (Key oldKey, Key newKey)
+    {
+        OldKey = oldKey;
+        NewKey = newKey;
+    }
 
-	/// <summary>
-	/// Gets the new <see cref="Key"/> that is being used.
-	/// Use <see cref="Key.Empty"/> to check for empty.
-	/// </summary>
-	public Key NewKey { get; }
+    /// <summary>Gets the new <see cref="Key"/> that is being used. Use <see cref="Key.Empty"/> to check for empty.</summary>
+    public Key NewKey { get; }
 
-	/// <summary>
-	/// Creates a new instance of the <see cref="KeyChangedEventArgs"/> class
-	/// </summary>
-	/// <param name="oldKey"></param>
-	/// <param name="newKey"></param>
-	public KeyChangedEventArgs (Key oldKey, Key newKey)
-	{
-		this.OldKey = oldKey;
-		this.NewKey = newKey;
-	}
+    /// <summary>Gets the old <see cref="Key"/> that was set before the event. Use <see cref="Key.Empty"/> to check for empty.</summary>
+    public Key OldKey { get; }
 }

+ 9 - 19
Terminal.Gui/Input/KeystrokeNavigatorEventArgs.cs

@@ -1,22 +1,12 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event arguments for the <see cref="CollectionNavigatorBase.SearchStringChanged"/> event.
-	/// </summary>
-	public class KeystrokeNavigatorEventArgs : EventArgs {
-		/// <summary>
-		/// he current <see cref="SearchString"/>.
-		/// </summary>
-		public string SearchString { get; }
+/// <summary>Event arguments for the <see cref="CollectionNavigatorBase.SearchStringChanged"/> event.</summary>
+public class KeystrokeNavigatorEventArgs : EventArgs
+{
+    /// <summary>Initializes a new instance of <see cref="KeystrokeNavigatorEventArgs"/></summary>
+    /// <param name="searchString">The current <see cref="SearchString"/>.</param>
+    public KeystrokeNavigatorEventArgs (string searchString) { SearchString = searchString; }
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="KeystrokeNavigatorEventArgs"/>
-		/// </summary>
-		/// <param name="searchString">The current <see cref="SearchString"/>.</param>
-		public KeystrokeNavigatorEventArgs (string searchString)
-		{
-			SearchString = searchString;
-		}
-	}
+    /// <summary>he current <see cref="SearchString"/>.</summary>
+    public string SearchString { get; }
 }

+ 126 - 175
Terminal.Gui/Input/Mouse.cs

@@ -1,185 +1,136 @@
-using System;
-
 namespace Terminal.Gui;
 
-/// <summary>
-/// Mouse flags reported in <see cref="MouseEvent"/>.
-/// </summary>
-/// <remarks>
-/// They just happen to map to the ncurses ones.
-/// </remarks>
+/// <summary>Mouse flags reported in <see cref="MouseEvent"/>.</summary>
+/// <remarks>They just happen to map to the ncurses ones.</remarks>
 [Flags]
-public enum MouseFlags {
-	/// <summary>
-	/// The first mouse button was pressed.
-	/// </summary>
-	Button1Pressed = unchecked((int)0x2),
-	/// <summary>
-	/// The first mouse button was released.
-	/// </summary>
-	Button1Released = unchecked((int)0x1),
-	/// <summary>
-	/// The first mouse button was clicked (press+release).
-	/// </summary>
-	Button1Clicked = unchecked((int)0x4),
-	/// <summary>
-	/// The first mouse button was double-clicked.
-	/// </summary>
-	Button1DoubleClicked = unchecked((int)0x8),
-	/// <summary>
-	/// The first mouse button was triple-clicked.
-	/// </summary>
-	Button1TripleClicked = unchecked((int)0x10),
-	/// <summary>
-	/// The second mouse button was pressed.
-	/// </summary>
-	Button2Pressed = unchecked((int)0x80),
-	/// <summary>
-	/// The second mouse button was released.
-	/// </summary>
-	Button2Released = unchecked((int)0x40),
-	/// <summary>
-	/// The second mouse button was clicked (press+release).
-	/// </summary>
-	Button2Clicked = unchecked((int)0x100),
-	/// <summary>
-	/// The second mouse button was double-clicked.
-	/// </summary>
-	Button2DoubleClicked = unchecked((int)0x200),
-	/// <summary>
-	/// The second mouse button was triple-clicked.
-	/// </summary>
-	Button2TripleClicked = unchecked((int)0x400),
-	/// <summary>
-	/// The third mouse button was pressed.
-	/// </summary>
-	Button3Pressed = unchecked((int)0x2000),
-	/// <summary>
-	/// The third mouse button was released.
-	/// </summary>
-	Button3Released = unchecked((int)0x1000),
-	/// <summary>
-	/// The third mouse button was clicked (press+release).
-	/// </summary>
-	Button3Clicked = unchecked((int)0x4000),
-	/// <summary>
-	/// The third mouse button was double-clicked.
-	/// </summary>
-	Button3DoubleClicked = unchecked((int)0x8000),
-	/// <summary>
-	/// The third mouse button was triple-clicked.
-	/// </summary>
-	Button3TripleClicked = unchecked((int)0x10000),
-	/// <summary>
-	/// The fourth mouse button was pressed.
-	/// </summary>
-	Button4Pressed = unchecked((int)0x80000),
-	/// <summary>
-	/// The fourth mouse button was released.
-	/// </summary>
-	Button4Released = unchecked((int)0x40000),
-	/// <summary>
-	/// The fourth button was clicked (press+release).
-	/// </summary>
-	Button4Clicked = unchecked((int)0x100000),
-	/// <summary>
-	/// The fourth button was double-clicked.
-	/// </summary>
-	Button4DoubleClicked = unchecked((int)0x200000),
-	/// <summary>
-	/// The fourth button was triple-clicked.
-	/// </summary>
-	Button4TripleClicked = unchecked((int)0x400000),
-	/// <summary>
-	/// Flag: the shift key was pressed when the mouse button took place.
-	/// </summary>
-	ButtonShift = unchecked((int)0x2000000),
-	/// <summary>
-	/// Flag: the ctrl key was pressed when the mouse button took place.
-	/// </summary>
-	ButtonCtrl = unchecked((int)0x1000000),
-	/// <summary>
-	/// Flag: the alt key was pressed when the mouse button took place.
-	/// </summary>
-	ButtonAlt = unchecked((int)0x4000000),
-	/// <summary>
-	/// The mouse position is being reported in this event.
-	/// </summary>
-	ReportMousePosition = unchecked((int)0x8000000),
-	/// <summary>
-	/// Vertical button wheeled up.
-	/// </summary>
-	WheeledUp = unchecked((int)0x10000000),
-	/// <summary>
-	/// Vertical button wheeled down.
-	/// </summary>
-	WheeledDown = unchecked((int)0x20000000),
-	/// <summary>
-	/// Vertical button wheeled up while pressing ButtonShift.
-	/// </summary>
-	WheeledLeft = ButtonShift | WheeledUp,
-	/// <summary>
-	/// Vertical button wheeled down while pressing ButtonShift.
-	/// </summary>
-	WheeledRight = ButtonShift | WheeledDown,
-	/// <summary>
-	/// Mask that captures all the events.
-	/// </summary>
-	AllEvents = unchecked((int)0x7ffffff),
+public enum MouseFlags
+{
+    /// <summary>The first mouse button was pressed.</summary>
+    Button1Pressed = 0x2,
+
+    /// <summary>The first mouse button was released.</summary>
+    Button1Released = 0x1,
+
+    /// <summary>The first mouse button was clicked (press+release).</summary>
+    Button1Clicked = 0x4,
+
+    /// <summary>The first mouse button was double-clicked.</summary>
+    Button1DoubleClicked = 0x8,
+
+    /// <summary>The first mouse button was triple-clicked.</summary>
+    Button1TripleClicked = 0x10,
+
+    /// <summary>The second mouse button was pressed.</summary>
+    Button2Pressed = 0x80,
+
+    /// <summary>The second mouse button was released.</summary>
+    Button2Released = 0x40,
+
+    /// <summary>The second mouse button was clicked (press+release).</summary>
+    Button2Clicked = 0x100,
+
+    /// <summary>The second mouse button was double-clicked.</summary>
+    Button2DoubleClicked = 0x200,
+
+    /// <summary>The second mouse button was triple-clicked.</summary>
+    Button2TripleClicked = 0x400,
+
+    /// <summary>The third mouse button was pressed.</summary>
+    Button3Pressed = 0x2000,
+
+    /// <summary>The third mouse button was released.</summary>
+    Button3Released = 0x1000,
+
+    /// <summary>The third mouse button was clicked (press+release).</summary>
+    Button3Clicked = 0x4000,
+
+    /// <summary>The third mouse button was double-clicked.</summary>
+    Button3DoubleClicked = 0x8000,
+
+    /// <summary>The third mouse button was triple-clicked.</summary>
+    Button3TripleClicked = 0x10000,
+
+    /// <summary>The fourth mouse button was pressed.</summary>
+    Button4Pressed = 0x80000,
+
+    /// <summary>The fourth mouse button was released.</summary>
+    Button4Released = 0x40000,
+
+    /// <summary>The fourth button was clicked (press+release).</summary>
+    Button4Clicked = 0x100000,
+
+    /// <summary>The fourth button was double-clicked.</summary>
+    Button4DoubleClicked = 0x200000,
+
+    /// <summary>The fourth button was triple-clicked.</summary>
+    Button4TripleClicked = 0x400000,
+
+    /// <summary>Flag: the shift key was pressed when the mouse button took place.</summary>
+    ButtonShift = 0x2000000,
+
+    /// <summary>Flag: the ctrl key was pressed when the mouse button took place.</summary>
+    ButtonCtrl = 0x1000000,
+
+    /// <summary>Flag: the alt key was pressed when the mouse button took place.</summary>
+    ButtonAlt = 0x4000000,
+
+    /// <summary>The mouse position is being reported in this event.</summary>
+    ReportMousePosition = 0x8000000,
+
+    /// <summary>Vertical button wheeled up.</summary>
+    WheeledUp = 0x10000000,
+
+    /// <summary>Vertical button wheeled down.</summary>
+    WheeledDown = 0x20000000,
+
+    /// <summary>Vertical button wheeled up while pressing ButtonShift.</summary>
+    WheeledLeft = ButtonShift | WheeledUp,
+
+    /// <summary>Vertical button wheeled down while pressing ButtonShift.</summary>
+    WheeledRight = ButtonShift | WheeledDown,
+
+    /// <summary>Mask that captures all the events.</summary>
+    AllEvents = 0x7ffffff
 }
 
 // TODO: Merge MouseEvent and MouseEventEventArgs into a single class.
 
 /// <summary>
-/// Low-level construct that conveys the details of mouse events, such
-/// as coordinates and button state, from ConsoleDrivers up to <see cref="Application"/> and
-/// Views.
+///     Low-level construct that conveys the details of mouse events, such as coordinates and button state, from
+///     ConsoleDrivers up to <see cref="Application"/> and Views.
 /// </summary>
-/// <remarks>The <see cref="Application"/> class includes the <see cref="Application.MouseEvent"/>
-/// Action which takes a MouseEvent argument.</remarks>
-public class MouseEvent {
-	/// <summary>
-	/// The X (column) location for the mouse event.
-	/// </summary>
-	public int X { get; set; }
-
-	/// <summary>
-	/// The Y (column) location for the mouse event.
-	/// </summary>
-	public int Y { get; set; }
-
-	/// <summary>
-	/// Flags indicating the kind of mouse event that is being posted.
-	/// </summary>
-	public MouseFlags Flags { get; set; }
-
-	/// <summary>
-	/// The offset X (column) location for the mouse event.
-	/// </summary>
-	public int OfX { get; set; }
-
-	/// <summary>
-	/// The offset Y (column) location for the mouse event.
-	/// </summary>
-	public int OfY { get; set; }
-
-	/// <summary>
-	/// The current view at the location for the mouse event.
-	/// </summary>
-	public View View { get; set; }
-
-	/// <summary>
-	/// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber.
-	/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
-	/// </summary>
-	public bool Handled { get; set; }
-
-	/// <summary>
-	/// Returns a <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.
-	/// </summary>
-	/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.</returns>
-	public override string ToString ()
-	{
-		return $"({X},{Y}):{Flags}";
-	}
+/// <remarks>
+///     The <see cref="Application"/> class includes the <see cref="Application.MouseEvent"/> Action which takes a
+///     MouseEvent argument.
+/// </remarks>
+public class MouseEvent
+{
+    /// <summary>Flags indicating the kind of mouse event that is being posted.</summary>
+    public MouseFlags Flags { get; set; }
+
+    /// <summary>
+    ///     Indicates if the current mouse event has already been processed and the driver should stop notifying any other
+    ///     event subscriber. Its important to set this value to true specially when updating any View's layout from inside the
+    ///     subscriber method.
+    /// </summary>
+    public bool Handled { get; set; }
+
+    /// <summary>The offset X (column) location for the mouse event.</summary>
+    public int OfX { get; set; }
+
+    /// <summary>The offset Y (column) location for the mouse event.</summary>
+    public int OfY { get; set; }
+
+    /// <summary>The current view at the location for the mouse event.</summary>
+    public View View { get; set; }
+
+    /// <summary>The X (column) location for the mouse event.</summary>
+    public int X { get; set; }
+
+    /// <summary>The Y (column) location for the mouse event.</summary>
+    public int Y { get; set; }
+
+    /// <summary>Returns a <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.</summary>
+    /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.</returns>
+    public override string ToString () { return $"({X},{Y}):{Flags}"; }
 }

+ 28 - 29
Terminal.Gui/Input/MouseEventEventArgs.cs

@@ -1,33 +1,32 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Specifies the event arguments for <see cref="Terminal.Gui.MouseEvent"/>. This is a higher-level construct
-	/// than the wrapped <see cref="MouseEvent"/> class and is used for the events defined on <see cref="View"/>
-	/// and subclasses of View (e.g. <see cref="View.MouseEnter"/> and <see cref="View.MouseClick"/>).
-	/// </summary>
-	public class MouseEventEventArgs : EventArgs {
-		/// <summary>
-		/// Constructs.
-		/// </summary>
-		/// <param name="me">The mouse event.</param>
-		public MouseEventEventArgs (MouseEvent me) => MouseEvent = me;
+/// <summary>
+///     Specifies the event arguments for <see cref="Terminal.Gui.MouseEvent"/>. This is a higher-level construct than
+///     the wrapped <see cref="MouseEvent"/> class and is used for the events defined on <see cref="View"/> and subclasses
+///     of View (e.g. <see cref="View.MouseEnter"/> and <see cref="View.MouseClick"/>).
+/// </summary>
+public class MouseEventEventArgs : EventArgs
+{
+    /// <summary>Constructs.</summary>
+    /// <param name="me">The mouse event.</param>
+    public MouseEventEventArgs (MouseEvent me) { MouseEvent = me; }
 
-		// TODO: Merge MouseEvent and MouseEventEventArgs into a single class.
-		/// <summary>
-		/// The <see cref="Terminal.Gui.MouseEvent"/> for the event.
-		/// </summary>
-		public MouseEvent MouseEvent { get; set; }
+    /// <summary>
+    ///     Indicates if the current mouse event has already been processed and the driver should stop notifying any other
+    ///     event subscriber. Its important to set this value to true specially when updating any View's layout from inside the
+    ///     subscriber method.
+    /// </summary>
+    /// <remarks>
+    ///     This property forwards to the <see cref="MouseEvent.Handled"/> property and is provided as a convenience and
+    ///     for backwards compatibility
+    /// </remarks>
+    public bool Handled
+    {
+        get => MouseEvent.Handled;
+        set => MouseEvent.Handled = value;
+    }
 
-		/// <summary>
-		/// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber.
-		/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
-		/// </summary>
-		/// <remarks>This property forwards to the <see cref="MouseEvent.Handled"/> property and is provided as a convenience and for
-		/// backwards compatibility</remarks>
-		public bool Handled {
-			get => MouseEvent.Handled;
-			set => MouseEvent.Handled = value;
-		}
-	}
+    // TODO: Merge MouseEvent and MouseEventEventArgs into a single class.
+    /// <summary>The <see cref="Terminal.Gui.MouseEvent"/> for the event.</summary>
+    public MouseEvent MouseEvent { get; set; }
 }

+ 16 - 26
Terminal.Gui/Input/MouseFlagsChangedEventArgs.cs

@@ -1,30 +1,20 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Args for events that describe a change in <see cref="MouseFlags"/>
-	/// </summary>
-	public class MouseFlagsChangedEventArgs : EventArgs {
+/// <summary>Args for events that describe a change in <see cref="MouseFlags"/></summary>
+public class MouseFlagsChangedEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="MouseFlagsChangedEventArgs"/> class.</summary>
+    /// <param name="oldValue"></param>
+    /// <param name="newValue"></param>
+    public MouseFlagsChangedEventArgs (MouseFlags oldValue, MouseFlags newValue)
+    {
+        OldValue = oldValue;
+        NewValue = newValue;
+    }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="MouseFlagsChangedEventArgs"/> class.
-		/// </summary>
-		/// <param name="oldValue"></param>
-		/// <param name="newValue"></param>
-		public MouseFlagsChangedEventArgs (MouseFlags oldValue, MouseFlags newValue)
-		{
-			OldValue = oldValue;
-			NewValue = newValue;
-		}
+    /// <summary>The new value</summary>
+    public MouseFlags NewValue { get; }
 
-		/// <summary>
-		/// The old value before event
-		/// </summary>
-		public MouseFlags OldValue { get; }
-
-		/// <summary>
-		/// The new value
-		/// </summary>
-		public MouseFlags NewValue { get; }
-	}
+    /// <summary>The old value before event</summary>
+    public MouseFlags OldValue { get; }
 }

+ 9 - 20
Terminal.Gui/Input/PointEventArgs.cs

@@ -1,23 +1,12 @@
-using System;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event args for events which relate to a single <see cref="Point"/>
-	/// </summary>
-	public class PointEventArgs : EventArgs {
+/// <summary>Event args for events which relate to a single <see cref="Point"/></summary>
+public class PointEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="PointEventArgs"/> class</summary>
+    /// <param name="p"></param>
+    public PointEventArgs (Point p) { Point = p; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="PointEventArgs"/> class
-		/// </summary>
-		/// <param name="p"></param>
-		public PointEventArgs (Point p)
-		{
-			this.Point = p;
-		}
-
-		/// <summary>
-		/// The point the event happened at
-		/// </summary>
-		public Point Point { get; }
-	}
+    /// <summary>The point the event happened at</summary>
+    public Point Point { get; }
 }

+ 130 - 179
Terminal.Gui/Input/Responder.cs

@@ -13,193 +13,144 @@
 // Optimziations
 //   - Add rendering limitation to the exposed area
 
-using System;
-using System.Collections.Generic;
-using System.Linq;
 using System.Reflection;
 
 namespace Terminal.Gui;
-/// <summary>
-/// Responder base class implemented by objects that want to participate on keyboard and mouse input.
-/// </summary>
-public class Responder : IDisposable {
-	bool disposedValue;
 
+/// <summary>Responder base class implemented by objects that want to participate on keyboard and mouse input.</summary>
+public class Responder : IDisposable
+{
+    private bool disposedValue;
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> can focus.</summary>
+    /// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
+    public virtual bool CanFocus { get; set; }
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.</summary>
+    public virtual bool Enabled { get; set; } = true;
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> has focus.</summary>
+    /// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
+    public virtual bool HasFocus { get; }
+
+    /// <summary>Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.</summary>
+    public virtual bool Visible { get; set; } = true;
+
+    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.</summary>
+    public void Dispose ()
+    {
+        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+        Disposing?.Invoke (this, EventArgs.Empty);
+        Dispose (true);
+        GC.SuppressFinalize (this);
 #if DEBUG_IDISPOSABLE
-	/// <summary>
-	/// For debug purposes to verify objects are being disposed properly
-	/// </summary>
-	public bool WasDisposed = false;
-	/// <summary>
-	/// For debug purposes to verify objects are being disposed properly
-	/// </summary>
-	public int DisposedCount = 0;
-	/// <summary>
-	/// For debug purposes
-	/// </summary>
-	public static List<Responder> Instances = new List<Responder> ();
-	/// <summary>
-	/// For debug purposes
-	/// </summary>
-	public Responder ()
-	{
-		Instances.Add (this);
-	}
+        WasDisposed = true;
+
+        foreach (Responder instance in Instances.Where (x => x.WasDisposed).ToList ())
+        {
+            Instances.Remove (instance);
+        }
 #endif
+    }
+
+    /// <summary>Event raised when <see cref="Dispose()"/> has been called to signal that this object is being disposed.</summary>
+    public event EventHandler Disposing;
+
+    /// <summary>Method invoked when a mouse event is generated</summary>
+    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+    /// <param name="mouseEvent">Contains the details about the mouse event.</param>
+    public virtual bool MouseEvent (MouseEvent mouseEvent) { return false; }
+
+    /// <summary>Method invoked when the <see cref="CanFocus"/> property from a view is changed.</summary>
+    public virtual void OnCanFocusChanged () { }
+
+    /// <summary>Method invoked when the <see cref="Enabled"/> property from a view is changed.</summary>
+    public virtual void OnEnabledChanged () { }
+
+    /// <summary>Method invoked when a view gets focus.</summary>
+    /// <param name="view">The view that is losing focus.</param>
+    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+    public virtual bool OnEnter (View view) { return false; }
+
+    /// <summary>Method invoked when a view loses focus.</summary>
+    /// <param name="view">The view that is getting focus.</param>
+    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+    public virtual bool OnLeave (View view) { return false; }
+
+    /// <summary>
+    ///     Called when the mouse first enters the view; the view will now receives mouse events until the mouse leaves
+    ///     the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/> will be called.
+    /// </summary>
+    /// <param name="mouseEvent"></param>
+    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+    public virtual bool OnMouseEnter (MouseEvent mouseEvent) { return false; }
+
+    /// <summary>
+    ///     Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until the
+    ///     mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
+    /// </summary>
+    /// <param name="mouseEvent"></param>
+    /// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+    public virtual bool OnMouseLeave (MouseEvent mouseEvent) { return false; }
+
+    /// <summary>Method invoked when the <see cref="Visible"/> property from a view is changed.</summary>
+    public virtual void OnVisibleChanged () { }
+
+    /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
+    /// <remarks>
+    ///     If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
+    ///     unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
+    ///     inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
+    /// </remarks>
+    /// <param name="disposing"></param>
+    protected virtual void Dispose (bool disposing)
+    {
+        if (!disposedValue)
+        {
+            if (disposing)
+            {
+                // TODO: dispose managed state (managed objects)
+            }
+
+            disposedValue = true;
+        }
+    }
+
+    // TODO: v2 - nuke this
+    /// <summary>Utilty function to determine <paramref name="method"/> is overridden in the <paramref name="subclass"/>.</summary>
+    /// <param name="subclass">The view.</param>
+    /// <param name="method">The method name.</param>
+    /// <returns><see langword="true"/> if it's overridden, <see langword="false"/> otherwise.</returns>
+    internal static bool IsOverridden (Responder subclass, string method)
+    {
+        MethodInfo m = subclass.GetType ()
+                               .GetMethod (
+                                           method,
+                                           BindingFlags.Instance
+                                           | BindingFlags.Public
+                                           | BindingFlags.NonPublic
+                                           | BindingFlags.DeclaredOnly
+                                          );
+
+        if (m == null)
+        {
+            return false;
+        }
+
+        return m.GetBaseDefinition ().DeclaringType != m.DeclaringType;
+    }
 
-	/// <summary>
-	/// Event raised when <see cref="Dispose()"/> has been called to signal that this object is being disposed.
-	/// </summary>
-	public event EventHandler Disposing;
-
-	/// <summary>
-	/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
-	/// </summary>
-	/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
-	public virtual bool CanFocus { get; set; }
-
-	/// <summary>
-	/// Gets or sets a value indicating whether this <see cref="Responder"/> has focus.
-	/// </summary>
-	/// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
-	public virtual bool HasFocus { get; }
-
-	/// <summary>
-	/// Gets or sets a value indicating whether this <see cref="Responder"/> can respond to user interaction.
-	/// </summary>
-	public virtual bool Enabled { get; set; } = true;
-
-	/// <summary>
-	/// Gets or sets a value indicating whether this <see cref="Responder"/> and all its child controls are displayed.
-	/// </summary>
-	public virtual bool Visible { get; set; } = true;
-
-	/// <summary>
-	/// Method invoked when a mouse event is generated
-	/// </summary>
-	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-	/// <param name="mouseEvent">Contains the details about the mouse event.</param>
-	public virtual bool MouseEvent (MouseEvent mouseEvent)
-	{
-		return false;
-	}
-
-	/// <summary>
-	/// Called when the mouse first enters the view; the view will now
-	/// receives mouse events until the mouse leaves the view. At which time, <see cref="OnMouseLeave(Gui.MouseEvent)"/>
-	/// will be called.
-	/// </summary>
-	/// <param name="mouseEvent"></param>
-	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-	public virtual bool OnMouseEnter (MouseEvent mouseEvent)
-	{
-		return false;
-	}
-
-	/// <summary>
-	/// Called when the mouse has moved outside of the view; the view will no longer receive mouse events (until
-	/// the mouse moves within the view again and <see cref="OnMouseEnter(Gui.MouseEvent)"/> is called).
-	/// </summary>
-	/// <param name="mouseEvent"></param>
-	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-	public virtual bool OnMouseLeave (MouseEvent mouseEvent)
-	{
-		return false;
-	}
-
-	/// <summary>
-	/// Method invoked when a view gets focus.
-	/// </summary>
-	/// <param name="view">The view that is losing focus.</param>
-	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-	public virtual bool OnEnter (View view)
-	{
-		return false;
-	}
-
-	/// <summary>
-	/// Method invoked when a view loses focus.
-	/// </summary>
-	/// <param name="view">The view that is getting focus.</param>
-	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-	public virtual bool OnLeave (View view)
-	{
-		return false;
-	}
-
-	/// <summary>
-	/// Method invoked when the <see cref="CanFocus"/> property from a view is changed.
-	/// </summary>
-	public virtual void OnCanFocusChanged () { }
-
-	/// <summary>
-	/// Method invoked when the <see cref="Enabled"/> property from a view is changed.
-	/// </summary>
-	public virtual void OnEnabledChanged () { }
-
-	/// <summary>
-	/// Method invoked when the <see cref="Visible"/> property from a view is changed.
-	/// </summary>
-	public virtual void OnVisibleChanged () { }
-
-	// TODO: v2 - nuke this
-	/// <summary>
-	/// Utilty function to determine <paramref name="method"/> is overridden in the <paramref name="subclass"/>.
-	/// </summary>
-	/// <param name="subclass">The view.</param>
-	/// <param name="method">The method name.</param>
-	/// <returns><see langword="true"/> if it's overridden, <see langword="false"/> otherwise.</returns>
-	internal static bool IsOverridden (Responder subclass, string method)
-	{
-		MethodInfo m = subclass.GetType ().GetMethod (method,
-			BindingFlags.Instance
-			| BindingFlags.Public
-			| BindingFlags.NonPublic
-			| BindingFlags.DeclaredOnly);
-		if (m == null) {
-			return false;
-		}
-		return m.GetBaseDefinition ().DeclaringType != m.DeclaringType;
-	}
-
-	/// <summary>
-	/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
-	/// </summary>
-	/// <remarks>
-	/// If disposing equals true, the method has been called directly
-	/// or indirectly by a user's code. Managed and unmanaged resources
-	/// can be disposed.
-	/// If disposing equals false, the method has been called by the
-	/// runtime from inside the finalizer and you should not reference
-	/// other objects. Only unmanaged resources can be disposed.
-	/// </remarks>
-	/// <param name="disposing"></param>
-	protected virtual void Dispose (bool disposing)
-	{
-		if (!disposedValue) {
-			if (disposing) {
-				// TODO: dispose managed state (managed objects)
-			}
-
-			disposedValue = true;
-		}
-	}
-
-	/// <summary>
-	/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
-	/// </summary>
-	public void Dispose ()
-	{
-		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
-		Disposing?.Invoke (this, EventArgs.Empty);
-		Dispose (disposing: true);
-		GC.SuppressFinalize (this);
 #if DEBUG_IDISPOSABLE
-		WasDisposed = true;
+    /// <summary>For debug purposes to verify objects are being disposed properly</summary>
+    public bool WasDisposed;
+
+    /// <summary>For debug purposes to verify objects are being disposed properly</summary>
+    public int DisposedCount = 0;
+
+    /// <summary>For debug purposes</summary>
+    public static List<Responder> Instances = new ();
 
-		foreach (var instance in Instances.Where (x => x.WasDisposed).ToList ()) {
-			Instances.Remove (instance);
-		}
+    /// <summary>For debug purposes</summary>
+    public Responder () { Instances.Add (this); }
 #endif
-	}
 }

+ 171 - 149
Terminal.Gui/Input/ShortcutHelper.cs

@@ -1,152 +1,174 @@
-using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
-/// <summary>
-/// Represents a helper to manipulate shortcut keys used on views.
-/// </summary>
-public class ShortcutHelper {
-	// TODO: Update this to use Key, not KeyCode
-	private KeyCode shortcut;
-
-	/// <summary>
-	/// This is the global setting that can be used as a global shortcut to invoke the action on the view.
-	/// </summary>
-	public virtual KeyCode Shortcut {
-		get => shortcut;
-		set {
-			if (shortcut != value && (PostShortcutValidation (value) || value is KeyCode.Null)) {
-				shortcut = value;
-			}
-		}
-	}
-
-	/// <summary>
-	/// The keystroke combination used in the <see cref="Shortcut"/> as string.
-	/// </summary>
-	public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter);
-	
-	/// <summary>
-	/// Return key as string.
-	/// </summary>
-	/// <param name="key">The key to extract.</param>
-	/// <param name="knm">Correspond to the non modifier key.</param>
-	static string GetKeyToString (KeyCode key, out KeyCode knm)
-	{
-		if (key == KeyCode.Null) {
-			knm = KeyCode.Null;
-			return "";
-		}
-
-		knm = key;
-		var mK = key & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask);
-		knm &= ~mK;
-		for (uint i = (uint)KeyCode.F1; i < (uint)KeyCode.F12; i++) {
-			if (knm == (KeyCode)i) {
-				mK |= (KeyCode)i;
-			}
-		}
-		knm &= ~mK;
-		uint.TryParse (knm.ToString (), out uint c);
-		var s = mK == KeyCode.Null ? "" : mK.ToString ();
-		if (s != "" && (knm != KeyCode.Null || c > 0)) {
-			s += ",";
-		}
-		s += c == 0 ? knm == KeyCode.Null ? "" : knm.ToString () : ((char)c).ToString ();
-		return s;
-	}
-
-	/// <summary>
-	/// Allows to retrieve a <see cref="KeyCode"/> from a <see cref="ShortcutTag"/>
-	/// </summary>
-	/// <param name="tag">The key as string.</param>
-	/// <param name="delimiter">The delimiter string.</param>
-	public static KeyCode GetShortcutFromTag (string tag, Rune delimiter = default)
-	{
-		var sCut = tag;
-		if (string.IsNullOrEmpty (sCut)) {
-			return default;
-		}
-
-		KeyCode key = KeyCode.Null;
-		//var hasCtrl = false;
-		if (delimiter == default) {
-			delimiter = MenuBar.ShortcutDelimiter;
-		}
-
-		string [] keys = sCut.Split (delimiter.ToString());
-		for (int i = 0; i < keys.Length; i++) {
-			var k = keys [i];
-			if (k == "Ctrl") {
-				//hasCtrl = true;
-				key |= KeyCode.CtrlMask;
-			} else if (k == "Shift") {
-				key |= KeyCode.ShiftMask;
-			} else if (k == "Alt") {
-				key |= KeyCode.AltMask;
-			} else if (k.StartsWith ("F") && k.Length > 1) {
-				int.TryParse (k.Substring (1).ToString (), out int n);
-				for (uint j = (uint)KeyCode.F1; j <= (uint)KeyCode.F12; j++) {
-					int.TryParse (((KeyCode)j).ToString ().Substring (1), out int f);
-					if (f == n) {
-						key |= (KeyCode)j;
-					}
-				}
-			} else {
-				key |= (KeyCode)Enum.Parse (typeof (KeyCode), k.ToString ());
-			}
-		}
-
-		return key;
-	}
-
-	/// <summary>
-	/// Lookup for a <see cref="KeyCode"/> on range of keys.
-	/// </summary>
-	/// <param name="key">The source key.</param>
-	/// <param name="first">First key in range.</param>
-	/// <param name="last">Last key in range.</param>
-	public static bool CheckKeysFlagRange (KeyCode key, KeyCode first, KeyCode last)
-	{
-		for (uint i = (uint)first; i < (uint)last; i++) {
-			if ((key | (KeyCode)i) == key) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	/// <summary>
-	/// Used at key down or key press validation.
-	/// </summary>
-	/// <param name="key">The key to validate.</param>
-	/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
-	public static bool PreShortcutValidation (KeyCode key)
-	{
-		if ((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) == 0 && !CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12)) {
-			return false;
-		}
-
-		return true;
-	}
-
-	/// <summary>
-	/// Used at key up validation.
-	/// </summary>
-	/// <param name="key">The key to validate.</param>
-	/// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
-	public static bool PostShortcutValidation (KeyCode key)
-	{
-		GetKeyToString (key, out KeyCode knm);
-
-		if (CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12) ||
-			((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) != 0 && knm != KeyCode.Null)) {
-			return true;
-		}
-		Debug.WriteLine ($"WARNING: {Key.ToString (key)} is not a valid shortcut key.");
-		return false;
-	}
-}
 
+/// <summary>Represents a helper to manipulate shortcut keys used on views.</summary>
+public class ShortcutHelper
+{
+    // TODO: Update this to use Key, not KeyCode
+    private KeyCode shortcut;
+
+    /// <summary>This is the global setting that can be used as a global shortcut to invoke the action on the view.</summary>
+    public virtual KeyCode Shortcut
+    {
+        get => shortcut;
+        set
+        {
+            if (shortcut != value && (PostShortcutValidation (value) || value is KeyCode.Null))
+            {
+                shortcut = value;
+            }
+        }
+    }
+
+    /// <summary>The keystroke combination used in the <see cref="Shortcut"/> as string.</summary>
+    public virtual string ShortcutTag => Key.ToString (shortcut, MenuBar.ShortcutDelimiter);
+
+    /// <summary>Lookup for a <see cref="KeyCode"/> on range of keys.</summary>
+    /// <param name="key">The source key.</param>
+    /// <param name="first">First key in range.</param>
+    /// <param name="last">Last key in range.</param>
+    public static bool CheckKeysFlagRange (KeyCode key, KeyCode first, KeyCode last)
+    {
+        for (var i = (uint)first; i < (uint)last; i++)
+        {
+            if ((key | (KeyCode)i) == key)
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>Allows to retrieve a <see cref="KeyCode"/> from a <see cref="ShortcutTag"/></summary>
+    /// <param name="tag">The key as string.</param>
+    /// <param name="delimiter">The delimiter string.</param>
+    public static KeyCode GetShortcutFromTag (string tag, Rune delimiter = default)
+    {
+        string sCut = tag;
+
+        if (string.IsNullOrEmpty (sCut))
+        {
+            return default (KeyCode);
+        }
+
+        var key = KeyCode.Null;
+
+        //var hasCtrl = false;
+        if (delimiter == default (Rune))
+        {
+            delimiter = MenuBar.ShortcutDelimiter;
+        }
+
+        string [] keys = sCut.Split (delimiter.ToString ());
+
+        for (var i = 0; i < keys.Length; i++)
+        {
+            string k = keys [i];
+
+            if (k == "Ctrl")
+            {
+                //hasCtrl = true;
+                key |= KeyCode.CtrlMask;
+            }
+            else if (k == "Shift")
+            {
+                key |= KeyCode.ShiftMask;
+            }
+            else if (k == "Alt")
+            {
+                key |= KeyCode.AltMask;
+            }
+            else if (k.StartsWith ("F") && k.Length > 1)
+            {
+                int.TryParse (k.Substring (1), out int n);
+
+                for (var j = (uint)KeyCode.F1; j <= (uint)KeyCode.F12; j++)
+                {
+                    int.TryParse (((KeyCode)j).ToString ().Substring (1), out int f);
+
+                    if (f == n)
+                    {
+                        key |= (KeyCode)j;
+                    }
+                }
+            }
+            else
+            {
+                key |= (KeyCode)Enum.Parse (typeof (KeyCode), k);
+            }
+        }
+
+        return key;
+    }
+
+    /// <summary>Used at key up validation.</summary>
+    /// <param name="key">The key to validate.</param>
+    /// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
+    public static bool PostShortcutValidation (KeyCode key)
+    {
+        GetKeyToString (key, out KeyCode knm);
+
+        if (CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12) || ((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) != 0 && knm != KeyCode.Null))
+        {
+            return true;
+        }
+
+        Debug.WriteLine ($"WARNING: {Key.ToString (key)} is not a valid shortcut key.");
+
+        return false;
+    }
+
+    /// <summary>Used at key down or key press validation.</summary>
+    /// <param name="key">The key to validate.</param>
+    /// <returns><c>true</c> if is valid.<c>false</c>otherwise.</returns>
+    public static bool PreShortcutValidation (KeyCode key)
+    {
+        if ((key & (KeyCode.CtrlMask | KeyCode.ShiftMask | KeyCode.AltMask)) == 0
+            && !CheckKeysFlagRange (key, KeyCode.F1, KeyCode.F12))
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    /// <summary>Return key as string.</summary>
+    /// <param name="key">The key to extract.</param>
+    /// <param name="knm">Correspond to the non modifier key.</param>
+    private static string GetKeyToString (KeyCode key, out KeyCode knm)
+    {
+        if (key == KeyCode.Null)
+        {
+            knm = KeyCode.Null;
+
+            return "";
+        }
+
+        knm = key;
+        KeyCode mK = key & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask);
+        knm &= ~mK;
+
+        for (var i = (uint)KeyCode.F1; i < (uint)KeyCode.F12; i++)
+        {
+            if (knm == (KeyCode)i)
+            {
+                mK |= (KeyCode)i;
+            }
+        }
+
+        knm &= ~mK;
+        uint.TryParse (knm.ToString (), out uint c);
+        string s = mK == KeyCode.Null ? "" : mK.ToString ();
+
+        if (s != "" && (knm != KeyCode.Null || c > 0))
+        {
+            s += ",";
+        }
+
+        s += c == 0 ? knm == KeyCode.Null ? "" : knm.ToString () : ((char)c).ToString ();
+
+        return s;
+    }
+}

+ 374 - 372
Terminal.Gui/MainLoop.cs

@@ -4,378 +4,380 @@
 // Authors:
 //   Miguel de Icaza ([email protected])
 //
-using System;
-using System.Collections.Generic;
+
 using System.Collections.ObjectModel;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Interface to create a platform specific <see cref="MainLoop"/> driver.
-	/// </summary>
-	internal interface IMainLoopDriver {
-		/// <summary>
-		/// Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.
-		/// </summary>
-		/// <remarks>
-		/// Call <see cref="TearDown"/> to release resources.
-		/// </remarks>
-		/// <param name="mainLoop">Main loop.</param>
-		void Setup (MainLoop mainLoop);
-
-		/// <summary>
-		/// Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.
-		/// </summary>
-		void Wakeup ();
-
-		/// <summary>
-		/// Must report whether there are any events pending, or even block waiting for events.
-		/// </summary>
-		/// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
-		bool EventsPending ();
-
-		/// <summary>
-		/// The iteration function.
-		/// </summary>
-		void Iteration ();
-
-		/// <summary>
-		/// Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.
-		/// </summary>
-		void TearDown ();
-	}
-
-
-	/// <summary>
-	///   The MainLoop monitors timers and idle handlers.
-	/// </summary>
-	/// <remarks>
-	///   Monitoring of file descriptors is only available on Unix, there
-	///   does not seem to be a way of supporting this on Windows.
-	/// </remarks>
-	internal class MainLoop : IDisposable {
-
-		internal SortedList<long, Timeout> _timeouts = new SortedList<long, Timeout> ();
-		readonly object _timeoutsLockToken = new object ();
-
-		/// <summary>
-		/// The idle handlers and lock that must be held while manipulating them
-		/// </summary>
-		readonly object _idleHandlersLock = new object ();
-		internal List<Func<bool>> _idleHandlers = new List<Func<bool>> ();
-
-		/// <summary>
-		/// Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks.
-		/// A shorter limit time can be added at the end, but it will be called before an
-		///  earlier addition that has a longer limit time.
-		/// </summary>
-		internal SortedList<long, Timeout> Timeouts => _timeouts;
-
-		/// <summary>
-		/// Gets a copy of the list of all idle handlers.
-		/// </summary>
-		internal ReadOnlyCollection<Func<bool>> IdleHandlers {
-			get {
-				lock (_idleHandlersLock) {
-					return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// The current <see cref="IMainLoopDriver"/> in use.
-		/// </summary>
-		/// <value>The main loop driver.</value>
-		internal IMainLoopDriver MainLoopDriver { get; private set; }
-
-		/// <summary>
-		/// Invoked when a new timeout is added. To be used in the case
-		/// when <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
-		/// </summary>
-		internal event EventHandler<TimeoutEventArgs> TimeoutAdded;
-
-		/// <summary>
-		///  Creates a new MainLoop. 
-		/// </summary>
-		/// <remarks>
-		/// Use <see cref="Dispose"/> to release resources.
-		/// </remarks>
-		/// <param name="driver">The <see cref="ConsoleDriver"/> instance
-		/// (one of the implementations FakeMainLoop, UnixMainLoop, NetMainLoop or WindowsMainLoop).</param>
-		internal MainLoop (IMainLoopDriver driver)
-		{
-			MainLoopDriver = driver;
-			driver.Setup (this);
-		}
-
-		/// <summary>
-		///   Adds specified idle handler function to <see cref="MainLoop"/> processing. 
-		///   The handler function will be called once per iteration of the main loop after other events have been handled.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///   Remove an idle handler by calling <see cref="RemoveIdle(Func{bool})"/> with the token this method returns.
-		/// </para>
-		/// <para>
-		///   If the <paramref name="idleHandler"/> returns  <see langword="false"/> it will be removed and not called subsequently.
-		/// </para>
-		/// </remarks>
-		/// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
-		internal Func<bool> AddIdle (Func<bool> idleHandler)
-		{
-			lock (_idleHandlersLock) {
-				_idleHandlers.Add (idleHandler);
-			}
-
-			MainLoopDriver.Wakeup ();
-			return idleHandler;
-		}
-
-		/// <summary>
-		///   Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.
-		/// </summary>
-		/// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
-		/// Returns <c>true</c>if the idle handler is successfully removed; otherwise, <c>false</c>.
-		///  This method also returns <c>false</c> if the idle handler is not found.
-		internal bool RemoveIdle (Func<bool> token)
-		{
-			lock (_idleHandlersLock) {
-				return _idleHandlers.Remove (token);
-			}
-		}
-
-		void AddTimeout (TimeSpan time, Timeout timeout)
-		{
-			lock (_timeoutsLockToken) {
-				var k = (DateTime.UtcNow + time).Ticks;
-				_timeouts.Add (NudgeToUniqueKey (k), timeout);
-				TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
-			}
-		}
-
-		/// <summary>
-		///   Adds a timeout to the <see cref="MainLoop"/>.
-		/// </summary>
-		/// <remarks>
-		///   When time specified passes, the callback will be invoked.
-		///   If the callback returns true, the timeout will be reset, repeating
-		///   the invocation. If it returns false, the timeout will stop and be removed.
-		///
-		///   The returned value is a token that can be used to stop the timeout
-		///   by calling <see cref="RemoveTimeout(object)"/>.
-		/// </remarks>
-		internal object AddTimeout (TimeSpan time, Func<bool> callback)
-		{
-			if (callback == null) {
-				throw new ArgumentNullException (nameof (callback));
-			}
-			var timeout = new Timeout () {
-				Span = time,
-				Callback = callback
-			};
-			AddTimeout (time, timeout);
-			return timeout;
-		}
-
-		/// <summary>
-		///   Removes a previously scheduled timeout
-		/// </summary>
-		/// <remarks>
-		///   The token parameter is the value returned by AddTimeout.
-		/// </remarks>
-		/// Returns <c>true</c>if the timeout is successfully removed; otherwise, <c>false</c>.
-		/// This method also returns <c>false</c> if the timeout is not found.
-		internal bool RemoveTimeout (object token)
-		{
-			lock (_timeoutsLockToken) {
-				var idx = _timeouts.IndexOfValue (token as Timeout);
-				if (idx == -1) {
-					return false;
-				}
-				_timeouts.RemoveAt (idx);
-			}
-			return true;
-		}
-
-		void RunTimers ()
-		{
-			var now = DateTime.UtcNow.Ticks;
-			SortedList<long, Timeout> copy;
-
-			// lock prevents new timeouts being added
-			// after we have taken the copy but before
-			// we have allocated a new list (which would
-			// result in lost timeouts or errors during enumeration)
-			lock (_timeoutsLockToken) {
-				copy = _timeouts;
-				_timeouts = new SortedList<long, Timeout> ();
-			}
-
-			foreach ((var k, var timeout) in copy) {
-				if (k < now) {
-					if (timeout.Callback ()) {
-						AddTimeout (timeout.Span, timeout);
-					}
-				} else {
-					lock (_timeoutsLockToken) {
-						_timeouts.Add (NudgeToUniqueKey (k), timeout);
-					}
-				}
-			}
-		}
-
-		/// <summary>
-		/// Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle handlers.
-		/// </summary>
-		/// <param name="waitTimeout">Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
-		/// there are no active timers.</param>
-		/// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
-		internal bool CheckTimersAndIdleHandlers (out int waitTimeout)
-		{
-			var now = DateTime.UtcNow.Ticks;
-
-			waitTimeout = 0;
-
-			lock (_timeouts) {
-				if (_timeouts.Count > 0) {
-					waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-					if (waitTimeout < 0) {
-						// This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
-						// This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
-						// and no event occurred in elapsed time when the 'poll' is start running again.
-						waitTimeout = 0;
-					}
-					return true;
-				}
-				// ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
-				// the timeout is -1.
-				waitTimeout = -1;
-			}
-
-			// There are no timers set, check if there are any idle handlers
-
-			lock (_idleHandlers) {
-				return _idleHandlers.Count > 0;
-			}
-		}
-
-		/// <summary>
-		/// Finds the closest number to <paramref name="k"/> that is not
-		/// present in <see cref="_timeouts"/> (incrementally).
-		/// </summary>
-		/// <param name="k"></param>
-		/// <returns></returns>
-		long NudgeToUniqueKey (long k)
-		{
-			lock (_timeoutsLockToken) {
-				while (_timeouts.ContainsKey (k)) {
-					k++;
-				}
-			}
-
-			return k;
-		}
-
-		void RunIdle ()
-		{
-			List<Func<bool>> iterate;
-			lock (_idleHandlersLock) {
-				iterate = _idleHandlers;
-				_idleHandlers = new List<Func<bool>> ();
-			}
-
-			foreach (var idle in iterate) {
-				if (idle ()) {
-					lock (_idleHandlersLock) {
-						_idleHandlers.Add (idle);
-					}
-				}
-			}
-		}
-
-		/// <summary>
-		/// Used for unit tests.
-		/// </summary>
-		internal bool Running { get; set; }
-
-		/// <summary>
-		///   Determines whether there are pending events to be processed.
-		/// </summary>
-		/// <remarks>
-		///   You can use this method if you want to probe if events are pending.
-		///   Typically used if you need to flush the input queue while still
-		///   running some of your own code in your main thread.
-		/// </remarks>
-		internal bool EventsPending ()
-		{
-			return MainLoopDriver.EventsPending ();
-		}
-
-		/// <summary>
-		///   Runs one iteration of timers and file watches
-		/// </summary>
-		/// <remarks>
-		///   Use this to process all pending events (timers, idle handlers and file watches).
-		///
-		///   <code>
-		///     while (main.EventsPending ()) RunIteration ();
-		///   </code>
-		/// </remarks>
-		internal void RunIteration ()
-		{
-			lock (_timeouts) {
-				if (_timeouts.Count > 0) {
-					RunTimers ();
-				}
-			}
-
-			MainLoopDriver.Iteration ();
-
-			var runIdle = false;
-			lock (_idleHandlersLock) {
-				runIdle = _idleHandlers.Count > 0;
-			}
-
-			if (runIdle) {
-				RunIdle ();
-			}
-		}
-
-		/// <summary>
-		///   Runs the <see cref="MainLoop"/>. Used only for unit tests.
-		/// </summary>
-		internal void Run ()
-		{
-			var prev = Running;
-			Running = true;
-			while (Running) {
-				EventsPending ();
-				RunIteration ();
-			}
-			Running = prev;
-		}
-
-		/// <summary>
-		/// Wakes up the <see cref="MainLoop"/> that might be waiting on input.
-		/// </summary>
-		internal void Wakeup () => MainLoopDriver?.Wakeup ();
-
-		/// <summary>
-		/// Stops the main loop driver and calls <see cref="IMainLoopDriver.Wakeup"/>. Used only for unit tests.
-		/// </summary>
-		internal void Stop ()
-		{
-			Running = false;
-			Wakeup ();
-		}
-
-		/// <inheritdoc/>
-		public void Dispose ()
-		{
-			GC.SuppressFinalize (this);
-			Stop ();
-			Running = false;
-			MainLoopDriver?.TearDown ();
-			MainLoopDriver = null;
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
+internal interface IMainLoopDriver
+{
+    /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
+    /// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
+    bool EventsPending ();
+
+    /// <summary>The iteration function.</summary>
+    void Iteration ();
+
+    /// <summary>Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.</summary>
+    /// <remarks>Call <see cref="TearDown"/> to release resources.</remarks>
+    /// <param name="mainLoop">Main loop.</param>
+    void Setup (MainLoop mainLoop);
+
+    /// <summary>Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.</summary>
+    void TearDown ();
+
+    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
+    void Wakeup ();
+}
+
+/// <summary>The MainLoop monitors timers and idle handlers.</summary>
+/// <remarks>
+///     Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
+///     on Windows.
+/// </remarks>
+internal class MainLoop : IDisposable
+{
+    internal List<Func<bool>> _idleHandlers = new ();
+    internal SortedList<long, Timeout> _timeouts = new ();
+
+    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
+    private readonly object _idleHandlersLock = new ();
+
+    private readonly object _timeoutsLockToken = new ();
+
+    /// <summary>Creates a new MainLoop.</summary>
+    /// <remarks>Use <see cref="Dispose"/> to release resources.</remarks>
+    /// <param name="driver">
+    ///     The <see cref="ConsoleDriver"/> instance (one of the implementations FakeMainLoop, UnixMainLoop,
+    ///     NetMainLoop or WindowsMainLoop).
+    /// </param>
+    internal MainLoop (IMainLoopDriver driver)
+    {
+        MainLoopDriver = driver;
+        driver.Setup (this);
+    }
+
+    /// <summary>Gets a copy of the list of all idle handlers.</summary>
+    internal ReadOnlyCollection<Func<bool>> IdleHandlers
+    {
+        get
+        {
+            lock (_idleHandlersLock)
+            {
+                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
+            }
+        }
+    }
+
+    /// <summary>The current <see cref="IMainLoopDriver"/> in use.</summary>
+    /// <value>The main loop driver.</value>
+    internal IMainLoopDriver MainLoopDriver { get; private set; }
+
+    /// <summary>Used for unit tests.</summary>
+    internal bool Running { get; set; }
+
+    /// <summary>
+    ///     Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks. A shorter limit time can be
+    ///     added at the end, but it will be called before an earlier addition that has a longer limit time.
+    /// </summary>
+    internal SortedList<long, Timeout> Timeouts => _timeouts;
+
+    /// <inheritdoc/>
+    public void Dispose ()
+    {
+        GC.SuppressFinalize (this);
+        Stop ();
+        Running = false;
+        MainLoopDriver?.TearDown ();
+        MainLoopDriver = null;
+    }
+
+    /// <summary>
+    ///     Adds specified idle handler function to <see cref="MainLoop"/> processing. The handler function will be called
+    ///     once per iteration of the main loop after other events have been handled.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Remove an idle handler by calling <see cref="RemoveIdle(Func{bool})"/> with the token this method returns.</para>
+    ///     <para>
+    ///         If the <paramref name="idleHandler"/> returns  <see langword="false"/> it will be removed and not called
+    ///         subsequently.
+    ///     </para>
+    /// </remarks>
+    /// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
+    internal Func<bool> AddIdle (Func<bool> idleHandler)
+    {
+        lock (_idleHandlersLock)
+        {
+            _idleHandlers.Add (idleHandler);
+        }
+
+        MainLoopDriver.Wakeup ();
+
+        return idleHandler;
+    }
+
+    /// <summary>Adds a timeout to the <see cref="MainLoop"/>.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    internal object AddTimeout (TimeSpan time, Func<bool> callback)
+    {
+        if (callback == null)
+        {
+            throw new ArgumentNullException (nameof (callback));
+        }
+
+        var timeout = new Timeout { Span = time, Callback = callback };
+        AddTimeout (time, timeout);
+
+        return timeout;
+    }
+
+    /// <summary>
+    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
+    ///     handlers.
+    /// </summary>
+    /// <param name="waitTimeout">
+    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
+    ///     there are no active timers.
+    /// </param>
+    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
+    internal bool CheckTimersAndIdleHandlers (out int waitTimeout)
+    {
+        long now = DateTime.UtcNow.Ticks;
+
+        waitTimeout = 0;
+
+        lock (_timeouts)
+        {
+            if (_timeouts.Count > 0)
+            {
+                waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+
+                if (waitTimeout < 0)
+                {
+                    // This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
+                    // This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
+                    // and no event occurred in elapsed time when the 'poll' is start running again.
+                    waitTimeout = 0;
+                }
+
+                return true;
+            }
+
+            // ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
+            // the timeout is -1.
+            waitTimeout = -1;
+        }
+
+        // There are no timers set, check if there are any idle handlers
+
+        lock (_idleHandlers)
+        {
+            return _idleHandlers.Count > 0;
+        }
+    }
+
+    /// <summary>Determines whether there are pending events to be processed.</summary>
+    /// <remarks>
+    ///     You can use this method if you want to probe if events are pending. Typically used if you need to flush the
+    ///     input queue while still running some of your own code in your main thread.
+    /// </remarks>
+    internal bool EventsPending () { return MainLoopDriver.EventsPending (); }
+
+    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
+    /// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
+    /// Returns
+    /// <c>true</c>
+    /// if the idle handler is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the idle handler is not found.
+    internal bool RemoveIdle (Func<bool> token)
+    {
+        lock (_idleHandlersLock)
+        {
+            return _idleHandlers.Remove (token);
+        }
+    }
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
+    /// Returns
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.
+    internal bool RemoveTimeout (object token)
+    {
+        lock (_timeoutsLockToken)
+        {
+            int idx = _timeouts.IndexOfValue (token as Timeout);
+
+            if (idx == -1)
+            {
+                return false;
+            }
+
+            _timeouts.RemoveAt (idx);
+        }
+
+        return true;
+    }
+
+    /// <summary>Runs the <see cref="MainLoop"/>. Used only for unit tests.</summary>
+    internal void Run ()
+    {
+        bool prev = Running;
+        Running = true;
+
+        while (Running)
+        {
+            EventsPending ();
+            RunIteration ();
+        }
+
+        Running = prev;
+    }
+
+    /// <summary>Runs one iteration of timers and file watches</summary>
+    /// <remarks>
+    ///     Use this to process all pending events (timers, idle handlers and file watches).
+    ///     <code>
+    ///     while (main.EventsPending ()) RunIteration ();
+    ///   </code>
+    /// </remarks>
+    internal void RunIteration ()
+    {
+        lock (_timeouts)
+        {
+            if (_timeouts.Count > 0)
+            {
+                RunTimers ();
+            }
+        }
+
+        MainLoopDriver.Iteration ();
+
+        var runIdle = false;
+
+        lock (_idleHandlersLock)
+        {
+            runIdle = _idleHandlers.Count > 0;
+        }
+
+        if (runIdle)
+        {
+            RunIdle ();
+        }
+    }
+
+    /// <summary>Stops the main loop driver and calls <see cref="IMainLoopDriver.Wakeup"/>. Used only for unit tests.</summary>
+    internal void Stop ()
+    {
+        Running = false;
+        Wakeup ();
+    }
+
+    /// <summary>
+    ///     Invoked when a new timeout is added. To be used in the case when
+    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
+    /// </summary>
+    internal event EventHandler<TimeoutEventArgs> TimeoutAdded;
+
+    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>
+    internal void Wakeup () { MainLoopDriver?.Wakeup (); }
+
+    private void AddTimeout (TimeSpan time, Timeout timeout)
+    {
+        lock (_timeoutsLockToken)
+        {
+            long k = (DateTime.UtcNow + time).Ticks;
+            _timeouts.Add (NudgeToUniqueKey (k), timeout);
+            TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
+        }
+    }
+
+    /// <summary>
+    ///     Finds the closest number to <paramref name="k"/> that is not present in <see cref="_timeouts"/>
+    ///     (incrementally).
+    /// </summary>
+    /// <param name="k"></param>
+    /// <returns></returns>
+    private long NudgeToUniqueKey (long k)
+    {
+        lock (_timeoutsLockToken)
+        {
+            while (_timeouts.ContainsKey (k))
+            {
+                k++;
+            }
+        }
+
+        return k;
+    }
+
+    private void RunIdle ()
+    {
+        List<Func<bool>> iterate;
+
+        lock (_idleHandlersLock)
+        {
+            iterate = _idleHandlers;
+            _idleHandlers = new List<Func<bool>> ();
+        }
+
+        foreach (Func<bool> idle in iterate)
+        {
+            if (idle ())
+            {
+                lock (_idleHandlersLock)
+                {
+                    _idleHandlers.Add (idle);
+                }
+            }
+        }
+    }
+
+    private void RunTimers ()
+    {
+        long now = DateTime.UtcNow.Ticks;
+        SortedList<long, Timeout> copy;
+
+        // lock prevents new timeouts being added
+        // after we have taken the copy but before
+        // we have allocated a new list (which would
+        // result in lost timeouts or errors during enumeration)
+        lock (_timeoutsLockToken)
+        {
+            copy = _timeouts;
+            _timeouts = new SortedList<long, Timeout> ();
+        }
+
+        foreach ((long k, Timeout timeout) in copy)
+        {
+            if (k < now)
+            {
+                if (timeout.Callback ())
+                {
+                    AddTimeout (timeout.Span, timeout);
+                }
+            }
+            else
+            {
+                lock (_timeoutsLockToken)
+                {
+                    _timeouts.Add (NudgeToUniqueKey (k), timeout);
+                }
+            }
+        }
+    }
 }

+ 47 - 72
Terminal.Gui/RunState.cs

@@ -1,79 +1,54 @@
-using System;
-using System.Collections.Generic;
-
-namespace Terminal.Gui;
-
-/// <summary>
-/// The execution state for a <see cref="Toplevel"/> view.
-/// </summary>
-public class RunState : IDisposable {
-	/// <summary>
-	/// Initializes a new <see cref="RunState"/> class.
-	/// </summary>
-	/// <param name="view"></param>
-	public RunState (Toplevel view)
-	{
-		Toplevel = view;
-	}
-	/// <summary>
-	/// The <see cref="Toplevel"/> belonging to this <see cref="RunState"/>.
-	/// </summary>
-	public Toplevel Toplevel { get; internal set; }
-
+namespace Terminal.Gui;
+
+/// <summary>The execution state for a <see cref="Toplevel"/> view.</summary>
+public class RunState : IDisposable
+{
+    /// <summary>Initializes a new <see cref="RunState"/> class.</summary>
+    /// <param name="view"></param>
+    public RunState (Toplevel view) { Toplevel = view; }
+
+    /// <summary>The <see cref="Toplevel"/> belonging to this <see cref="RunState"/>.</summary>
+    public Toplevel Toplevel { get; internal set; }
+
+    /// <summary>Releases all resource used by the <see cref="RunState"/> object.</summary>
+    /// <remarks>Call <see cref="Dispose()"/> when you are finished using the <see cref="RunState"/>.</remarks>
+    /// <remarks>
+    ///     <see cref="Dispose()"/> method leaves the <see cref="RunState"/> in an unusable state. After calling
+    ///     <see cref="Dispose()"/>, you must release all references to the <see cref="RunState"/> so the garbage collector can
+    ///     reclaim the memory that the <see cref="RunState"/> was occupying.
+    /// </remarks>
+    public void Dispose ()
+    {
+        Dispose (true);
+        GC.SuppressFinalize (this);
 #if DEBUG_IDISPOSABLE
-	/// <summary>
-	/// For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly
-	/// </summary>
-	public bool WasDisposed = false;
+        WasDisposed = true;
+#endif
+    }
+
+    /// <summary>Releases all resource used by the <see cref="RunState"/> object.</summary>
+    /// <param name="disposing">If set to <see langword="true"/> we are disposing and should dispose held objects.</param>
+    protected virtual void Dispose (bool disposing)
+    {
+        if (Toplevel != null && disposing)
+        {
+            throw new InvalidOperationException (
+                                                 "You must clean up (Dispose) the Toplevel before calling Application.RunState.Dispose"
+                                                );
+        }
+    }
 
-	/// <summary>
-	/// For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly
-	/// </summary>
-	public int DisposedCount = 0;
+#if DEBUG_IDISPOSABLE
+    /// <summary>For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly</summary>
+    public bool WasDisposed;
 
-	/// <summary>
-	/// For debug (see DEBUG_IDISPOSABLE define) purposes; the runstate instances that have been created
-	/// </summary>
-	public static List<RunState> Instances = new List<RunState> ();
+    /// <summary>For debug (see DEBUG_IDISPOSABLE define) purposes to verify objects are being disposed properly</summary>
+    public int DisposedCount = 0;
 
-	/// <summary>
-	/// Creates a new RunState object.
-	/// </summary>
-	public RunState ()
-	{
-		Instances.Add (this);
-	}
-#endif
+    /// <summary>For debug (see DEBUG_IDISPOSABLE define) purposes; the runstate instances that have been created</summary>
+    public static List<RunState> Instances = new ();
 
-	/// <summary>
-	/// Releases all resource used by the <see cref="RunState"/> object.
-	/// </summary>
-	/// <remarks>
-	/// Call <see cref="Dispose()"/> when you are finished using the <see cref="RunState"/>. 
-	/// </remarks>
-	/// <remarks>
-	/// <see cref="Dispose()"/> method leaves the <see cref="RunState"/> in an unusable state. After
-	/// calling <see cref="Dispose()"/>, you must release all references to the
-	/// <see cref="RunState"/> so the garbage collector can reclaim the memory that the
-	/// <see cref="RunState"/> was occupying.
-	/// </remarks>
-	public void Dispose ()
-	{
-		Dispose (true);
-		GC.SuppressFinalize (this);
-#if DEBUG_IDISPOSABLE
-		WasDisposed = true;
+    /// <summary>Creates a new RunState object.</summary>
+    public RunState () { Instances.Add (this); }
 #endif
-	}
-
-	/// <summary>
-	/// Releases all resource used by the <see cref="RunState"/> object.
-	/// </summary>
-	/// <param name="disposing">If set to <see langword="true"/> we are disposing and should dispose held objects.</param>
-	protected virtual void Dispose (bool disposing)
-	{
-		if (Toplevel != null && disposing) {
-			throw new InvalidOperationException ("You must clean up (Dispose) the Toplevel before calling Application.RunState.Dispose");
-		}
-	}
 }

+ 9 - 21
Terminal.Gui/RunStateEventArgs.cs

@@ -1,24 +1,12 @@
-using System;
-using static Terminal.Gui.Application;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Event arguments for events about <see cref="RunState"/>
-	/// </summary>
-	public class RunStateEventArgs : EventArgs {
+/// <summary>Event arguments for events about <see cref="RunState"/></summary>
+public class RunStateEventArgs : EventArgs
+{
+    /// <summary>Creates a new instance of the <see cref="RunStateEventArgs"/> class</summary>
+    /// <param name="state"></param>
+    public RunStateEventArgs (RunState state) { State = state; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="RunStateEventArgs"/> class
-		/// </summary>
-		/// <param name="state"></param>
-		public RunStateEventArgs (RunState state)
-		{
-			State = state;
-		}
-
-		/// <summary>
-		/// The state being reported on by the event
-		/// </summary>
-		public RunState State { get; }
-	}
+    /// <summary>The state being reported on by the event</summary>
+    public RunState State { get; }
 }

+ 242 - 195
Terminal.Gui/StackExtensions.cs

@@ -1,196 +1,243 @@
-using System;
-using System.Collections.Generic;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Extension of <see cref="Stack{T}"/> helper to work with specific <see cref="IEqualityComparer{T}"/>
-	/// </summary>
-	public static class StackExtensions {
-		/// <summary>
-		/// Replaces an stack object values that match with the value to replace.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		/// <param name="valueToReplace">Value to replace.</param>
-		/// <param name="valueToReplaceWith">Value to replace with to what matches the value to replace.</param>
-		/// <param name="comparer">The comparison object.</param>
-		public static void Replace<T> (this Stack<T> stack, T valueToReplace,
-			T valueToReplaceWith, IEqualityComparer<T> comparer = null)
-		{
-			comparer = comparer ?? EqualityComparer<T>.Default;
-
-			var temp = new Stack<T> ();
-			while (stack.Count > 0) {
-				var value = stack.Pop ();
-				if (comparer.Equals (value, valueToReplace)) {
-					stack.Push (valueToReplaceWith);
-					break;
-				}
-				temp.Push (value);
-			}
-
-			while (temp.Count > 0)
-				stack.Push (temp.Pop ());
-		}
-
-		/// <summary>
-		/// Swap two stack objects values that matches with the both values.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		/// <param name="valueToSwapFrom">Value to swap from.</param>
-		/// <param name="valueToSwapTo">Value to swap to.</param>
-		/// <param name="comparer">The comparison object.</param>
-		public static void Swap<T> (this Stack<T> stack, T valueToSwapFrom,
-			T valueToSwapTo, IEqualityComparer<T> comparer = null)
-		{
-			comparer = comparer ?? EqualityComparer<T>.Default;
-
-			int index = stack.Count - 1;
-			T [] stackArr = new T [stack.Count];
-			while (stack.Count > 0) {
-				var value = stack.Pop ();
-				if (comparer.Equals (value, valueToSwapFrom)) {
-					stackArr [index] = valueToSwapTo;
-				} else if (comparer.Equals (value, valueToSwapTo)) {
-					stackArr [index] = valueToSwapFrom;
-				} else {
-					stackArr [index] = value;
-				}
-				index--;
-			}
-
-			for (int i = 0; i < stackArr.Length; i++)
-				stack.Push (stackArr [i]);
-		}
-
-		/// <summary>
-		/// Move the first stack object value to the end.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		public static void MoveNext<T> (this Stack<T> stack)
-		{
-			var temp = new Stack<T> ();
-			var last = stack.Pop ();
-			while (stack.Count > 0) {
-				var value = stack.Pop ();
-				temp.Push (value);
-			}
-			temp.Push (last);
-
-			while (temp.Count > 0)
-				stack.Push (temp.Pop ());
-		}
-
-		/// <summary>
-		/// Move the last stack object value to the top.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		public static void MovePrevious<T> (this Stack<T> stack)
-		{
-			var temp = new Stack<T> ();
-			T first = default;
-			while (stack.Count > 0) {
-				var value = stack.Pop ();
-				temp.Push (value);
-				if (stack.Count == 1) {
-					first = stack.Pop ();
-				}
-			}
-
-			while (temp.Count > 0)
-				stack.Push (temp.Pop ());
-			stack.Push (first);
-		}
-
-		/// <summary>
-		/// Find all duplicates stack objects values.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		/// <param name="comparer">The comparison object.</param>
-		/// <returns>The duplicates stack object.</returns>
-		public static Stack<T> FindDuplicates<T> (this Stack<T> stack, IEqualityComparer<T> comparer = null)
-		{
-			comparer = comparer ?? EqualityComparer<T>.Default;
-
-			var dup = new Stack<T> ();
-			T [] stackArr = stack.ToArray ();
-			for (int i = 0; i < stackArr.Length; i++) {
-				var value = stackArr [i];
-				for (int j = i + 1; j < stackArr.Length; j++) {
-					var valueToFind = stackArr [j];
-					if (comparer.Equals (value, valueToFind) && !Contains (dup, valueToFind)) {
-						dup.Push (value);
-					}
-				}
-			}
-
-			return dup;
-		}
-
-		/// <summary>
-		/// Check if the stack object contains the value to find.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		/// <param name="valueToFind">Value to find.</param>
-		/// <param name="comparer">The comparison object.</param>
-		/// <returns><c>true</c> If the value was found.<c>false</c> otherwise.</returns>
-		public static bool Contains<T> (this Stack<T> stack, T valueToFind, IEqualityComparer<T> comparer = null)
-		{
-			comparer = comparer ?? EqualityComparer<T>.Default;
-
-			foreach (T obj in stack) {
-				if (comparer.Equals (obj, valueToFind)) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		/// <summary>
-		/// Move the stack object value to the index.
-		/// </summary>
-		/// <typeparam name="T">The stack object type.</typeparam>
-		/// <param name="stack">The stack object.</param>
-		/// <param name="valueToMove">Value to move.</param>
-		/// <param name="index">The index where to move.</param>
-		/// <param name="comparer">The comparison object.</param>
-		public static void MoveTo<T> (this Stack<T> stack, T valueToMove, int index = 0,
-			IEqualityComparer<T> comparer = null)
-		{
-			if (index < 0) {
-				return;
-			}
-
-			comparer = comparer ?? EqualityComparer<T>.Default;
-
-			var temp = new Stack<T> ();
-			var toMove = default (T);
-			var stackCount = stack.Count;
-			var count = 0;
-			while (stack.Count > 0) {
-				var value = stack.Pop ();
-				if (comparer.Equals (value, valueToMove)) {
-					toMove = value;
-					break;
-				}
-				temp.Push (value);
-				count++;
-			}
-
-			int idx = 0;
-			while (stack.Count < stackCount) {
-				if (count - idx == index) {
-					stack.Push (toMove);
-				} else {
-					stack.Push (temp.Pop ());
-				}
-				idx++;
-			}
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>Extension of <see cref="Stack{T}"/> helper to work with specific <see cref="IEqualityComparer{T}"/></summary>
+public static class StackExtensions
+{
+    /// <summary>Check if the stack object contains the value to find.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    /// <param name="valueToFind">Value to find.</param>
+    /// <param name="comparer">The comparison object.</param>
+    /// <returns><c>true</c> If the value was found.<c>false</c> otherwise.</returns>
+    public static bool Contains<T> (this Stack<T> stack, T valueToFind, IEqualityComparer<T> comparer = null)
+    {
+        comparer = comparer ?? EqualityComparer<T>.Default;
+
+        foreach (T obj in stack)
+        {
+            if (comparer.Equals (obj, valueToFind))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /// <summary>Find all duplicates stack objects values.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    /// <param name="comparer">The comparison object.</param>
+    /// <returns>The duplicates stack object.</returns>
+    public static Stack<T> FindDuplicates<T> (this Stack<T> stack, IEqualityComparer<T> comparer = null)
+    {
+        comparer = comparer ?? EqualityComparer<T>.Default;
+
+        Stack<T> dup = new ();
+        T [] stackArr = stack.ToArray ();
+
+        for (var i = 0; i < stackArr.Length; i++)
+        {
+            T value = stackArr [i];
+
+            for (int j = i + 1; j < stackArr.Length; j++)
+            {
+                T valueToFind = stackArr [j];
+
+                if (comparer.Equals (value, valueToFind) && !Contains (dup, valueToFind))
+                {
+                    dup.Push (value);
+                }
+            }
+        }
+
+        return dup;
+    }
+
+    /// <summary>Move the first stack object value to the end.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    public static void MoveNext<T> (this Stack<T> stack)
+    {
+        Stack<T> temp = new ();
+        T last = stack.Pop ();
+
+        while (stack.Count > 0)
+        {
+            T value = stack.Pop ();
+            temp.Push (value);
+        }
+
+        temp.Push (last);
+
+        while (temp.Count > 0)
+        {
+            stack.Push (temp.Pop ());
+        }
+    }
+
+    /// <summary>Move the last stack object value to the top.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    public static void MovePrevious<T> (this Stack<T> stack)
+    {
+        Stack<T> temp = new ();
+        T first = default;
+
+        while (stack.Count > 0)
+        {
+            T value = stack.Pop ();
+            temp.Push (value);
+
+            if (stack.Count == 1)
+            {
+                first = stack.Pop ();
+            }
+        }
+
+        while (temp.Count > 0)
+        {
+            stack.Push (temp.Pop ());
+        }
+
+        stack.Push (first);
+    }
+
+    /// <summary>Move the stack object value to the index.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    /// <param name="valueToMove">Value to move.</param>
+    /// <param name="index">The index where to move.</param>
+    /// <param name="comparer">The comparison object.</param>
+    public static void MoveTo<T> (
+        this Stack<T> stack,
+        T valueToMove,
+        int index = 0,
+        IEqualityComparer<T> comparer = null
+    )
+    {
+        if (index < 0)
+        {
+            return;
+        }
+
+        comparer = comparer ?? EqualityComparer<T>.Default;
+
+        Stack<T> temp = new ();
+        var toMove = default (T);
+        int stackCount = stack.Count;
+        var count = 0;
+
+        while (stack.Count > 0)
+        {
+            T value = stack.Pop ();
+
+            if (comparer.Equals (value, valueToMove))
+            {
+                toMove = value;
+
+                break;
+            }
+
+            temp.Push (value);
+            count++;
+        }
+
+        var idx = 0;
+
+        while (stack.Count < stackCount)
+        {
+            if (count - idx == index)
+            {
+                stack.Push (toMove);
+            }
+            else
+            {
+                stack.Push (temp.Pop ());
+            }
+
+            idx++;
+        }
+    }
+
+    /// <summary>Replaces an stack object values that match with the value to replace.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    /// <param name="valueToReplace">Value to replace.</param>
+    /// <param name="valueToReplaceWith">Value to replace with to what matches the value to replace.</param>
+    /// <param name="comparer">The comparison object.</param>
+    public static void Replace<T> (
+        this Stack<T> stack,
+        T valueToReplace,
+        T valueToReplaceWith,
+        IEqualityComparer<T> comparer = null
+    )
+    {
+        comparer = comparer ?? EqualityComparer<T>.Default;
+
+        Stack<T> temp = new ();
+
+        while (stack.Count > 0)
+        {
+            T value = stack.Pop ();
+
+            if (comparer.Equals (value, valueToReplace))
+            {
+                stack.Push (valueToReplaceWith);
+
+                break;
+            }
+
+            temp.Push (value);
+        }
+
+        while (temp.Count > 0)
+        {
+            stack.Push (temp.Pop ());
+        }
+    }
+
+    /// <summary>Swap two stack objects values that matches with the both values.</summary>
+    /// <typeparam name="T">The stack object type.</typeparam>
+    /// <param name="stack">The stack object.</param>
+    /// <param name="valueToSwapFrom">Value to swap from.</param>
+    /// <param name="valueToSwapTo">Value to swap to.</param>
+    /// <param name="comparer">The comparison object.</param>
+    public static void Swap<T> (
+        this Stack<T> stack,
+        T valueToSwapFrom,
+        T valueToSwapTo,
+        IEqualityComparer<T> comparer = null
+    )
+    {
+        comparer = comparer ?? EqualityComparer<T>.Default;
+
+        int index = stack.Count - 1;
+        T [] stackArr = new T [stack.Count];
+
+        while (stack.Count > 0)
+        {
+            T value = stack.Pop ();
+
+            if (comparer.Equals (value, valueToSwapFrom))
+            {
+                stackArr [index] = valueToSwapTo;
+            }
+            else if (comparer.Equals (value, valueToSwapTo))
+            {
+                stackArr [index] = valueToSwapFrom;
+            }
+            else
+            {
+                stackArr [index] = value;
+            }
+
+            index--;
+        }
+
+        for (var i = 0; i < stackArr.Length; i++)
+        {
+            stack.Push (stackArr [i]);
+        }
+    }
 }

+ 4 - 0
Terminal.Gui/Terminal.Gui.csproj.DotSettings

@@ -0,0 +1,4 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeEditing/Localization/Localizable/@EntryValue">Yes</s:String>
+	<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp120</s:String>
+</wpf:ResourceDictionary>

+ 196 - 181
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -1,182 +1,197 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Autocomplete for a <see cref="TextField"/> which shows suggestions within the box.
-	/// Displayed suggestions can be completed using the tab key.
-	/// </summary>
-	public class AppendAutocomplete : AutocompleteBase {
-
-		private TextField textField;
-
-		/// <inheritdoc/>
-		public override View HostControl { get => textField; set => textField = (TextField)value; }
-
-		/// <summary>
-		/// The color used for rendering the appended text. Note that only
-		/// <see cref="ColorScheme.Normal"/> is used and then only <see cref="Attribute.Foreground"/>
-		/// (Background comes from <see cref="HostControl"/>).
-		/// </summary>
-		public override ColorScheme ColorScheme { get; set; }
-
-		/// <summary>
-		///	Creates a new instance of the <see cref="AppendAutocomplete"/> class.
-		/// </summary>
-		public AppendAutocomplete (TextField textField)
-		{
-			this.textField = textField;
-			SelectionKey = KeyCode.Tab;
-
-			ColorScheme = new ColorScheme {
-				Normal = new Attribute (Color.DarkGray, Color.Black),
-				Focus = new Attribute (Color.DarkGray, Color.Black),
-				HotNormal = new Attribute (Color.DarkGray, Color.Black),
-				HotFocus = new Attribute (Color.DarkGray, Color.Black),
-				Disabled = new Attribute (Color.DarkGray, Color.Black),
-			};
-		}
-
-		/// <inheritdoc/>
-		public override void ClearSuggestions ()
-		{
-			base.ClearSuggestions ();
-			textField.SetNeedsDisplay ();
-		}
-
-		/// <inheritdoc/>
-		public override bool MouseEvent (MouseEvent me, bool fromHost = false)
-		{
-			return false;
-		}
-
-		/// <inheritdoc/>
-		public override bool ProcessKey (Key a)
-		{
-			var key = a.KeyCode;
-			if (key == SelectionKey) {
-				return this.AcceptSelectionIfAny ();
-			} else
-			if (key == KeyCode.CursorUp) {
-				return this.CycleSuggestion (1);
-			} else
-			if (key == KeyCode.CursorDown) {
-				return this.CycleSuggestion (-1);
-			} else if (key == CloseKey && Suggestions.Any ()) {
-				ClearSuggestions ();
-				_suspendSuggestions = true;
-				return true;
-			}
-
-			if (char.IsLetterOrDigit ((char)a)) {
-				_suspendSuggestions = false;
-			}
-
-			return false;
-		}
-		bool _suspendSuggestions = false;
-
-		/// <inheritdoc/>
-		public override void GenerateSuggestions (AutocompleteContext context)
-		{
-			if (_suspendSuggestions) {
-				_suspendSuggestions = false;
-				return;
-			}
-			base.GenerateSuggestions (context);
-		}
-
-		/// <summary>
-		/// Renders the current suggestion into the <see cref="TextField"/>
-		/// </summary>
-		public override void RenderOverlay (Point renderAt)
-		{
-			if (!this.MakingSuggestion ()) {
-				return;
-			}
-
-			// draw it like its selected even though its not
-			Application.Driver.SetAttribute (new Attribute (ColorScheme.Normal.Foreground, textField.ColorScheme.Focus.Background));
-			textField.Move (textField.Text.Length, 0);
-
-			var suggestion = this.Suggestions.ElementAt (this.SelectedIdx);
-			var fragment = suggestion.Replacement.Substring (suggestion.Remove);
-
-			int spaceAvailable = textField.Bounds.Width - textField.Text.GetColumns ();
-			int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ());
-
-			if (spaceAvailable < spaceRequired) {
-				fragment = new string (
-					fragment.TakeWhile (c => (spaceAvailable -= ((Rune)c).GetColumns ()) >= 0)
-					.ToArray ()
-				);
-			}
-
-			Application.Driver.AddStr (fragment);
-		}
-
-		/// <summary>
-		/// Accepts the current autocomplete suggestion displaying in the text box.
-		/// Returns true if a valid suggestion was being rendered and acceptable or
-		/// false if no suggestion was showing.
-		/// </summary>
-		/// <returns></returns>
-		internal bool AcceptSelectionIfAny ()
-		{
-			if (this.MakingSuggestion ()) {
-
-				var insert = this.Suggestions.ElementAt (this.SelectedIdx);
-				var newText = textField.Text;
-				newText = newText.Substring (0, newText.Length - insert.Remove);
-				newText += insert.Replacement;
-				textField.Text = newText;
-
-				this.textField.MoveEnd ();
-
-				this.ClearSuggestions ();
-				return true;
-			}
-
-			return false;
-		}
-
-		internal void SetTextTo (FileSystemInfo fileSystemInfo)
-		{
-			var newText = fileSystemInfo.FullName;
-			if (fileSystemInfo is DirectoryInfo) {
-				newText += System.IO.Path.DirectorySeparatorChar;
-			}
-			textField.Text = newText;
-			textField.MoveEnd ();
-		}
-
-		/// <summary>
-		/// Returns true if there is a suggestion that can be made and the control
-		/// is in a state where user would expect to see auto-complete (i.e. focused and
-		/// cursor in right place).
-		/// </summary>
-		/// <returns></returns>
-		private bool MakingSuggestion ()
-		{
-			return Suggestions.Any () && this.SelectedIdx != -1 && textField.HasFocus && textField.CursorIsAtEnd ();
-		}
-
-		private bool CycleSuggestion (int direction)
-		{
-			if (this.Suggestions.Count <= 1) {
-				return false;
-			}
-
-			this.SelectedIdx = (this.SelectedIdx + direction) % this.Suggestions.Count;
-
-			if (this.SelectedIdx < 0) {
-				this.SelectedIdx = this.Suggestions.Count () - 1;
-			}
-			textField.SetNeedsDisplay ();
-			return true;
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>
+///     Autocomplete for a <see cref="TextField"/> which shows suggestions within the box. Displayed suggestions can
+///     be completed using the tab key.
+/// </summary>
+public class AppendAutocomplete : AutocompleteBase
+{
+    private bool _suspendSuggestions;
+    private TextField textField;
+
+    /// <summary>Creates a new instance of the <see cref="AppendAutocomplete"/> class.</summary>
+    public AppendAutocomplete (TextField textField)
+    {
+        this.textField = textField;
+        SelectionKey = KeyCode.Tab;
+
+        ColorScheme = new ColorScheme
+        {
+            Normal = new Attribute (Color.DarkGray, Color.Black),
+            Focus = new Attribute (Color.DarkGray, Color.Black),
+            HotNormal = new Attribute (Color.DarkGray, Color.Black),
+            HotFocus = new Attribute (Color.DarkGray, Color.Black),
+            Disabled = new Attribute (Color.DarkGray, Color.Black)
+        };
+    }
+
+    /// <summary>
+    ///     The color used for rendering the appended text. Note that only <see cref="ColorScheme.Normal"/> is used and
+    ///     then only <see cref="Attribute.Foreground"/> (Background comes from <see cref="HostControl"/>).
+    /// </summary>
+    public override ColorScheme ColorScheme { get; set; }
+
+    /// <inheritdoc/>
+    public override View HostControl
+    {
+        get => textField;
+        set => textField = (TextField)value;
+    }
+
+    /// <inheritdoc/>
+    public override void ClearSuggestions ()
+    {
+        base.ClearSuggestions ();
+        textField.SetNeedsDisplay ();
+    }
+
+    /// <inheritdoc/>
+    public override void GenerateSuggestions (AutocompleteContext context)
+    {
+        if (_suspendSuggestions)
+        {
+            _suspendSuggestions = false;
+
+            return;
+        }
+
+        base.GenerateSuggestions (context);
+    }
+
+    /// <inheritdoc/>
+    public override bool MouseEvent (MouseEvent me, bool fromHost = false) { return false; }
+
+    /// <inheritdoc/>
+    public override bool ProcessKey (Key a)
+    {
+        KeyCode key = a.KeyCode;
+
+        if (key == SelectionKey)
+        {
+            return AcceptSelectionIfAny ();
+        }
+
+        if (key == KeyCode.CursorUp)
+        {
+            return CycleSuggestion (1);
+        }
+
+        if (key == KeyCode.CursorDown)
+        {
+            return CycleSuggestion (-1);
+        }
+
+        if (key == CloseKey && Suggestions.Any ())
+        {
+            ClearSuggestions ();
+            _suspendSuggestions = true;
+
+            return true;
+        }
+
+        if (char.IsLetterOrDigit ((char)a))
+        {
+            _suspendSuggestions = false;
+        }
+
+        return false;
+    }
+
+    /// <summary>Renders the current suggestion into the <see cref="TextField"/></summary>
+    public override void RenderOverlay (Point renderAt)
+    {
+        if (!MakingSuggestion ())
+        {
+            return;
+        }
+
+        // draw it like its selected even though its not
+        Application.Driver.SetAttribute (
+                                         new Attribute (
+                                                        ColorScheme.Normal.Foreground,
+                                                        textField.ColorScheme.Focus.Background
+                                                       )
+                                        );
+        textField.Move (textField.Text.Length, 0);
+
+        Suggestion suggestion = Suggestions.ElementAt (SelectedIdx);
+        string fragment = suggestion.Replacement.Substring (suggestion.Remove);
+
+        int spaceAvailable = textField.Bounds.Width - textField.Text.GetColumns ();
+        int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ());
+
+        if (spaceAvailable < spaceRequired)
+        {
+            fragment = new string (
+                                   fragment.TakeWhile (c => (spaceAvailable -= ((Rune)c).GetColumns ()) >= 0)
+                                           .ToArray ()
+                                  );
+        }
+
+        Application.Driver.AddStr (fragment);
+    }
+
+    /// <summary>
+    ///     Accepts the current autocomplete suggestion displaying in the text box. Returns true if a valid suggestion was
+    ///     being rendered and acceptable or false if no suggestion was showing.
+    /// </summary>
+    /// <returns></returns>
+    internal bool AcceptSelectionIfAny ()
+    {
+        if (MakingSuggestion ())
+        {
+            Suggestion insert = Suggestions.ElementAt (SelectedIdx);
+            string newText = textField.Text;
+            newText = newText.Substring (0, newText.Length - insert.Remove);
+            newText += insert.Replacement;
+            textField.Text = newText;
+
+            textField.MoveEnd ();
+
+            ClearSuggestions ();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    internal void SetTextTo (FileSystemInfo fileSystemInfo)
+    {
+        string newText = fileSystemInfo.FullName;
+
+        if (fileSystemInfo is DirectoryInfo)
+        {
+            newText += Path.DirectorySeparatorChar;
+        }
+
+        textField.Text = newText;
+        textField.MoveEnd ();
+    }
+
+    private bool CycleSuggestion (int direction)
+    {
+        if (Suggestions.Count <= 1)
+        {
+            return false;
+        }
+
+        SelectedIdx = (SelectedIdx + direction) % Suggestions.Count;
+
+        if (SelectedIdx < 0)
+        {
+            SelectedIdx = Suggestions.Count () - 1;
+        }
+
+        textField.SetNeedsDisplay ();
+
+        return true;
+    }
+
+    /// <summary>
+    ///     Returns true if there is a suggestion that can be made and the control is in a state where user would expect
+    ///     to see auto-complete (i.e. focused and cursor in right place).
+    /// </summary>
+    /// <returns></returns>
+    private bool MakingSuggestion () { return Suggestions.Any () && SelectedIdx != -1 && textField.HasFocus && textField.CursorIsAtEnd (); }
 }

+ 55 - 66
Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs

@@ -1,88 +1,77 @@
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
+using System.Collections.ObjectModel;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
-	/// <summary>
-	/// Abstract implementation of <see cref="IAutocomplete"/> allows
-	/// for tailoring how autocomplete is rendered/interacted with.
-	/// </summary>
-	public abstract class AutocompleteBase : IAutocomplete {
+/// <summary>
+///     Abstract implementation of <see cref="IAutocomplete"/> allows for tailoring how autocomplete is
+///     rendered/interacted with.
+/// </summary>
+public abstract class AutocompleteBase : IAutocomplete
+{
+    /// <inheritdoc/>
+    public abstract View HostControl { get; set; }
 
-		/// <inheritdoc/>
-		public abstract View HostControl { get; set; }
-		/// <inheritdoc/>
-		public bool PopupInsideContainer { get; set; }
+    /// <inheritdoc/>
+    public bool PopupInsideContainer { get; set; }
 
-		/// <inheritdoc/>
-		public ISuggestionGenerator SuggestionGenerator { get; set; } = new SingleWordSuggestionGenerator ();
+    /// <inheritdoc/>
+    public ISuggestionGenerator SuggestionGenerator { get; set; } = new SingleWordSuggestionGenerator ();
 
-		/// <inheritdoc/>
-		public virtual int MaxWidth { get; set; } = 10;
+    /// <inheritdoc/>
+    public virtual int MaxWidth { get; set; } = 10;
 
-		/// <inheritdoc/>
-		public virtual int MaxHeight { get; set; } = 6;
+    /// <inheritdoc/>
+    public virtual int MaxHeight { get; set; } = 6;
 
-		/// <inheritdoc/>
+    /// <inheritdoc/>
+    /// <inheritdoc/>
+    public virtual bool Visible { get; set; }
 
-		/// <inheritdoc/>
-		public virtual bool Visible { get; set; }
+    /// <inheritdoc/>
+    public virtual ReadOnlyCollection<Suggestion> Suggestions { get; set; } = new (new Suggestion [0]);
 
-		/// <inheritdoc/>
-		public virtual ReadOnlyCollection<Suggestion> Suggestions { get; set; } = new ReadOnlyCollection<Suggestion> (new Suggestion [0]);
+    /// <inheritdoc/>
+    public virtual int SelectedIdx { get; set; }
 
+    /// <inheritdoc/>
+    public abstract ColorScheme ColorScheme { get; set; }
 
-		/// <inheritdoc/>
-		public virtual int SelectedIdx { get; set; }
+    // TODO: Update to use Key instead of KeyCode
+    /// <inheritdoc/>
+    public virtual KeyCode SelectionKey { get; set; } = KeyCode.Enter;
 
-		/// <inheritdoc/>
-		public abstract ColorScheme ColorScheme { get; set; }
+    // TODO: Update to use Key instead of KeyCode
+    /// <inheritdoc/>
+    public virtual KeyCode CloseKey { get; set; } = KeyCode.Esc;
 
-		// TODO: Update to use Key instead of KeyCode
-		/// <inheritdoc/>
-		public virtual KeyCode SelectionKey { get; set; } = KeyCode.Enter;
+    // TODO: Update to use Key instead of KeyCode
+    /// <inheritdoc/>
+    public virtual KeyCode Reopen { get; set; } = KeyCode.Space | KeyCode.CtrlMask | KeyCode.AltMask;
 
-		// TODO: Update to use Key instead of KeyCode
-		/// <inheritdoc/>
-		public virtual KeyCode CloseKey { get; set; } = KeyCode.Esc;
+    /// <inheritdoc/>
+    public virtual AutocompleteContext Context { get; set; }
 
-		// TODO: Update to use Key instead of KeyCode
-		/// <inheritdoc/>
-		public virtual KeyCode Reopen { get; set; } = KeyCode.Space | KeyCode.CtrlMask | KeyCode.AltMask;
+    /// <inheritdoc/>
+    public abstract bool MouseEvent (MouseEvent me, bool fromHost = false);
 
-		/// <inheritdoc/>
-		public virtual AutocompleteContext Context { get; set; }
+    /// <inheritdoc/>
+    public abstract bool ProcessKey (Key a);
 
-		/// <inheritdoc/>
-		public abstract bool MouseEvent (MouseEvent me, bool fromHost = false);
+    /// <inheritdoc/>
+    public abstract void RenderOverlay (Point renderAt);
 
-		/// <inheritdoc/>
-		public abstract bool ProcessKey (Key a);
-		/// <inheritdoc/>
-		public abstract void RenderOverlay (Point renderAt);
+    /// <inheritdoc/>
+    /// >
+    public virtual void ClearSuggestions () { Suggestions = Enumerable.Empty<Suggestion> ().ToList ().AsReadOnly (); }
 
-		/// <inheritdoc/>>
-		public virtual void ClearSuggestions ()
-		{
-			Suggestions = Enumerable.Empty<Suggestion> ().ToList ().AsReadOnly ();
-		}
+    /// <inheritdoc/>
+    public virtual void GenerateSuggestions (AutocompleteContext context)
+    {
+        Suggestions = SuggestionGenerator.GenerateSuggestions (context).ToList ().AsReadOnly ();
 
-		/// <inheritdoc/>
-		public virtual void GenerateSuggestions (AutocompleteContext context)
-		{
-			Suggestions = SuggestionGenerator.GenerateSuggestions (context).ToList ().AsReadOnly ();
+        EnsureSelectedIdxIsValid ();
+    }
 
-			EnsureSelectedIdxIsValid ();
-		}
-
-		/// <summary>
-		/// Updates <see cref="SelectedIdx"/> to be a valid index within <see cref="Suggestions"/>
-		/// </summary>
-		public virtual void EnsureSelectedIdxIsValid ()
-		{
-			SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx));
-		}
-	}
+    /// <summary>Updates <see cref="SelectedIdx"/> to be a valid index within <see cref="Suggestions"/></summary>
+    public virtual void EnsureSelectedIdxIsValid () { SelectedIdx = Math.Max (0, Math.Min (Suggestions.Count - 1, SelectedIdx)); }
 }
-

+ 20 - 31
Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs

@@ -1,36 +1,25 @@
-using System.Collections.Generic;
-using System.Text;
+namespace Terminal.Gui;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Describes the current state of a <see cref="View"/> which
-	/// is proposing autocomplete. Suggestions are based on this state.
-	/// </summary>
-	public class AutocompleteContext
-	{
-		/// <summary>
-		/// The text on the current line.
-		/// </summary>
-		public List<RuneCell> CurrentLine { get; set; }
+/// <summary>
+///     Describes the current state of a <see cref="View"/> which is proposing autocomplete. Suggestions are based on
+///     this state.
+/// </summary>
+public class AutocompleteContext
+{
+    /// <summary>Creates a new instance of the <see cref="AutocompleteContext"/> class</summary>
+    public AutocompleteContext (List<RuneCell> currentLine, int cursorPosition, bool canceled = false)
+    {
+        CurrentLine = currentLine;
+        CursorPosition = cursorPosition;
+        Canceled = canceled;
+    }
 
-		/// <summary>
-		/// The position of the input cursor within the <see cref="CurrentLine"/>.
-		/// </summary>
-		public int CursorPosition { get; set; }
+    /// <summary>Gets or sets if the autocomplete was canceled from popup.</summary>
+    public bool Canceled { get; set; }
 
-		/// <summary>
-		/// Gets or sets if the autocomplete was canceled from popup.
-		/// </summary>
-		public bool Canceled { get; set; }
+    /// <summary>The text on the current line.</summary>
+    public List<RuneCell> CurrentLine { get; set; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="AutocompleteContext"/> class
-		/// </summary>
-		public AutocompleteContext (List<RuneCell> currentLine, int cursorPosition, bool canceled = false)
-		{
-			CurrentLine = currentLine;
-			CursorPosition = cursorPosition;
-			Canceled = canceled;
-		}
-	}
+    /// <summary>The position of the input cursor within the <see cref="CurrentLine"/>.</summary>
+    public int CursorPosition { get; set; }
 }

+ 87 - 122
Terminal.Gui/Text/Autocomplete/IAutocomplete.cs

@@ -1,123 +1,88 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Renders an overlay on another view at a given point that allows selecting
-	/// from a range of 'autocomplete' options.
-	/// </summary>
-	public interface IAutocomplete {
-
-		/// <summary>
-		/// The host control that will use autocomplete.
-		/// </summary>
-		View HostControl { get; set; }
-
-		/// <summary>
-		/// Gets or sets where the popup will be displayed.
-		/// </summary>
-		bool PopupInsideContainer { get; set; }
-
-		/// <summary>
-		/// The maximum width of the autocomplete dropdown
-		/// </summary>
-		int MaxWidth { get; set; }
-
-		/// <summary>
-		/// The maximum number of visible rows in the autocomplete dropdown to render
-		/// </summary>
-		int MaxHeight { get; set; }
-
-		/// <summary>
-		/// True if the autocomplete should be considered open and visible
-		/// </summary>
-		bool Visible { get; set; }
-
-		/// <summary>
-		/// The strings that form the current list of suggestions to render
-		/// based on what the user has typed so far.
-		/// </summary>
-		ReadOnlyCollection<Suggestion> Suggestions { get; set; }
-
-		/// <summary>
-		/// The currently selected index into <see cref="Suggestions"/> that the user has highlighted
-		/// </summary>
-		int SelectedIdx { get; set; }
-
-		/// <summary>
-		/// The colors to use to render the overlay. Accessing this property before
-		/// the Application has been initialized will cause an error
-		/// </summary>
-		ColorScheme ColorScheme { get; set; }
-
-		// TODO: Update to use Key instead of KeyCode
-		/// <summary>
-		/// The key that the user must press to accept the currently selected autocomplete suggestion
-		/// </summary>
-		KeyCode SelectionKey { get; set; }
-
-		// TODO: Update to use Key instead of KeyCode
-		/// <summary>
-		/// The key that the user can press to close the currently popped autocomplete menu
-		/// </summary>
-		KeyCode CloseKey { get; set; }
-
-		// TODO: Update to use Key instead of KeyCode
-		/// <summary>
-		/// The key that the user can press to reopen the currently popped autocomplete menu
-		/// </summary>
-		KeyCode Reopen { get; set; }
-
-		/// <summary>
-		/// The context used by the autocomplete menu.
-		/// </summary>
-		AutocompleteContext Context { get; set; }
-
-		/// <summary>
-		/// Renders the autocomplete dialog inside the given <see cref="HostControl"/> at the
-		/// given point.
-		/// </summary>
-		/// <param name="renderAt"></param>
-		void RenderOverlay (Point renderAt);
-
-		/// <summary>
-		/// Handle key events before <see cref="HostControl"/> e.g. to make key events like
-		/// up/down apply to the autocomplete control instead of changing the cursor position in
-		/// the underlying text view.
-		/// </summary>
-		/// <param name="a">The key event.</param>
-		/// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-		bool ProcessKey (Key a);
-
-		/// <summary>
-		/// Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like
-		/// report/click apply to the autocomplete control instead of changing the cursor position in
-		/// the underlying text view.
-		/// </summary>
-		/// <param name="me">The mouse event.</param>
-		/// <param name="fromHost">If was called from the popup or from the host.</param>
-		/// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
-		bool MouseEvent (MouseEvent me, bool fromHost = false);
-
-		/// <summary>
-		/// Clears <see cref="Suggestions"/>
-		/// </summary>
-		void ClearSuggestions ();
-
-		/// <summary>
-		/// Gets or Sets the class responsible for generating <see cref="Suggestions"/>
-		/// based on a given <see cref="AutocompleteContext"/> of the <see cref="HostControl"/>.
-		/// </summary>
-		ISuggestionGenerator SuggestionGenerator { get; set; }
-
-		/// <summary>
-		/// Populates <see cref="Suggestions"/> with all <see cref="Suggestion"/> 
-		/// proposed by <see cref="SuggestionGenerator"/> at the given <paramref name="context"/>
-		/// (cursor position)
-		/// </summary>
-		void GenerateSuggestions (AutocompleteContext context);
-	}
+using System.Collections.ObjectModel;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
+///     options.
+/// </summary>
+public interface IAutocomplete
+{
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>The key that the user can press to close the currently popped autocomplete menu</summary>
+    KeyCode CloseKey { get; set; }
+
+    /// <summary>
+    ///     The colors to use to render the overlay. Accessing this property before the Application has been initialized
+    ///     will cause an error
+    /// </summary>
+    ColorScheme ColorScheme { get; set; }
+
+    /// <summary>The context used by the autocomplete menu.</summary>
+    AutocompleteContext Context { get; set; }
+
+    /// <summary>The host control that will use autocomplete.</summary>
+    View HostControl { get; set; }
+
+    /// <summary>The maximum number of visible rows in the autocomplete dropdown to render</summary>
+    int MaxHeight { get; set; }
+
+    /// <summary>The maximum width of the autocomplete dropdown</summary>
+    int MaxWidth { get; set; }
+
+    /// <summary>Gets or sets where the popup will be displayed.</summary>
+    bool PopupInsideContainer { get; set; }
+
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>The key that the user can press to reopen the currently popped autocomplete menu</summary>
+    KeyCode Reopen { get; set; }
+
+    /// <summary>The currently selected index into <see cref="Suggestions"/> that the user has highlighted</summary>
+    int SelectedIdx { get; set; }
+
+    // TODO: Update to use Key instead of KeyCode
+    /// <summary>The key that the user must press to accept the currently selected autocomplete suggestion</summary>
+    KeyCode SelectionKey { get; set; }
+
+    /// <summary>
+    ///     Gets or Sets the class responsible for generating <see cref="Suggestions"/> based on a given
+    ///     <see cref="AutocompleteContext"/> of the <see cref="HostControl"/>.
+    /// </summary>
+    ISuggestionGenerator SuggestionGenerator { get; set; }
+
+    /// <summary>The strings that form the current list of suggestions to render based on what the user has typed so far.</summary>
+    ReadOnlyCollection<Suggestion> Suggestions { get; set; }
+
+    /// <summary>True if the autocomplete should be considered open and visible</summary>
+    bool Visible { get; set; }
+
+    /// <summary>Clears <see cref="Suggestions"/></summary>
+    void ClearSuggestions ();
+
+    /// <summary>
+    ///     Populates <see cref="Suggestions"/> with all <see cref="Suggestion"/> proposed by
+    ///     <see cref="SuggestionGenerator"/> at the given <paramref name="context"/> (cursor position)
+    /// </summary>
+    void GenerateSuggestions (AutocompleteContext context);
+
+    /// <summary>
+    ///     Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like report/click apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="me">The mouse event.</param>
+    /// <param name="fromHost">If was called from the popup or from the host.</param>
+    /// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
+    bool MouseEvent (MouseEvent me, bool fromHost = false);
+
+    /// <summary>
+    ///     Handle key events before <see cref="HostControl"/> e.g. to make key events like up/down apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="a">The key event.</param>
+    /// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
+    bool ProcessKey (Key a);
+
+    /// <summary>Renders the autocomplete dialog inside the given <see cref="HostControl"/> at the given point.</summary>
+    /// <param name="renderAt"></param>
+    void RenderOverlay (Point renderAt);
 }

+ 14 - 23
Terminal.Gui/Text/Autocomplete/ISuggestionGenerator.cs

@@ -1,24 +1,15 @@
-using System.Collections.Generic;
-using System.Text;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Generates autocomplete <see cref="Suggestion"/> based on a given cursor location within a string
-	/// </summary>
-	public interface ISuggestionGenerator {
-
-		/// <summary>
-		/// Generates autocomplete <see cref="Suggestion"/> based on a given <paramref name="context"/>
-		/// </summary>
-		IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context);
-
-		/// <summary>
-		/// Returns <see langword="true"/> if <paramref name="rune"/> is a character that
-		/// would continue autocomplete suggesting. Returns <see langword="false"/> if it
-		/// is a 'breaking' character (i.e. terminating current word boundary)
-		/// </summary>
-		bool IsWordChar (Rune rune);
-
-	}
+namespace Terminal.Gui;
+
+/// <summary>Generates autocomplete <see cref="Suggestion"/> based on a given cursor location within a string</summary>
+public interface ISuggestionGenerator
+{
+    /// <summary>Generates autocomplete <see cref="Suggestion"/> based on a given <paramref name="context"/></summary>
+    IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context);
+
+    /// <summary>
+    ///     Returns <see langword="true"/> if <paramref name="rune"/> is a character that would continue autocomplete
+    ///     suggesting. Returns <see langword="false"/> if it is a 'breaking' character (i.e. terminating current word
+    ///     boundary)
+    /// </summary>
+    bool IsWordChar (Rune rune);
 }
-

+ 578 - 518
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -1,519 +1,579 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// Renders an overlay on another view at a given point that allows selecting
-	/// from a range of 'autocomplete' options.
-	/// </summary>
-	public abstract class PopupAutocomplete : AutocompleteBase {
-
-		private class Popup : View {
-			PopupAutocomplete autocomplete;
-
-			public Popup (PopupAutocomplete autocomplete)
-			{
-				this.autocomplete = autocomplete;
-				CanFocus = true;
-				WantMousePositionReports = true;
-			}
-
-			public override void OnDrawContent (Rect contentArea)
-			{
-				if (autocomplete.LastPopupPos == null) {
-					return;
-				}
-
-				autocomplete.RenderOverlay ((Point)autocomplete.LastPopupPos);
-			}
-
-
-			public override bool MouseEvent (MouseEvent mouseEvent)
-			{
-				return autocomplete.MouseEvent (mouseEvent);
-			}
-		}
-
-		private View top, popup;
-		private bool closed;
-		int toRenderLength;
-
-		private Point? LastPopupPos { get; set; }
-
-		private ColorScheme colorScheme;
-		private View hostControl;
-
-		/// <summary>
-		/// The host control to handle.
-		/// </summary>
-		public override View HostControl {
-			get => hostControl;
-			set {
-				hostControl = value;
-				top = hostControl.SuperView;
-				if (top != null) {
-					top.DrawContent += Top_DrawContent;
-					top.DrawContentComplete += Top_DrawContentComplete;
-					top.Removed += Top_Removed;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Creates a new instance of the <see cref="PopupAutocomplete"/> class.
-		/// </summary>
-		public PopupAutocomplete ()
-		{
-			PopupInsideContainer = true;
-		}
-
-		private void Top_Removed (object sender, SuperViewChangedEventArgs e)
-		{
-			Visible = false;
-			ManipulatePopup ();
-		}
-
-		private void Top_DrawContentComplete (object sender, DrawEventArgs e)
-		{
-			ManipulatePopup ();
-		}
-
-		private void Top_DrawContent (object sender, DrawEventArgs e)
-		{
-			if (!closed) {
-				ReopenSuggestions ();
-			}
-			ManipulatePopup ();
-			if (Visible) {
-				top.BringSubviewToFront (popup);
-			}
-		}
-
-		private void ManipulatePopup ()
-		{
-			if (Visible && popup == null) {
-				popup = new Popup (this) {
-					Frame = Rect.Empty
-				};
-				top?.Add (popup);
-			}
-
-			if (!Visible && popup != null) {
-				top?.Remove (popup);
-				popup.Dispose ();
-				popup = null;
-			}
-		}
-
-		/// <summary>
-		/// When more suggestions are available than can be rendered the user
-		/// can scroll down the dropdown list. This indicates how far down they
-		/// have gone
-		/// </summary>
-		public virtual int ScrollOffset { get; set; }
-
-		/// <summary>
-		/// The colors to use to render the overlay. Accessing this property before
-		/// the Application has been initialized will cause an error
-		/// </summary>
-		public override ColorScheme ColorScheme {
-			get {
-				if (colorScheme == null) {
-					colorScheme = Colors.ColorSchemes ["Menu"];
-				}
-				return colorScheme;
-			}
-			set {
-				colorScheme = value;
-			}
-		}
-
-		/// <summary>
-		/// Renders the autocomplete dialog inside or outside the given <see cref="HostControl"/> at the
-		/// given point.
-		/// </summary>
-		/// <param name="renderAt"></param>
-		public override void RenderOverlay (Point renderAt)
-		{
-			if (!Context.Canceled && Suggestions.Count > 0 && !Visible && HostControl?.HasFocus == true) {
-				ProcessKey (new ((KeyCode)(Suggestions [0].Title [0])));
-			} else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0) {
-				LastPopupPos = null;
-				Visible = false;
-				if (Suggestions.Count == 0) {
-					Context.Canceled = false;
-				}
-				return;
-			}
-
-			LastPopupPos = renderAt;
-
-			int height, width;
-
-			if (PopupInsideContainer) {
-				// don't overspill vertically
-				height = Math.Min (HostControl.Bounds.Height - renderAt.Y, MaxHeight);
-				// There is no space below, lets see if can popup on top
-				if (height < Suggestions.Count && HostControl.Bounds.Height - renderAt.Y >= height) {
-					// Verifies that the upper limit available is greater than the lower limit
-					if (renderAt.Y > HostControl.Bounds.Height - renderAt.Y) {
-						renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0);
-						height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
-					}
-				}
-			} else {
-				// don't overspill vertically
-				height = Math.Min (Math.Min (top.Bounds.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
-				// There is no space below, lets see if can popup on top
-				if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height) {
-					// Verifies that the upper limit available is greater than the lower limit
-					if (HostControl.Frame.Y > top.Bounds.Height - HostControl.Frame.Y) {
-						renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
-						height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
-					}
-				} else {
-					renderAt.Y = HostControl.Frame.Bottom;
-				}
-			}
-
-			if (ScrollOffset > Suggestions.Count - height) {
-				ScrollOffset = 0;
-			}
-			var toRender = Suggestions.Skip (ScrollOffset).Take (height).ToArray ();
-			toRenderLength = toRender.Length;
-
-			if (toRender.Length == 0) {
-				return;
-			}
-
-			width = Math.Min (MaxWidth, toRender.Max (s => s.Title.Length));
-
-			if (PopupInsideContainer) {
-				// don't overspill horizontally, let's see if can be displayed on the left
-				if (width > HostControl.Bounds.Width - renderAt.X) {
-					// Verifies that the left limit available is greater than the right limit
-					if (renderAt.X > HostControl.Bounds.Width - renderAt.X) {
-						renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
-						width = Math.Min (width, LastPopupPos.Value.X);
-					} else {
-						width = Math.Min (width, HostControl.Bounds.Width - renderAt.X);
-					}
-				}
-			} else {
-				// don't overspill horizontally, let's see if can be displayed on the left
-				if (width > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) {
-					// Verifies that the left limit available is greater than the right limit
-					if (renderAt.X + HostControl.Frame.X > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) {
-						renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
-						width = Math.Min (width, LastPopupPos.Value.X);
-					} else {
-						width = Math.Min (width, top.Bounds.Width - renderAt.X);
-					}
-				}
-			}
-
-			if (PopupInsideContainer) {
-				popup.Frame = new Rect (
-					new Point (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
-					new Size (width, height));
-			} else {
-				popup.Frame = new Rect (
-					new Point (HostControl.Frame.X + renderAt.X, renderAt.Y),
-					new Size (width, height));
-			}
-
-			popup.Move (0, 0);
-
-			for (int i = 0; i < toRender.Length; i++) {
-
-				if (i == SelectedIdx - ScrollOffset) {
-					Application.Driver.SetAttribute (ColorScheme.Focus);
-				} else {
-					Application.Driver.SetAttribute (ColorScheme.Normal);
-				}
-
-				popup.Move (0, i);
-
-				var text = TextFormatter.ClipOrPad (toRender [i].Title, width);
-
-				Application.Driver.AddStr (text);
-			}
-		}
-
-		/// <inheritdoc/>
-		public override void EnsureSelectedIdxIsValid ()
-		{
-			base.EnsureSelectedIdxIsValid ();
-
-			// if user moved selection up off top of current scroll window
-			if (SelectedIdx < ScrollOffset) {
-				ScrollOffset = SelectedIdx;
-			}
-
-			// if user moved selection down past bottom of current scroll window
-			while (toRenderLength > 0 && SelectedIdx >= ScrollOffset + toRenderLength) {
-				ScrollOffset++;
-			}
-		}
-		/// <summary>
-		/// Handle key events before <see cref="HostControl"/> e.g. to make key events like
-		/// up/down apply to the autocomplete control instead of changing the cursor position in
-		/// the underlying text view.
-		/// </summary>
-		/// <param name="a">The key event.</param>
-		/// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
-		public override bool ProcessKey (Key a)
-		{
-			if (SuggestionGenerator.IsWordChar ((Rune)a)) {
-				Visible = true;
-				ManipulatePopup ();
-				closed = false;
-				return false;
-			}
-
-			if (a.KeyCode == Reopen) {
-				Context.Canceled = false;
-				return ReopenSuggestions ();
-			}
-
-			if (closed || Suggestions.Count == 0) {
-				Visible = false;
-				if (!closed) {
-					Close ();
-				}
-				return false;
-			}
-
-			if (a.KeyCode == KeyCode.CursorDown) {
-				MoveDown ();
-				return true;
-			}
-
-			if (a.KeyCode == KeyCode.CursorUp) {
-				MoveUp ();
-				return true;
-			}
-
-			// TODO : Revisit this
-			/*if (a.ConsoleDriverKey == Key.CursorLeft || a.ConsoleDriverKey == Key.CursorRight) {
-				GenerateSuggestions (a.ConsoleDriverKey == Key.CursorLeft ? -1 : 1);
-				if (Suggestions.Count == 0) {
-					Visible = false;
-					if (!closed) {
-						Close ();
-					}
-				}
-				return false;
-			}*/
-
-			if (a.KeyCode == SelectionKey) {
-				return Select ();
-			}
-
-			if (a.KeyCode == CloseKey) {
-				Close ();
-				Context.Canceled = true;
-				return true;
-			}
-
-			return false;
-		}
-
-		/// <summary>
-		/// Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like
-		/// report/click apply to the autocomplete control instead of changing the cursor position in
-		/// the underlying text view.
-		/// </summary>
-		/// <param name="me">The mouse event.</param>
-		/// <param name="fromHost">If was called from the popup or from the host.</param>
-		/// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
-		public override bool MouseEvent (MouseEvent me, bool fromHost = false)
-		{
-			if (fromHost) {
-				if (!Visible) {
-					return false;
-				}
-
-				// TODO: Revisit this
-				//GenerateSuggestions ();
-
-				if (Visible && Suggestions.Count == 0) {
-					Visible = false;
-					HostControl?.SetNeedsDisplay ();
-					return true;
-				} else if (!Visible && Suggestions.Count > 0) {
-					Visible = true;
-					HostControl?.SetNeedsDisplay ();
-					Application.UngrabMouse ();
-					return false;
-				} else {
-					// not in the popup
-					if (Visible && HostControl != null) {
-						Visible = false;
-						closed = false;
-					}
-					HostControl?.SetNeedsDisplay ();
-				}
-				return false;
-			}
-
-			if (popup == null || Suggestions.Count == 0) {
-				ManipulatePopup ();
-				return false;
-			}
-
-			if (me.Flags == MouseFlags.ReportMousePosition) {
-				RenderSelectedIdxByMouse (me);
-				return true;
-			}
-
-			if (me.Flags == MouseFlags.Button1Clicked) {
-				SelectedIdx = me.Y - ScrollOffset;
-				return Select ();
-			}
-
-			if (me.Flags == MouseFlags.WheeledDown) {
-				MoveDown ();
-				return true;
-			}
-
-			if (me.Flags == MouseFlags.WheeledUp) {
-				MoveUp ();
-				return true;
-			}
-
-			return false;
-		}
-
-		/// <summary>
-		/// Render the current selection in the Autocomplete context menu by the mouse reporting.
-		/// </summary>
-		/// <param name="me"></param>
-		protected void RenderSelectedIdxByMouse (MouseEvent me)
-		{
-			if (SelectedIdx != me.Y - ScrollOffset) {
-				SelectedIdx = me.Y - ScrollOffset;
-				if (LastPopupPos != null) {
-					RenderOverlay ((Point)LastPopupPos);
-				}
-			}
-		}
-
-
-		/// <summary>
-		/// Completes the autocomplete selection process. Called when user hits the <see cref="IAutocomplete.SelectionKey"/>.
-		/// </summary>
-		/// <returns></returns>
-		protected bool Select ()
-		{
-			if (SelectedIdx >= 0 && SelectedIdx < Suggestions.Count) {
-				var accepted = Suggestions [SelectedIdx];
-
-				return InsertSelection (accepted);
-
-			}
-
-			return false;
-		}
-
-		/// <summary>
-		/// Called when the user confirms a selection at the current cursor location in
-		/// the <see cref="HostControl"/>. The <paramref name="accepted"/> string
-		/// is the full autocomplete word to be inserted. Typically a host will have to
-		/// remove some characters such that the <paramref name="accepted"/> string 
-		/// completes the word instead of simply being appended.
-		/// </summary>
-		/// <param name="accepted"></param>
-		/// <returns>True if the insertion was possible otherwise false</returns>
-		protected virtual bool InsertSelection (Suggestion accepted)
-		{
-			SetCursorPosition (Context.CursorPosition + accepted.Remove);
-			// delete the text
-			for (int i = 0; i < accepted.Remove; i++) {
-				DeleteTextBackwards ();
-			}
-
-			InsertText (accepted.Replacement);
-			return true;
-		}
-
-		/// <summary>
-		/// Deletes the text backwards before insert the selected text in the <see cref="HostControl"/>.
-		/// </summary>
-		protected abstract void DeleteTextBackwards ();
-
-		/// <summary>
-		/// Insert the selected text in the <see cref="HostControl"/>.
-		/// </summary>
-		/// <param name="accepted"></param>
-		protected abstract void InsertText (string accepted);
-
-		/// <summary>
-		/// Set the cursor position in the <see cref="HostControl"/>.
-		/// </summary>
-		/// <param name="column"></param>
-		protected abstract void SetCursorPosition (int column);
-
-		/// <summary>
-		/// Closes the Autocomplete context menu if it is showing and <see cref="IAutocomplete.ClearSuggestions"/>
-		/// </summary>
-		protected void Close ()
-		{
-			ClearSuggestions ();
-			Visible = false;
-			closed = true;
-			HostControl?.SetNeedsDisplay ();
-			ManipulatePopup ();
-		}
-
-		/// <summary>
-		/// Moves the selection in the Autocomplete context menu up one
-		/// </summary>
-		protected void MoveUp ()
-		{
-			SelectedIdx--;
-			if (SelectedIdx < 0) {
-				SelectedIdx = Suggestions.Count - 1;
-			}
-			EnsureSelectedIdxIsValid ();
-			HostControl?.SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// Moves the selection in the Autocomplete context menu down one
-		/// </summary>
-		protected void MoveDown ()
-		{
-			SelectedIdx++;
-			if (SelectedIdx > Suggestions.Count - 1) {
-				SelectedIdx = 0;
-			}
-			EnsureSelectedIdxIsValid ();
-			HostControl?.SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// Reopen the popup after it has been closed.
-		/// </summary>
-		/// <returns></returns>
-		protected bool ReopenSuggestions ()
-		{
-			// TODO: Revisit
-			//GenerateSuggestions ();
-
-			if (Suggestions.Count > 0) {
-				Visible = true;
-				closed = false;
-				HostControl?.SetNeedsDisplay ();
-				return true;
-			}
-			return false;
-		}
-	}
+namespace Terminal.Gui;
+
+/// <summary>
+///     Renders an overlay on another view at a given point that allows selecting from a range of 'autocomplete'
+///     options.
+/// </summary>
+public abstract class PopupAutocomplete : AutocompleteBase
+{
+    private bool closed;
+    private ColorScheme colorScheme;
+    private View hostControl;
+    private View top, popup;
+    private int toRenderLength;
+
+    /// <summary>Creates a new instance of the <see cref="PopupAutocomplete"/> class.</summary>
+    public PopupAutocomplete () { PopupInsideContainer = true; }
+
+    /// <summary>
+    ///     The colors to use to render the overlay. Accessing this property before the Application has been initialized
+    ///     will cause an error
+    /// </summary>
+    public override ColorScheme ColorScheme
+    {
+        get
+        {
+            if (colorScheme == null)
+            {
+                colorScheme = Colors.ColorSchemes ["Menu"];
+            }
+
+            return colorScheme;
+        }
+        set => colorScheme = value;
+    }
+
+    /// <summary>The host control to handle.</summary>
+    public override View HostControl
+    {
+        get => hostControl;
+        set
+        {
+            hostControl = value;
+            top = hostControl.SuperView;
+
+            if (top != null)
+            {
+                top.DrawContent += Top_DrawContent;
+                top.DrawContentComplete += Top_DrawContentComplete;
+                top.Removed += Top_Removed;
+            }
+        }
+    }
+
+    /// <summary>
+    ///     When more suggestions are available than can be rendered the user can scroll down the dropdown list. This
+    ///     indicates how far down they have gone
+    /// </summary>
+    public virtual int ScrollOffset { get; set; }
+
+    private Point? LastPopupPos { get; set; }
+
+    /// <inheritdoc/>
+    public override void EnsureSelectedIdxIsValid ()
+    {
+        base.EnsureSelectedIdxIsValid ();
+
+        // if user moved selection up off top of current scroll window
+        if (SelectedIdx < ScrollOffset)
+        {
+            ScrollOffset = SelectedIdx;
+        }
+
+        // if user moved selection down past bottom of current scroll window
+        while (toRenderLength > 0 && SelectedIdx >= ScrollOffset + toRenderLength)
+        {
+            ScrollOffset++;
+        }
+    }
+
+    /// <summary>
+    ///     Handle mouse events before <see cref="HostControl"/> e.g. to make mouse events like report/click apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="me">The mouse event.</param>
+    /// <param name="fromHost">If was called from the popup or from the host.</param>
+    /// <returns><c>true</c>if the mouse can be handled <c>false</c>otherwise.</returns>
+    public override bool MouseEvent (MouseEvent me, bool fromHost = false)
+    {
+        if (fromHost)
+        {
+            if (!Visible)
+            {
+                return false;
+            }
+
+            // TODO: Revisit this
+            //GenerateSuggestions ();
+
+            if (Visible && Suggestions.Count == 0)
+            {
+                Visible = false;
+                HostControl?.SetNeedsDisplay ();
+
+                return true;
+            }
+
+            if (!Visible && Suggestions.Count > 0)
+            {
+                Visible = true;
+                HostControl?.SetNeedsDisplay ();
+                Application.UngrabMouse ();
+
+                return false;
+            }
+
+            // not in the popup
+            if (Visible && HostControl != null)
+            {
+                Visible = false;
+                closed = false;
+            }
+
+            HostControl?.SetNeedsDisplay ();
+
+            return false;
+        }
+
+        if (popup == null || Suggestions.Count == 0)
+        {
+            ManipulatePopup ();
+
+            return false;
+        }
+
+        if (me.Flags == MouseFlags.ReportMousePosition)
+        {
+            RenderSelectedIdxByMouse (me);
+
+            return true;
+        }
+
+        if (me.Flags == MouseFlags.Button1Clicked)
+        {
+            SelectedIdx = me.Y - ScrollOffset;
+
+            return Select ();
+        }
+
+        if (me.Flags == MouseFlags.WheeledDown)
+        {
+            MoveDown ();
+
+            return true;
+        }
+
+        if (me.Flags == MouseFlags.WheeledUp)
+        {
+            MoveUp ();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Handle key events before <see cref="HostControl"/> e.g. to make key events like up/down apply to the
+    ///     autocomplete control instead of changing the cursor position in the underlying text view.
+    /// </summary>
+    /// <param name="a">The key event.</param>
+    /// <returns><c>true</c>if the key can be handled <c>false</c>otherwise.</returns>
+    public override bool ProcessKey (Key a)
+    {
+        if (SuggestionGenerator.IsWordChar ((Rune)a))
+        {
+            Visible = true;
+            ManipulatePopup ();
+            closed = false;
+
+            return false;
+        }
+
+        if (a.KeyCode == Reopen)
+        {
+            Context.Canceled = false;
+
+            return ReopenSuggestions ();
+        }
+
+        if (closed || Suggestions.Count == 0)
+        {
+            Visible = false;
+
+            if (!closed)
+            {
+                Close ();
+            }
+
+            return false;
+        }
+
+        if (a.KeyCode == KeyCode.CursorDown)
+        {
+            MoveDown ();
+
+            return true;
+        }
+
+        if (a.KeyCode == KeyCode.CursorUp)
+        {
+            MoveUp ();
+
+            return true;
+        }
+
+        // TODO : Revisit this
+        /*if (a.ConsoleDriverKey == Key.CursorLeft || a.ConsoleDriverKey == Key.CursorRight) {
+            GenerateSuggestions (a.ConsoleDriverKey == Key.CursorLeft ? -1 : 1);
+            if (Suggestions.Count == 0) {
+                Visible = false;
+                if (!closed) {
+                    Close ();
+                }
+            }
+            return false;
+        }*/
+
+        if (a.KeyCode == SelectionKey)
+        {
+            return Select ();
+        }
+
+        if (a.KeyCode == CloseKey)
+        {
+            Close ();
+            Context.Canceled = true;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>Renders the autocomplete dialog inside or outside the given <see cref="HostControl"/> at the given point.</summary>
+    /// <param name="renderAt"></param>
+    public override void RenderOverlay (Point renderAt)
+    {
+        if (!Context.Canceled && Suggestions.Count > 0 && !Visible && HostControl?.HasFocus == true)
+        {
+            ProcessKey (new Key ((KeyCode)Suggestions [0].Title [0]));
+        }
+        else if (!Visible || HostControl?.HasFocus == false || Suggestions.Count == 0)
+        {
+            LastPopupPos = null;
+            Visible = false;
+
+            if (Suggestions.Count == 0)
+            {
+                Context.Canceled = false;
+            }
+
+            return;
+        }
+
+        LastPopupPos = renderAt;
+
+        int height, width;
+
+        if (PopupInsideContainer)
+        {
+            // don't overspill vertically
+            height = Math.Min (HostControl.Bounds.Height - renderAt.Y, MaxHeight);
+
+            // There is no space below, lets see if can popup on top
+            if (height < Suggestions.Count && HostControl.Bounds.Height - renderAt.Y >= height)
+            {
+                // Verifies that the upper limit available is greater than the lower limit
+                if (renderAt.Y > HostControl.Bounds.Height - renderAt.Y)
+                {
+                    renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0);
+                    height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1);
+                }
+            }
+        }
+        else
+        {
+            // don't overspill vertically
+            height = Math.Min (Math.Min (top.Bounds.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count);
+
+            // There is no space below, lets see if can popup on top
+            if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height)
+            {
+                // Verifies that the upper limit available is greater than the lower limit
+                if (HostControl.Frame.Y > top.Bounds.Height - HostControl.Frame.Y)
+                {
+                    renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0);
+                    height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y);
+                }
+            }
+            else
+            {
+                renderAt.Y = HostControl.Frame.Bottom;
+            }
+        }
+
+        if (ScrollOffset > Suggestions.Count - height)
+        {
+            ScrollOffset = 0;
+        }
+
+        Suggestion [] toRender = Suggestions.Skip (ScrollOffset).Take (height).ToArray ();
+        toRenderLength = toRender.Length;
+
+        if (toRender.Length == 0)
+        {
+            return;
+        }
+
+        width = Math.Min (MaxWidth, toRender.Max (s => s.Title.Length));
+
+        if (PopupInsideContainer)
+        {
+            // don't overspill horizontally, let's see if can be displayed on the left
+            if (width > HostControl.Bounds.Width - renderAt.X)
+            {
+                // Verifies that the left limit available is greater than the right limit
+                if (renderAt.X > HostControl.Bounds.Width - renderAt.X)
+                {
+                    renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
+                    width = Math.Min (width, LastPopupPos.Value.X);
+                }
+                else
+                {
+                    width = Math.Min (width, HostControl.Bounds.Width - renderAt.X);
+                }
+            }
+        }
+        else
+        {
+            // don't overspill horizontally, let's see if can be displayed on the left
+            if (width > top.Bounds.Width - (renderAt.X + HostControl.Frame.X))
+            {
+                // Verifies that the left limit available is greater than the right limit
+                if (renderAt.X + HostControl.Frame.X > top.Bounds.Width - (renderAt.X + HostControl.Frame.X))
+                {
+                    renderAt.X -= Math.Min (width, LastPopupPos.Value.X);
+                    width = Math.Min (width, LastPopupPos.Value.X);
+                }
+                else
+                {
+                    width = Math.Min (width, top.Bounds.Width - renderAt.X);
+                }
+            }
+        }
+
+        if (PopupInsideContainer)
+        {
+            popup.Frame = new Rect (
+                                    new Point (HostControl.Frame.X + renderAt.X, HostControl.Frame.Y + renderAt.Y),
+                                    new Size (width, height)
+                                   );
+        }
+        else
+        {
+            popup.Frame = new Rect (
+                                    new Point (HostControl.Frame.X + renderAt.X, renderAt.Y),
+                                    new Size (width, height)
+                                   );
+        }
+
+        popup.Move (0, 0);
+
+        for (var i = 0; i < toRender.Length; i++)
+        {
+            if (i == SelectedIdx - ScrollOffset)
+            {
+                Application.Driver.SetAttribute (ColorScheme.Focus);
+            }
+            else
+            {
+                Application.Driver.SetAttribute (ColorScheme.Normal);
+            }
+
+            popup.Move (0, i);
+
+            string text = TextFormatter.ClipOrPad (toRender [i].Title, width);
+
+            Application.Driver.AddStr (text);
+        }
+    }
+
+    /// <summary>
+    ///     Closes the Autocomplete context menu if it is showing and <see cref="IAutocomplete.ClearSuggestions"/>
+    /// </summary>
+    protected void Close ()
+    {
+        ClearSuggestions ();
+        Visible = false;
+        closed = true;
+        HostControl?.SetNeedsDisplay ();
+        ManipulatePopup ();
+    }
+
+    /// <summary>Deletes the text backwards before insert the selected text in the <see cref="HostControl"/>.</summary>
+    protected abstract void DeleteTextBackwards ();
+
+    /// <summary>
+    ///     Called when the user confirms a selection at the current cursor location in the <see cref="HostControl"/>. The
+    ///     <paramref name="accepted"/> string is the full autocomplete word to be inserted. Typically a host will have to
+    ///     remove some characters such that the <paramref name="accepted"/> string completes the word instead of simply being
+    ///     appended.
+    /// </summary>
+    /// <param name="accepted"></param>
+    /// <returns>True if the insertion was possible otherwise false</returns>
+    protected virtual bool InsertSelection (Suggestion accepted)
+    {
+        SetCursorPosition (Context.CursorPosition + accepted.Remove);
+
+        // delete the text
+        for (var i = 0; i < accepted.Remove; i++)
+        {
+            DeleteTextBackwards ();
+        }
+
+        InsertText (accepted.Replacement);
+
+        return true;
+    }
+
+    /// <summary>Insert the selected text in the <see cref="HostControl"/>.</summary>
+    /// <param name="accepted"></param>
+    protected abstract void InsertText (string accepted);
+
+    /// <summary>Moves the selection in the Autocomplete context menu down one</summary>
+    protected void MoveDown ()
+    {
+        SelectedIdx++;
+
+        if (SelectedIdx > Suggestions.Count - 1)
+        {
+            SelectedIdx = 0;
+        }
+
+        EnsureSelectedIdxIsValid ();
+        HostControl?.SetNeedsDisplay ();
+    }
+
+    /// <summary>Moves the selection in the Autocomplete context menu up one</summary>
+    protected void MoveUp ()
+    {
+        SelectedIdx--;
+
+        if (SelectedIdx < 0)
+        {
+            SelectedIdx = Suggestions.Count - 1;
+        }
+
+        EnsureSelectedIdxIsValid ();
+        HostControl?.SetNeedsDisplay ();
+    }
+
+    /// <summary>Render the current selection in the Autocomplete context menu by the mouse reporting.</summary>
+    /// <param name="me"></param>
+    protected void RenderSelectedIdxByMouse (MouseEvent me)
+    {
+        if (SelectedIdx != me.Y - ScrollOffset)
+        {
+            SelectedIdx = me.Y - ScrollOffset;
+
+            if (LastPopupPos != null)
+            {
+                RenderOverlay ((Point)LastPopupPos);
+            }
+        }
+    }
+
+    /// <summary>Reopen the popup after it has been closed.</summary>
+    /// <returns></returns>
+    protected bool ReopenSuggestions ()
+    {
+        // TODO: Revisit
+        //GenerateSuggestions ();
+
+        if (Suggestions.Count > 0)
+        {
+            Visible = true;
+            closed = false;
+            HostControl?.SetNeedsDisplay ();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>
+    ///     Completes the autocomplete selection process. Called when user hits the
+    ///     <see cref="IAutocomplete.SelectionKey"/>.
+    /// </summary>
+    /// <returns></returns>
+    protected bool Select ()
+    {
+        if (SelectedIdx >= 0 && SelectedIdx < Suggestions.Count)
+        {
+            Suggestion accepted = Suggestions [SelectedIdx];
+
+            return InsertSelection (accepted);
+        }
+
+        return false;
+    }
+
+    /// <summary>Set the cursor position in the <see cref="HostControl"/>.</summary>
+    /// <param name="column"></param>
+    protected abstract void SetCursorPosition (int column);
+
+    private void ManipulatePopup ()
+    {
+        if (Visible && popup == null)
+        {
+            popup = new Popup (this) { Frame = Rect.Empty };
+            top?.Add (popup);
+        }
+
+        if (!Visible && popup != null)
+        {
+            top?.Remove (popup);
+            popup.Dispose ();
+            popup = null;
+        }
+    }
+
+    private void Top_DrawContent (object sender, DrawEventArgs e)
+    {
+        if (!closed)
+        {
+            ReopenSuggestions ();
+        }
+
+        ManipulatePopup ();
+
+        if (Visible)
+        {
+            top.BringSubviewToFront (popup);
+        }
+    }
+
+    private void Top_DrawContentComplete (object sender, DrawEventArgs e) { ManipulatePopup (); }
+
+    private void Top_Removed (object sender, SuperViewChangedEventArgs e)
+    {
+        Visible = false;
+        ManipulatePopup ();
+    }
+
+    private class Popup : View
+    {
+        private readonly PopupAutocomplete autocomplete;
+
+        public Popup (PopupAutocomplete autocomplete)
+        {
+            this.autocomplete = autocomplete;
+            CanFocus = true;
+            WantMousePositionReports = true;
+        }
+
+        public override bool MouseEvent (MouseEvent mouseEvent) { return autocomplete.MouseEvent (mouseEvent); }
+
+        public override void OnDrawContent (Rect contentArea)
+        {
+            if (autocomplete.LastPopupPos == null)
+            {
+                return;
+            }
+
+            autocomplete.RenderOverlay ((Point)autocomplete.LastPopupPos);
+        }
+    }
 }
-

+ 95 - 94
Terminal.Gui/Text/Autocomplete/SingleWordSuggestionGenerator.cs

@@ -1,107 +1,108 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+namespace Terminal.Gui;
 
+/// <summary>
+///     <see cref="ISuggestionGenerator"/> which suggests from a collection of words those that match the
+///     <see cref="AutocompleteContext"/>. You can update <see cref="AllSuggestions"/> at any time to change candidates
+///     considered for autocomplete.
+/// </summary>
+public class SingleWordSuggestionGenerator : ISuggestionGenerator
+{
+    /// <summary>The full set of all strings that can be suggested.</summary>
+    /// <returns></returns>
+    public virtual List<string> AllSuggestions { get; set; } = new ();
 
-namespace Terminal.Gui {
+    /// <inheritdoc/>
+    public IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context)
+    {
+        // if there is nothing to pick from
+        if (AllSuggestions.Count == 0)
+        {
+            return Enumerable.Empty<Suggestion> ();
+        }
 
-	/// <summary>
-	/// <see cref="ISuggestionGenerator"/> which suggests from a collection
-	/// of words those that match the <see cref="AutocompleteContext"/>. You
-	/// can update <see cref="AllSuggestions"/> at any time to change candidates
-	/// considered for autocomplete.
-	/// </summary>
-	public class SingleWordSuggestionGenerator : ISuggestionGenerator {
+        List<Rune> line = context.CurrentLine.Select (c => c.Rune).ToList ();
+        string currentWord = IdxToWord (line, context.CursorPosition, out int startIdx);
+        context.CursorPosition = startIdx < 1 ? startIdx : Math.Min (startIdx + 1, line.Count);
 
-		/// <summary>
-		/// The full set of all strings that can be suggested.
-		/// </summary>
-		/// <returns></returns>
-		public virtual List<string> AllSuggestions { get; set; } = new List<string> ();
+        if (string.IsNullOrWhiteSpace (currentWord))
+        {
+            return Enumerable.Empty<Suggestion> ();
+        }
 
-		/// <inheritdoc/>
-		public IEnumerable<Suggestion> GenerateSuggestions (AutocompleteContext context)
-		{
-			// if there is nothing to pick from
-			if (AllSuggestions.Count == 0) {
-				return Enumerable.Empty<Suggestion> ();
-			}
+        return AllSuggestions.Where (
+                                     o =>
+                                         o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase)
+                                         && !o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase)
+                                    )
+                             .Select (o => new Suggestion (currentWord.Length, o))
+                             .ToList ()
+                             .AsReadOnly ();
+    }
 
-			var line = context.CurrentLine.Select (c => c.Rune).ToList ();
-			var currentWord = IdxToWord (line, context.CursorPosition, out int startIdx);
-			context.CursorPosition = startIdx < 1 ? startIdx : Math.Min (startIdx + 1, line.Count);
+    /// <summary>
+    ///     Return true if the given symbol should be considered part of a word and can be contained in matches. Base
+    ///     behavior is to use <see cref="char.IsLetterOrDigit(char)"/>
+    /// </summary>
+    /// <param name="rune">The rune.</param>
+    /// <returns></returns>
+    public virtual bool IsWordChar (Rune rune) { return char.IsLetterOrDigit ((char)rune.Value); }
 
-			if (string.IsNullOrWhiteSpace (currentWord)) {
-				return Enumerable.Empty<Suggestion> ();
-			} else {
-				return AllSuggestions.Where (o =>
-				o.StartsWith (currentWord, StringComparison.CurrentCultureIgnoreCase) &&
-				!o.Equals (currentWord, StringComparison.CurrentCultureIgnoreCase)
-				).Select (o => new Suggestion (currentWord.Length, o))
-					.ToList ().AsReadOnly ();
+    /// <summary>
+    ///     <para>
+    ///         Given a <paramref name="line"/> of characters, returns the word which ends at <paramref name="idx"/> or null.
+    ///         Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
+    ///     </para>
+    ///     <para>
+    ///         Use this method to determine whether autocomplete should be shown when the cursor is at a given point in a
+    ///         line and to get the word from which suggestions should be generated. Use the <paramref name="columnOffset"/> to
+    ///         indicate if search the word at left (negative), at right (positive) or at the current column (zero) which is
+    ///         the default.
+    ///     </para>
+    /// </summary>
+    /// <param name="line"></param>
+    /// <param name="idx"></param>
+    /// <param name="startIdx">The start index of the word.</param>
+    /// <param name="columnOffset"></param>
+    /// <returns></returns>
+    protected virtual string IdxToWord (List<Rune> line, int idx, out int startIdx, int columnOffset = 0)
+    {
+        var sb = new StringBuilder ();
+        startIdx = idx;
 
-			}
-		}
+        // get the ending word index
+        while (startIdx < line.Count)
+        {
+            if (IsWordChar (line [startIdx]))
+            {
+                startIdx++;
+            }
+            else
+            {
+                break;
+            }
+        }
 
-		/// <summary>
-		/// Return true if the given symbol should be considered part of a word
-		/// and can be contained in matches. Base behavior is to use <see cref="char.IsLetterOrDigit(char)"/>
-		/// </summary>
-		/// <param name="rune">The rune.</param>
-		/// <returns></returns>
-		public virtual bool IsWordChar (Rune rune)
-		{
-			return Char.IsLetterOrDigit ((char)rune.Value);
-		}
+        // It isn't a word char then there is no way to autocomplete that word
+        if (startIdx == idx && columnOffset != 0)
+        {
+            return null;
+        }
 
-		/// <summary>
-		/// <para>
-		/// Given a <paramref name="line"/> of characters, returns the word which ends at <paramref name="idx"/> 
-		/// or null. Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
-		/// </para>
-		/// 
-		/// <para>
-		/// Use this method to determine whether autocomplete should be shown when the cursor is at
-		/// a given point in a line and to get the word from which suggestions should be generated.
-		/// Use the <paramref name="columnOffset"/> to indicate if search the word at left (negative),
-		/// at right (positive) or at the current column (zero) which is the default.
-		/// </para>
-		/// </summary>
-		/// <param name="line"></param>
-		/// <param name="idx"></param>
-		/// <param name="startIdx">The start index of the word.</param>
-		/// <param name="columnOffset"></param>
-		/// <returns></returns>
-		protected virtual string IdxToWord (List<Rune> line, int idx, out int startIdx, int columnOffset = 0)
-		{
-			StringBuilder sb = new StringBuilder ();
-			startIdx = idx;
+        // we are at the end of a word. Work out what has been typed so far
+        while (startIdx-- > 0)
+        {
+            if (IsWordChar (line [startIdx]))
+            {
+                sb.Insert (0, (char)line [startIdx].Value);
+            }
+            else
+            {
+                break;
+            }
+        }
 
-			// get the ending word index
-			while (startIdx < line.Count) {
-				if (IsWordChar (line [startIdx])) {
-					startIdx++;
-				} else {
-					break;
-				}
-			}
+        startIdx = Math.Max (startIdx, 0);
 
-			// It isn't a word char then there is no way to autocomplete that word
-			if (startIdx == idx && columnOffset != 0) {
-				return null;
-			}
-
-			// we are at the end of a word. Work out what has been typed so far
-			while (startIdx-- > 0) {
-				if (IsWordChar (line [startIdx])) {
-					sb.Insert (0, (char)line [startIdx].Value);
-				} else {
-					break;
-				}
-			}
-			startIdx = Math.Max (startIdx, 0);
-			return sb.ToString ();
-		}
-	}
+        return sb.ToString ();
+    }
 }

+ 26 - 34
Terminal.Gui/Text/Autocomplete/Suggestion.cs

@@ -1,38 +1,30 @@
-namespace Terminal.Gui {
-	/// <summary>
-	/// A replacement suggestion made by <see cref="IAutocomplete"/>
-	/// </summary>
-	public class Suggestion {
-		/// <summary>
-		/// The number of characters to remove at the current cursor position
-		/// before adding the <see cref="Replacement"/>
-		/// </summary>
-		public int Remove { get; }
+namespace Terminal.Gui;
 
-		/// <summary>
-		/// The user visible description for the <see cref="Replacement"/>. Typically
-		/// this would be the same as <see cref="Replacement"/> but may vary in advanced
-		/// use cases (e.g. Title= "ctor", Replacement = "MyClass()\n{\n}")
-		/// </summary>
-		public string Title { get; }
+/// <summary>A replacement suggestion made by <see cref="IAutocomplete"/></summary>
+public class Suggestion
+{
+    /// <summary>Creates a new instance of the <see cref="Suggestion"/> class.</summary>
+    /// <param name="remove"></param>
+    /// <param name="replacement"></param>
+    /// <param name="title">User visible title for the suggestion or null if the same as <paramref name="replacement"/>.</param>
+    public Suggestion (int remove, string replacement, string title = null)
+    {
+        Remove = remove;
+        Replacement = replacement;
+        Title = title ?? replacement;
+    }
 
-		/// <summary>
-		/// The replacement text that will be added
-		/// </summary>
-		public string Replacement { get; }
+    /// <summary>
+    ///     The number of characters to remove at the current cursor position before adding the <see cref="Replacement"/>
+    /// </summary>
+    public int Remove { get; }
 
-		/// <summary>
-		/// Creates a new instance of the <see cref="Suggestion"/> class.
-		/// </summary>
-		/// <param name="remove"></param>
-		/// <param name="replacement"></param>
-		/// <param name="title">User visible title for the suggestion or null if the same
-		/// as <paramref name="replacement"/>.</param>
-		public Suggestion (int remove, string replacement, string title = null)
-		{
-			Remove = remove;
-			Replacement = replacement;
-			Title = title ?? replacement;
-		}
-	}
+    /// <summary>The replacement text that will be added</summary>
+    public string Replacement { get; }
+
+    /// <summary>
+    ///     The user visible description for the <see cref="Replacement"/>. Typically this would be the same as
+    ///     <see cref="Replacement"/> but may vary in advanced use cases (e.g. Title= "ctor", Replacement = "MyClass()\n{\n}")
+    /// </summary>
+    public string Title { get; }
 }

+ 17 - 33
Terminal.Gui/Text/CollectionNavigator.cs

@@ -1,40 +1,24 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
+using System.Collections;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
-	/// <inheritdoc/>
-	/// <remarks>This implementation is based on a static <see cref="Collection"/> of objects.</remarks>
-	public class CollectionNavigator : CollectionNavigatorBase {
-		/// <summary>
-		/// The collection of objects to search. <see cref="object.ToString()"/> is used to search the collection.
-		/// </summary>
-		public IList Collection { get; set; }
+/// <inheritdoc/>
+/// <remarks>This implementation is based on a static <see cref="Collection"/> of objects.</remarks>
+public class CollectionNavigator : CollectionNavigatorBase
+{
+    /// <summary>Constructs a new CollectionNavigator.</summary>
+    public CollectionNavigator () { }
 
-		/// <summary>
-		/// Constructs a new CollectionNavigator.
-		/// </summary>
-		public CollectionNavigator () { }
+    /// <summary>Constructs a new CollectionNavigator for the given collection.</summary>
+    /// <param name="collection"></param>
+    public CollectionNavigator (IList collection) { Collection = collection; }
 
-		/// <summary>
-		/// Constructs a new CollectionNavigator for the given collection.
-		/// </summary>
-		/// <param name="collection"></param>
-		public CollectionNavigator (IList collection) => Collection = collection;
+    /// <summary>The collection of objects to search. <see cref="object.ToString()"/> is used to search the collection.</summary>
+    public IList Collection { get; set; }
 
-		/// <inheritdoc/>
-		protected override object ElementAt (int idx)
-		{
-			return Collection [idx];
-		}
+    /// <inheritdoc/>
+    protected override object ElementAt (int idx) { return Collection [idx]; }
 
-		/// <inheritdoc/>
-		protected override int GetCollectionLength ()
-		{
-			return Collection.Count;
-		}
-
-	}
+    /// <inheritdoc/>
+    protected override int GetCollectionLength () { return Collection.Count; }
 }

+ 219 - 211
Terminal.Gui/Text/CollectionNavigatorBase.cs

@@ -1,214 +1,222 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
-/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. 
-/// The <see cref="SearchString"/> is used to find the next item in the collection that matches the search string
-/// when <see cref="GetNextMatchingItem(int, char)"/> is called.
-/// <para>
-/// If the user types keystrokes that can't be found in the collection, 
-/// the search string is cleared and the next item is found that starts with the last keystroke.
-/// </para>
-/// <para>
-/// If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.
-/// </para>
+///     Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. The
+///     <see cref="SearchString"/> is used to find the next item in the collection that matches the search string when
+///     <see cref="GetNextMatchingItem(int, char)"/> is called.
+///     <para>
+///         If the user types keystrokes that can't be found in the collection, the search string is cleared and the next
+///         item is found that starts with the last keystroke.
+///     </para>
+///     <para>If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.</para>
 /// </summary>
-public abstract class CollectionNavigatorBase {
-	DateTime _lastKeystroke = DateTime.Now;
-
-	/// <summary>
-	/// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is
-	/// reset on each call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
-	/// </summary>
-	public int TypingDelay { get; set; } = 500;
-
-	/// <summary>
-	/// The comparer function to use when searching the collection.
-	/// </summary>
-	public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
-
-	/// <summary>
-	/// This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.
-	/// </summary>
-	public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
-
-	string _searchString = "";
-
-	/// <summary>
-	/// Gets the current search string. This includes the set of keystrokes that have been pressed
-	/// since the last unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
-	/// </summary>
-	public string SearchString {
-		get => _searchString;
-		private set {
-			_searchString = value;
-			OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
-		}
-	}
-
-	/// <summary>
-	/// Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the <see cref="SearchStringChanged"/> event.
-	/// </summary>
-	/// <param name="e"></param>
-	public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) => SearchStringChanged?.Invoke (this, e);
-
-	/// <summary>
-	/// Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the provided character (typically
-	/// from a key press).
-	/// </summary>
-	/// <param name="currentIndex">The index in the collection to start the search from.</param>
-	/// <param name="keyStruck">The character of the key the user pressed.</param>
-	/// <returns>The index of the item that matches what the user has typed. 
-	/// Returns <see langword="-1"/> if no item in the collection matched.</returns>
-	public int GetNextMatchingItem (int currentIndex, char keyStruck)
-	{
-		if (!char.IsControl (keyStruck)) {
-
-			// maybe user pressed 'd' and now presses 'd' again.
-			// a candidate search is things that begin with "dd"
-			// but if we find none then we must fallback on cycling
-			// d instead and discard the candidate state
-			string candidateState = "";
-
-			// is it a second or third (etc) keystroke within a short time
-			if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
-				// "dd" is a candidate
-				candidateState = SearchString + keyStruck;
-			} else {
-				// its a fresh keystroke after some time
-				// or its first ever key press
-				SearchString = new string (keyStruck, 1);
-			}
-
-			int idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
-				// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
-				candidateState.Length > 1);
-
-			if (idxCandidate != -1) {
-				// found "dd" so candidate search string is accepted
-				_lastKeystroke = DateTime.Now;
-				SearchString = candidateState;
-				return idxCandidate;
-			}
-
-			//// nothing matches "dd" so discard it as a candidate
-			//// and just cycle "d" instead
-			_lastKeystroke = DateTime.Now;
-			idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
-
-			// if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
-			// instead of "can" + 'd').
-			if (SearchString.Length > 1 && idxCandidate == -1) {
-				// ignore it since we're still within the typing delay
-				// don't add it to SearchString either
-				return currentIndex;
-			}
-
-			// if no changes to current state manifested
-			if (idxCandidate == currentIndex || idxCandidate == -1) {
-				// clear history and treat as a fresh letter
-				ClearSearchString ();
-
-				// match on the fresh letter alone
-				SearchString = new string (keyStruck, 1);
-				idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
-				return idxCandidate == -1 ? currentIndex : idxCandidate;
-			}
-
-			// Found another "d" or just leave index as it was
-			return idxCandidate;
-
-		} else {
-			// clear state because keypress was a control char
-			ClearSearchString ();
-
-			// control char indicates no selection
-			return -1;
-		}
-	}
-
-	/// <summary>
-	/// Gets the index of the next item in the collection that matches <paramref name="search"/>. 
-	/// </summary>
-	/// <param name="currentIndex">The index in the collection to start the search from.</param>
-	/// <param name="search">The search string to use.</param>
-	/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
-	/// if there are multiple matches for <paramref name="search"/>.
-	/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default), 
-	/// the next matching item will be returned, even if it is above in the collection.
-	/// </param>
-	/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
-	internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
-	{
-		if (string.IsNullOrEmpty (search)) {
-			return -1;
-		}
-
-		int collectionLength = GetCollectionLength ();
-
-		if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) {
-			// we are already at a match
-			if (minimizeMovement) {
-				// if we would rather not jump around (e.g. user is typing lots of text to get this match)
-				return currentIndex;
-			}
-
-			for (int i = 1; i < collectionLength; i++) {
-				//circular
-				int idxCandidate = (i + currentIndex) % collectionLength;
-				if (IsMatch (search, ElementAt (idxCandidate))) {
-					return idxCandidate;
-				}
-			}
-
-			// nothing else starts with the search term
-			return currentIndex;
-		} else {
-			// search terms no longer match the current selection or there is none
-			for (int i = 0; i < collectionLength; i++) {
-				if (IsMatch (search, ElementAt (i))) {
-					return i;
-				}
-			}
-
-			// Nothing matches
-			return -1;
-		}
-	}
-
-	/// <summary>
-	/// Return the number of elements in the collection
-	/// </summary>
-	protected abstract int GetCollectionLength ();
-
-	bool IsMatch (string search, object value) => value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false;
-
-	/// <summary>
-	/// Returns the collection being navigated element at <paramref name="idx"/>.
-	/// </summary>
-	/// <returns></returns>
-	protected abstract object ElementAt (int idx);
-
-	void ClearSearchString ()
-	{
-		SearchString = "";
-		_lastKeystroke = DateTime.Now;
-	}
-
-	/// <summary>
-	/// Returns true if <paramref name="a"/> is a searchable key
-	/// (e.g. letters, numbers, etc) that are valid to pass to this
-	/// class for search filtering.
-	/// </summary>
-	/// <param name="a"></param>
-	/// <returns></returns>
-	public static bool IsCompatibleKey (Key a)
-	{
-		var rune = a.AsRune;
-		return rune != default && !Rune.IsControl (rune);
-	}
-}
+public abstract class CollectionNavigatorBase
+{
+    private DateTime _lastKeystroke = DateTime.Now;
+    private string _searchString = "";
+
+    /// <summary>The comparer function to use when searching the collection.</summary>
+    public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
+
+    /// <summary>
+    ///     Gets the current search string. This includes the set of keystrokes that have been pressed since the last
+    ///     unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
+    /// </summary>
+    public string SearchString
+    {
+        get => _searchString;
+        private set
+        {
+            _searchString = value;
+            OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
+        }
+    }
+
+    /// <summary>
+    ///     Gets or sets the number of milliseconds to delay before clearing the search string. The delay is reset on each
+    ///     call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
+    /// </summary>
+    public int TypingDelay { get; set; } = 500;
+
+    /// <summary>
+    ///     Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the
+    ///     provided character (typically from a key press).
+    /// </summary>
+    /// <param name="currentIndex">The index in the collection to start the search from.</param>
+    /// <param name="keyStruck">The character of the key the user pressed.</param>
+    /// <returns>
+    ///     The index of the item that matches what the user has typed. Returns <see langword="-1"/> if no item in the
+    ///     collection matched.
+    /// </returns>
+    public int GetNextMatchingItem (int currentIndex, char keyStruck)
+    {
+        if (!char.IsControl (keyStruck))
+        {
+            // maybe user pressed 'd' and now presses 'd' again.
+            // a candidate search is things that begin with "dd"
+            // but if we find none then we must fallback on cycling
+            // d instead and discard the candidate state
+            var candidateState = "";
+
+            // is it a second or third (etc) keystroke within a short time
+            if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay))
+            {
+                // "dd" is a candidate
+                candidateState = SearchString + keyStruck;
+            }
+            else
+            {
+                // its a fresh keystroke after some time
+                // or its first ever key press
+                SearchString = new string (keyStruck, 1);
+            }
+
+            int idxCandidate = GetNextMatchingItem (
+                                                    currentIndex,
+                                                    candidateState,
+
+                                                    // prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
+                                                    candidateState.Length > 1
+                                                   );
+
+            if (idxCandidate != -1)
+            {
+                // found "dd" so candidate search string is accepted
+                _lastKeystroke = DateTime.Now;
+                SearchString = candidateState;
+
+                return idxCandidate;
+            }
+
+            //// nothing matches "dd" so discard it as a candidate
+            //// and just cycle "d" instead
+            _lastKeystroke = DateTime.Now;
+            idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
+
+            // if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
+            // instead of "can" + 'd').
+            if (SearchString.Length > 1 && idxCandidate == -1)
+            {
+                // ignore it since we're still within the typing delay
+                // don't add it to SearchString either
+                return currentIndex;
+            }
+
+            // if no changes to current state manifested
+            if (idxCandidate == currentIndex || idxCandidate == -1)
+            {
+                // clear history and treat as a fresh letter
+                ClearSearchString ();
+
+                // match on the fresh letter alone
+                SearchString = new string (keyStruck, 1);
+                idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
+
+                return idxCandidate == -1 ? currentIndex : idxCandidate;
+            }
+
+            // Found another "d" or just leave index as it was
+            return idxCandidate;
+        }
+
+        // clear state because keypress was a control char
+        ClearSearchString ();
+
+        // control char indicates no selection
+        return -1;
+    }
+
+    /// <summary>
+    ///     Returns true if <paramref name="a"/> is a searchable key (e.g. letters, numbers, etc) that are valid to pass
+    ///     to this class for search filtering.
+    /// </summary>
+    /// <param name="a"></param>
+    /// <returns></returns>
+    public static bool IsCompatibleKey (Key a)
+    {
+        Rune rune = a.AsRune;
+
+        return rune != default (Rune) && !Rune.IsControl (rune);
+    }
+
+    /// <summary>
+    ///     Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the
+    ///     <see cref="SearchStringChanged"/> event.
+    /// </summary>
+    /// <param name="e"></param>
+    public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) { SearchStringChanged?.Invoke (this, e); }
+
+    /// <summary>This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.</summary>
+    public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
+
+    /// <summary>Returns the collection being navigated element at <paramref name="idx"/>.</summary>
+    /// <returns></returns>
+    protected abstract object ElementAt (int idx);
+
+    /// <summary>Return the number of elements in the collection</summary>
+    protected abstract int GetCollectionLength ();
+
+    /// <summary>Gets the index of the next item in the collection that matches <paramref name="search"/>.</summary>
+    /// <param name="currentIndex">The index in the collection to start the search from.</param>
+    /// <param name="search">The search string to use.</param>
+    /// <param name="minimizeMovement">
+    ///     Set to <see langword="true"/> to stop the search on the first match if there are
+    ///     multiple matches for <paramref name="search"/>. e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If
+    ///     <see langword="false"/> (the default), the next matching item will be returned, even if it is above in the
+    ///     collection.
+    /// </param>
+    /// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
+    internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
+    {
+        if (string.IsNullOrEmpty (search))
+        {
+            return -1;
+        }
+
+        int collectionLength = GetCollectionLength ();
+
+        if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex)))
+        {
+            // we are already at a match
+            if (minimizeMovement)
+            {
+                // if we would rather not jump around (e.g. user is typing lots of text to get this match)
+                return currentIndex;
+            }
+
+            for (var i = 1; i < collectionLength; i++)
+            {
+                //circular
+                int idxCandidate = (i + currentIndex) % collectionLength;
+
+                if (IsMatch (search, ElementAt (idxCandidate)))
+                {
+                    return idxCandidate;
+                }
+            }
+
+            // nothing else starts with the search term
+            return currentIndex;
+        }
+
+        // search terms no longer match the current selection or there is none
+        for (var i = 0; i < collectionLength; i++)
+        {
+            if (IsMatch (search, ElementAt (i)))
+            {
+                return i;
+            }
+        }
+
+        // Nothing matches
+        return -1;
+    }
+
+    private void ClearSearchString ()
+    {
+        SearchString = "";
+        _lastKeystroke = DateTime.Now;
+    }
+
+    private bool IsMatch (string search, object value) { return value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false; }
+}

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

@@ -1,169 +1,149 @@
 using System.Globalization;
-using System.Text;
 using Wcwidth;
 
 namespace Terminal.Gui;
 
-/// <summary>
-/// Extends <see cref="System.Text.Rune"/> to support TUI text manipulation.
-/// </summary>
-public static class RuneExtensions {
-	/// <summary>
-	/// Maximum Unicode code point.
-	/// </summary>
-	public static int MaxUnicodeCodePoint = 0x10FFFF;
-
-	/// <summary>
-	/// Gets the number of columns the rune occupies in the terminal.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune">The rune to measure.</param>
-	/// <returns>
-	/// The number of columns required to fit the rune, 0 if the argument is the null character, or
-	/// -1 if the value is not printable, 
-	/// otherwise the number of columns that the rune occupies.
-	/// </returns>
-	public static int GetColumns (this Rune rune)
-	{
-		return UnicodeCalculator.GetWidth (rune);
-	}
-
-	/// <summary>
-	/// Returns <see langword="true"/> if the rune is a combining character.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune"></param>
-	/// <returns></returns>
-	public static bool IsCombiningMark (this System.Text.Rune rune)
-	{
-		UnicodeCategory category = Rune.GetUnicodeCategory (rune);
-		return Rune.GetUnicodeCategory (rune) == UnicodeCategory.NonSpacingMark
-			|| category == UnicodeCategory.SpacingCombiningMark
-			|| category == UnicodeCategory.EnclosingMark;
-	}
-
-	/// <summary>
-	/// Ensures the rune is not a control character and can be displayed by translating characters below 0x20
-	/// to equivalent, printable, Unicode chars.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune"></param>
-	/// <returns></returns>
-	public static Rune MakePrintable (this System.Text.Rune rune) => Rune.IsControl (rune) ? new Rune (rune.Value + 0x2400) : rune;
-
-	/// <summary>
-	/// Get number of bytes required to encode the rune, based on the provided encoding.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune">The rune to probe.</param>
-	/// <param name="encoding">The encoding used; the default is UTF8.</param>
-	/// <returns>The number of bytes required.</returns>
-	public static int GetEncodingLength (this Rune rune, Encoding encoding = null)
-	{
-		encoding ??= Encoding.UTF8;
-		var bytes = encoding.GetBytes (rune.ToString ().ToCharArray ());
-		var offset = 0;
-		if (bytes [^1] == 0) {
-			offset++;
-		}
-		return bytes.Length - offset;
-	}
-
-	/// <summary>
-	/// Writes into the destination buffer starting at offset the UTF8 encoded version of the rune.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune">The rune to encode.</param>
-	/// <param name="dest">The destination buffer.</param>
-	/// <param name="start">Starting offset to look into.</param>
-	/// <param name="count">Number of bytes valid in the buffer, or -1 to make it the length of the buffer.</param>
-	/// <returns>he number of bytes written into the destination buffer.</returns>
-	public static int Encode (this Rune rune, byte [] dest, int start = 0, int count = -1)
-	{
-		var bytes = Encoding.UTF8.GetBytes (rune.ToString ());
-		var length = 0;
-		for (var i = 0; i < (count == -1 ? bytes.Length : count); i++) {
-			if (bytes [i] == 0) {
-				break;
-			}
-			dest [start + i] = bytes [i];
-			length++;
-		}
-		return length;
-	}
-
-	/// <summary>
-	/// Attempts to decode the rune as a surrogate pair to UTF-16.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune">The rune to decode.</param>
-	/// <param name="chars">The chars if the rune is a surrogate pair. Null otherwise.</param>
-	/// <returns><see langword="true"/> if the rune is a valid surrogate pair; <see langword="false"/> otherwise.</returns>
-	public static bool DecodeSurrogatePair (this Rune rune, out char [] chars)
-	{
-		if (rune.IsSurrogatePair ()) {
-			chars = rune.ToString ().ToCharArray ();
-			return true;
-		}
-		chars = null;
-		return false;
-	}
-
-	/// <summary>
-	/// Attempts to encode (as UTF-16) a surrogate pair.
-	/// </summary>
-	/// <param name="highSurrogate">The high surrogate code point.</param>
-	/// <param name="lowSurrogate">The low surrogate code point.</param>
-	/// <param name="result">The encoded rune.</param>
-	/// <returns><see langword="true"/> if the encoding succeeded; <see langword="false"/> otherwise.</returns>
-	public static bool EncodeSurrogatePair (char highSurrogate, char lowSurrogate, out Rune result)
-	{
-		result = default;
-		if (char.IsSurrogatePair (highSurrogate, lowSurrogate)) {
-			result = (Rune)char.ConvertToUtf32 (highSurrogate, lowSurrogate);
-			return true;
-		}
-		return false;
-	}
-
-	/// <summary>
-	/// Reports whether a rune is a surrogate code point.
-	/// </summary>
-	/// <remarks>
-	/// This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.
-	/// </remarks>
-	/// <param name="rune">The rune to probe.</param>
-	/// <returns><see langword="true"/> if the rune is a surrogate code point; <see langword="false"/> otherwise.</returns>
-	public static bool IsSurrogatePair (this Rune rune)
-	{
-		return char.IsSurrogatePair (rune.ToString (), 0);
-	}
-
-	/// <summary>
-	/// Reports if the provided array of bytes can be encoded as UTF-8.
-	/// </summary>
-	/// <param name="buffer">The byte array to probe.</param>
-	/// <value><c>true</c> if is valid; otherwise, <c>false</c>.</value>
-	public static bool CanBeEncodedAsRune (byte [] buffer)
-	{
-		var str = Encoding.Unicode.GetString (buffer);
-		foreach (var rune in str.EnumerateRunes ()) {
-			if (rune == Rune.ReplacementChar) {
-				return false;
-			}
-		}
-		return true;
-	}
-}
+/// <summary>Extends <see cref="System.Text.Rune"/> to support TUI text manipulation.</summary>
+public static class RuneExtensions
+{
+    /// <summary>Maximum Unicode code point.</summary>
+    public static int MaxUnicodeCodePoint = 0x10FFFF;
+
+    /// <summary>Reports if the provided array of bytes can be encoded as UTF-8.</summary>
+    /// <param name="buffer">The byte array to probe.</param>
+    /// <value><c>true</c> if is valid; otherwise, <c>false</c>.</value>
+    public static bool CanBeEncodedAsRune (byte [] buffer)
+    {
+        string str = Encoding.Unicode.GetString (buffer);
+
+        foreach (Rune rune in str.EnumerateRunes ())
+        {
+            if (rune == Rune.ReplacementChar)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /// <summary>Attempts to decode the rune as a surrogate pair to UTF-16.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune">The rune to decode.</param>
+    /// <param name="chars">The chars if the rune is a surrogate pair. Null otherwise.</param>
+    /// <returns><see langword="true"/> if the rune is a valid surrogate pair; <see langword="false"/> otherwise.</returns>
+    public static bool DecodeSurrogatePair (this Rune rune, out char [] chars)
+    {
+        if (rune.IsSurrogatePair ())
+        {
+            chars = rune.ToString ().ToCharArray ();
+
+            return true;
+        }
+
+        chars = null;
+
+        return false;
+    }
+
+    /// <summary>Writes into the destination buffer starting at offset the UTF8 encoded version of the rune.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune">The rune to encode.</param>
+    /// <param name="dest">The destination buffer.</param>
+    /// <param name="start">Starting offset to look into.</param>
+    /// <param name="count">Number of bytes valid in the buffer, or -1 to make it the length of the buffer.</param>
+    /// <returns>he number of bytes written into the destination buffer.</returns>
+    public static int Encode (this Rune rune, byte [] dest, int start = 0, int count = -1)
+    {
+        byte [] bytes = Encoding.UTF8.GetBytes (rune.ToString ());
+        var length = 0;
+
+        for (var i = 0; i < (count == -1 ? bytes.Length : count); i++)
+        {
+            if (bytes [i] == 0)
+            {
+                break;
+            }
+
+            dest [start + i] = bytes [i];
+            length++;
+        }
+
+        return length;
+    }
+
+    /// <summary>Attempts to encode (as UTF-16) a surrogate pair.</summary>
+    /// <param name="highSurrogate">The high surrogate code point.</param>
+    /// <param name="lowSurrogate">The low surrogate code point.</param>
+    /// <param name="result">The encoded rune.</param>
+    /// <returns><see langword="true"/> if the encoding succeeded; <see langword="false"/> otherwise.</returns>
+    public static bool EncodeSurrogatePair (char highSurrogate, char lowSurrogate, out Rune result)
+    {
+        result = default (Rune);
+
+        if (char.IsSurrogatePair (highSurrogate, lowSurrogate))
+        {
+            result = (Rune)char.ConvertToUtf32 (highSurrogate, lowSurrogate);
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /// <summary>Gets the number of columns the rune occupies in the terminal.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune">The rune to measure.</param>
+    /// <returns>
+    ///     The number of columns required to fit the rune, 0 if the argument is the null character, or -1 if the value is
+    ///     not printable, otherwise the number of columns that the rune occupies.
+    /// </returns>
+    public static int GetColumns (this Rune rune) { return UnicodeCalculator.GetWidth (rune); }
+
+    /// <summary>Get number of bytes required to encode the rune, based on the provided encoding.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune">The rune to probe.</param>
+    /// <param name="encoding">The encoding used; the default is UTF8.</param>
+    /// <returns>The number of bytes required.</returns>
+    public static int GetEncodingLength (this Rune rune, Encoding encoding = null)
+    {
+        encoding ??= Encoding.UTF8;
+        byte [] bytes = encoding.GetBytes (rune.ToString ().ToCharArray ());
+        var offset = 0;
+
+        if (bytes [^1] == 0)
+        {
+            offset++;
+        }
+
+        return bytes.Length - offset;
+    }
+
+    /// <summary>Returns <see langword="true"/> if the rune is a combining character.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune"></param>
+    /// <returns></returns>
+    public static bool IsCombiningMark (this Rune rune)
+    {
+        UnicodeCategory category = Rune.GetUnicodeCategory (rune);
+
+        return Rune.GetUnicodeCategory (rune) == UnicodeCategory.NonSpacingMark
+               || category == UnicodeCategory.SpacingCombiningMark
+               || category == UnicodeCategory.EnclosingMark;
+    }
+
+    /// <summary>Reports whether a rune is a surrogate code point.</summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune">The rune to probe.</param>
+    /// <returns><see langword="true"/> if the rune is a surrogate code point; <see langword="false"/> otherwise.</returns>
+    public static bool IsSurrogatePair (this Rune rune) { return char.IsSurrogatePair (rune.ToString (), 0); }
+
+    /// <summary>
+    ///     Ensures the rune is not a control character and can be displayed by translating characters below 0x20 to
+    ///     equivalent, printable, Unicode chars.
+    /// </summary>
+    /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
+    /// <param name="rune"></param>
+    /// <returns></returns>
+    public static Rune MakePrintable (this Rune rune) { return Rune.IsControl (rune) ? new Rune (rune.Value + 0x2400) : rune; }
+}

部分文件因为文件数量过多而无法显示