Fixes #4243 - ConsoleDriverFacade.CreateClipboard now honors FakeDriver.FakeBehaviors.UseFakeClipboard (#4244)

* updatd ConsoleDriverFacade.CreateClipboard to honor FakeDriver.FakeBehaviors.UseFakeClipboard

* Code cleanup of fake driver v2
This commit is contained in:
Tig
2025-09-12 12:32:31 -07:00
committed by GitHub
parent e869f842e3
commit 18b602e980
15 changed files with 292 additions and 331 deletions

View File

@@ -1,5 +1,4 @@
using System.Diagnostics;
#nullable enable
namespace Terminal.Gui.App;
/// <summary>Provides cut, copy, and paste support for the OS clipboard.</summary>
@@ -20,10 +19,10 @@ namespace Terminal.Gui.App;
/// </remarks>
public static class Clipboard
{
private static string _contents = string.Empty;
private static string? _contents = string.Empty;
/// <summary>Gets (copies from) or sets (pastes to) the contents of the OS clipboard.</summary>
public static string Contents
public static string? Contents
{
get
{
@@ -31,13 +30,8 @@ public static class Clipboard
{
if (IsSupported)
{
string clipData = Application.Driver?.Clipboard.GetClipboardData ();
if (clipData is null)
{
// throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
clipData = string.Empty;
}
// throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
string? clipData = Application.Driver?.Clipboard?.GetClipboardData () ?? string.Empty;
_contents = clipData;
}
@@ -55,12 +49,9 @@ public static class Clipboard
{
if (IsSupported)
{
if (value is null)
{
value = string.Empty;
}
value ??= string.Empty;
Application.Driver?.Clipboard.SetClipboardData (value);
Application.Driver?.Clipboard?.SetClipboardData (value);
}
_contents = value;
@@ -74,126 +65,5 @@ public static class Clipboard
/// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
/// <remarks></remarks>
public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false;
/// <summary>Copies the _contents of the OS clipboard to <paramref name="result"/> if possible.</summary>
/// <param name="result">The _contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
/// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
public static bool TryGetClipboardData (out string result)
{
if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result))
{
_contents = result;
return true;
}
result = string.Empty;
return false;
}
/// <summary>Pastes the <paramref name="text"/> to the OS clipboard if possible.</summary>
/// <param name="text">The text to paste to the OS clipboard.</param>
/// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
public static bool TrySetClipboardData (string text)
{
if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text))
{
_contents = text;
return true;
}
return false;
}
}
/// <summary>
/// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
/// CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
/// </summary>
internal static class ClipboardProcessRunner
{
public static (int exitCode, string result) Bash (
string commandLine,
string inputText = "",
bool waitForOutput = false
)
{
var arguments = $"-c \"{commandLine}\"";
(int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
return (exitCode, result.TrimEnd ());
}
public static bool DoubleWaitForExit (this Process process)
{
bool result = process.WaitForExit (500);
if (result)
{
process.WaitForExit ();
}
return result;
}
public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
public static (int exitCode, string result) Process (
string cmd,
string arguments,
string input = null,
bool waitForOutput = true
)
{
var output = string.Empty;
using (var process = new Process
{
StartInfo = new()
{
FileName = cmd,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true
}
})
{
TaskCompletionSource<bool> eventHandled = new ();
process.Start ();
if (!string.IsNullOrEmpty (input))
{
process.StandardInput.Write (input);
process.StandardInput.Close ();
}
if (!process.WaitForExit (5000))
{
var timeoutError =
$@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
throw new TimeoutException (timeoutError);
}
if (waitForOutput && process.StandardOutput.Peek () != -1)
{
output = process.StandardOutput.ReadToEnd ();
}
if (process.ExitCode > 0)
{
output = $@"Process failed to run. Command line: {cmd} {arguments}.
Output: {output}
Error: {process.StandardError.ReadToEnd ()}";
}
return (process.ExitCode, output);
}
}
}
public static bool IsSupported => Application.Driver?.Clipboard?.IsSupported ?? false;
}

View File

@@ -0,0 +1,79 @@
#nullable enable
using System.Diagnostics;
namespace Terminal.Gui.App;
/// <summary>
/// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
/// CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
/// </summary>
internal static class ClipboardProcessRunner
{
public static (int exitCode, string result) Bash (
string commandLine,
string inputText = "",
bool waitForOutput = false
)
{
var arguments = $"-c \"{commandLine}\"";
(int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
return (exitCode, result.TrimEnd ());
}
public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
public static (int exitCode, string result) Process (
string cmd,
string arguments,
string? input = null,
bool waitForOutput = true
)
{
var output = string.Empty;
using var process = new Process ();
process.StartInfo = new()
{
FileName = cmd,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true
};
TaskCompletionSource<bool> eventHandled = new ();
process.Start ();
if (!string.IsNullOrEmpty (input))
{
process.StandardInput.Write (input);
process.StandardInput.Close ();
}
if (!process.WaitForExit (5000))
{
var timeoutError =
$@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
throw new TimeoutException (timeoutError);
}
if (waitForOutput && process.StandardOutput.Peek () != -1)
{
output = process.StandardOutput.ReadToEnd ();
}
if (process.ExitCode > 0)
{
output = $@"Process failed to run. Command line: {cmd} {arguments}.
Output: {output}
Error: {process.StandardError.ReadToEnd ()}";
}
return (process.ExitCode, output);
}
}

View File

@@ -1,4 +1,5 @@
//
#nullable enable
//
// FakeDriver.cs: A fake IConsoleDriver for unit tests.
//
@@ -36,7 +37,7 @@ public class FakeDriver : ConsoleDriver
public bool UseFakeClipboard { get; internal set; }
}
public static Behaviors FakeBehaviors = new ();
public static Behaviors FakeBehaviors { get; } = new ();
public override bool SupportsTrueColor => false;
/// <inheritdoc />
@@ -47,8 +48,8 @@ public class FakeDriver : ConsoleDriver
public FakeDriver ()
{
Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
if (FakeBehaviors.UseFakeClipboard)
{
@@ -87,7 +88,7 @@ public class FakeDriver : ConsoleDriver
FakeConsole.Clear ();
}
private FakeMainLoop _mainLoopDriver;
private FakeMainLoop? _mainLoopDriver;
public override MainLoop Init ()
{
@@ -124,7 +125,7 @@ public class FakeDriver : ConsoleDriver
for (int row = top; row < rows; row++)
{
if (!_dirtyLines [row])
if (!_dirtyLines! [row])
{
continue;
}
@@ -144,7 +145,7 @@ public class FakeDriver : ConsoleDriver
for (; col < cols; col++)
{
if (!Contents [row, col].IsDirty)
if (!Contents! [row, col].IsDirty)
{
if (output.Length > 0)
{
@@ -168,7 +169,7 @@ public class FakeDriver : ConsoleDriver
lastCol = col;
}
Attribute attr = Contents [row, col].Attribute.Value;
Attribute attr = Contents [row, col].Attribute!.Value;
// Performance: Only send the escape sequence if the attribute has changed.
if (attr != redrawAttr)
@@ -209,18 +210,18 @@ public class FakeDriver : ConsoleDriver
//SetCursorVisibility (savedVisibility);
void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
void WriteToConsole (StringBuilder outputSb, ref int lastColumn, int row, ref int outputWidth)
{
FakeConsole.CursorTop = row;
FakeConsole.CursorLeft = lastCol;
FakeConsole.CursorLeft = lastColumn;
foreach (char c in output.ToString ())
foreach (char c in outputSb.ToString ())
{
FakeConsole.Write (c);
}
output.Clear ();
lastCol += outputWidth;
outputSb.Clear ();
lastColumn += outputWidth;
outputWidth = 0;
}
@@ -506,7 +507,7 @@ public class FakeDriver : ConsoleDriver
public class FakeClipboard : ClipboardBase
{
public Exception FakeException;
public Exception? FakeException { get; set; }
private readonly bool _isSupportedAlwaysFalse;
private string _contents = string.Empty;
@@ -536,19 +537,14 @@ public class FakeDriver : ConsoleDriver
return _contents;
}
protected override void SetClipboardDataImpl (string text)
protected override void SetClipboardDataImpl (string? text)
{
if (text is null)
{
throw new ArgumentNullException (nameof (text));
}
if (FakeException is { })
{
throw FakeException;
}
_contents = text;
_contents = text ?? throw new ArgumentNullException (nameof (text));
}
}

View File

@@ -1,5 +1,5 @@
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
#nullable enable
using System.Runtime.InteropServices;
namespace Terminal.Gui.Drivers;
@@ -11,7 +11,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
private CursorVisibility _lastCursor = CursorVisibility.Default;
/// <summary>The event fired when the terminal is resized.</summary>
public event EventHandler<SizeChangedEventArgs> SizeChanged;
public event EventHandler<SizeChangedEventArgs>? SizeChanged;
public IInputProcessor InputProcessor { get; }
public IOutputBuffer OutputBuffer => _outputBuffer;
@@ -48,6 +48,13 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
private void CreateClipboard ()
{
if (FakeDriver.FakeBehaviors.UseFakeClipboard)
{
Clipboard = new FakeDriver.FakeClipboard ();
return;
}
PlatformID p = Environment.OSVersion.Platform;
if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
@@ -88,7 +95,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
/// to.
/// </summary>
/// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
public Region Clip
public Region? Clip
{
get => _outputBuffer.Clip;
set => _outputBuffer.Clip = value;
@@ -114,7 +121,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
/// The contents of the application output. The driver outputs this buffer to the terminal.
/// <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
/// </summary>
public Cell [,] Contents
public Cell [,]? Contents
{
get => _outputBuffer.Contents;
set => _outputBuffer.Contents = value;
@@ -229,7 +236,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
/// <summary>
/// Raised each time <see cref="ConsoleDriver.ClearContents"/> is called. For benchmarking.
/// </summary>
public event EventHandler<EventArgs> ClearedContents;
public event EventHandler<EventArgs>? ClearedContents;
/// <summary>
/// Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
@@ -397,7 +404,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
}
/// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
public event EventHandler<Key> KeyDown;
public event EventHandler<Key>? KeyDown;
/// <summary>Event fired when a key is released.</summary>
/// <remarks>
@@ -405,10 +412,10 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
/// processing is
/// complete.
/// </remarks>
public event EventHandler<Key> KeyUp;
public event EventHandler<Key>? KeyUp;
/// <summary>Event fired when a mouse event occurs.</summary>
public event EventHandler<MouseEventArgs> MouseEvent;
public event EventHandler<MouseEventArgs>? MouseEvent;
/// <summary>Simulates a key press.</summary>
/// <param name="keyChar">The key character.</param>

View File

@@ -1,4 +1,5 @@
namespace Terminal.Gui.Drivers;
#nullable enable
namespace Terminal.Gui.Drivers;
/// <summary>
/// Interface for v2 driver abstraction layer

View File

@@ -12,7 +12,7 @@ public interface IOutputBuffer
/// <summary>
/// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
/// </summary>
Cell [,] Contents { get; set; }
Cell [,]? Contents { get; set; }
/// <summary>
/// Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject