From 84f79b2326970ef7ddb8279958c6259356f01f3d Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 29 Sep 2021 22:22:23 +0100 Subject: [PATCH] Fixes #1445. Fixing more the Curses and WSL clipboard. (#1448) * Fixes #1445. Fixing more the Curses and WSL clipboard. * Fixing unit tests. * Changing namespace. * Fixes WSL2 clipboard unit test. * Upgrades devcontainer with the MainLoop fix. * Fixes pasting with no selection and with lines break. * Prevents the event button click being fired after a button pressed with mouse move. * Fixes the char [ not being processed. --- .devcontainer/devcontainer.json | 7 +- .../CursesDriver/CursesDriver.cs | 95 ++++++++++++++----- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 2 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 12 ++- Terminal.Gui/Core/MainLoop.cs | 2 +- Terminal.Gui/Views/TextView.cs | 16 +++- UnitTests/AutocompleteTests.cs | 2 +- UnitTests/ClipboardTests.cs | 43 ++++----- 8 files changed, 117 insertions(+), 62 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2d4f2dce7..9c098b85c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Terminal.Gui Codespace", - "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:0.201.7-5.0", + "image": "mcr.microsoft.com/vscode/devcontainers/dotnet:5.0", "settings": { "terminal.integrated.defaultProfile.linux": "pwsh" }, @@ -16,9 +16,10 @@ "jmrog.vscode-nuget-package-manager", "coenraads.bracket-pair-colorizer", "vscode-icons-team.vscode-icons", - "editorconfig.editorconfig" + "editorconfig.editorconfig", + "formulahendry.dotnet-test-explorer" ], - "postCreateCommand": "dotnet restore && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings", + "postCreateCommand": "dotnet restore && dotnet clean && dotnet build --configuration Release --no-restore && dotnet test --configuration Debug --no-restore --verbosity normal --collect:'XPlat Code Coverage' --settings UnitTests/coverlet.runsettings" } // Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index d15e62390..57e20051b 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -923,7 +923,7 @@ namespace Terminal.Gui { public static bool Is_WSL_Platform () { - var result = BashRunner.Run ("uname -a"); + var result = BashRunner.Run ("uname -a", runCurses: false); if (result.Contains ("microsoft") && result.Contains ("WSL")) { return true; } @@ -1205,13 +1205,18 @@ namespace Terminal.Gui { } class CursesClipboard : ClipboardBase { - public override bool IsSupported => CheckSupport (); + public CursesClipboard () + { + IsSupported = CheckSupport (); + } + + public override bool IsSupported { get; } bool CheckSupport () { try { - var result = BashRunner.Run ("which xclip"); - return BashRunner.FileExists (result); + var result = BashRunner.Run ("which xclip", runCurses: false); + return result.FileExists (); } catch (Exception) { // Permissions issue. return false; @@ -1246,7 +1251,7 @@ namespace Terminal.Gui { } static class BashRunner { - public static string Run (string commandLine, bool output = true, string inputText = "") + public static string Run (string commandLine, bool output = true, string inputText = "", bool runCurses = true) { var arguments = $"-c \"{commandLine}\""; @@ -1276,6 +1281,10 @@ namespace Terminal.Gui { throw new Exception (timeoutError); } if (process.ExitCode == 0) { + if (runCurses && Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } return outputBuilder.ToString (); } @@ -1298,12 +1307,16 @@ namespace Terminal.Gui { process.StandardInput.Write (inputText); process.StandardInput.Close (); process.WaitForExit (); + if (runCurses && Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } return inputText; } } } - static bool DoubleWaitForExit (this System.Diagnostics.Process process) + public static bool DoubleWaitForExit (this System.Diagnostics.Process process) { var result = process.WaitForExit (500); if (result) { @@ -1312,7 +1325,7 @@ namespace Terminal.Gui { return result; } - public static bool FileExists (string value) + public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); } @@ -1332,23 +1345,24 @@ namespace Terminal.Gui { IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard"); IntPtr clearContentsRegister = sel_registerName ("clearContents"); - public override bool IsSupported => CheckSupport (); - - bool CheckSupport () - { - var result = BashRunner.Run ("which pbcopy"); - if (!BashRunner.FileExists (result)) { - return false; - } - result = BashRunner.Run ("which pbpaste"); - return BashRunner.FileExists (result); - } - public MacOSXClipboard () { utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text"); nsStringPboardType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "NSStringPboardType"); generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister); + IsSupported = CheckSupport (); + } + + public override bool IsSupported { get; } + + bool CheckSupport () + { + var result = BashRunner.Run ("which pbcopy"); + if (!result.FileExists ()) { + return false; + } + result = BashRunner.Run ("which pbpaste"); + return result.FileExists (); } protected override string GetClipboardDataImpl () @@ -1392,19 +1406,28 @@ namespace Terminal.Gui { } class WSLClipboard : ClipboardBase { - public override bool IsSupported => CheckSupport (); + public WSLClipboard () + { + IsSupported = CheckSupport (); + } + + public override bool IsSupported { get; } bool CheckSupport () { - var result = BashRunner.Run ("which powershell.exe"); - return BashRunner.FileExists (result); + try { + var result = BashRunner.Run ("which powershell.exe"); + return result.FileExists (); + } catch (System.Exception) { + return false; + } //var result = BashRunner.Run ("which powershell.exe"); - //if (!BashRunner.FileExists (result)) { + //if (!result.FileExists ()) { // return false; //} //result = BashRunner.Run ("which clip.exe"); - //return BashRunner.FileExists (result); + //return result.FileExists (); } protected override string GetClipboardDataImpl () @@ -1413,13 +1436,25 @@ namespace Terminal.Gui { StartInfo = new System.Diagnostics.ProcessStartInfo { RedirectStandardOutput = true, FileName = "powershell.exe", - Arguments = "-noprofile -command \"Get-Clipboard\"" + Arguments = "-noprofile -command \"Get-Clipboard\"", + UseShellExecute = Application.Driver is CursesDriver, + CreateNoWindow = true } }) { powershell.Start (); var result = powershell.StandardOutput.ReadToEnd (); powershell.StandardOutput.Close (); powershell.WaitForExit (); + if (!powershell.DoubleWaitForExit ()) { + var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}. + Output: {powershell.StandardOutput.ReadToEnd ()} + Error: {powershell.StandardError.ReadToEnd ()}"; + throw new Exception (timeoutError); + } + if (Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } return result.TrimEnd (); } } @@ -1434,6 +1469,16 @@ namespace Terminal.Gui { }) { powershell.Start (); powershell.WaitForExit (); + if (!powershell.DoubleWaitForExit ()) { + var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}. + Output: {powershell.StandardOutput.ReadToEnd ()} + Error: {powershell.StandardError.ReadToEnd ()}"; + throw new Exception (timeoutError); + } + if (Application.Driver is CursesDriver) { + Curses.raw (); + Curses.noecho (); + } } //using (var clipExe = new System.Diagnostics.Process { diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 3aa4f76d4..67cc397a4 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -253,7 +253,7 @@ namespace Terminal.Gui { } break; case 27: - case 91: + //case 91: ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] { consoleKeyInfo }; ConsoleModifiers mod = consoleKeyInfo.Modifiers; int delay = 0; diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index e9250b91a..60f26dac9 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -862,13 +862,14 @@ namespace Terminal.Gui { case WindowsConsole.EventType.Mouse: var me = ToDriverMouse (inputEvent.MouseEvent); mouseHandler (me); - if (isButtonReleased) { + if (processButtonClick) { mouseHandler ( new MouseEvent () { X = me.X, Y = me.Y, Flags = ProcessButtonClick (inputEvent.MouseEvent) }); + processButtonClick = false; } break; @@ -898,6 +899,7 @@ namespace Terminal.Gui { Point point; int buttonPressedCount; bool isOneFingerDoubleClicked = false; + bool processButtonClick; MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) { @@ -1008,6 +1010,7 @@ namespace Terminal.Gui { mouseFlag |= MouseFlags.ReportMousePosition; point = new Point (); isButtonReleased = false; + processButtonClick = false; } else { point = new Point () { X = mouseEvent.MousePosition.X, @@ -1850,7 +1853,12 @@ namespace Terminal.Gui { } class WindowsClipboard : ClipboardBase { - public override bool IsSupported => IsClipboardFormatAvailable (cfUnicodeText) ? true : false; + public WindowsClipboard () + { + IsSupported = IsClipboardFormatAvailable (cfUnicodeText); + } + + public override bool IsSupported { get; } protected override string GetClipboardDataImpl () { diff --git a/Terminal.Gui/Core/MainLoop.cs b/Terminal.Gui/Core/MainLoop.cs index c6bdbbd4f..e53e43a03 100644 --- a/Terminal.Gui/Core/MainLoop.cs +++ b/Terminal.Gui/Core/MainLoop.cs @@ -122,8 +122,8 @@ namespace Terminal.Gui { while (timeouts.ContainsKey (k)) { k = (DateTime.UtcNow + time).Ticks; } + timeouts.Add (k, timeout); } - timeouts.Add (k, timeout); } /// diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index bdeee6d6e..448ab696f 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -2738,15 +2738,23 @@ namespace Terminal.Gui { } SetWrapModel (); + var contents = Clipboard.Contents; if (copyWithoutSelection) { - var runeList = Clipboard.Contents == null ? new List () : Clipboard.Contents.ToRuneList (); - model.AddLine (currentRow, runeList); - currentRow++; + var runeList = contents == null ? new List () : contents.ToRuneList (); + if ((runeList?.Count > 0 && runeList [0] == '\n') + || (runeList?.Count > 1 && runeList [0] == '\r' + && runeList [1] == '\n')) { + + InsertText (contents); + } else { + model.AddLine (currentRow, runeList); + currentRow++; + } } else { if (selecting) { ClearRegion (); } - InsertText (Clipboard.Contents); + InsertText (contents); copyWithoutSelection = false; } UpdateWrapModel (); diff --git a/UnitTests/AutocompleteTests.cs b/UnitTests/AutocompleteTests.cs index ac671f5a8..4eaf4908d 100644 --- a/UnitTests/AutocompleteTests.cs +++ b/UnitTests/AutocompleteTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Terminal.Gui; using Xunit; -namespace UnitTests { +namespace Terminal.Gui.Core { public class AutocompleteTests { [Fact][AutoInitShutdown] diff --git a/UnitTests/ClipboardTests.cs b/UnitTests/ClipboardTests.cs index d12aa852a..25c59d180 100644 --- a/UnitTests/ClipboardTests.cs +++ b/UnitTests/ClipboardTests.cs @@ -5,10 +5,9 @@ using Xunit; namespace Terminal.Gui.Core { public class ClipboardTests { [Fact] + [AutoInitShutdown] public void Contents_Gets_Sets () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - var clipText = "This is a clipboard unit test."; Clipboard.Contents = clipText; @@ -17,29 +16,23 @@ namespace Terminal.Gui.Core { Application.Run (); Assert.Equal (clipText, Clipboard.Contents); - - Application.Shutdown (); } [Fact] + [AutoInitShutdown] public void IsSupported_Get () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - if (Clipboard.IsSupported) { Assert.True (Clipboard.IsSupported); } else { Assert.False (Clipboard.IsSupported); } - - Application.Shutdown (); } [Fact] + [AutoInitShutdown] public void TryGetClipboardData_Gets_From_OS_Clipboard () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - var clipText = "Trying to get from the OS clipboard."; Clipboard.Contents = clipText; @@ -54,15 +47,12 @@ namespace Terminal.Gui.Core { Assert.False (Clipboard.TryGetClipboardData (out string result)); Assert.NotEqual (clipText, result); } - - Application.Shutdown (); } [Fact] + [AutoInitShutdown] public void TrySetClipboardData_Sets_The_OS_Clipboard () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - var clipText = "Trying to set the OS clipboard."; if (Clipboard.IsSupported) { Assert.True (Clipboard.TrySetClipboardData (clipText)); @@ -79,17 +69,15 @@ namespace Terminal.Gui.Core { } else { Assert.NotEqual (clipText, Clipboard.Contents); } - - Application.Shutdown (); } [Fact] + [AutoInitShutdown] public void Contents_Gets_From_OS_Clipboard () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - var clipText = "This is a clipboard unit test to get clipboard from OS."; var exit = false; + var getClipText = ""; Application.Iteration += () => { if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) { @@ -117,6 +105,8 @@ namespace Terminal.Gui.Core { pwsh.Start (); pwsh.WaitForExit (); } + getClipText = Clipboard.Contents.ToString (); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) { using (Process copy = new Process { StartInfo = new ProcessStartInfo { @@ -129,6 +119,8 @@ namespace Terminal.Gui.Core { copy.StandardInput.Close (); copy.WaitForExit (); } + getClipText = Clipboard.Contents.ToString (); + } else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) { if (Is_WSL_Platform ()) { try { @@ -160,6 +152,9 @@ namespace Terminal.Gui.Core { } catch { exit = true; } + if (!exit) { + getClipText = Clipboard.Contents.ToString (); + } Application.RequestStop (); return; } @@ -181,6 +176,9 @@ namespace Terminal.Gui.Core { bash.StandardInput.Close (); bash.WaitForExit (); } + if (!exit) { + getClipText = Clipboard.Contents.ToString (); + } } Application.RequestStop (); @@ -189,17 +187,14 @@ namespace Terminal.Gui.Core { Application.Run (); if (!exit) { - Assert.Equal (clipText, Clipboard.Contents); + Assert.Equal (clipText, getClipText); } - - Application.Shutdown (); } [Fact] + [AutoInitShutdown] public void Contents_Sets_The_OS_Clipboard () { - Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true))); - var clipText = "This is a clipboard unit test to set the OS clipboard."; var clipReadText = ""; var exit = false; @@ -279,8 +274,6 @@ namespace Terminal.Gui.Core { if (!exit) { Assert.Equal (clipText, clipReadText); } - - Application.Shutdown (); } bool Is_WSL_Platform ()