diff --git a/Examples/NativeAot/Publish_linux-x64_Debug.sh b/Examples/NativeAot/Publish_linux-x64_Debug.sh index b58a83cc1..3b537fcf1 100644 --- a/Examples/NativeAot/Publish_linux-x64_Debug.sh +++ b/Examples/NativeAot/Publish_linux-x64_Debug.sh @@ -1,5 +1,5 @@ #!/bin/bash -dotnet clean -dotnet build +dotnet clean -c Debug +dotnet build -c Debug dotnet publish -c Debug -r linux-x64 --self-contained diff --git a/Examples/NativeAot/Publish_linux-x64_Release.sh b/Examples/NativeAot/Publish_linux-x64_Release.sh index 3cb8feb26..5179907a8 100644 --- a/Examples/NativeAot/Publish_linux-x64_Release.sh +++ b/Examples/NativeAot/Publish_linux-x64_Release.sh @@ -1,5 +1,5 @@ #!/bin/bash -dotnet clean -dotnet build +dotnet clean -c Release +dotnet build -c Release dotnet publish -c Release -r linux-x64 --self-contained diff --git a/Examples/NativeAot/Publish_osx-x64_Debug.sh b/Examples/NativeAot/Publish_osx-x64_Debug.sh index 1fe41513c..9470c7edf 100644 --- a/Examples/NativeAot/Publish_osx-x64_Debug.sh +++ b/Examples/NativeAot/Publish_osx-x64_Debug.sh @@ -1,5 +1,5 @@ #!/bin/bash -dotnet clean -dotnet build +dotnet clean -c Debug +dotnet build -c Debug dotnet publish -c Debug -r osx-x64 --self-contained diff --git a/Examples/NativeAot/Publish_osx-x64_Release.sh b/Examples/NativeAot/Publish_osx-x64_Release.sh index 06b748b79..bfeaedf9c 100644 --- a/Examples/NativeAot/Publish_osx-x64_Release.sh +++ b/Examples/NativeAot/Publish_osx-x64_Release.sh @@ -1,5 +1,5 @@ #!/bin/bash -dotnet clean -dotnet build +dotnet clean -c Release +dotnet build -c Release dotnet publish -c Release -r osx-x64 --self-contained diff --git a/Terminal.Gui/Configuration/DeepCloner.cs b/Terminal.Gui/Configuration/DeepCloner.cs index 9734d56c9..0d918625c 100644 --- a/Terminal.Gui/Configuration/DeepCloner.cs +++ b/Terminal.Gui/Configuration/DeepCloner.cs @@ -278,17 +278,37 @@ public static class DeepCloner // Determine dictionary type and comparer Type [] genericArgs = type.GetGenericArguments (); - Type dictType = genericArgs.Length == 2 - ? typeof (Dictionary<,>).MakeGenericType (genericArgs) - : typeof (Dictionary); + Type dictType; + + if (genericArgs.Length == 2) + { + if (type.GetGenericTypeDefinition () == typeof (Dictionary<,>)) + { + dictType = typeof (Dictionary<,>).MakeGenericType (genericArgs); + } + else if (type.GetGenericTypeDefinition () == typeof (ConcurrentDictionary<,>)) + { + dictType = typeof (ConcurrentDictionary<,>).MakeGenericType (genericArgs); + } + else + { + throw new InvalidOperationException ( + $"Unsupported dictionary type: {type}. Only Dictionary<,> and ConcurrentDictionary<,> are supported."); + } + } + else + { + dictType = typeof (Dictionary); + } + object? comparer = type.GetProperty ("Comparer")?.GetValue (source); // Create a temporary dictionary to hold cloned key-value pairs IDictionary tempDict = CreateDictionaryInstance (dictType, comparer); visited.TryAdd (source, tempDict); - object? lastKey = null; + try { // Clone all key-value pairs @@ -311,7 +331,9 @@ public static class DeepCloner 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); + 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 diff --git a/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs index 350697d58..087f3b17d 100644 --- a/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver/CursesDriver.cs @@ -536,6 +536,8 @@ internal class CursesDriver : ConsoleDriver return true; } + private EscSeqUtils.DECSCUSR_Style? _currentDecscusrStyle; + /// public override bool SetCursorVisibility (CursorVisibility visibility) { @@ -547,17 +549,19 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests) { Curses.curs_set (((int)visibility >> 16) & 0x000000FF); + Curses.leaveok (_window!.Handle, !Force16Colors); } if (visibility != CursorVisibility.Invisible) { - _mainLoopDriver?.WriteRaw ( - EscSeqUtils.CSI_SetCursorStyle ( - (EscSeqUtils.DECSCUSR_Style) - (((int)visibility >> 24) - & 0xFF) - ) - ); + if (_currentDecscusrStyle is null || _currentDecscusrStyle != (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF)) + { + _currentDecscusrStyle = (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF); + + _mainLoopDriver?.WriteRaw ( + EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)_currentDecscusrStyle) + ); + } } _currentCursorVisibility = visibility; diff --git a/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs index 50053f4bb..6408b66b8 100644 --- a/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs +++ b/Terminal.Gui/Drivers/CursesDriver/UnixMainLoop.cs @@ -249,8 +249,7 @@ internal class UnixMainLoop : IMainLoopDriver private class Watch { - // BUGBUG: Fix this nullable issue. - public Func Callback; + public Func? Callback; public Condition Condition; public int File; } diff --git a/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs b/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs index 1c7697d0c..a8d6cc5c1 100644 --- a/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs +++ b/Terminal.Gui/ViewBase/ViewCollectionHelpers.cs @@ -10,7 +10,7 @@ internal static class ViewCollectionHelpers // The list parameter might be the live `_subviews`, so freeze it under a lock lock (list) { - return [.. list]; // C# 12 slice copy (= new List(list).ToArray()) + return list.ToArray (); // It’s slightly less “fancy C# 12”, but much safer in multithreaded code } } diff --git a/Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs b/Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs index 91901ed3d..395ba3747 100644 --- a/Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/DeepClonerTests.cs @@ -551,4 +551,91 @@ public class DeepClonerTests Assert.True (stopwatch.ElapsedMilliseconds < 1000); // Ensure it completes within 1 second } + [Fact] + public void CloneDictionary_ShouldClone_NormalDictionary () + { + // Arrange: A supported generic dictionary + var original = new Dictionary + { + ["one"] = 1, + ["two"] = 2, + ["three"] = 3 + }; + + // Act + var cloned = DeepCloner.DeepClone (original); + + // Assert + Assert.NotNull (cloned); + Assert.NotSame (original, cloned); // must be a new instance + Assert.Equal (original, cloned); // must have same contents + Assert.Equal (original.Count, cloned.Count); + Assert.Equal (original ["one"], cloned ["one"]); + Assert.Equal (original ["two"], cloned ["two"]); + Assert.Equal (original ["three"], cloned ["three"]); + } + + [Fact] + public void CloneDictionary_ShouldClone_ConcurrentDictionary () + { + // Arrange + var original = new ConcurrentDictionary + { + ["a"] = "alpha", + ["b"] = "beta" + }; + + // Act + var cloned = DeepCloner.DeepClone (original); + + // Assert + Assert.NotSame (original, cloned); + Assert.Equal (original, cloned); + } + + [Fact] + public void CloneDictionary_Empty_Dictionary_ShouldWork () + { + // Arrange + var original = new Dictionary (); + + // Act + var cloned = DeepCloner.DeepClone (original); + + // Assert + Assert.NotSame (original, cloned); + Assert.Empty (cloned!); + } + + [Fact] + public void CloneDictionary_Empty_ConcurrentDictionary_ShouldWork () + { + // Arrange + var original = new ConcurrentDictionary (); + + // Act + var cloned = DeepCloner.DeepClone (original); + + // Assert + Assert.NotSame (original, cloned); + Assert.Empty (cloned!); + } + + [Fact] + public void CloneDictionary_With_Unsupported_Dictionary_Throws () + { + // Arrange: A generic dictionary type (SortedDictionary for example) + var unsupportedDict = new SortedDictionary + { + { 1, "A" }, + { 2, "B" } + }; + + // Act & Assert: DeepCloner should throw + Assert.ThrowsAny (() => + { + // This should throw, because DeepCloner does not support SortedDictionary + _ = DeepCloner.DeepClone (unsupportedDict); + }); + } } diff --git a/local_packages/Terminal.Gui.2.0.0.nupkg b/local_packages/Terminal.Gui.2.0.0.nupkg index 1ad376653..593e60f43 100644 Binary files a/local_packages/Terminal.Gui.2.0.0.nupkg and b/local_packages/Terminal.Gui.2.0.0.nupkg differ diff --git a/local_packages/Terminal.Gui.2.0.0.snupkg b/local_packages/Terminal.Gui.2.0.0.snupkg index 2b702f59a..3ed73e02a 100644 Binary files a/local_packages/Terminal.Gui.2.0.0.snupkg and b/local_packages/Terminal.Gui.2.0.0.snupkg differ