#nullable enable
//
// FakeDriver.cs: A fake IConsoleDriver for unit tests.
//
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Terminal.Gui.Drivers;
/// Implements a mock IConsoleDriver for unit testing
public class FakeDriver : ConsoleDriver
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
public class Behaviors
{
public Behaviors (
bool useFakeClipboard = false,
bool fakeClipboardAlwaysThrowsNotSupportedException = false,
bool fakeClipboardIsSupportedAlwaysTrue = false
)
{
UseFakeClipboard = useFakeClipboard;
FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException;
FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
// double check usage is correct
Debug.Assert (!useFakeClipboard && !fakeClipboardAlwaysThrowsNotSupportedException);
Debug.Assert (!useFakeClipboard && !fakeClipboardIsSupportedAlwaysTrue);
}
public bool FakeClipboardAlwaysThrowsNotSupportedException { get; internal set; }
public bool FakeClipboardIsSupportedAlwaysFalse { get; internal set; }
public bool UseFakeClipboard { get; internal set; }
}
public static Behaviors FakeBehaviors { get; } = new ();
public override bool SupportsTrueColor => false;
///
public override void WriteRaw (string ansi) { }
public FakeDriver ()
{
// FakeDriver implies UnitTests
RunningUnitTests = true;
//base.Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
//base.Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
if (FakeBehaviors.UseFakeClipboard)
{
Clipboard = new FakeClipboard (
FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException,
FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse
);
}
else
{
if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
{
Clipboard = new WindowsClipboard ();
}
else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
{
Clipboard = new MacOSXClipboard ();
}
else
{
if (PlatformDetection.IsWSLPlatform ())
{
Clipboard = new WSLClipboard ();
}
else
{
Clipboard = new UnixClipboard ();
}
}
}
}
public override void End ()
{
FakeConsole.ResetColor ();
FakeConsole.Clear ();
}
public override void Init ()
{
FakeConsole.MockKeyPresses.Clear ();
//Cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH;
//Rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT;
FakeConsole.Clear ();
SetScreenSize (80,25);
ResizeScreen ();
ClearContents ();
CurrentAttribute = new (Color.White, Color.Black);
}
public override bool UpdateScreen ()
{
var updated = false;
int savedRow = FakeConsole.CursorTop;
int savedCol = FakeConsole.CursorLeft;
bool savedCursorVisible = FakeConsole.CursorVisible;
var top = 0;
var left = 0;
int rows = Rows;
int cols = Cols;
var output = new StringBuilder ();
var redrawAttr = new Attribute ();
int lastCol = -1;
for (int row = top; row < rows; row++)
{
if (!_dirtyLines! [row])
{
continue;
}
updated = true;
FakeConsole.CursorTop = row;
FakeConsole.CursorLeft = 0;
_dirtyLines [row] = false;
output.Clear ();
for (int col = left; col < cols; col++)
{
lastCol = -1;
var outputWidth = 0;
for (; col < cols; col++)
{
if (!Contents! [row, col].IsDirty)
{
if (output.Length > 0)
{
WriteToConsole (output, ref lastCol, row, ref outputWidth);
}
else if (lastCol == -1)
{
lastCol = col;
}
if (lastCol + 1 < cols)
{
lastCol++;
}
continue;
}
if (lastCol == -1)
{
lastCol = col;
}
Attribute attr = Contents [row, col].Attribute!.Value;
// Performance: Only send the escape sequence if the attribute has changed.
if (attr != redrawAttr)
{
redrawAttr = attr;
FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
}
outputWidth++;
Rune rune = Contents [row, col].Rune;
output.Append (rune.ToString ());
if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
{
WriteToConsole (output, ref lastCol, row, ref outputWidth);
FakeConsole.CursorLeft--;
}
Contents [row, col].IsDirty = false;
}
}
if (output.Length > 0)
{
FakeConsole.CursorTop = row;
FakeConsole.CursorLeft = lastCol;
foreach (char c in output.ToString ())
{
FakeConsole.Write (c);
}
}
}
FakeConsole.CursorTop = 0;
FakeConsole.CursorLeft = 0;
//SetCursorVisibility (savedVisibility);
void WriteToConsole (StringBuilder outputSb, ref int lastColumn, int row, ref int outputWidth)
{
FakeConsole.CursorTop = row;
FakeConsole.CursorLeft = lastColumn;
foreach (char c in outputSb.ToString ())
{
FakeConsole.Write (c);
}
outputSb.Clear ();
lastColumn += outputWidth;
outputWidth = 0;
}
FakeConsole.CursorTop = savedRow;
FakeConsole.CursorLeft = savedCol;
FakeConsole.CursorVisible = savedCursorVisible;
return updated;
}
private CursorVisibility _savedCursorVisibility;
///
public override bool GetCursorVisibility (out CursorVisibility visibility)
{
visibility = FakeConsole.CursorVisible
? CursorVisibility.Default
: CursorVisibility.Invisible;
return FakeConsole.CursorVisible;
}
///
public override bool SetCursorVisibility (CursorVisibility visibility)
{
_savedCursorVisibility = visibility;
return FakeConsole.CursorVisible = visibility == CursorVisibility.Default;
}
private bool EnsureCursorVisibility ()
{
if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows))
{
GetCursorVisibility (out CursorVisibility cursorVisibility);
_savedCursorVisibility = cursorVisibility;
SetCursorVisibility (CursorVisibility.Invisible);
return false;
}
SetCursorVisibility (_savedCursorVisibility);
return FakeConsole.CursorVisible;
}
private readonly AnsiResponseParser _parser = new ();
///
internal override IAnsiResponseParser GetParser () { return _parser; }
///
/// Sets the screen size for testing purposes. Only available in FakeDriver.
/// This method updates the driver's dimensions and triggers the ScreenChanged event.
///
/// The new width in columns.
/// The new height in rows.
public override void SetScreenSize (int width, int height) { SetBufferSize (width, height); }
public void SetBufferSize (int width, int height)
{
FakeConsole.SetBufferSize (width, height);
Cols = width;
Rows = height;
SetConsoleSize (width, height);
ProcessResize ();
}
public void SetConsoleSize (int width, int height)
{
FakeConsole.SetConsoleSize (width, height);
FakeConsole.SetBufferSize (width, height);
if (width != Cols || height != Rows)
{
SetBufferSize (width, height);
Cols = width;
Rows = height;
}
ProcessResize ();
}
public void SetWindowPosition (int left, int top)
{
if (Left > 0 || Top > 0)
{
Left = 0;
Top = 0;
}
FakeConsole.SetWindowPosition (Left, Top);
}
private void ProcessResize ()
{
ResizeScreen ();
ClearContents ();
OnSizeChanged (new (new (Cols, Rows)));
}
public virtual void ResizeScreen ()
{
if (FakeConsole.WindowHeight > 0)
{
// Can raise an exception while it is still resizing.
try
{
FakeConsole.CursorTop = 0;
FakeConsole.CursorLeft = 0;
FakeConsole.WindowTop = 0;
FakeConsole.WindowLeft = 0;
}
catch (IOException)
{
return;
}
catch (ArgumentOutOfRangeException)
{
return;
}
}
// CONCURRENCY: Unsynchronized access to Clip is not safe.
Clip = new (Screen);
}
public override void UpdateCursor ()
{
if (!EnsureCursorVisibility ())
{
return;
}
// Prevents the exception to size changing during resizing.
try
{
// BUGBUG: Why is this using BufferWidth/Height and now Cols/Rows?
if (Col >= 0 && Col < FakeConsole.BufferWidth && Row >= 0 && Row < FakeConsole.BufferHeight)
{
FakeConsole.SetCursorPosition (Col, Row);
}
}
catch (IOException)
{ }
catch (ArgumentOutOfRangeException)
{ }
}
#region Not Implemented
public override void Suspend ()
{
//throw new NotImplementedException ();
}
#endregion
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}