#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 }