Преглед изворни кода

Merge branch 'v2_develop' into v2_release

Tig пре 3 месеци
родитељ
комит
0f7f291e58
42 измењених фајлова са 587 додато и 326 уклоњено
  1. 0 1
      Examples/UICatalog/Scenarios/VkeyPacketSimulator.cs
  2. 9 11
      Terminal.Gui/Application/Application.Run.cs
  3. 15 1
      Terminal.Gui/Application/Application.Toplevel.cs
  4. 11 10
      Terminal.Gui/Configuration/ConfigurationManager.cs
  5. 22 11
      Terminal.Gui/Configuration/DeepCloner.cs
  6. 1 1
      Terminal.Gui/Configuration/SourcesManager.cs
  7. 4 0
      Terminal.Gui/Configuration/ThemeManager.cs
  8. 1 1
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  9. 10 1
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  10. 18 8
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  11. 0 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  12. 0 1
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  13. 33 8
      Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
  14. 0 1
      Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
  15. 0 1
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
  16. 32 27
      Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
  17. 1 1
      Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs
  18. 4 2
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  19. 0 8
      Terminal.Gui/View/View.Layout.cs
  20. 2 1
      Terminal.Gui/Views/TextInput/TextField.cs
  21. 3 1
      Terminal.Gui/Views/TextInput/TextModel.cs
  22. 37 16
      Terminal.Gui/Views/TextInput/TextView.cs
  23. 0 1
      Tests/TerminalGuiFluentTesting/GuiTestContext.cs
  24. 12 12
      Tests/UnitTests/Configuration/ConfigurationMangerTests.cs
  25. 142 12
      Tests/UnitTests/Configuration/SettingsScopeTests.cs
  26. 1 1
      Tests/UnitTests/Configuration/ThemeManagerTests.cs
  27. 11 12
      Tests/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs
  28. 132 38
      Tests/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
  29. 20 7
      Tests/UnitTests/Views/AppendAutocompleteTests.cs
  30. 37 2
      Tests/UnitTests/Views/TextViewTests.cs
  31. 0 1
      Tests/UnitTests/Views/ToplevelTests.cs
  32. 0 119
      Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs
  33. 0 1
      Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs
  34. 1 1
      docfx/docs/drawing.md
  35. 1 1
      docfx/docs/events.md
  36. 1 1
      docfx/docs/index.md
  37. 1 1
      docfx/docs/keyboard.md
  38. 1 1
      docfx/docs/mouse.md
  39. 23 0
      docfx/docs/newinv2.md
  40. 1 1
      docfx/docs/toc.yml
  41. BIN
      local_packages/Terminal.Gui.2.0.0.nupkg
  42. BIN
      local_packages/Terminal.Gui.2.0.0.snupkg

+ 0 - 1
Examples/UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using Terminal.Gui;
-using Terminal.Gui.ConsoleDrivers;
 
 namespace UICatalog.Scenarios;
 

+ 9 - 11
Terminal.Gui/Application/Application.Run.cs

@@ -150,10 +150,10 @@ public static partial class Application // Run (Begin, Run, End, Stop)
                 }
             }
 
-            if (TopLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
-            {
-                throw new ArgumentException ("There are duplicates Toplevel IDs");
-            }
+            //if (TopLevels.FindDuplicates (new ToplevelEqualityComparer ()).Count > 0)
+            //{
+            //    throw new ArgumentException ("There are duplicates Toplevel IDs");
+            //}
         }
 
         if (Top is null)
@@ -588,25 +588,23 @@ public static partial class Application // Run (Begin, Run, End, Stop)
 
         // End the RunState.Toplevel
         // First, take it off the Toplevel Stack
-        if (TopLevels.Count > 0)
+        if (TopLevels.TryPop (out Toplevel? topOfStack))
         {
-            if (TopLevels.Peek () != runState.Toplevel)
+            if (topOfStack != runState.Toplevel)
             {
                 // If 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 (TopLevels.Count > 0)
+        if (TopLevels.TryPeek (out Toplevel? newTop))
         {
-            Top = TopLevels.Peek ();
-            Top.SetNeedsDraw ();
+            Top = newTop;
+            Top?.SetNeedsDraw ();
         }
 
         if (runState.Toplevel is { HasFocus: true })

+ 15 - 1
Terminal.Gui/Application/Application.Toplevel.cs

@@ -1,12 +1,26 @@
 #nullable enable
+using System.Collections.Concurrent;
+
 namespace Terminal.Gui;
 
 public static partial class Application // Toplevel handling
 {
     // BUGBUG: Technically, this is not the full lst of TopLevels. There be dragons here, e.g. see how Toplevel.Id is used. What
 
+    private static readonly ConcurrentStack<Toplevel> _topLevels = new ();
+    private static readonly object _topLevelsLock = new ();
+
     /// <summary>Holds the stack of TopLevel views.</summary>
-    internal static Stack<Toplevel> TopLevels { get; } = new ();
+    internal static ConcurrentStack<Toplevel> TopLevels
+    {
+        get
+        {
+            lock (_topLevelsLock)
+            {
+                return _topLevels;
+            }
+        }
+    }
 
     /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>

+ 11 - 10
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -8,7 +8,6 @@ using System.Reflection;
 using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Serialization;
-using Terminal.Gui.Configuration;
 
 namespace Terminal.Gui;
 
@@ -150,7 +149,7 @@ public static class ConfigurationManager
     private static ImmutableSortedDictionary<string, ConfigProperty>? _uninitializedConfigPropertiesCache;
 
 #pragma warning disable IDE1006 // Naming Styles
-    private static readonly object __uninitializedConfigPropertiesCacheCacheLock = new ();
+    private static readonly object _uninitializedConfigPropertiesCacheCacheLock = new ();
 #pragma warning restore IDE1006 // Naming Styles
 
     /// <summary>
@@ -181,7 +180,7 @@ public static class ConfigurationManager
         ConfigProperty.Initialize ();
 
         // Cache all configuration properties
-        lock (__uninitializedConfigPropertiesCacheCacheLock)
+        lock (_uninitializedConfigPropertiesCacheCacheLock)
         {
             // _allConfigProperties: for ordered, iterable access (LINQ-friendly)
             // _frozenConfigPropertyCache: for high-speed key lookup (frozen)
@@ -777,15 +776,17 @@ public static class ConfigurationManager
             return _uninitializedConfigPropertiesCache;
         }
 
-        // Filter properties by scope using the cached ScopeType property instead of reflection
-        IEnumerable<KeyValuePair<string, ConfigProperty>>? filtered = _uninitializedConfigPropertiesCache?.Where (cp => cp.Value.ScopeType == scopeType);
-
-        Debug.Assert (filtered is { });
+        lock (_uninitializedConfigPropertiesCacheCacheLock)
+        {
+            // Filter properties by scope using the cached ScopeType property instead of reflection
+            IEnumerable<KeyValuePair<string, ConfigProperty>>? filtered = _uninitializedConfigPropertiesCache?.Where (cp => cp.Value.ScopeType == scopeType);
 
-        IEnumerable<KeyValuePair<string, ConfigProperty>> configPropertiesByScope = filtered as KeyValuePair<string, ConfigProperty> [] ?? filtered.ToArray ();
-        Debug.Assert (configPropertiesByScope.All (v => !v.Value.HasValue));
+            Debug.Assert (filtered is { });
 
-        return configPropertiesByScope;
+            IEnumerable<KeyValuePair<string, ConfigProperty>> configPropertiesByScope = filtered as KeyValuePair<string, ConfigProperty> [] ?? filtered.ToArray ();
+            Debug.Assert (configPropertiesByScope.All (v => !v.Value.HasValue));
+            return configPropertiesByScope;
+        }
     }
 
     /// <summary>

+ 22 - 11
Terminal.Gui/Configuration/DeepCloner.cs

@@ -285,21 +285,32 @@ public static class DeepCloner
         IDictionary tempDict = CreateDictionaryInstance (dictType, comparer);
         visited.TryAdd (source, tempDict);
 
-        // Clone all key-value pairs
-        foreach (object? key in sourceDict.Keys)
-        {
-            object? clonedKey = DeepCloneInternal (key, visited);
-            object? clonedValue = DeepCloneInternal (sourceDict [key], visited);
 
-            if (tempDict.Contains (clonedKey!))
-            {
-                tempDict [clonedKey!] = clonedValue;
-            }
-            else
+        object? lastKey = null;
+        try
+        {
+            // Clone all key-value pairs
+            foreach (object? key in sourceDict.Keys)
             {
-                tempDict.Add (clonedKey!, clonedValue);
+                lastKey = key;
+                object? clonedKey = DeepCloneInternal (key, visited);
+                object? clonedValue = DeepCloneInternal (sourceDict [key], visited);
+
+                if (tempDict.Contains (clonedKey!))
+                {
+                    tempDict [clonedKey!] = clonedValue;
+                }
+                else
+                {
+                    tempDict.Add (clonedKey!, clonedValue);
+                }
             }
         }
+        catch (InvalidOperationException ex)
+        {
+            // Handle cases where the dictionary is modified during enumeration
+            throw new InvalidOperationException ($"Error cloning dictionary ({source}) (last key was \"{lastKey}\"). Ensure the source dictionary is not modified during cloning.", ex);
+        }
 
         // If the original dictionary type has a parameterless constructor, create a new instance
         if (type.GetConstructor (Type.EmptyTypes) != null)

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

@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
 using System.Text.Json;
 
-namespace Terminal.Gui.Configuration;
+namespace Terminal.Gui;
 
 /// <summary>
 ///    Manages the <see cref="ConfigurationManager"/> Sources and provides the API for loading them. Source is a location where a configuration can be stored. Sources are defined in <see cref="ConfigLocations"/>.

+ 4 - 0
Terminal.Gui/Configuration/ThemeManager.cs

@@ -101,6 +101,10 @@ public static class ThemeManager
     /// <returns></returns>
     public static string GetCurrentThemeName () { return Theme!; }
 
+    // TODO: Add a lock around Theme and Themes
+    // TODO: For now, this test can't run in parallel with other tests that access Theme or Themes.
+    // TODO: ThemeScopeList_WithThemes_ClonesSuccessfully
+
     /// <summary>
     ///     Gets the Themes dictionary. <see cref="GetThemes"/> is preferred.
     ///     The backing store is <c><see cref="ConfigurationManager.Settings"/> ["Themes"]</c>.

+ 1 - 1
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -1,7 +1,7 @@
 using System.Globalization;
 using System.Runtime.InteropServices;
 
-namespace Terminal.Gui.ConsoleDrivers;
+namespace Terminal.Gui;
 
 // QUESTION: This class combines Windows specific code with cross-platform code. Should this be split into two classes?
 /// <summary>Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.</summary>

+ 10 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -4,7 +4,6 @@
 //
 
 using System.Runtime.InteropServices;
-using Terminal.Gui.ConsoleDrivers;
 using Unix.Terminal;
 
 namespace Terminal.Gui;
@@ -777,6 +776,10 @@ internal class CursesDriver : ConsoleDriver
                 wch -= 60;
                 k = KeyCode.ShiftMask | KeyCode.AltMask | MapCursesKey (wch);
             }
+            else if (wch == 520) // Ctrl+Delete
+            {
+                k = KeyCode.CtrlMask | KeyCode.Delete;
+            }
 
             OnKeyDown (new Key (k));
             OnKeyUp (new Key (k));
@@ -878,6 +881,12 @@ internal class CursesDriver : ConsoleDriver
             OnKeyDown (key);
             OnKeyUp (key);
         }
+        else if (wch == 8) // Ctrl+Backspace
+        {
+            k = KeyCode.Backspace | KeyCode.CtrlMask;
+            OnKeyDown (new Key (k));
+            OnKeyUp (new Key (k));
+        }
         else if (wch == Curses.KeyTab)
         {
             k = MapCursesKey (wch);

+ 18 - 8
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -1,6 +1,5 @@
 #nullable enable
 using System.Globalization;
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui;
 
@@ -1123,6 +1122,17 @@ public static class EscSeqUtils
                                              (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
                                              true);
                 }
+                else if (consoleKeyInfo is { Key: 0, KeyChar: '\b' })
+                {
+                    key = ConsoleKey.Backspace;
+
+                    newConsoleKeyInfo = new (
+                                             consoleKeyInfo.KeyChar,
+                                             key,
+                                             (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
+                                             (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
+                                             true);
+                }
                 else if (consoleKeyInfo.Key == 0)
                 {
                     key = (ConsoleKey)(char)(consoleKeyInfo.KeyChar + (uint)ConsoleKey.A - 1);
@@ -1148,7 +1158,7 @@ public static class EscSeqUtils
 
                 break;
             default:
-                uint ck = MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey);
+                uint ck = ConsoleKeyMapping.MapKeyCodeToConsoleKey ((KeyCode)consoleKeyInfo.KeyChar, out bool isConsoleKey);
 
                 if (isConsoleKey)
                 {
@@ -1404,12 +1414,12 @@ public static class EscSeqUtils
                 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);
+                    return ConsoleKeyMapping.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);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
         }
 
         // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
@@ -1420,14 +1430,14 @@ public static class EscSeqUtils
                 return KeyCode.Tab;
             }
 
-            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+            return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)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 ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
         }
 
         if ((ConsoleKey)keyInfo.KeyChar is >= ConsoleKey.A and <= ConsoleKey.Z)
@@ -1458,7 +1468,7 @@ public static class EscSeqUtils
                 || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
             {
                 // NetDriver doesn't support Shift-Ctrl/Shift-Alt combos
-                return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.Key);
             }
 
             if (keyInfo.Modifiers == ConsoleModifiers.Shift)
@@ -1473,7 +1483,7 @@ public static class EscSeqUtils
             return (KeyCode)keyInfo.Key;
         }
 
-        return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+        return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
     }
 
     private static async Task ProcessButtonClickedAsync ()

+ 0 - 2
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -2,8 +2,6 @@
 // FakeConsole.cs: A fake .NET Windows Console API implementation for unit tests.
 //
 
-using Terminal.Gui.ConsoleDrivers;
-
 namespace Terminal.Gui;
 
 #pragma warning disable RCS1138 // Add summary to documentation comment.

+ 0 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -4,7 +4,6 @@
 
 using System.Diagnostics;
 using System.Runtime.InteropServices;
-using Terminal.Gui.ConsoleDrivers;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 

+ 33 - 8
Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs

@@ -81,7 +81,7 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
 
     public MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
-        var mouseFlags = MouseFlags.ReportMousePosition;
+        var mouseFlags = MouseFlags.None;
 
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
@@ -100,9 +100,6 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
             {
                 mouseFlags |= MouseFlags.Button3Released;
 
-                // Removes the moved flag when raising released flags (see https://github.com/gui-cs/Terminal.Gui/issues/4088)
-                mouseFlags &= ~MouseFlags.ReportMousePosition;
-
                 _lastWasPressed [2] = false;
             }
         }
@@ -123,14 +120,44 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
             }
         }
 
+        if (e.EventFlags != EventFlags.NoEvent)
+        {
+            switch (e.EventFlags)
+            {
+                case EventFlags.MouseMoved:
+                    mouseFlags |= MouseFlags.ReportMousePosition;
+
+                    break;
+            }
+        }
+
+        if (e.ControlKeyState != ControlKeyState.NoControlKeyPressed)
+        {
+            switch (e.ControlKeyState)
+            {
+                case ControlKeyState.RightAltPressed:
+                case ControlKeyState.LeftAltPressed:
+                    mouseFlags |= MouseFlags.ButtonAlt;
+
+                    break;
+                case ControlKeyState.RightControlPressed:
+                case ControlKeyState.LeftControlPressed:
+                    mouseFlags |= MouseFlags.ButtonCtrl;
+
+                    break;
+                case ControlKeyState.ShiftPressed:
+                    mouseFlags |= MouseFlags.ButtonShift;
+
+                    break;
+            }
+        }
+
         var result = new MouseEventArgs
         {
             Position = new (e.MousePosition.X, e.MousePosition.Y),
             Flags = mouseFlags
         };
 
-        // TODO: Return keys too
-
         return result;
     }
 
@@ -154,8 +181,6 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
             {
                 current |= releasedFlag;
 
-                // Removes the moved flag when raising released flags (see https://github.com/gui-cs/Terminal.Gui/issues/4088)
-                current &= ~MouseFlags.ReportMousePosition;
                 _lastWasPressed [buttonIndex] = false;
             }
         }

+ 0 - 1
Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs

@@ -1,5 +1,4 @@
 #nullable enable
-using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 

+ 0 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs

@@ -2,7 +2,6 @@
 using System.Collections.Concurrent;
 using System.ComponentModel;
 using System.Runtime.InteropServices;
-using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 

+ 32 - 27
Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs

@@ -19,8 +19,6 @@
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
-using static Terminal.Gui.SpinnerStyle;
 
 namespace Terminal.Gui;
 
@@ -76,7 +74,7 @@ internal class WindowsDriver : ConsoleDriver
 
     public static WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
     {
-        if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet)
+        if (keyEvent.wVirtualKeyCode != (ConsoleKeyMapping.VK)ConsoleKey.Packet)
         {
             return keyEvent;
         }
@@ -106,8 +104,8 @@ internal class WindowsDriver : ConsoleDriver
                                            mod.HasFlag (ConsoleModifiers.Shift),
                                            mod.HasFlag (ConsoleModifiers.Alt),
                                            mod.HasFlag (ConsoleModifiers.Control));
-        cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
-        uint scanCode = GetScanCodeFromConsoleKeyInfo (cKeyInfo);
+        cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+        uint scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (cKeyInfo);
 
         return new WindowsConsole.KeyEventRecord
         {
@@ -115,7 +113,7 @@ internal class WindowsDriver : ConsoleDriver
             bKeyDown = keyEvent.bKeyDown,
             dwControlKeyState = keyEvent.dwControlKeyState,
             wRepeatCount = keyEvent.wRepeatCount,
-            wVirtualKeyCode = (VK)cKeyInfo.Key,
+            wVirtualKeyCode = (ConsoleKeyMapping.VK)cKeyInfo.Key,
             wVirtualScanCode = (ushort)scanCode
         };
     }
@@ -139,7 +137,7 @@ internal class WindowsDriver : ConsoleDriver
         {
             controlKey |= WindowsConsole.ControlKeyState.ShiftPressed;
             keyEvent.UnicodeChar = '\0';
-            keyEvent.wVirtualKeyCode = VK.SHIFT;
+            keyEvent.wVirtualKeyCode = ConsoleKeyMapping.VK.SHIFT;
         }
 
         if (alt)
@@ -147,7 +145,7 @@ internal class WindowsDriver : ConsoleDriver
             controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed;
             controlKey |= WindowsConsole.ControlKeyState.RightAltPressed;
             keyEvent.UnicodeChar = '\0';
-            keyEvent.wVirtualKeyCode = VK.MENU;
+            keyEvent.wVirtualKeyCode = ConsoleKeyMapping.VK.MENU;
         }
 
         if (control)
@@ -155,7 +153,7 @@ internal class WindowsDriver : ConsoleDriver
             controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed;
             controlKey |= WindowsConsole.ControlKeyState.RightControlPressed;
             keyEvent.UnicodeChar = '\0';
-            keyEvent.wVirtualKeyCode = VK.CONTROL;
+            keyEvent.wVirtualKeyCode = ConsoleKeyMapping.VK.CONTROL;
         }
 
         keyEvent.dwControlKeyState = controlKey;
@@ -174,7 +172,7 @@ internal class WindowsDriver : ConsoleDriver
         //} else {
         //	keyEvent.wVirtualKeyCode = '\0';
         //}
-        keyEvent.wVirtualKeyCode = (VK)key;
+        keyEvent.wVirtualKeyCode = (ConsoleKeyMapping.VK)key;
 
         input.KeyEvent = keyEvent;
 
@@ -492,7 +490,7 @@ internal class WindowsDriver : ConsoleDriver
         switch (inputEvent.EventType)
         {
             case WindowsConsole.EventType.Key:
-                if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet)
+                if (inputEvent.KeyEvent.wVirtualKeyCode == (ConsoleKeyMapping.VK)ConsoleKey.Packet)
                 {
                     // Used to pass Unicode characters as if they were keystrokes.
                     // The VK_PACKET key is the low word of a 32-bit
@@ -681,7 +679,7 @@ internal class WindowsDriver : ConsoleDriver
             case ConsoleKey.OemMinus:
                 // These virtual key codes are mapped differently depending on the keyboard layout in use.
                 // We use the Win32 API to map them to the correct character.
-                uint mapResult = MapVKtoChar ((VK)keyInfo.Key);
+                uint mapResult = ConsoleKeyMapping.MapVKtoChar ((ConsoleKeyMapping.VK)keyInfo.Key);
 
                 if (mapResult == 0)
                 {
@@ -738,7 +736,7 @@ internal class WindowsDriver : ConsoleDriver
 
                     // Return the mappedChar with modifiers. Because mappedChar is un-shifted, if Shift was down
                     // we should keep it
-                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
+                    return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
                 }
 
                 // KeyChar is printable
@@ -751,12 +749,12 @@ internal class WindowsDriver : ConsoleDriver
                 if (keyInfo.Modifiers != ConsoleModifiers.Shift)
                 {
                     // If Shift wasn't down we don't need to do anything but return the mappedChar
-                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
+                    return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
                 }
 
                 // 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);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
         }
 
         // A..Z are special cased:
@@ -772,13 +770,13 @@ internal class WindowsDriver : ConsoleDriver
                 // AltGr support - AltGr is equivalent to Ctrl+Alt
                 if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
                 {
-                    return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+                    return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
                 }
             }
 
             if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control))
             {
-                return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
             }
 
             if ((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ keyInfoEx.CapsLock)
@@ -789,11 +787,11 @@ internal class WindowsDriver : ConsoleDriver
                     if (keyInfo.KeyChar <= 'Z')
                     {
                         return (KeyCode)keyInfo.Key | KeyCode.ShiftMask;
-                }
+                    }
 
                     // Always return the KeyChar because it may be an Á, À with Oem1, etc
                     return (KeyCode)keyInfo.KeyChar;
-            }
+                }
             }
 
             if (keyInfo.KeyChar <= 'z')
@@ -806,44 +804,51 @@ internal class WindowsDriver : ConsoleDriver
         }
 
         // Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+        // Also handle the key ASCII value 127 (BACK)
         if (Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key))
         {
             // If the key is JUST a modifier, return it as just that key
-            if (keyInfo.Key == (ConsoleKey)VK.SHIFT)
+            if (keyInfo.Key == (ConsoleKey)ConsoleKeyMapping.VK.SHIFT)
             { // Shift 16
                 return KeyCode.ShiftMask;
             }
 
-            if (keyInfo.Key == (ConsoleKey)VK.CONTROL)
+            if (keyInfo.Key == (ConsoleKey)ConsoleKeyMapping.VK.CONTROL)
             { // Ctrl 17
                 return KeyCode.CtrlMask;
             }
 
-            if (keyInfo.Key == (ConsoleKey)VK.MENU)
+            if (keyInfo.Key == (ConsoleKey)ConsoleKeyMapping.VK.MENU)
             { // Alt 18
                 return KeyCode.AltMask;
             }
 
             if (keyInfo.KeyChar == 0)
             {
-                return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+            }
+
+            // Backspace (ASCII 127)
+            if (keyInfo.KeyChar == '\u007f')
+            {
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.Key);
             }
 
             if (keyInfo.Key != ConsoleKey.None)
             {
-                return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+                return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
             }
 
-            return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+            return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
         }
 
         // Handle control keys (e.g. CursorUp)
         if (Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))
         {
-            return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
+            return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
         }
 
-        return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
+        return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
     }
 
     private MouseFlags ProcessButtonClick (WindowsConsole.MouseEventRecord mouseEvent)

+ 1 - 1
Terminal.Gui/Text/Autocomplete/AutocompleteBase.cs

@@ -43,7 +43,7 @@ public abstract class AutocompleteBase : IAutocomplete
     public virtual Key CloseKey { get; set; } = Key.Esc;
 
     /// <inheritdoc/>
-    public virtual Key Reopen { get; set; } = Key.Space.WithCtrl.WithAlt;
+    public virtual Key Reopen { get; set; } = Key.Space.WithShift;
 
     /// <inheritdoc/>
     public virtual AutocompleteContext Context { get; set; }

+ 4 - 2
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -285,7 +285,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         if (PopupInsideContainer)
         {
             // don't overspill vertically
-            height = Math.Min (HostControl.Viewport.Height - renderAt.Y, MaxHeight);
+            height = Math.Min (Math.Min (HostControl!.Viewport.Height - renderAt.Y, MaxHeight), Suggestions.Count);
 
             // There is no space below, lets see if can popup on top
             if (height < Suggestions.Count && HostControl.Viewport.Height - renderAt.Y >= height)
@@ -419,8 +419,9 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         ClearSuggestions ();
         Visible = false;
         _closed = true;
-        HostControl?.SetNeedsDraw ();
         //RemovePopupFromTop ();
+        _popup.Visible = false;
+        HostControl?.SetNeedsDraw ();
     }
 
     /// <summary>Deletes the text backwards before insert the selected text in the <see cref="HostControl"/>.</summary>
@@ -507,6 +508,7 @@ public abstract partial class PopupAutocomplete : AutocompleteBase
         {
             Visible = true;
             _closed = false;
+            _popup.Visible = true;
             HostControl?.SetNeedsDraw ();
 
             return true;

+ 0 - 8
Terminal.Gui/View/View.Layout.cs

@@ -800,14 +800,6 @@ public partial class View // Layout APIs
             SuperView?.SetNeedsLayout ();
         }
 
-        if (SuperView is null)
-        {
-            foreach (Toplevel tl in Application.TopLevels)
-            {
-                // tl.SetNeedsDraw ();
-            }
-        }
-
         if (this is not Adornment adornment)
         {
             return;

+ 2 - 1
Terminal.Gui/Views/TextInput/TextField.cs

@@ -1015,7 +1015,6 @@ public class TextField : View, IDesignable
 
         RenderCaption ();
 
-        DrawAutocomplete ();
         _isDrawing = false;
 
         return true;
@@ -1686,6 +1685,8 @@ public class TextField : View, IDesignable
         }
 
         GenerateSuggestions ();
+
+        DrawAutocomplete ();
     }
 
     private void DrawAutocomplete ()

+ 3 - 1
Terminal.Gui/Views/TextInput/TextModel.cs

@@ -269,9 +269,11 @@ internal class TextModel
 
                     if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
                     {
+                        List<Cell> line = GetLine (nRow);
+
                         if (lastValidCol > -1)
                         {
-                            nCol = lastValidCol;
+                            nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
                         }
 
                         return;

+ 37 - 16
Terminal.Gui/Views/TextInput/TextView.cs

@@ -1715,6 +1715,7 @@ public class TextView : View, IDesignable
             PositionCursor ();
             _lastWasKill = false;
             _columnTrack = CurrentColumn;
+            SetNeedsDraw ();
         }
         else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked))
         {
@@ -1735,6 +1736,7 @@ public class TextView : View, IDesignable
             PositionCursor ();
             _lastWasKill = false;
             _columnTrack = CurrentColumn;
+            SetNeedsDraw ();
         }
         else if (ev.Flags == ContextMenu!.MouseFlags)
         {
@@ -1747,6 +1749,8 @@ public class TextView : View, IDesignable
             //ShowContextMenu ();
         }
 
+        OnUnwrappedCursorPosition ();
+
         return true;
     }
 
@@ -1757,7 +1761,7 @@ public class TextView : View, IDesignable
         List<Cell> line = GetCurrentLine ();
         CurrentColumn = line.Count;
         TrackColumn ();
-        PositionCursor ();
+        DoNeededAction ();
     }
 
     /// <summary>Will scroll the <see cref="TextView"/> to the first line and position the cursor there.</summary>
@@ -1768,8 +1772,7 @@ public class TextView : View, IDesignable
         CurrentColumn = 0;
         _leftColumn = 0;
         TrackColumn ();
-        PositionCursor ();
-        SetNeedsDraw ();
+        DoNeededAction ();
     }
 
     /// <summary>
@@ -2661,6 +2664,11 @@ public class TextView : View, IDesignable
 
     private void DoNeededAction ()
     {
+        if (!NeedsDraw && (IsSelecting || _wrapNeeded || !Used))
+        {
+            SetNeedsDraw ();
+        }
+
         if (NeedsDraw)
         {
             Adjust ();
@@ -2668,6 +2676,7 @@ public class TextView : View, IDesignable
         else
         {
             PositionCursor ();
+            OnUnwrappedCursorPosition ();
         }
     }
 
@@ -3385,8 +3394,24 @@ public class TextView : View, IDesignable
         }
         else if (newPos.HasValue)
         {
-            int restCount = currentLine.Count - CurrentColumn;
-            currentLine.RemoveRange (CurrentColumn, restCount);
+            int restCount;
+
+            if (newPos.Value.row == CurrentRow)
+            {
+                restCount = currentLine.Count - CurrentColumn;
+                currentLine.RemoveRange (CurrentColumn, restCount);
+            }
+            else
+            {
+                while (CurrentRow != newPos.Value.row)
+                {
+                    restCount = currentLine.Count;
+                    currentLine.RemoveRange (0, restCount);
+
+                    CurrentRow--;
+                    currentLine = GetCurrentLine ();
+                }
+            }
 
             if (_wordWrap)
             {
@@ -3540,8 +3565,7 @@ public class TextView : View, IDesignable
     private void MoveEndOfLine ()
     {
         List<Cell> currentLine = GetCurrentLine ();
-        CurrentColumn = Math.Max (currentLine.Count - (ReadOnly ? 1 : 0), 0);
-        Adjust ();
+        CurrentColumn = currentLine.Count;
         DoNeededAction ();
     }
 
@@ -3572,7 +3596,6 @@ public class TextView : View, IDesignable
             }
         }
 
-        Adjust ();
         DoNeededAction ();
 
         return true;
@@ -3654,10 +3677,6 @@ public class TextView : View, IDesignable
                     _topRow++;
                     SetNeedsDraw ();
                 }
-                else
-                {
-                    return false;
-                }
             }
             else
             {
@@ -3665,7 +3684,6 @@ public class TextView : View, IDesignable
             }
         }
 
-        Adjust ();
         DoNeededAction ();
 
         return true;
@@ -3680,7 +3698,6 @@ public class TextView : View, IDesignable
 
         CurrentColumn = 0;
         _leftColumn = 0;
-        Adjust ();
         DoNeededAction ();
     }
 
@@ -3743,7 +3760,6 @@ public class TextView : View, IDesignable
             CurrentRow = newPos.Value.row;
         }
 
-        Adjust ();
         DoNeededAction ();
     }
 
@@ -3757,7 +3773,6 @@ public class TextView : View, IDesignable
             CurrentRow = newPos.Value.row;
         }
 
-        Adjust ();
         DoNeededAction ();
     }
 
@@ -4031,6 +4046,7 @@ public class TextView : View, IDesignable
     private void ProcessKillWordForward ()
     {
         ResetColumnTrack ();
+        StopSelecting ();
         KillWordForward ();
     }
 
@@ -4578,6 +4594,11 @@ public class TextView : View, IDesignable
 
     private void StopSelecting ()
     {
+        if (IsSelecting)
+        {
+            SetNeedsDraw ();
+        }
+
         _shiftSelecting = false;
         IsSelecting = false;
         _isButtonShift = false;

+ 0 - 1
Tests/TerminalGuiFluentTesting/GuiTestContext.cs

@@ -2,7 +2,6 @@
 using System.Text;
 using Microsoft.Extensions.Logging;
 using Terminal.Gui;
-using Terminal.Gui.ConsoleDrivers;
 
 namespace TerminalGuiFluentTesting;
 

+ 12 - 12
Tests/UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -414,7 +414,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
                                 """;
 
             // Update default config first (lower precedence)
-            SourcesManager?.Load (Settings, defaultConfig, "default-test", ConfigLocations.LibraryResources);
+            ConfigurationManager.SourcesManager?.Load (Settings, defaultConfig, "default-test", ConfigLocations.LibraryResources);
 
             // Then load runtime config, which should override default
             Load (ConfigLocations.Runtime);
@@ -447,7 +447,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
             ResetToHardCodedDefaults ();
 
             // Serialize to a JSON string
-            string json = SourcesManager?.ToJson (Settings);
+            string json = ConfigurationManager.SourcesManager?.ToJson (Settings);
 
             // Write the JSON string to the file
             File.WriteAllText ("hard_coded_defaults_config.json", json);
@@ -476,7 +476,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
             Enable (ConfigLocations.LibraryResources);
 
             // Serialize to a JSON string
-            string json = SourcesManager?.ToJson (Settings);
+            string json = ConfigurationManager.SourcesManager?.ToJson (Settings);
 
             // Write the JSON string to the file
             File.WriteAllText ("library_defaults_config.json", json);
@@ -688,7 +688,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				}
 			}";
 
-        SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
+        ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
 
         // AbNormal is not a Scheme attribute
         json = @"
@@ -711,7 +711,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				}
 			}";
 
-        SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
+        ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
 
         // Modify hotNormal background only
         json = @"
@@ -733,9 +733,9 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				}
 			}";
 
-        SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
+        ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime);
 
-        SourcesManager?.Load (Settings, "{}}", "test", ConfigLocations.Runtime);
+        ConfigurationManager.SourcesManager?.Load (Settings, "{}}", "test", ConfigLocations.Runtime);
 
         Assert.NotEqual (0, _jsonErrors.Length);
 
@@ -773,7 +773,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				]
 			}";
 
-        var jsonException = Assert.Throws<JsonException> (() => SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
+        var jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
         Assert.StartsWith ("foreground: \"\"brownish\"\"", jsonException.Message);
 
         // AbNormal is not a Scheme attribute
@@ -797,7 +797,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				]
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
+        jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
         Assert.StartsWith ("AbNormal:", jsonException.Message);
 
         // Modify hotNormal background only
@@ -820,7 +820,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				]
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
+        jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
         Assert.StartsWith ("background:", jsonException.Message);
 
         // Unknown property
@@ -829,7 +829,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
 				""Unknown"" : ""Not known""
 			}";
 
-        jsonException = Assert.Throws<JsonException> (() => SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
+        jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.SourcesManager?.Load (Settings, json, "test", ConfigLocations.Runtime));
         Assert.StartsWith ("Unknown:", jsonException.Message);
 
         Assert.Equal (0, _jsonErrors.Length);
@@ -989,7 +989,7 @@ public class ConfigurationManagerTests (ITestOutputHelper output)
             ResetToCurrentValues ();
             ThrowOnJsonErrors = true;
 
-            SourcesManager?.Load (Settings, json, "UpdateFromJson", ConfigLocations.Runtime);
+            ConfigurationManager.SourcesManager?.Load (Settings, json, "UpdateFromJson", ConfigLocations.Runtime);
 
             Assert.Equal ("Default", ThemeManager.Theme);
 

+ 142 - 12
Tests/UnitTests/Configuration/SettingsScopeTests.cs

@@ -1,5 +1,5 @@
-using System.Collections.Concurrent;
-using UnitTests;
+#nullable enable
+using System.Collections.Concurrent;
 using static Terminal.Gui.ConfigurationManager;
 
 namespace Terminal.Gui.ConfigurationTests;
@@ -12,7 +12,7 @@ public class SettingsScopeTests
         // arrange
         Enable (ConfigLocations.HardCoded);
 
-        Assert.Equal (Key.Esc, (Key)Settings! ["Application.QuitKey"].PropertyValue);
+        Assert.Equal (Key.Esc, (Key)Settings! ["Application.QuitKey"].PropertyValue!);
 
         ThrowOnJsonErrors = true;
 
@@ -27,7 +27,7 @@ public class SettingsScopeTests
         Load (ConfigLocations.Runtime);
 
         // assert
-        Assert.Equal (Key.Q.WithCtrl, (Key)Settings ["Application.QuitKey"].PropertyValue);
+        Assert.Equal (Key.Q.WithCtrl, (Key)Settings ["Application.QuitKey"].PropertyValue!);
 
         // clean up
         Disable (resetToHardCodedDefaults: true);
@@ -43,11 +43,11 @@ public class SettingsScopeTests
         ThrowOnJsonErrors = true;
 
         ConfigProperty themesConfigProperty = Settings! ["Themes"];
-        ConcurrentDictionary<string, ThemeScope> dict = themesConfigProperty.PropertyValue as ConcurrentDictionary<string, ThemeScope>;
+        ConcurrentDictionary<string, ThemeScope> dict = (themesConfigProperty.PropertyValue as ConcurrentDictionary<string, ThemeScope>)!;
 
         Assert.NotNull (dict);
         Assert.Single (dict);
-        Assert.NotEmpty ((ConcurrentDictionary<string, ThemeScope>)themesConfigProperty.PropertyValue);
+        Assert.NotEmpty (((ConcurrentDictionary<string, ThemeScope>)themesConfigProperty.PropertyValue!)!);
 
         ThemeScope scope = dict [ThemeManager.DEFAULT_THEME_NAME];
         Assert.NotNull (scope);
@@ -111,16 +111,16 @@ public class SettingsScopeTests
         Load (ConfigLocations.LibraryResources);
 
         // arrange
-        Assert.Equal (Key.Esc, (Key)Settings! ["Application.QuitKey"].PropertyValue);
+        Assert.Equal (Key.Esc, (Key)Settings!["Application.QuitKey"].PropertyValue!);
 
         Assert.Equal (
                       Key.F6,
-                      (Key)Settings ["Application.NextTabGroupKey"].PropertyValue
+                      (Key)Settings["Application.NextTabGroupKey"].PropertyValue!
                      );
 
         Assert.Equal (
                       Key.F6.WithShift,
-                      (Key)Settings ["Application.PrevTabGroupKey"].PropertyValue
+                      (Key)Settings["Application.PrevTabGroupKey"].PropertyValue!
                      );
 
         // act
@@ -154,9 +154,9 @@ public class SettingsScopeTests
         updatedSettings ["Application.PrevTabGroupKey"].PropertyValue = Key.B;
 
         Settings.UpdateFrom (updatedSettings);
-        Assert.Equal (KeyCode.End, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
-        Assert.Equal (KeyCode.F, ((Key)updatedSettings ["Application.NextTabGroupKey"].PropertyValue).KeyCode);
-        Assert.Equal (KeyCode.B, ((Key)updatedSettings ["Application.PrevTabGroupKey"].PropertyValue).KeyCode);
+        Assert.Equal (KeyCode.End, ((Key)Settings["Application.QuitKey"].PropertyValue!).KeyCode);
+        Assert.Equal (KeyCode.F, ((Key)updatedSettings["Application.NextTabGroupKey"].PropertyValue!).KeyCode);
+        Assert.Equal (KeyCode.B, ((Key)updatedSettings["Application.PrevTabGroupKey"].PropertyValue!).KeyCode);
         Disable (resetToHardCodedDefaults: true);
     }
 
@@ -218,4 +218,134 @@ public class SettingsScopeTests
 
         Disable (resetToHardCodedDefaults: true);
     }
+    
+    private class ConfigPropertyMock
+    {
+        public object? PropertyValue { get; init; }
+        public bool Immutable { get; init; }
+    }
+
+    private class SettingsScopeMock : Dictionary<string, ConfigPropertyMock>
+    {
+        public string? Theme { get; set; }
+    }
+
+
+    [Fact]
+    public void SettingsScopeMockWithKey_CreatesDeepCopy ()
+    {
+        SettingsScopeMock? source = new ()
+        {
+            Theme = "Dark",
+            ["KeyBinding"] = new () { PropertyValue = new Key (KeyCode.A) { Handled = true } },
+            ["Counts"] = new () { PropertyValue = new Dictionary<string, int> { { "X", 1 } } }
+        };
+        SettingsScopeMock? result = DeepCloner.DeepClone (source);
+
+        Assert.NotNull (result);
+        Assert.NotSame (source, result);
+        Assert.Equal (source.Theme, result!.Theme);
+        Assert.NotSame (source ["KeyBinding"], result ["KeyBinding"]);
+        Assert.NotSame (source ["Counts"], result ["Counts"]);
+
+        ConfigPropertyMock clonedKeyProp = result ["KeyBinding"];
+        var clonedKey = (Key)clonedKeyProp.PropertyValue!;
+        Assert.NotSame (source ["KeyBinding"].PropertyValue, clonedKey);
+        Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).KeyCode, clonedKey.KeyCode);
+        Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).Handled, clonedKey.Handled);
+
+        Assert.Equal ((Dictionary<string, int>)source ["Counts"].PropertyValue!, (Dictionary<string, int>)result ["Counts"].PropertyValue!);
+
+        // Modify result, ensure source unchanged
+        result.Theme = "Light";
+        clonedKey.Handled = false;
+        ((Dictionary<string, int>)result ["Counts"].PropertyValue!).Add ("Y", 2);
+        Assert.Equal ("Dark", source.Theme);
+        Assert.True (((Key)source ["KeyBinding"].PropertyValue!).Handled);
+        Assert.Single ((Dictionary<string, int>)source ["Counts"].PropertyValue!);
+        Disable (resetToHardCodedDefaults: true);
+    }
+
+    [Fact /*(Skip = "This test randomly fails due to a concurrent change to something. Needs to be moved to non-parallel tests.")*/]
+    public void ThemeScopeList_WithThemes_ClonesSuccessfully ()
+    {
+        // Arrange: Create a ThemeScope and verify a property exists
+        ThemeScope defaultThemeScope = new ThemeScope ();
+        defaultThemeScope.LoadHardCodedDefaults ();
+        Assert.True (defaultThemeScope.ContainsKey ("Button.DefaultHighlightStyle"));
+
+        ThemeScope darkThemeScope = new ThemeScope ();
+        darkThemeScope.LoadHardCodedDefaults ();
+        Assert.True (darkThemeScope.ContainsKey ("Button.DefaultHighlightStyle"));
+
+        // Create a Themes list with two themes
+        List<Dictionary<string, ThemeScope>> themesList =
+        [
+            new () { { "Default", defaultThemeScope } },
+            new () { { "Dark", darkThemeScope } }
+        ];
+
+        // Create a SettingsScope and set the Themes property
+        SettingsScope settingsScope = new SettingsScope ();
+        settingsScope.LoadHardCodedDefaults ();
+        Assert.True (settingsScope.ContainsKey ("Themes"));
+        settingsScope ["Themes"].PropertyValue = themesList;
+
+        // Act
+        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
+
+        // Assert
+        Assert.NotNull (result);
+        Assert.IsType<SettingsScope> (result);
+        SettingsScope resultScope = (SettingsScope)result;
+        Assert.True (resultScope.ContainsKey ("Themes"));
+
+        Assert.NotNull (resultScope ["Themes"].PropertyValue);
+
+        List<Dictionary<string, ThemeScope>> clonedThemes = (List<Dictionary<string, ThemeScope>>)resultScope ["Themes"].PropertyValue!;
+        Assert.Equal (2, clonedThemes.Count);
+        Disable (resetToHardCodedDefaults: true);
+    }
+
+    [Fact]
+    public void Empty_SettingsScope_ClonesSuccessfully ()
+    {
+        // Arrange: Create a SettingsScope 
+        var settingsScope = new SettingsScope ();
+        Assert.True (settingsScope.ContainsKey ("Themes"));
+
+        // Act
+        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
+
+        // Assert
+        Assert.NotNull (result);
+        Assert.IsType<SettingsScope> (result);
+
+        Assert.True (result.ContainsKey ("Themes"));
+        Disable (resetToHardCodedDefaults: true);
+    }
+
+    [Fact]
+    public void SettingsScope_With_Themes_Set_ClonesSuccessfully ()
+    {
+        // Arrange: Create a SettingsScope 
+        var settingsScope = new SettingsScope ();
+        Assert.True (settingsScope.ContainsKey ("Themes"));
+
+        settingsScope ["Themes"].PropertyValue = new List<Dictionary<string, ThemeScope>>
+        {
+            new() { { "Default", new () } },
+            new() { { "Dark", new () } }
+        };
+
+        // Act
+        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
+
+        // Assert
+        Assert.NotNull (result);
+        Assert.IsType<SettingsScope> (result);
+        Assert.True (result.ContainsKey ("Themes"));
+        Assert.NotNull (result ["Themes"].PropertyValue);
+        Disable (resetToHardCodedDefaults: true);
+    }
 }

+ 1 - 1
Tests/UnitTests/Configuration/ThemeManagerTests.cs

@@ -272,7 +272,7 @@ public class ThemeManagerTests (ITestOutputHelper output)
 
         output.WriteLine ($"Total Settings Size: {(MemorySizeEstimator.EstimateSize (Settings!)) / 1024} Kb");
 
-        string json = SourcesManager?.ToJson (Settings)!;
+        string json = ConfigurationManager.SourcesManager?.ToJson (Settings)!;
 
         // In memory size should be less than the size of the json
         output.WriteLine ($"JSON size: {json.Length / 1024} Kb");

+ 11 - 12
Tests/UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs

@@ -1,6 +1,5 @@
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
-namespace Terminal.Gui.ConsoleDrivers;
+namespace Terminal.Gui.ConsoleDriverTests;
 
 public class ConsoleKeyMappingTests
 {
@@ -365,7 +364,7 @@ public class ConsoleKeyMappingTests
     )
     {
         var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
-        KeyCode keyCode = MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+        KeyCode keyCode = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
 
         Assert.Equal (keyCode, expectedKeyCode);
     }
@@ -395,9 +394,9 @@ public class ConsoleKeyMappingTests
         KeyCode expectedKeyCode
     )
     {
-        ConsoleModifiers modifiers = GetModifiers (shift, alt, control);
+        ConsoleModifiers modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
         var keyCode = (KeyCode)keyChar;
-        keyCode = MapToKeyCodeModifiers (modifiers, keyCode);
+        keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
 
         Assert.Equal (keyCode, expectedKeyCode);
     }
@@ -414,7 +413,7 @@ public class ConsoleKeyMappingTests
     )
     {
         var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
-        uint scanCode = GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
+        uint scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
 
         Assert.Equal (scanCode, expectedScanCode);
     }
@@ -492,12 +491,12 @@ public class ConsoleKeyMappingTests
         KeyCode expectedKeyCode
     )
     {
-        ConsoleModifiers modifiers = GetModifiers (true, false, false);
-        uint keyChar = GetKeyChar (unicodeChar, modifiers);
+        ConsoleModifiers modifiers = ConsoleKeyMapping.GetModifiers (true, false, false);
+        uint keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
         Assert.Equal (keyChar, expectedKeyChar);
 
         var keyCode = (KeyCode)keyChar;
-        keyCode = MapToKeyCodeModifiers (modifiers, keyCode);
+        keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
 
         Assert.Equal (keyCode, expectedKeyCode);
     }
@@ -541,12 +540,12 @@ public class ConsoleKeyMappingTests
         KeyCode expectedKeyCode
     )
     {
-        ConsoleModifiers modifiers = GetModifiers (false, false, false);
-        uint keyChar = GetKeyChar (unicodeChar, modifiers);
+        ConsoleModifiers modifiers = ConsoleKeyMapping.GetModifiers (false, false, false);
+        uint keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
         Assert.Equal (keyChar, expectedKeyChar);
 
         var keyCode = (KeyCode)keyChar;
-        keyCode = MapToKeyCodeModifiers (modifiers, keyCode);
+        keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
 
         Assert.Equal (keyCode, expectedKeyCode);
     }

+ 132 - 38
Tests/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs

@@ -1,7 +1,8 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.ConsoleDrivers;
 using InputRecord = Terminal.Gui.WindowsConsole.InputRecord;
 using ButtonState = Terminal.Gui.WindowsConsole.ButtonState;
+using EventFlags = Terminal.Gui.WindowsConsole.EventFlags;
+using ControlKeyState = Terminal.Gui.WindowsConsole.ControlKeyState;
 using MouseEventRecord = Terminal.Gui.WindowsConsole.MouseEventRecord;
 
 namespace UnitTests.ConsoleDrivers.V2;
@@ -102,7 +103,7 @@ public class WindowsInputProcessorTests
                                MousePosition = new (32, 31),
                                ButtonState = ButtonState.NoButtonPressed,
                                ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
-                               EventFlags = WindowsConsole.EventFlags.MouseMoved
+                               EventFlags = EventFlags.MouseMoved
                            }
                        });
 
@@ -139,7 +140,7 @@ public class WindowsInputProcessorTests
                                MousePosition = new (32, 31),
                                ButtonState = state,
                                ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
-                               EventFlags = WindowsConsole.EventFlags.MouseMoved
+                               EventFlags = EventFlags.MouseMoved
                            }
                        });
 
@@ -199,9 +200,9 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Pressed),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -209,9 +210,9 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.Button2Pressed, MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button2Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button2Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button2Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -219,9 +220,9 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button3Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -229,9 +230,9 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.Button4Pressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button4Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button4Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button4Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.ReportMousePosition)
             }
         };
 
@@ -239,9 +240,9 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.RightmostButtonPressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -251,11 +252,11 @@ public class WindowsInputProcessorTests
             new []
             {
                 Tuple.Create (
-                              ButtonState.Button1Pressed | ButtonState.Button2Pressed,
+                              ButtonState.Button1Pressed | ButtonState.Button2Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed,
                               MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -264,11 +265,11 @@ public class WindowsInputProcessorTests
             new []
             {
                 Tuple.Create (
-                              ButtonState.Button3Pressed | ButtonState.Button4Pressed,
+                              ButtonState.Button3Pressed | ButtonState.Button4Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed,
                               MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.Button3Pressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -278,10 +279,10 @@ public class WindowsInputProcessorTests
             new []
             {
                 Tuple.Create (
-                              ButtonState.Button1Pressed | ButtonState.Button2Pressed,
+                              ButtonState.Button1Pressed | ButtonState.Button2Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed,
                               MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.Button2Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Released | MouseFlags.Button2Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
             }
         };
 
@@ -290,31 +291,124 @@ public class WindowsInputProcessorTests
         {
             new []
             {
-                Tuple.Create (ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed,
+                              MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
 
                 // Can swap between without raising the released
-                Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
-                Tuple.Create (ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.Button3Pressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create (ButtonState.RightmostButtonPressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
 
                 // Now with neither we get released
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released),
-                Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
+            }
+        };
+
+        // Test for ControlKeyState buttons pressed and handled
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.LeftAltPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftAltPressed, MouseFlags.Button1Released | MouseFlags.ButtonAlt),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftAltPressed, MouseFlags.None | MouseFlags.ButtonAlt)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.RightAltPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightAltPressed, MouseFlags.Button1Released | MouseFlags.ButtonAlt),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightAltPressed, MouseFlags.None | MouseFlags.ButtonAlt)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.LeftControlPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonCtrl),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftControlPressed, MouseFlags.Button1Released | MouseFlags.ButtonCtrl),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftControlPressed, MouseFlags.None | MouseFlags.ButtonCtrl)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.RightControlPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonCtrl),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightControlPressed, MouseFlags.Button1Released | MouseFlags.ButtonCtrl),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightControlPressed, MouseFlags.None | MouseFlags.ButtonCtrl)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.ShiftPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonShift),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ShiftPressed, MouseFlags.Button1Released | MouseFlags.ButtonShift),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ShiftPressed, MouseFlags.None | MouseFlags.ButtonShift)
+            }
+        };
+
+        // Test for ControlKeyState buttons pressed and not handled
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.Button1Pressed),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.None)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.Button1Pressed),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.None)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.Button1Pressed),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.None)
+            }
+        };
+
+        yield return new object []
+        {
+            new []
+            {
+                Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.Button1Pressed),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.Button1Released),
+                Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.None)
             }
         };
     }
 
     [Theory]
     [MemberData (nameof (MouseFlagTestData))]
-    internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, MouseFlags> [] inputOutputPairs)
+    internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, EventFlags, ControlKeyState, MouseFlags> [] inputOutputPairs)
     {
         var processor = new WindowsInputProcessor (new ());
 
-        foreach (Tuple<ButtonState, MouseFlags> pair in inputOutputPairs)
+        foreach (Tuple<ButtonState, EventFlags, ControlKeyState, MouseFlags> pair in inputOutputPairs)
         {
-            var mockEvent = new MouseEventRecord { ButtonState = pair.Item1 };
+            var mockEvent = new MouseEventRecord { ButtonState = pair.Item1, EventFlags = pair.Item2, ControlKeyState = pair.Item3};
             MouseEventArgs result = processor.ToDriverMouse (mockEvent);
 
-            Assert.Equal (pair.Item2, result.Flags);
+            Assert.Equal (pair.Item4, result.Flags);
         }
     }
 }

+ 20 - 7
Tests/UnitTests/Views/AppendAutocompleteTests.cs

@@ -10,10 +10,12 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
     public void TestAutoAppend_AfterCloseKey_NoAutocomplete ()
     {
         TextField tf = GetTextFieldsInViewSuggesting ("fish");
-        View.SetClipToScreen ();
+
         // f is typed and suggestion is "fish"
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
+        View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -22,8 +24,8 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('e', ConsoleKey.Escape, false, false, false);
 
         // Suggestion should disappear
-        View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         DriverAssert.AssertDriverContentsAre ("f", output);
         Assert.Equal ("f", tf.Text);
 
@@ -46,6 +48,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -54,16 +57,16 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('\0', ConsoleKey.Escape, false, false, false);
 
         // Suggestion should disappear
-        View.SetClipToScreen ();
         tf.Draw ();
         DriverAssert.AssertDriverContentsAre ("f", output);
         Assert.Equal ("f", tf.Text);
 
         // Should reappear when you press next letter
         Application.Driver?.SendKeys ('i', ConsoleKey.I, false, false, false);
-        tf.PositionCursor ();
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
+        tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("fi", tf.Text);
         Application.Top.Dispose ();
@@ -81,6 +84,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -90,6 +94,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
 
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("friend", output);
         Assert.Equal ("f", tf.Text);
@@ -98,6 +103,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys (' ', cycleKey, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -114,6 +120,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -139,6 +146,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -160,10 +168,11 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
 
         tf.Autocomplete = new AppendAutocomplete (tf);
         var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
-        generator.AllSuggestions = new List<string> { "FISH" };
+        generator.AllSuggestions = new() { "FISH" };
 
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("", output);
         tf.NewKeyDownEvent (Key.M);
@@ -175,6 +184,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         // Even though there is no match on case we should still get the suggestion
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("my fISH", output);
         Assert.Equal ("my f", tf.Text);
@@ -196,17 +206,19 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
 
         tf.Autocomplete = new AppendAutocomplete (tf);
         var generator = (SingleWordSuggestionGenerator)tf.Autocomplete.SuggestionGenerator;
-        generator.AllSuggestions = new List<string> { "fish" };
+        generator.AllSuggestions = new() { "fish" };
 
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("", output);
 
-        tf.NewKeyDownEvent (new Key ('f'));
+        tf.NewKeyDownEvent (new ('f'));
 
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre ("fish", output);
         Assert.Equal ("f", tf.Text);
@@ -240,6 +252,7 @@ public class AppendAutocompleteTests (ITestOutputHelper output)
         Application.Driver?.SendKeys ('f', ConsoleKey.F, false, false, false);
         View.SetClipToScreen ();
         tf.Draw ();
+        View.SetClipToScreen ();
         tf.PositionCursor ();
         DriverAssert.AssertDriverContentsAre (expectRender, output);
         Assert.Equal ("f", tf.Text);

+ 37 - 2
Tests/UnitTests/Views/TextViewTests.cs

@@ -4905,23 +4905,29 @@ This is the second line.
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.False (tv.ReadOnly);
         Assert.True (tv.CanFocus);
+        Assert.False (tv.IsSelecting);
 
         var g = (SingleWordSuggestionGenerator)tv.Autocomplete.SuggestionGenerator;
 
         tv.CanFocus = false;
         Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
+        Assert.False (tv.IsSelecting);
         tv.CanFocus = true;
         Assert.False (tv.NewKeyDownEvent (Key.CursorLeft));
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
         Assert.Equal (new (1, 0), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.End.WithCtrl));
         Assert.Equal (2, tv.CurrentRow);
         Assert.Equal (23, tv.CurrentColumn);
         Assert.Equal (tv.CurrentColumn, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 2), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.False (tv.NewKeyDownEvent (Key.CursorRight));
         Assert.NotNull (tv.Autocomplete);
         Assert.Empty (g.AllSuggestions);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.F.WithShift));
         tv.Draw ();
 
@@ -4931,6 +4937,7 @@ This is the second line.
                      );
         Assert.Equal (new (24, 2), tv.CursorPosition);
         Assert.Empty (tv.Autocomplete.Suggestions);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Z.WithCtrl));
         tv.Draw ();
 
@@ -4940,6 +4947,7 @@ This is the second line.
                      );
         Assert.Equal (new (23, 2), tv.CursorPosition);
         Assert.Empty (tv.Autocomplete.Suggestions);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.R.WithCtrl));
         tv.Draw ();
 
@@ -4949,6 +4957,7 @@ This is the second line.
                      );
         Assert.Equal (new (24, 2), tv.CursorPosition);
         Assert.Empty (tv.Autocomplete.Suggestions);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Backspace));
 
         Assert.Equal (
@@ -4969,6 +4978,7 @@ This is the second line.
         Assert.Equal ("line", g.AllSuggestions [4]);
         Assert.Equal ("second", g.AllSuggestions [5]);
         Assert.Equal ("third", g.AllSuggestions [^1]);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.F.WithShift));
         tv.Draw ();
 
@@ -4979,6 +4989,7 @@ This is the second line.
         Assert.Equal (new (24, 2), tv.CursorPosition);
         Assert.Single (tv.Autocomplete.Suggestions);
         Assert.Equal ("first", tv.Autocomplete.Suggestions [0].Replacement);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Enter));
 
         Assert.Equal (
@@ -4992,68 +5003,85 @@ This is the second line.
         tv.Autocomplete.ClearSuggestions ();
         Assert.Empty (g.AllSuggestions);
         Assert.Empty (tv.Autocomplete.Suggestions);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.PageUp));
         Assert.Equal (24, tv.GetCurrentLine ().Count);
         Assert.Equal (new (24, 1), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (new (Key.PageUp)));
         Assert.Equal (23, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 0), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.PageDown));
         Assert.Equal (24, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 1), tv.CursorPosition); // gets the previous length
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.V.WithCtrl));
         Assert.Equal (28, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 2), tv.CursorPosition); // gets the previous length
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.PageUp.WithShift));
         Assert.Equal (24, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 1), tv.CursorPosition); // gets the previous length
         Assert.Equal (24 + Environment.NewLine.Length, tv.SelectedLength);
         Assert.Equal ($".{Environment.NewLine}This is the third line.", tv.SelectedText);
+        Assert.True (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.PageDown.WithShift));
         Assert.Equal (28, tv.GetCurrentLine ().Count);
         Assert.Equal (new (23, 2), tv.CursorPosition); // gets the previous length
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.True (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Home.WithCtrl));
         Assert.Equal (Point.Empty, tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.N.WithCtrl));
         Assert.Equal (new (0, 1), tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.P.WithCtrl));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorDown));
         Assert.Equal (new (0, 1), tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorUp));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorDown.WithShift));
         Assert.Equal (new (0, 1), tv.CursorPosition);
         Assert.Equal (23 + Environment.NewLine.Length, tv.SelectedLength);
         Assert.Equal ($"This is the first line.{Environment.NewLine}", tv.SelectedText);
+        Assert.True (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorUp.WithShift));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.True (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.F.WithCtrl));
         Assert.Equal (new (1, 0), tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.B.WithCtrl));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorRight));
         Assert.Equal (new (1, 0), tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.CursorLeft));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
@@ -5103,6 +5131,7 @@ This is the second line.
                       tv.Text
                      );
         Assert.Equal (new (21, 0), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Backspace));
 
         Assert.Equal (
@@ -5110,6 +5139,7 @@ This is the second line.
                       tv.Text
                      );
         Assert.Equal (new (20, 0), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Backspace));
 
         Assert.Equal (
@@ -5117,6 +5147,7 @@ This is the second line.
                       tv.Text
                      );
         Assert.Equal (new (19, 0), tv.CursorPosition);
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Home));
         Assert.Equal (Point.Empty, tv.CursorPosition);
         Assert.Equal (0, tv.SelectedLength);
@@ -5421,7 +5452,6 @@ This is the second line.
         Assert.Equal (0, tv.SelectedLength);
         Assert.Equal ("", tv.SelectedText);
         Assert.False (tv.IsSelecting);
-        Assert.False (tv.IsSelecting);
         Assert.True (tv.NewKeyDownEvent (Key.Backspace.WithCtrl));
         Assert.Equal ($"This is the second line.{Environment.NewLine}This is the third ", tv.Text);
         Assert.Equal (new (18, 1), tv.CursorPosition);
@@ -5493,6 +5523,7 @@ This is the second line.
         Assert.False (tv.Used);
         Assert.True (tv.AllowsTab);
         Assert.Equal (new (18, 2), tv.CursorPosition);
+        Assert.True (tv.IsSelecting);
         tv.AllowsTab = false;
         Assert.False (tv.NewKeyDownEvent (Key.Tab));
 
@@ -5511,6 +5542,7 @@ This is the second line.
                       $"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third \t",
                       tv.Text
                      );
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.AllowsTab);
         tv.AllowsTab = false;
         Assert.False (tv.NewKeyDownEvent (Key.Tab.WithShift));
@@ -5519,6 +5551,7 @@ This is the second line.
                       $"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third \t",
                       tv.Text
                      );
+        Assert.False (tv.IsSelecting);
         Assert.False (tv.AllowsTab);
         tv.AllowsTab = true;
         Assert.True (tv.NewKeyDownEvent (Key.Tab.WithShift));
@@ -5527,6 +5560,7 @@ This is the second line.
                       $"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ",
                       tv.Text
                      );
+        Assert.False (tv.IsSelecting);
         Assert.True (tv.AllowsTab);
         Assert.False (tv.NewKeyDownEvent (Key.F6));
         Assert.False (tv.NewKeyDownEvent (Application.NextTabGroupKey));
@@ -5535,6 +5569,7 @@ This is the second line.
 
         Assert.True (tv.NewKeyDownEvent (PopoverMenu.DefaultKey));
         Assert.True (tv.ContextMenu != null && tv.ContextMenu.Visible);
+        Assert.False (tv.IsSelecting);
         top.Dispose ();
     }
 
@@ -7105,7 +7140,7 @@ line.
         Assert.True (tv.NewMouseEvent (new () { Position = new (0, 3), Flags = MouseFlags.Button1Pressed }));
         tv.Draw ();
         Assert.Equal (new (0, 3), tv.CursorPosition);
-        Assert.Equal (new (13, 0), cp);
+        Assert.Equal (new (12, 0), cp);
 
         DriverAssert.AssertDriverContentsWithFrameAre (
                                                        @"

+ 0 - 1
Tests/UnitTests/Views/ToplevelTests.cs

@@ -743,7 +743,6 @@ public class ToplevelTests
     }
 
     [Fact]
-    [TestRespondersDisposed]
     public void Multi_Thread_Toplevels ()
     {
         Application.Init (new FakeDriver ());

+ 0 - 119
Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs

@@ -56,11 +56,6 @@ public class DeepClonerTests
         public bool Immutable { get; init; }
     }
 
-    private class SettingsScopeMock : Dictionary<string, ConfigPropertyMock>
-    {
-        public string? Theme { get; set; }
-    }
-
     private class ComplexKey
     {
         public int Id { get; init; }
@@ -544,120 +539,6 @@ public class DeepClonerTests
         Assert.Equal (source.Immutable, result.Immutable);
     }
 
-    [Fact]
-    public void SettingsScopeMockWithKey_CreatesDeepCopy ()
-    {
-        SettingsScopeMock? source = new ()
-        {
-            Theme = "Dark",
-            ["KeyBinding"] = new () { PropertyValue = new Key (KeyCode.A) { Handled = true } },
-            ["Counts"] = new () { PropertyValue = new Dictionary<string, int> { { "X", 1 } } }
-        };
-        SettingsScopeMock? result = DeepCloner.DeepClone (source);
-
-        Assert.NotNull (result);
-        Assert.NotSame (source, result);
-        Assert.Equal (source.Theme, result!.Theme);
-        Assert.NotSame (source ["KeyBinding"], result ["KeyBinding"]);
-        Assert.NotSame (source ["Counts"], result ["Counts"]);
-
-        ConfigPropertyMock clonedKeyProp = result ["KeyBinding"];
-        var clonedKey = (Key)clonedKeyProp.PropertyValue!;
-        Assert.NotSame (source ["KeyBinding"].PropertyValue, clonedKey);
-        Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).KeyCode, clonedKey.KeyCode);
-        Assert.Equal (((Key)source ["KeyBinding"].PropertyValue!).Handled, clonedKey.Handled);
-
-        Assert.Equal ((Dictionary<string, int>)source ["Counts"].PropertyValue!, (Dictionary<string, int>)result ["Counts"].PropertyValue!);
-
-        // Modify result, ensure source unchanged
-        result.Theme = "Light";
-        clonedKey.Handled = false;
-        ((Dictionary<string, int>)result ["Counts"].PropertyValue!).Add ("Y", 2);
-        Assert.Equal ("Dark", source.Theme);
-        Assert.True (((Key)source ["KeyBinding"].PropertyValue!).Handled);
-        Assert.Single ((Dictionary<string, int>)source ["Counts"].PropertyValue!);
-    }
-
-    [Fact]
-    public void ThemeScopeList_WithThemes_ClonesSuccessfully ()
-    {
-        // Arrange: Create a ThemeScope and verify a property exists
-        var defaultThemeScope = new ThemeScope ();
-        defaultThemeScope.LoadHardCodedDefaults ();
-        Assert.True (defaultThemeScope.ContainsKey ("Button.DefaultHighlightStyle"));
-
-        var darkThemeScope = new ThemeScope ();
-        darkThemeScope.LoadHardCodedDefaults ();
-        Assert.True (darkThemeScope.ContainsKey ("Button.DefaultHighlightStyle"));
-
-        // Create a Themes list with two themes
-        List<Dictionary<string, ThemeScope>> themesList =
-        [
-            new () { { "Default", defaultThemeScope } },
-            new () { { "Dark", darkThemeScope } }
-        ];
-
-        // Create a SettingsScope and set the Themes property
-        var settingsScope = new SettingsScope ();
-        settingsScope.LoadHardCodedDefaults ();
-        Assert.True (settingsScope.ContainsKey ("Themes"));
-        settingsScope ["Themes"].PropertyValue = themesList;
-
-        // Act
-        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
-
-        // Assert
-        Assert.NotNull (result);
-        Assert.IsType<SettingsScope> (result);
-        var resultScope = (SettingsScope)result;
-        Assert.True (resultScope.ContainsKey ("Themes"));
-
-        Assert.NotNull (resultScope ["Themes"].PropertyValue);
-
-        List<Dictionary<string, ThemeScope>> clonedThemes = (List<Dictionary<string, ThemeScope>>)resultScope ["Themes"].PropertyValue!;
-        Assert.Equal (2, clonedThemes.Count);
-    }
-
-    [Fact]
-    public void Empty_SettingsScope_ClonesSuccessfully ()
-    {
-        // Arrange: Create a SettingsScope 
-        var settingsScope = new SettingsScope ();
-        Assert.True (settingsScope.ContainsKey ("Themes"));
-
-        // Act
-        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
-
-        // Assert
-        Assert.NotNull (result);
-        Assert.IsType<SettingsScope> (result);
-
-        Assert.True (result.ContainsKey ("Themes"));
-    }
-
-    [Fact]
-    public void SettingsScope_With_Themes_Set_ClonesSuccessfully ()
-    {
-        // Arrange: Create a SettingsScope 
-        var settingsScope = new SettingsScope ();
-        Assert.True (settingsScope.ContainsKey ("Themes"));
-
-        settingsScope ["Themes"].PropertyValue = new List<Dictionary<string, ThemeScope>>
-        {
-            new() { { "Default", new () } },
-            new() { { "Dark", new () } }
-        };
-
-        // Act
-        SettingsScope? result = DeepCloner.DeepClone (settingsScope);
-
-        // Assert
-        Assert.NotNull (result);
-        Assert.IsType<SettingsScope> (result);
-        Assert.True (result.ContainsKey ("Themes"));
-        Assert.NotNull (result ["Themes"].PropertyValue);
-    }
-
     [Fact]
     public void LargeObject_PerformsWithinLimit ()
     {

+ 0 - 1
Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs

@@ -1,6 +1,5 @@
 using System.Reflection;
 using System.Text.Json;
-using Terminal.Gui.Configuration;
 
 public class SourcesManagerTests
 {

+ 1 - 1
docfx/docs/drawing.md

@@ -63,7 +63,7 @@ The @Terminal.Gui.Application MainLoop will iterate over all Views in the view h
 12) DrawComplete is raised.
 13) The current View's Frame NOT INCLUDING the Margin is excluded from the current Clip region.
 
-Most of the steps above can be overridden by developers using the standard [Terminal.Gui Cancellable Work Pattern](cancellable_work_pattern.md). For example, the base @Terminal.Gui.View always clears the viewport. To override this, a subclass can override @Terminal.Gui.View.OnClearingViewport to simply return `true`. Or, a user of `View` can subscribe to the @Terminal.Gui.View.ClearingViewport event and set the `Cancel` argument to `true`.
+Most of the steps above can be overridden by developers using the standard [Terminal.Gui Cancellable Work Pattern](cancellable-work-pattern.md). For example, the base @Terminal.Gui.View always clears the viewport. To override this, a subclass can override @Terminal.Gui.View.OnClearingViewport to simply return `true`. Or, a user of `View` can subscribe to the @Terminal.Gui.View.ClearingViewport event and set the `Cancel` argument to `true`.
 
 Then, after the above steps have completed, the Mainloop will iterate through all views in the view hierarchy again, this time calling Draw on any @Terminal.Gui.View.Margin objects, using the cached Clip region mentioned above. This enables Margin to be transparent.
 

+ 1 - 1
docfx/docs/events.md

@@ -4,7 +4,7 @@ Terminal.Gui exposes and uses events in many places. This deep dive covers the p
 
 ## See Also
 
-* [Cancellable Work Pattern](cancellable_work_pattern.md)
+* [Cancellable Work Pattern](cancellable-work-pattern.md)
 * [Command Deep Dive](command.md)
 
 ## Tenets for Terminal.Gui Events (Unless you know better ones...)

+ 1 - 1
docfx/docs/index.md

@@ -21,7 +21,7 @@ See [What's New in V2 For more](newinv2.md).
 ## Conceptual Documentation
 
 * [Arrangement API](arrangement.md)
-* [Cancellable Work Pattern](cancellable_work_pattern.md)
+* [Cancellable Work Pattern](cancellable-work-pattern.md)
 * [Configuration and Theme Manager](config.md)
 * [Command Deep Dive](command.md)
 * [Cursor Deep Dive](cursor.md)

+ 1 - 1
docfx/docs/keyboard.md

@@ -2,7 +2,7 @@
 
 ## See Also
 
-* [Cancellable Work Pattern](cancellable_work_pattern.md)
+* [Cancellable Work Pattern](cancellable-work-pattern.md)
 * [Command Deep Dive](command.md)
 
 ## Tenets for Terminal.Gui Keyboard Handling (Unless you know better ones...)

+ 1 - 1
docfx/docs/mouse.md

@@ -2,7 +2,7 @@
 
 ## See Also
 
-* [Cancellable Work Pattern](cancellable_work_pattern.md)
+* [Cancellable Work Pattern](cancellable-work-pattern.md)
 * [Command Deep Dive](command.md)
 
 

+ 23 - 0
docfx/docs/newinv2.md

@@ -28,6 +28,8 @@ The entire library has been reviewed and simplified. As a result, the API is mor
 * *New!* @Terminal.Gui.PosAlign - Aligns a set of views horizontally or vertically (left, right, center, etc...).
 * *New!* @Terminal.Gui.View.Arrangement enables tiled and overlapped view arrangement and moving/resizing Views with the keyboard and mouse. See [Arrangement](arrangement.md).
 * *Improved!* Keyboard [Navigation](navigation.md) has been revamped to be more reliability and ensure TUI apps built with Terminal.Gui are accessible. 
+* *New!* Sizable/Movable views - Any view can now be set to have resizeable borders and/or be dragged around.
+* *Improved!* Consistent tabbing behavior - Tab navigation now behaves as expected, cleanly and consistently.
 
 ## New and Improved Built-in Views
 
@@ -41,12 +43,30 @@ The entire library has been reviewed and simplified. As a result, the API is mor
 * *[MenuBar](~/api/Terminal.Gui.MenuBar.yml)* - COMING SOON! New implementation based on `Bar`
 * *[ContextMenu](~/api/Terminal.Gui.ContextMenu.yml)* - COMING SOON! New implementation based on `Bar`
 * *[FileDialog](~/api/Terminal.Gui.FileDialog.yml)* - The new, modern file dialog includes icons (in TUI!) for files/folders, search, and a `TreeView`. 
+* *[TableView](tableview.md)* - No longer just DataTable, now supports any collections, checkboxes and even expandable trees
 * *@"Terminal.Gui.ColorPicker"* - Fully supports TrueColor with the ability to choose a color using HSV, RGB, or HSL as well as W3C standard color names.
 
+## Beauty
+
+Terminal.Gui has never been prettier
+
+* *ShowBorders* - Get that 3D 'pop' for your buttons
+* *Gradient* - Render beautiful true color borders, titles etc with the new Gradient API
+
+
 ## Configuration Manager
 
 Terminal.Gui now supports a configuration manager enabling library and app settings to be persisted and loaded from the file system. See [Configuration Manager](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/overview.html#configuration-manager) for details.
 
+## Logging & Metrics
+
+Terminal.Gui now features multi level logging of engine internals and system performance metrics (redraws, invoke durations etc).  Never again wonder why your frame rate is low, or a given terminal/distro does not behave as expected.
+See [Logging](logging.md) for details.
+
+## Sixel Image Support
+
+Recently added to Windows Terminal and long supported in mainstream linux terminals, this graphics protcol allows images and even animations to be rendered directly into the console.
+
 ## Updated Keyboard API
 
 The API for handling keyboard input is significantly improved. See [Keyboard API](keyboard.md).
@@ -63,3 +83,6 @@ The API for mouse input is now internally consistent and easiser to use.
 * More granular APIs are provided to ease handling specific mouse actions. See [Mouse API](mouse.md).
 * Views can use the `View.Highlight` event to have the view be visibly highlighted on various mouse events.
 * Views can set `View.WantContinousButtonPresses = true` to ahve their `Command.Accept` command be invoked repeatedly as the user holds a mouse button down on the view.
+
+## AOT support
+*AOT/single file app support* now works out of the box.

+ 1 - 1
docfx/docs/toc.yml

@@ -9,7 +9,7 @@
 - name: Arrangement
   href: arrangement.md
 - name: Cancellable Work Pattern
-  href: cancellable_work_pattern.md
+  href: cancellable-work-pattern.md
 - name: Configuration
   href: config.md
 - name: Command Deep Dive

BIN
local_packages/Terminal.Gui.2.0.0.nupkg


BIN
local_packages/Terminal.Gui.2.0.0.snupkg