From ca37d898e8c95beee4ac9dd3df94f03c6907a6d3 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 15 Jul 2025 02:15:08 +0100 Subject: [PATCH 001/104] Consider width2 chars that are not IsBmp --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index fb8e26815f..fdf4c1cd42 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -146,16 +146,37 @@ public void Write (IOutputBuffer buffer) outputBuffer [position].Empty = false; - if (buffer.Contents [row, col].Rune.IsBmp) + var rune = buffer.Contents [row, col].Rune; + int width = rune.GetColumns (); + + if (rune.IsBmp) { - outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value; + if (width == 1) + { + // Single-width char, just encode first UTF-16 char + outputBuffer [position].Char = (char)rune.Value; + } + else if (width == 2 && col + 1 < buffer.Cols) + { + // Double-width char: encode to UTF-16 surrogate pair and write both halves + var utf16 = new char [2]; + rune.EncodeToUtf16 (utf16); + outputBuffer [position].Char = utf16 [0]; + outputBuffer [position].Empty = false; + + // Write second half into next cell + col++; + position = row * buffer.Cols + col; + outputBuffer [position].Char = utf16 [1]; + outputBuffer [position].Empty = false; + } } else { //outputBuffer [position].Empty = true; outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; - if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols) + if (width > 1 && col + 1 < buffer.Cols) { // TODO: This is a hack to deal with non-BMP and wide characters. col++; From a37baf0dda7cc9496b86ab60999eec3381bc424f Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 15 Jul 2025 02:21:17 +0100 Subject: [PATCH 002/104] Apply same fix in WindowsDriver --- .../Drivers/WindowsDriver/WindowsDriver.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 59941924fc..8de9290254 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -344,16 +344,37 @@ public override bool UpdateScreen () _outputBuffer [position].Empty = false; - if (Contents [row, col].Rune.IsBmp) + var rune = Contents [row, col].Rune; + int width = rune.GetColumns (); + + if (rune.IsBmp) { - _outputBuffer [position].Char = (char)Contents [row, col].Rune.Value; + if (width == 1) + { + // Single-width char, just encode first UTF-16 char + _outputBuffer [position].Char = (char)rune.Value; + } + else if (width == 2 && col + 1 < Cols) + { + // Double-width char: encode to UTF-16 surrogate pair and write both halves + var utf16 = new char [2]; + rune.EncodeToUtf16 (utf16); + _outputBuffer [position].Char = utf16 [0]; + _outputBuffer [position].Empty = false; + + // Write second half into next cell + col++; + position = row * Cols + col; + _outputBuffer [position].Char = utf16 [1]; + _outputBuffer [position].Empty = false; + } } else { //_outputBuffer [position].Empty = true; _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; - if (Contents [row, col].Rune.GetColumns () > 1 && col + 1 < Cols) + if (width > 1 && col + 1 < Cols) { // TODO: This is a hack to deal with non-BMP and wide characters. col++; From 1a91f8f8197baa992dfa95b83179afcbb85b080f Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 15 Jul 2025 02:26:56 +0100 Subject: [PATCH 003/104] Explicitly use type of local variable --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 2 +- Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index fdf4c1cd42..c21f592728 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -146,7 +146,7 @@ public void Write (IOutputBuffer buffer) outputBuffer [position].Empty = false; - var rune = buffer.Contents [row, col].Rune; + Rune rune = buffer.Contents [row, col].Rune; int width = rune.GetColumns (); if (rune.IsBmp) diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index 8de9290254..ac91db3f2a 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -344,7 +344,7 @@ public override bool UpdateScreen () _outputBuffer [position].Empty = false; - var rune = Contents [row, col].Rune; + Rune rune = Contents [row, col].Rune; int width = rune.GetColumns (); if (rune.IsBmp) From 788ce4653a21e010408fd7d0b8856f8decb7237d Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 17 Jul 2025 02:01:23 +0100 Subject: [PATCH 004/104] Revert changes to WindowsDriver --- .../Drivers/WindowsDriver/WindowsDriver.cs | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs index ac91db3f2a..59941924fc 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsDriver.cs @@ -344,37 +344,16 @@ public override bool UpdateScreen () _outputBuffer [position].Empty = false; - Rune rune = Contents [row, col].Rune; - int width = rune.GetColumns (); - - if (rune.IsBmp) + if (Contents [row, col].Rune.IsBmp) { - if (width == 1) - { - // Single-width char, just encode first UTF-16 char - _outputBuffer [position].Char = (char)rune.Value; - } - else if (width == 2 && col + 1 < Cols) - { - // Double-width char: encode to UTF-16 surrogate pair and write both halves - var utf16 = new char [2]; - rune.EncodeToUtf16 (utf16); - _outputBuffer [position].Char = utf16 [0]; - _outputBuffer [position].Empty = false; - - // Write second half into next cell - col++; - position = row * Cols + col; - _outputBuffer [position].Char = utf16 [1]; - _outputBuffer [position].Empty = false; - } + _outputBuffer [position].Char = (char)Contents [row, col].Rune.Value; } else { //_outputBuffer [position].Empty = true; _outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; - if (width > 1 && col + 1 < Cols) + if (Contents [row, col].Rune.GetColumns () > 1 && col + 1 < Cols) { // TODO: This is a hack to deal with non-BMP and wide characters. col++; From 6a59d034f8f9c4b8b4c50d7cb807c9c6cad0f59a Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 17 Jul 2025 02:49:25 +0100 Subject: [PATCH 005/104] Assume we are running in a terminal that supports true color by default unless user explicitly forces 16 --- Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index a2cc34c497..a70b089567 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -25,7 +25,6 @@ internal class MainLoopCoordinator : IMainLoopCoordinator private ConsoleDriverFacade _facade; private Task _inputTask; private readonly ITimedEvents _timedEvents; - private readonly bool _isWindowsTerminal; private readonly SemaphoreSlim _startupSemaphore = new (0, 1); @@ -61,7 +60,6 @@ IMainLoop loop _inputProcessor = inputProcessor; _outputFactory = outputFactory; _loop = loop; - _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; } /// @@ -162,11 +160,6 @@ private void BuildFacadeIfPossible () _loop.AnsiRequestScheduler, _loop.WindowSizeMonitor); - if (!_isWindowsTerminal) - { - Application.Force16Colors = _facade.Force16Colors = true; - } - Application.Driver = _facade; _startupSemaphore.Release (); From 4983e0c4909248fb28b44880250c7067ac571428 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 21 Jul 2025 02:53:09 +0100 Subject: [PATCH 006/104] Switch to SetAttribute and WriteConsole instead of WriteConsoleOutput for 16 color mode --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 182 +++++++++++++---------- 1 file changed, 103 insertions(+), 79 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index c21f592728..7cedadf398 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; -using static Terminal.Gui.Drivers.WindowsConsole; namespace Terminal.Gui.Drivers; @@ -59,6 +58,9 @@ private enum DesiredAccess : uint [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo); + [DllImport ("kernel32.dll", SetLastError = true)] + public static extern bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes); + private readonly nint _screenBuffer; // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). @@ -73,15 +75,31 @@ public WindowsOutput () return; } - _screenBuffer = CreateConsoleScreenBuffer ( - DesiredAccess.GenericRead | DesiredAccess.GenericWrite, - ShareMode.FileShareRead | ShareMode.FileShareWrite, - nint.Zero, - 1, - nint.Zero - ); + _screenBuffer = CreateScreenBuffer (); + + var backBuffer = CreateScreenBuffer (); + _doubleBuffer = [_screenBuffer, backBuffer]; + + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + } + + private int _activeDoubleBuffer = 0; + private nint [] _doubleBuffer = new nint[2]; - if (_screenBuffer == INVALID_HANDLE_VALUE) + private nint CreateScreenBuffer () + { + var buff = CreateConsoleScreenBuffer ( + DesiredAccess.GenericRead | DesiredAccess.GenericWrite, + ShareMode.FileShareRead | ShareMode.FileShareWrite, + nint.Zero, + 1, + nint.Zero + ); + + if (buff == INVALID_HANDLE_VALUE) { int err = Marshal.GetLastWin32Error (); @@ -91,10 +109,7 @@ public WindowsOutput () } } - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) - { - throw new Win32Exception (Marshal.GetLastWin32Error ()); - } + return buff; } public void Write (ReadOnlySpan str) @@ -218,91 +233,72 @@ public void Write (IOutputBuffer buffer) public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors) { + // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. + var buffer = force16Colors ? _doubleBuffer[_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; - //Debug.WriteLine ("WriteToConsole"); + var result = false; - //if (_screenBuffer == nint.Zero) - //{ - // ReadFromConsoleOutput (size, bufferSize, ref window); - //} + StringBuilder stringBuilder = new(); - var result = false; + stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0); - if (force16Colors) + Attribute? prev = null; + + foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer) { - var i = 0; - WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length]; + Attribute attr = info.Attribute; - foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer) + if (attr != prev) { - ci [i++] = new () - { - Char = new () { UnicodeChar = info.Char }, - Attributes = - (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4)) - }; + prev = attr; + AppendOrWrite (attr,force16Colors,stringBuilder,buffer); + _redrawTextStyle = attr.Style; } - result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window); - } - else - { - StringBuilder stringBuilder = new(); - stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0); - - Attribute? prev = null; - - foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer) + if (info.Char != '\x1b') { - Attribute attr = info.Attribute; - - if (attr != prev) + if (!info.Empty) { - prev = attr; - EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B); - EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B); - EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style); - _redrawTextStyle = attr.Style; - } - if (info.Char != '\x1b') - { - if (!info.Empty) - { - stringBuilder.Append (info.Char); - } - } - else - { - stringBuilder.Append (' '); + AppendOrWrite (info.Char, force16Colors, stringBuilder, buffer); } } + else + { + stringBuilder.Append (' '); + } + } - stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); - stringBuilder.Append (EscSeqUtils.CSI_HideCursor); + if (force16Colors) + { + SetConsoleActiveScreenBuffer (buffer); + return true; + } - // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed. - char [] rentedWriteArray = ArrayPool.Shared.Rent (minimumLength: stringBuilder.Length); - try - { - Span writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length); - stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length); + stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); + stringBuilder.Append (EscSeqUtils.CSI_HideCursor); - // Supply console with the new content. - result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); - } - finally - { - ArrayPool.Shared.Return (rentedWriteArray); - } + // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed. + char [] rentedWriteArray = ArrayPool.Shared.Rent (minimumLength: stringBuilder.Length); + try + { + Span writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length); + stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length); - foreach (SixelToRender sixel in Application.Sixel) - { - SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); - WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); - } + // Supply console with the new content. + result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); + } + finally + { + ArrayPool.Shared.Return (rentedWriteArray); + } + + foreach (SixelToRender sixel in Application.Sixel) + { + SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); + WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } if (!result) @@ -318,6 +314,34 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charIn return result; } + private void AppendOrWrite (char infoChar, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + { + + if (force16Colors) + { + WriteConsole (screenBuffer, [infoChar],1, out _, nint.Zero); + } + else + { + stringBuilder.Append (infoChar); + } + } + + private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + { + if (force16Colors) + { + var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4)); + SetConsoleTextAttribute (screenBuffer, as16ColorInt); + } + else + { + EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B); + EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B); + EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style); + } + } + public Size GetWindowSize () { var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX (); From ebb3b0639f490e6f5afa716f17c8ca6ac774529c Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 21 Jul 2025 03:11:19 +0100 Subject: [PATCH 007/104] Fix some cursor issues (WIP) --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 7cedadf398..3c4d619c90 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -238,6 +238,14 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charIn var result = false; + GetWindowSize (out var cursorPosition); + + if (force16Colors) + { + SetConsoleCursorPosition (buffer, new (0, 0)); + + } + StringBuilder stringBuilder = new(); stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); @@ -274,6 +282,7 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charIn if (force16Colors) { SetConsoleActiveScreenBuffer (buffer); + SetConsoleCursorPosition (buffer, new (cursorPosition.X, cursorPosition.Y)); return true; } @@ -343,6 +352,10 @@ private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder st } public Size GetWindowSize () + { + return GetWindowSize (out _); + } + public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) { var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); @@ -350,6 +363,7 @@ public Size GetWindowSize () if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) { //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + cursorPosition = default; return Size.Empty; } @@ -357,6 +371,7 @@ public Size GetWindowSize () csbi.srWindow.Right - csbi.srWindow.Left + 1, csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + cursorPosition = csbi.dwCursorPosition; return sz; } From c74df745a1bbc7af43e87aea72c052ab8004f229 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 03:23:29 +0100 Subject: [PATCH 008/104] Remove concept of 'dirty rows' from v2 as its never actually used --- Terminal.Gui/Drivers/V2/IOutputBuffer.cs | 5 - Terminal.Gui/Drivers/V2/NetOutput.cs | 6 -- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 131 ++++++++++++----------- 3 files changed, 69 insertions(+), 73 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/IOutputBuffer.cs b/Terminal.Gui/Drivers/V2/IOutputBuffer.cs index 80f33a0c09..89891cbb18 100644 --- a/Terminal.Gui/Drivers/V2/IOutputBuffer.cs +++ b/Terminal.Gui/Drivers/V2/IOutputBuffer.cs @@ -9,11 +9,6 @@ namespace Terminal.Gui.Drivers; /// public interface IOutputBuffer { - /// - /// As performance is a concern, we keep track of the dirty lines and only refresh those. - /// This is in addition to the dirty flag on each cell. - /// - public bool [] DirtyLines { get; } /// /// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called. diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 32ae497645..65511442a2 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -80,17 +80,11 @@ public void Write (IOutputBuffer buffer) return; } - if (!buffer.DirtyLines [row]) - { - continue; - } - if (!SetCursorPositionImpl (0, row)) { return; } - buffer.DirtyLines [row] = false; output.Clear (); for (int col = left; col < cols; col++) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 3c4d619c90..02d1447c30 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -1,7 +1,9 @@ #nullable enable +using System; using System.Buffers; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Text; using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; @@ -61,7 +63,7 @@ private enum DesiredAccess : uint [DllImport ("kernel32.dll", SetLastError = true)] public static extern bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes); - private readonly nint _screenBuffer; + private nint _screenBuffer; // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). private TextStyle _redrawTextStyle = TextStyle.None; @@ -122,7 +124,7 @@ public void Write (ReadOnlySpan str) public void Write (IOutputBuffer buffer) { - WindowsConsole.ExtendedCharInfo [] outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols]; + WindowsConsole.ExtendedCharInfo [][] outputBuffer = new WindowsConsole.ExtendedCharInfo[buffer.Rows] []; // TODO: probably do need this right? /* @@ -139,27 +141,14 @@ public void Write (IOutputBuffer buffer) for (var row = 0; row < buffer.Rows; row++) { - if (!buffer.DirtyLines [row]) - { - continue; - } + outputBuffer [row]= new WindowsConsole.ExtendedCharInfo[buffer.Cols]; - buffer.DirtyLines [row] = false; for (var col = 0; col < buffer.Cols; col++) { - int position = row * buffer.Cols + col; - outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault (); - - if (buffer.Contents [row, col].IsDirty == false) - { - outputBuffer [position].Empty = true; - outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; - - continue; - } - - outputBuffer [position].Empty = false; + int position = col; + outputBuffer [row][position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault (); + outputBuffer [row][position].Empty = false; Rune rune = buffer.Contents [row, col].Rune; int width = rune.GetColumns (); @@ -169,35 +158,35 @@ public void Write (IOutputBuffer buffer) if (width == 1) { // Single-width char, just encode first UTF-16 char - outputBuffer [position].Char = (char)rune.Value; + outputBuffer [row][position].Char = (char)rune.Value; } else if (width == 2 && col + 1 < buffer.Cols) { // Double-width char: encode to UTF-16 surrogate pair and write both halves var utf16 = new char [2]; rune.EncodeToUtf16 (utf16); - outputBuffer [position].Char = utf16 [0]; - outputBuffer [position].Empty = false; + outputBuffer [row][position].Char = utf16 [0]; + outputBuffer [row][position].Empty = false; // Write second half into next cell col++; - position = row * buffer.Cols + col; - outputBuffer [position].Char = utf16 [1]; - outputBuffer [position].Empty = false; + position = col; + outputBuffer [row][position].Char = utf16 [1]; + outputBuffer [row][position].Empty = false; } } else { //outputBuffer [position].Empty = true; - outputBuffer [position].Char = (char)Rune.ReplacementChar.Value; + outputBuffer [row][position].Char = (char)Rune.ReplacementChar.Value; if (width > 1 && col + 1 < buffer.Cols) { // TODO: This is a hack to deal with non-BMP and wide characters. col++; - position = row * buffer.Cols + col; - outputBuffer [position].Empty = false; - outputBuffer [position].Char = ' '; + position = col; + outputBuffer [row][position].Empty = false; + outputBuffer [row][position].Char = ' '; } } } @@ -231,58 +220,58 @@ public void Write (IOutputBuffer buffer) WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); } - public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors) + public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors) { // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. var buffer = force16Colors ? _doubleBuffer[_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; - var result = false; + _screenBuffer = buffer; - GetWindowSize (out var cursorPosition); - - if (force16Colors) - { - SetConsoleCursorPosition (buffer, new (0, 0)); + var result = false; - } + GetWindowSize (out var originalCursorPosition); StringBuilder stringBuilder = new(); - stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0); Attribute? prev = null; - foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer) + for (int row = 0; row < charInfoBuffer.Length; row++) { - Attribute attr = info.Attribute; + AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, _screenBuffer); - if (attr != prev) + for(int col =0;col < charInfoBuffer [row].Length;col++) { - prev = attr; - AppendOrWrite (attr,force16Colors,stringBuilder,buffer); - _redrawTextStyle = attr.Style; - } + var info = charInfoBuffer [row] [col]; + Attribute attr = info.Attribute; + + if (attr != prev) + { + prev = attr; + AppendOrWrite (attr, force16Colors, stringBuilder, buffer); + _redrawTextStyle = attr.Style; + } - if (info.Char != '\x1b') - { - if (!info.Empty) + if (info.Char != '\x1b') { + if (!info.Empty) + { - AppendOrWrite (info.Char, force16Colors, stringBuilder, buffer); + AppendOrWrite (info.Char, force16Colors, stringBuilder, buffer); + } + } + else + { + stringBuilder.Append (' '); } - } - else - { - stringBuilder.Append (' '); } } if (force16Colors) { SetConsoleActiveScreenBuffer (buffer); - SetConsoleCursorPosition (buffer, new (cursorPosition.X, cursorPosition.Y)); + SetConsoleCursorPosition (buffer, new (originalCursorPosition.X, originalCursorPosition.Y)); return true; } @@ -351,6 +340,20 @@ private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder st } } + private void AppendOrWriteCursorPosition (Point p, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + { + if (force16Colors) + { + SetConsoleCursorPosition (screenBuffer, new ((short)p.X, (short)p.Y)); + } + else + { + // CSI codes are 1 indexed + stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, p.Y+1, p.X+1); + } + } + public Size GetWindowSize () { return GetWindowSize (out _); @@ -427,15 +430,19 @@ public void Dispose () return; } - if (_screenBuffer != nint.Zero) + for (int i = 0; i < 2; i++) { - try - { - CloseHandle (_screenBuffer); - } - catch (Exception e) + var buffer = _doubleBuffer [i]; + if (buffer != nint.Zero) { - Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method"); + try + { + CloseHandle (buffer); + } + catch (Exception e) + { + Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method"); + } } } From b9dd4f5a242fc046ed825a9b4fd3374cdce168f3 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 03:24:58 +0100 Subject: [PATCH 009/104] Remove damageRegion as it does nothing --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 02d1447c30..95d93ed653 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -192,13 +192,6 @@ public void Write (IOutputBuffer buffer) } } - var damageRegion = new WindowsConsole.SmallRect - { - Top = 0, - Left = 0, - Bottom = (short)buffer.Rows, - Right = (short)buffer.Cols - }; //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window, if (!ConsoleDriver.RunningUnitTests @@ -206,7 +199,6 @@ public void Write (IOutputBuffer buffer) new (buffer.Cols, buffer.Rows), outputBuffer, bufferCoords, - damageRegion, Application.Driver!.Force16Colors)) { int err = Marshal.GetLastWin32Error (); @@ -216,11 +208,9 @@ public void Write (IOutputBuffer buffer) throw new Win32Exception (err); } } - - WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); } - public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors) + public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] charInfoBuffer, WindowsConsole.Coord bufferSize, bool force16Colors) { // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. var buffer = force16Colors ? _doubleBuffer[_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; From 5501ada74dff0ec4e8eeae8560ac4c7d3f36e05d Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 03:28:32 +0100 Subject: [PATCH 010/104] Make string builder to console writing simpler --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 95d93ed653..9e5f9efbce 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -268,20 +268,8 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] char stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); stringBuilder.Append (EscSeqUtils.CSI_HideCursor); - // TODO: Potentially could stackalloc whenever reasonably small (<= 8 kB?) write buffer is needed. - char [] rentedWriteArray = ArrayPool.Shared.Rent (minimumLength: stringBuilder.Length); - try - { - Span writeBuffer = rentedWriteArray.AsSpan(0, stringBuilder.Length); - stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length); - - // Supply console with the new content. - result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero); - } - finally - { - ArrayPool.Shared.Return (rentedWriteArray); - } + var span = stringBuilder.ToString ().AsSpan (); // still allocates the string + result = WriteConsole (_screenBuffer, span, (uint)span.Length, out _, nint.Zero); foreach (SixelToRender sixel in Application.Sixel) { From 6ce6b6b3599ccaf13a218439f395b2bcc27a4e3e Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 03:58:33 +0100 Subject: [PATCH 011/104] Radically simplify Write method --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 138 +++++------------------ 1 file changed, 26 insertions(+), 112 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 9e5f9efbce..d832968381 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -122,134 +122,43 @@ public void Write (ReadOnlySpan str) } } - public void Write (IOutputBuffer buffer) + public void Write (IOutputBuffer outputBuffer) { - WindowsConsole.ExtendedCharInfo [][] outputBuffer = new WindowsConsole.ExtendedCharInfo[buffer.Rows] []; + bool force16Colors = Application.Driver!.Force16Colors; - // TODO: probably do need this right? - /* - if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows)) - { - return; - }*/ - - var bufferCoords = new WindowsConsole.Coord - { - X = (short)buffer.Cols, //Clip.Width, - Y = (short)buffer.Rows //Clip.Height - }; - - for (var row = 0; row < buffer.Rows; row++) - { - outputBuffer [row]= new WindowsConsole.ExtendedCharInfo[buffer.Cols]; - - - for (var col = 0; col < buffer.Cols; col++) - { - int position = col; - outputBuffer [row][position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault (); - outputBuffer [row][position].Empty = false; - - Rune rune = buffer.Contents [row, col].Rune; - int width = rune.GetColumns (); - - if (rune.IsBmp) - { - if (width == 1) - { - // Single-width char, just encode first UTF-16 char - outputBuffer [row][position].Char = (char)rune.Value; - } - else if (width == 2 && col + 1 < buffer.Cols) - { - // Double-width char: encode to UTF-16 surrogate pair and write both halves - var utf16 = new char [2]; - rune.EncodeToUtf16 (utf16); - outputBuffer [row][position].Char = utf16 [0]; - outputBuffer [row][position].Empty = false; - - // Write second half into next cell - col++; - position = col; - outputBuffer [row][position].Char = utf16 [1]; - outputBuffer [row][position].Empty = false; - } - } - else - { - //outputBuffer [position].Empty = true; - outputBuffer [row][position].Char = (char)Rune.ReplacementChar.Value; - - if (width > 1 && col + 1 < buffer.Cols) - { - // TODO: This is a hack to deal with non-BMP and wide characters. - col++; - position = col; - outputBuffer [row][position].Empty = false; - outputBuffer [row][position].Char = ' '; - } - } - } - } - - - //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window, - if (!ConsoleDriver.RunningUnitTests - && !WriteToConsole ( - new (buffer.Cols, buffer.Rows), - outputBuffer, - bufferCoords, - Application.Driver!.Force16Colors)) - { - int err = Marshal.GetLastWin32Error (); - - if (err != 0) - { - throw new Win32Exception (err); - } - } - } - - public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] charInfoBuffer, WindowsConsole.Coord bufferSize, bool force16Colors) - { // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. - var buffer = force16Colors ? _doubleBuffer[_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; + var consoleBuffer = force16Colors ? _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; - _screenBuffer = buffer; + _screenBuffer = consoleBuffer; var result = false; GetWindowSize (out var originalCursorPosition); - StringBuilder stringBuilder = new(); - + StringBuilder stringBuilder = new (); Attribute? prev = null; - for (int row = 0; row < charInfoBuffer.Length; row++) + for (var row = 0; row < outputBuffer.Rows; row++) { AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, _screenBuffer); - for(int col =0;col < charInfoBuffer [row].Length;col++) + for (var col = 0; col < outputBuffer.Cols; col++) { - var info = charInfoBuffer [row] [col]; - Attribute attr = info.Attribute; + var cell = outputBuffer.Contents [row, col]; + var attr = cell.Attribute ?? prev ?? new (); if (attr != prev) { prev = attr; - AppendOrWrite (attr, force16Colors, stringBuilder, buffer); + AppendOrWrite (attr!, force16Colors, stringBuilder, (nint)consoleBuffer); _redrawTextStyle = attr.Style; } - if (info.Char != '\x1b') + if (cell.Rune.Value != '\x1b') { - if (!info.Empty) - { - - AppendOrWrite (info.Char, force16Colors, stringBuilder, buffer); - } + AppendOrWrite (cell.Rune, force16Colors, stringBuilder, consoleBuffer); } else { @@ -260,9 +169,9 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] char if (force16Colors) { - SetConsoleActiveScreenBuffer (buffer); - SetConsoleCursorPosition (buffer, new (originalCursorPosition.X, originalCursorPosition.Y)); - return true; + SetConsoleActiveScreenBuffer (consoleBuffer); + SetConsoleCursorPosition (consoleBuffer, new (originalCursorPosition.X, originalCursorPosition.Y)); + return; } stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); @@ -286,20 +195,25 @@ public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [][] char throw new Win32Exception (err); } } - - return result; } - private void AppendOrWrite (char infoChar, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) - { + private void AppendOrWrite (Rune rune, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + { if (force16Colors) { - WriteConsole (screenBuffer, [infoChar],1, out _, nint.Zero); + char [] chars = new char [2]; // allocate maximum space (2 chars for surrogate pair) + int written = rune.EncodeToUtf16 (chars); + + // Slice array to actual size used + char [] result = new char [written]; + Array.Copy (chars, result, written); + + WriteConsole (screenBuffer,chars ,(uint)written, out _, nint.Zero); } else { - stringBuilder.Append (infoChar); + stringBuilder.Append (rune); } } From e25beff5f9b157a3f9373938a6177ee4f0dd709d Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 04:01:34 +0100 Subject: [PATCH 012/104] Simplify conditional logic --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index d832968381..5aea387f86 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -127,9 +127,16 @@ public void Write (IOutputBuffer outputBuffer) bool force16Colors = Application.Driver!.Force16Colors; // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. - var consoleBuffer = force16Colors ? _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2] : _screenBuffer; - - _screenBuffer = consoleBuffer; + nint consoleBuffer; + if (force16Colors) + { + consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; + _screenBuffer = consoleBuffer; + } + else + { + consoleBuffer = _screenBuffer; + } var result = false; From 77fcdefeb62c1a375ebffe0b9776794e8749e32f Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 04:20:39 +0100 Subject: [PATCH 013/104] Simplify restoring cursor position --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 5aea387f86..9eb2a4bfdc 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -174,15 +174,7 @@ public void Write (IOutputBuffer outputBuffer) } } - if (force16Colors) - { - SetConsoleActiveScreenBuffer (consoleBuffer); - SetConsoleCursorPosition (consoleBuffer, new (originalCursorPosition.X, originalCursorPosition.Y)); - return; - } - - stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); - stringBuilder.Append (EscSeqUtils.CSI_HideCursor); + AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, consoleBuffer); var span = stringBuilder.ToString ().AsSpan (); // still allocates the string result = WriteConsole (_screenBuffer, span, (uint)span.Length, out _, nint.Zero); From e08b2d6efe00ee5548430b7d216cab26c42b7a40 Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 04:42:38 +0100 Subject: [PATCH 014/104] Reference local variable for console buffer --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 9eb2a4bfdc..25ee693083 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -1,9 +1,6 @@ #nullable enable -using System; -using System.Buffers; using System.ComponentModel; using System.Runtime.InteropServices; -using System.Text; using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; @@ -148,13 +145,20 @@ public void Write (IOutputBuffer outputBuffer) for (var row = 0; row < outputBuffer.Rows; row++) { - AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, _screenBuffer); + AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, consoleBuffer); for (var col = 0; col < outputBuffer.Cols; col++) { var cell = outputBuffer.Contents [row, col]; var attr = cell.Attribute ?? prev ?? new (); + // If we have 10 width and are at 9 it's ok + // But if it's width 2 we can't write it so must skip + if (cell.Rune.GetColumns () + col > outputBuffer.Cols) + { + continue; + } + if (attr != prev) { prev = attr; @@ -162,7 +166,6 @@ public void Write (IOutputBuffer outputBuffer) _redrawTextStyle = attr.Style; } - if (cell.Rune.Value != '\x1b') { AppendOrWrite (cell.Rune, force16Colors, stringBuilder, consoleBuffer); @@ -176,13 +179,19 @@ public void Write (IOutputBuffer outputBuffer) AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, consoleBuffer); + if (force16Colors) + { + SetConsoleActiveScreenBuffer (consoleBuffer); + return; + } + var span = stringBuilder.ToString ().AsSpan (); // still allocates the string - result = WriteConsole (_screenBuffer, span, (uint)span.Length, out _, nint.Zero); + result = WriteConsole (consoleBuffer, span, (uint)span.Length, out _, nint.Zero); foreach (SixelToRender sixel in Application.Sixel) { SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); - WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (consoleBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } if (!result) From 711e4742d1f2343755291e8c4695d8f52225237d Mon Sep 17 00:00:00 2001 From: tznind Date: Tue, 22 Jul 2025 19:53:24 +0100 Subject: [PATCH 015/104] Reduce calls to ConsoleWrite by accumulating till attribute changes --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 44 ++++++++++++++++-------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 25ee693083..48e943847d 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -145,6 +145,8 @@ public void Write (IOutputBuffer outputBuffer) for (var row = 0; row < outputBuffer.Rows; row++) { + StringBuilder sbSinceLastAttrChange = new StringBuilder (); + AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, consoleBuffer); for (var col = 0; col < outputBuffer.Cols; col++) @@ -162,19 +164,28 @@ public void Write (IOutputBuffer outputBuffer) if (attr != prev) { prev = attr; + + // write everything out up till now + AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, consoleBuffer); + + // then change color/style etc AppendOrWrite (attr!, force16Colors, stringBuilder, (nint)consoleBuffer); + sbSinceLastAttrChange.Clear (); + sbSinceLastAttrChange.Append (cell.Rune); _redrawTextStyle = attr.Style; } - - if (cell.Rune.Value != '\x1b') - { - AppendOrWrite (cell.Rune, force16Colors, stringBuilder, consoleBuffer); - } else { - stringBuilder.Append (' '); + sbSinceLastAttrChange.Append (cell.Rune); } } + + // write trailing bits + if (sbSinceLastAttrChange.Length > 0) + { + AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, consoleBuffer); + sbSinceLastAttrChange.Clear (); + } } AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, consoleBuffer); @@ -206,22 +217,25 @@ public void Write (IOutputBuffer outputBuffer) } - private void AppendOrWrite (Rune rune, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + private void AppendOrWrite (string str, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) { - if (force16Colors) + if (str.Length == 0) { - char [] chars = new char [2]; // allocate maximum space (2 chars for surrogate pair) - int written = rune.EncodeToUtf16 (chars); + return; + } - // Slice array to actual size used - char [] result = new char [written]; - Array.Copy (chars, result, written); + // Replace escape characters with space + str = str.Replace ("\x1b", " "); - WriteConsole (screenBuffer,chars ,(uint)written, out _, nint.Zero); + + if (force16Colors) + { + var a = str.ToCharArray (); + WriteConsole (screenBuffer,a ,(uint)a.Length, out _, nint.Zero); } else { - stringBuilder.Append (rune); + stringBuilder.Append (str); } } From f6fbfc332940314e2dd04b38b82e7b605dd51af6 Mon Sep 17 00:00:00 2001 From: tznind Date: Sun, 27 Jul 2025 15:54:11 +0100 Subject: [PATCH 016/104] When resizing v2 16 color mode on windows, recreate the back buffer to match its size --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 48e943847d..4c9cc3024e 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -119,6 +119,26 @@ public void Write (ReadOnlySpan str) } } + public void RecreateBackBuffer () + { + int idx = (_activeDoubleBuffer + 1) % 2; + var inactiveBuffer = _doubleBuffer [idx]; + + DisposeBuffer (inactiveBuffer); + _doubleBuffer [idx] = CreateScreenBuffer (); + } + + private void DisposeBuffer (nint buffer) + { + if (buffer != 0 && buffer != INVALID_HANDLE_VALUE) + { + if (!CloseHandle (buffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to close screen buffer handle."); + } + } + } + public void Write (IOutputBuffer outputBuffer) { bool force16Colors = Application.Driver!.Force16Colors; @@ -268,9 +288,29 @@ private void AppendOrWriteCursorPosition (Point p, bool force16Colors, StringBui } } + private Size? _lastSize = null; public Size GetWindowSize () { - return GetWindowSize (out _); + + var newSize = GetWindowSize (out _); + + if (_lastSize == null || _lastSize != newSize) + { + // Back buffers only apply to 16 color mode so if not in that just ignore + if (!Application.Force16Colors) + { + return newSize; + } + + // User is resizing the screen, they can only ever resize the active + // buffer since. We now however have issue because background offscreen + // buffer will be wrong size, recreate it to ensure it doesn't result in + // differing active and back buffer sizes (which causes flickering of window size) + RecreateBackBuffer (); + _lastSize = newSize; + } + + return newSize; } public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) { From aaee6a00fa066120218b7550dd14cd62fc74a0c2 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Aug 2025 16:05:06 +0100 Subject: [PATCH 017/104] Fixes for VTS enabled --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 76 +++++++++++++++++------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 4c9cc3024e..f3f31bcd90 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -17,6 +17,9 @@ private static partial bool WriteConsole ( nint lpReserved ); + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern nint GetStdHandle (int nStdHandle); + [DllImport ("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle (nint handle); @@ -60,7 +63,14 @@ private enum DesiredAccess : uint [DllImport ("kernel32.dll", SetLastError = true)] public static extern bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes); + [DllImport ("kernel32.dll")] + private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + + private const int STD_OUTPUT_HANDLE = -11; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + private readonly nint _outputHandle; private nint _screenBuffer; + private readonly bool _isVirtualTerminal; // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). private TextStyle _redrawTextStyle = TextStyle.None; @@ -74,14 +84,25 @@ public WindowsOutput () return; } - _screenBuffer = CreateScreenBuffer (); - - var backBuffer = CreateScreenBuffer (); - _doubleBuffer = [_screenBuffer, backBuffer]; + _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); + _isVirtualTerminal = GetConsoleMode (_outputHandle, out uint mode) && (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + if (_isVirtualTerminal) { - throw new Win32Exception (Marshal.GetLastWin32Error ()); + //Enable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + } + else + { + _screenBuffer = CreateScreenBuffer (); + + var backBuffer = CreateScreenBuffer (); + _doubleBuffer = [_screenBuffer, backBuffer]; + + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } } } @@ -113,7 +134,7 @@ private nint CreateScreenBuffer () public void Write (ReadOnlySpan str) { - if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero)) + if (!WriteConsole (_isVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero)) { throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer."); } @@ -144,15 +165,18 @@ public void Write (IOutputBuffer outputBuffer) bool force16Colors = Application.Driver!.Force16Colors; // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. - nint consoleBuffer; + nint consoleBuffer = 0; if (force16Colors) { - consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; - _screenBuffer = consoleBuffer; + if (!_isVirtualTerminal) + { + consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; + _screenBuffer = consoleBuffer; + } } else { - consoleBuffer = _screenBuffer; + consoleBuffer = _outputHandle; } var result = false; @@ -297,7 +321,7 @@ public Size GetWindowSize () if (_lastSize == null || _lastSize != newSize) { // Back buffers only apply to 16 color mode so if not in that just ignore - if (!Application.Force16Colors) + if (_isVirtualTerminal) { return newSize; } @@ -317,7 +341,7 @@ public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX (); csbi.cbSize = (uint)Marshal.SizeOf (csbi); - if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) + if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); cursorPosition = default; @@ -384,18 +408,26 @@ public void Dispose () return; } - for (int i = 0; i < 2; i++) + if (_isVirtualTerminal) { - var buffer = _doubleBuffer [i]; - if (buffer != nint.Zero) + //Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + } + else + { + for (int i = 0; i < 2; i++) { - try - { - CloseHandle (buffer); - } - catch (Exception e) + var buffer = _doubleBuffer [i]; + if (buffer != nint.Zero) { - Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method"); + try + { + CloseHandle (buffer); + } + catch (Exception e) + { + Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method"); + } } } } From 51200cbf3f1e032f18f4db6c95a50c6b2805d246 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Aug 2025 16:20:48 +0100 Subject: [PATCH 018/104] Fix _lastSize never being assigned --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index f3f31bcd90..9c9009efe8 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -320,6 +320,8 @@ public Size GetWindowSize () if (_lastSize == null || _lastSize != newSize) { + _lastSize ??= newSize; + // Back buffers only apply to 16 color mode so if not in that just ignore if (_isVirtualTerminal) { @@ -331,11 +333,11 @@ public Size GetWindowSize () // buffer will be wrong size, recreate it to ensure it doesn't result in // differing active and back buffer sizes (which causes flickering of window size) RecreateBackBuffer (); - _lastSize = newSize; } return newSize; } + public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) { var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX (); From 41cfc5fd878aacc394a746836116ec827bb737b4 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Aug 2025 19:56:26 +0100 Subject: [PATCH 019/104] Fixes VTS for Force16Colors --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 9c9009efe8..713037e975 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -168,7 +168,11 @@ public void Write (IOutputBuffer outputBuffer) nint consoleBuffer = 0; if (force16Colors) { - if (!_isVirtualTerminal) + if (_isVirtualTerminal) + { + consoleBuffer = _outputHandle; + } + else { consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; _screenBuffer = consoleBuffer; From 6b4e56aefb2ce88237925fdb8c126860c5bcbc2f Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 1 Aug 2025 23:41:22 +0100 Subject: [PATCH 020/104] Fixes force16Colors in VTS --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 713037e975..c5314975c0 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -238,7 +238,7 @@ public void Write (IOutputBuffer outputBuffer) AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, consoleBuffer); - if (force16Colors) + if (force16Colors && !_isVirtualTerminal) { SetConsoleActiveScreenBuffer (consoleBuffer); return; @@ -276,7 +276,7 @@ private void AppendOrWrite (string str, bool force16Colors, StringBuilder string str = str.Replace ("\x1b", " "); - if (force16Colors) + if (force16Colors && !_isVirtualTerminal) { var a = str.ToCharArray (); WriteConsole (screenBuffer,a ,(uint)a.Length, out _, nint.Zero); @@ -291,8 +291,17 @@ private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder st { if (force16Colors) { - var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4)); - SetConsoleTextAttribute (screenBuffer, as16ColorInt); + if (_isVirtualTerminal) + { + stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ())); + stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ())); + EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style); + } + else + { + var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4)); + SetConsoleTextAttribute (screenBuffer, as16ColorInt); + } } else { @@ -304,7 +313,7 @@ private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder st private void AppendOrWriteCursorPosition (Point p, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) { - if (force16Colors) + if (force16Colors && !_isVirtualTerminal) { SetConsoleCursorPosition (screenBuffer, new ((short)p.X, (short)p.Y)); } From 0308f9224d19d0d10d8d975f2b77b4e979eda52f Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Aug 2025 00:05:53 +0100 Subject: [PATCH 021/104] Fixes escape sequences always echoing in non-VTS --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index c5314975c0..80e0632b3a 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -379,7 +379,7 @@ public void SetCursorVisibility (CursorVisibility visibility) return; } - if (Application.Driver!.Force16Colors) + if (!_isVirtualTerminal) { var info = new WindowsConsole.ConsoleCursorInfo { From e36a43ee75d6170d9df478c03b5011165c130028 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Aug 2025 00:08:41 +0100 Subject: [PATCH 022/104] Force Force16Colors in non-VTS. It have a bug in adding a newline in the last line --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 80e0632b3a..50a4edf52d 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -103,6 +103,9 @@ public WindowsOutput () { throw new Win32Exception (Marshal.GetLastWin32Error ()); } + + // Force 16 colors if not in virtual terminal mode. + Application.Force16Colors = true; } } From 35a4ecb06dc3700cdfa75be1576b6201a33d0651 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Aug 2025 04:36:21 +0100 Subject: [PATCH 023/104] WIP Add base class for NetOutput --- Terminal.Gui/Drivers/V2/NetOutput.cs | 176 ++----------------------- Terminal.Gui/Drivers/V2/OutputBase.cs | 182 ++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 167 deletions(-) create mode 100644 Terminal.Gui/Drivers/V2/OutputBase.cs diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 65511442a2..937e751ff3 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -6,15 +6,10 @@ namespace Terminal.Gui.Drivers; /// Implementation of that uses native dotnet /// methods e.g. /// -public class NetOutput : IConsoleOutput +public class NetOutput : OutputBase, IConsoleOutput { private readonly bool _isWinPlatform; - private CursorVisibility? _cachedCursorVisibility; - - // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). - private TextStyle _redrawTextStyle = TextStyle.None; - /// /// Creates a new instance of the class. /// @@ -44,157 +39,6 @@ public void Write (ReadOnlySpan text) Console.Out.Write (text); } - /// - public void Write (IOutputBuffer buffer) - { - if (ConsoleDriver.RunningUnitTests) - { - return; - } - - if (Console.WindowHeight < 1 - || buffer.Contents.Length != buffer.Rows * buffer.Cols - || buffer.Rows != Console.WindowHeight) - { - // return; - } - - var top = 0; - var left = 0; - int rows = buffer.Rows; - int cols = buffer.Cols; - var output = new StringBuilder (); - Attribute? redrawAttr = null; - int lastCol = -1; - - CursorVisibility? savedVisibility = _cachedCursorVisibility; - SetCursorVisibility (CursorVisibility.Invisible); - - const int maxCharsPerRune = 2; - Span runeBuffer = stackalloc char[maxCharsPerRune]; - - for (int row = top; row < rows; row++) - { - if (Console.WindowHeight < 1) - { - return; - } - - if (!SetCursorPositionImpl (0, row)) - { - return; - } - - output.Clear (); - - for (int col = left; col < cols; col++) - { - lastCol = -1; - var outputWidth = 0; - - for (; col < cols; col++) - { - if (!buffer.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 = buffer.Contents [row, col].Attribute.Value; - - // Performance: Only send the escape sequence if the attribute has changed. - if (attr != redrawAttr) - { - redrawAttr = attr; - - EscSeqUtils.CSI_AppendForegroundColorRGB ( - output, - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ); - - EscSeqUtils.CSI_AppendBackgroundColorRGB ( - output, - attr.Background.R, - attr.Background.G, - attr.Background.B - ); - - EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style); - - _redrawTextStyle = attr.Style; - } - - outputWidth++; - - // Avoid Rune.ToString() by appending the rune chars. - Rune rune = buffer.Contents [row, col].Rune; - int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer); - ReadOnlySpan runeChars = runeBuffer[..runeCharsWritten]; - output.Append (runeChars); - - if (buffer.Contents [row, col].CombiningMarks.Count > 0) - { - // AtlasEngine does not support NON-NORMALIZED combining marks in a way - // compatible with the driver architecture. Any CMs (except in the first col) - // are correctly combined with the base char, but are ALSO treated as 1 column - // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. - // - // For now, we just ignore the list of CMs. - //foreach (var combMark in Contents [row, col].CombiningMarks) { - // output.Append (combMark); - //} - // WriteToConsole (output, ref lastCol, row, ref outputWidth); - } - else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) - { - WriteToConsole (output, ref lastCol, row, ref outputWidth); - SetCursorPositionImpl (col - 1, row); - } - - buffer.Contents [row, col].IsDirty = false; - } - } - - if (output.Length > 0) - { - SetCursorPositionImpl (lastCol, row); - Console.Out.Write (output); - } - } - - foreach (SixelToRender s in Application.Sixel) - { - if (!string.IsNullOrWhiteSpace (s.SixelData)) - { - SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y); - Console.Out.Write (s.SixelData); - } - } - - SetCursorVisibility (savedVisibility ?? CursorVisibility.Default); - _cachedCursorVisibility = savedVisibility; - } - /// public Size GetWindowSize () { @@ -207,21 +51,19 @@ public Size GetWindowSize () return new (Console.WindowWidth, Console.WindowHeight); } - private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) - { - SetCursorPositionImpl (lastCol, row); - Console.Out.Write (output); - output.Clear (); - lastCol += outputWidth; - outputWidth = 0; - } /// public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); } private Point _lastCursorPosition; - private bool SetCursorPositionImpl (int col, int row) + /// + protected override void Write (StringBuilder output) + { + Console.Out.Write (output); + } + + protected override bool SetCursorPositionImpl (int col, int row) { if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row) { @@ -267,7 +109,7 @@ public void Dispose () } /// - public void SetCursorVisibility (CursorVisibility visibility) + public override void SetCursorVisibility (CursorVisibility visibility) { Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); } diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs new file mode 100644 index 0000000000..3a56c98fa4 --- /dev/null +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Terminal.Gui.Drivers; + +public abstract class OutputBase +{ + private CursorVisibility? _cachedCursorVisibility; + + // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). + private TextStyle _redrawTextStyle = TextStyle.None; + + /// + public void Write (IOutputBuffer buffer) + { + if (ConsoleDriver.RunningUnitTests) + { + return; + } + + if (Console.WindowHeight < 1 + || buffer.Contents.Length != buffer.Rows * buffer.Cols + || buffer.Rows != Console.WindowHeight) + { + // return; + } + + var top = 0; + var left = 0; + int rows = buffer.Rows; + int cols = buffer.Cols; + var output = new StringBuilder (); + Attribute? redrawAttr = null; + int lastCol = -1; + + CursorVisibility? savedVisibility = _cachedCursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + const int maxCharsPerRune = 2; + Span runeBuffer = stackalloc char [maxCharsPerRune]; + + for (int row = top; row < rows; row++) + { + if (Console.WindowHeight < 1) + { + return; + } + + if (!SetCursorPositionImpl (0, row)) + { + return; + } + + output.Clear (); + + for (int col = left; col < cols; col++) + { + lastCol = -1; + var outputWidth = 0; + + for (; col < cols; col++) + { + if (!buffer.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 = buffer.Contents [row, col].Attribute.Value; + + // Performance: Only send the escape sequence if the attribute has changed. + if (attr != redrawAttr) + { + redrawAttr = attr; + + EscSeqUtils.CSI_AppendForegroundColorRGB ( + output, + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ); + + EscSeqUtils.CSI_AppendBackgroundColorRGB ( + output, + attr.Background.R, + attr.Background.G, + attr.Background.B + ); + + EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style); + + _redrawTextStyle = attr.Style; + } + + outputWidth++; + + // Avoid Rune.ToString() by appending the rune chars. + Rune rune = buffer.Contents [row, col].Rune; + int runeCharsWritten = rune.EncodeToUtf16 (runeBuffer); + ReadOnlySpan runeChars = runeBuffer [..runeCharsWritten]; + output.Append (runeChars); + + if (buffer.Contents [row, col].CombiningMarks.Count > 0) + { + // AtlasEngine does not support NON-NORMALIZED combining marks in a way + // compatible with the driver architecture. Any CMs (except in the first col) + // are correctly combined with the base char, but are ALSO treated as 1 column + // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`. + // + // For now, we just ignore the list of CMs. + //foreach (var combMark in Contents [row, col].CombiningMarks) { + // output.Append (combMark); + //} + // WriteToConsole (output, ref lastCol, row, ref outputWidth); + } + else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) + { + WriteToConsole (output, ref lastCol, row, ref outputWidth); + SetCursorPositionImpl (col - 1, row); + } + + buffer.Contents [row, col].IsDirty = false; + } + } + + if (output.Length > 0) + { + SetCursorPositionImpl (lastCol, row); + Console.Out.Write (output); + } + } + + foreach (SixelToRender s in Application.Sixel) + { + if (!string.IsNullOrWhiteSpace (s.SixelData)) + { + SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y); + Console.Out.Write (s.SixelData); + } + } + + SetCursorVisibility (savedVisibility ?? CursorVisibility.Default); + _cachedCursorVisibility = savedVisibility; + } + + + private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) + { + SetCursorPositionImpl (lastCol, row); + Write (output); + output.Clear (); + lastCol += outputWidth; + outputWidth = 0; + } + + protected abstract void Write (StringBuilder output); + + protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY); + + public abstract void SetCursorVisibility (CursorVisibility visibility); +} From db073272932c92452526d78d31e17cc4e1b1cbb3 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Aug 2025 04:45:30 +0100 Subject: [PATCH 024/104] Abstract away how we change attribute --- Terminal.Gui/Drivers/V2/NetOutput.cs | 20 ++++++++++++++++++++ Terminal.Gui/Drivers/V2/OutputBase.cs | 17 ++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 937e751ff3..511422311d 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -57,6 +57,26 @@ public Size GetWindowSize () private Point _lastCursorPosition; + /// + protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle) + { + EscSeqUtils.CSI_AppendForegroundColorRGB ( + output, + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ); + + EscSeqUtils.CSI_AppendBackgroundColorRGB ( + output, + attr.Background.R, + attr.Background.G, + attr.Background.B + ); + + EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style); + } + /// protected override void Write (StringBuilder output) { diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index 3a56c98fa4..200b15f594 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -94,21 +94,7 @@ public void Write (IOutputBuffer buffer) { redrawAttr = attr; - EscSeqUtils.CSI_AppendForegroundColorRGB ( - output, - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ); - - EscSeqUtils.CSI_AppendBackgroundColorRGB ( - output, - attr.Background.R, - attr.Background.G, - attr.Background.B - ); - - EscSeqUtils.CSI_AppendTextStyleChange (output, _redrawTextStyle, attr.Style); + AppendOrWriteAttribute (output, attr, _redrawTextStyle); _redrawTextStyle = attr.Style; } @@ -164,6 +150,7 @@ public void Write (IOutputBuffer buffer) _cachedCursorVisibility = savedVisibility; } + protected abstract void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle); private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { From 41323ac389d99d8c32e7719eec0588d300a8d3ee Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Aug 2025 05:14:13 +0100 Subject: [PATCH 025/104] WIP - Make WindowsOutput use base class --- Terminal.Gui/Drivers/V2/OutputBase.cs | 2 +- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 112 ++++++++++++++--------- 2 files changed, 72 insertions(+), 42 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index 200b15f594..1624ae0e0b 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -14,7 +14,7 @@ public abstract class OutputBase private TextStyle _redrawTextStyle = TextStyle.None; /// - public void Write (IOutputBuffer buffer) + public virtual void Write (IOutputBuffer buffer) { if (ConsoleDriver.RunningUnitTests) { diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 50a4edf52d..7bca97e4a6 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -1,11 +1,12 @@ #nullable enable using System.ComponentModel; using System.Runtime.InteropServices; +using System.Text; using Microsoft.Extensions.Logging; namespace Terminal.Gui.Drivers; -internal partial class WindowsOutput : IConsoleOutput +internal partial class WindowsOutput : OutputBase, IConsoleOutput { [LibraryImport ("kernel32.dll", EntryPoint = "WriteConsoleW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] [return: MarshalAs (UnmanagedType.Bool)] @@ -71,10 +72,7 @@ private enum DesiredAccess : uint private readonly nint _outputHandle; private nint _screenBuffer; private readonly bool _isVirtualTerminal; - - // Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange(). - private TextStyle _redrawTextStyle = TextStyle.None; - + public WindowsOutput () { Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}"); @@ -163,29 +161,33 @@ private void DisposeBuffer (nint buffer) } } - public void Write (IOutputBuffer outputBuffer) + public override void Write (IOutputBuffer outputBuffer) { - bool force16Colors = Application.Driver!.Force16Colors; + _force16Colors = Application.Driver!.Force16Colors; + _everythingStringBuilder = new StringBuilder (); // for 16 color mode we will write to a backing buffer then flip it to the active one at the end to avoid jitter. - nint consoleBuffer = 0; - if (force16Colors) + _consoleBuffer = 0; + if (_force16Colors) { if (_isVirtualTerminal) { - consoleBuffer = _outputHandle; + _consoleBuffer = _outputHandle; } else { - consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; - _screenBuffer = consoleBuffer; + _consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; + _screenBuffer = _consoleBuffer; } } else { - consoleBuffer = _outputHandle; + _consoleBuffer = _outputHandle; } + base.Write (outputBuffer); + /* + var result = false; GetWindowSize (out var originalCursorPosition); @@ -198,7 +200,7 @@ public void Write (IOutputBuffer outputBuffer) { StringBuilder sbSinceLastAttrChange = new StringBuilder (); - AppendOrWriteCursorPosition (new (0, row), force16Colors, stringBuilder, consoleBuffer); + AppendOrWriteCursorPosition (new (0, row), _force16Colors, stringBuilder, _consoleBuffer); for (var col = 0; col < outputBuffer.Cols; col++) { @@ -217,10 +219,10 @@ public void Write (IOutputBuffer outputBuffer) prev = attr; // write everything out up till now - AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, consoleBuffer); + AppendOrWrite (sbSinceLastAttrChange.ToString (), _force16Colors, stringBuilder, _consoleBuffer); // then change color/style etc - AppendOrWrite (attr!, force16Colors, stringBuilder, (nint)consoleBuffer); + AppendOrWrite (attr!, _force16Colors, stringBuilder, (nint)_consoleBuffer); sbSinceLastAttrChange.Clear (); sbSinceLastAttrChange.Append (cell.Rune); _redrawTextStyle = attr.Style; @@ -234,83 +236,99 @@ public void Write (IOutputBuffer outputBuffer) // write trailing bits if (sbSinceLastAttrChange.Length > 0) { - AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, consoleBuffer); + AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, _consoleBuffer); sbSinceLastAttrChange.Clear (); } } - AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, consoleBuffer); + AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, _consoleBuffer); if (force16Colors && !_isVirtualTerminal) { - SetConsoleActiveScreenBuffer (consoleBuffer); + SetConsoleActiveScreenBuffer (_consoleBuffer); return; } var span = stringBuilder.ToString ().AsSpan (); // still allocates the string - result = WriteConsole (consoleBuffer, span, (uint)span.Length, out _, nint.Zero); + result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero); foreach (SixelToRender sixel in Application.Sixel) { SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); - WriteConsole (consoleBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); + WriteConsole (_consoleBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); } - if (!result) + */ + + if (_force16Colors && !_isVirtualTerminal) { - int err = Marshal.GetLastWin32Error (); + SetConsoleActiveScreenBuffer (_consoleBuffer); + } + else + { + var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string - if (err != 0) + var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero); + if (!result) { - throw new Win32Exception (err); + int err = Marshal.GetLastWin32Error (); + + if (err != 0) + { + throw new Win32Exception (err); + } } } } - - - private void AppendOrWrite (string str, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + /// + protected override void Write (StringBuilder output) { - if (str.Length == 0) + if (output.Length == 0) { return; } + var str = output.ToString (); + // Replace escape characters with space str = str.Replace ("\x1b", " "); - if (force16Colors && !_isVirtualTerminal) + if (_force16Colors && !_isVirtualTerminal) { var a = str.ToCharArray (); - WriteConsole (screenBuffer,a ,(uint)a.Length, out _, nint.Zero); + WriteConsole (_screenBuffer,a ,(uint)a.Length, out _, nint.Zero); } else { - stringBuilder.Append (str); + _everythingStringBuilder.Append (str); } } - private void AppendOrWrite (Attribute attr, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) + /// + protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle) { + var force16Colors = Application.Force16Colors; + if (force16Colors) { if (_isVirtualTerminal) { - stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ())); - stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ())); - EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style); + output.Append (EscSeqUtils.CSI_SetForegroundColor (attr.Foreground.GetAnsiColorCode ())); + output.Append (EscSeqUtils.CSI_SetBackgroundColor (attr.Background.GetAnsiColorCode ())); + EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style); } else { var as16ColorInt = (ushort)((int)attr.Foreground.GetClosestNamedColor16 () | ((int)attr.Background.GetClosestNamedColor16 () << 4)); - SetConsoleTextAttribute (screenBuffer, as16ColorInt); + SetConsoleTextAttribute (_screenBuffer, as16ColorInt); } } else { - EscSeqUtils.CSI_AppendForegroundColorRGB (stringBuilder, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B); - EscSeqUtils.CSI_AppendBackgroundColorRGB (stringBuilder, attr.Background.R, attr.Background.G, attr.Background.B); - EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style); + EscSeqUtils.CSI_AppendForegroundColorRGB (output, attr.Foreground.R, attr.Foreground.G, attr.Foreground.B); + EscSeqUtils.CSI_AppendBackgroundColorRGB (output, attr.Background.R, attr.Background.G, attr.Background.B); + EscSeqUtils.CSI_AppendTextStyleChange (output, redrawTextStyle, attr.Style); } } @@ -374,8 +392,17 @@ public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) return sz; } + + /// + protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY) + { + // TODO: Too many methods with this name lets try and standardize to base class name + SetCursorPosition (screenPositionX,screenPositionY); + return true; + } + /// - public void SetCursorVisibility (CursorVisibility visibility) + public override void SetCursorVisibility (CursorVisibility visibility) { if (ConsoleDriver.RunningUnitTests) { @@ -417,6 +444,9 @@ public void SetCursorPosition (int col, int row) } private bool _isDisposed; + private bool _force16Colors; + private nint _consoleBuffer; + private StringBuilder _everythingStringBuilder; /// public void Dispose () From 9cf7e4bb8841accb5b42443e90ae6aaf386894ac Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Aug 2025 05:18:01 +0100 Subject: [PATCH 026/104] WIP working to fix set cursor position --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 31 +++++++++--------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 7bca97e4a6..bb0c19c1fb 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -290,10 +290,6 @@ protected override void Write (StringBuilder output) var str = output.ToString (); - // Replace escape characters with space - str = str.Replace ("\x1b", " "); - - if (_force16Colors && !_isVirtualTerminal) { var a = str.ToCharArray (); @@ -332,19 +328,6 @@ protected override void AppendOrWriteAttribute (StringBuilder output, Attribute } } - private void AppendOrWriteCursorPosition (Point p, bool force16Colors, StringBuilder stringBuilder, nint screenBuffer) - { - if (force16Colors && !_isVirtualTerminal) - { - SetConsoleCursorPosition (screenBuffer, new ((short)p.X, (short)p.Y)); - } - else - { - // CSI codes are 1 indexed - stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); - EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, p.Y+1, p.X+1); - } - } private Size? _lastSize = null; public Size GetWindowSize () @@ -396,8 +379,18 @@ public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) /// protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY) { - // TODO: Too many methods with this name lets try and standardize to base class name - SetCursorPosition (screenPositionX,screenPositionY); + + if (_force16Colors && !_isVirtualTerminal) + { + SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY)); + } + else + { + // CSI codes are 1 indexed + _everythingStringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition); + EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1); + } + return true; } From 66eccb719f02cbaf027cc3f8221b206bba5008ec Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 2 Aug 2025 05:20:45 +0100 Subject: [PATCH 027/104] Remove commented out code --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 73 ------------------------ 1 file changed, 73 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index bb0c19c1fb..d7afd2022f 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -186,79 +186,6 @@ public override void Write (IOutputBuffer outputBuffer) } base.Write (outputBuffer); - /* - - var result = false; - - GetWindowSize (out var originalCursorPosition); - - StringBuilder stringBuilder = new (); - - Attribute? prev = null; - - for (var row = 0; row < outputBuffer.Rows; row++) - { - StringBuilder sbSinceLastAttrChange = new StringBuilder (); - - AppendOrWriteCursorPosition (new (0, row), _force16Colors, stringBuilder, _consoleBuffer); - - for (var col = 0; col < outputBuffer.Cols; col++) - { - var cell = outputBuffer.Contents [row, col]; - var attr = cell.Attribute ?? prev ?? new (); - - // If we have 10 width and are at 9 it's ok - // But if it's width 2 we can't write it so must skip - if (cell.Rune.GetColumns () + col > outputBuffer.Cols) - { - continue; - } - - if (attr != prev) - { - prev = attr; - - // write everything out up till now - AppendOrWrite (sbSinceLastAttrChange.ToString (), _force16Colors, stringBuilder, _consoleBuffer); - - // then change color/style etc - AppendOrWrite (attr!, _force16Colors, stringBuilder, (nint)_consoleBuffer); - sbSinceLastAttrChange.Clear (); - sbSinceLastAttrChange.Append (cell.Rune); - _redrawTextStyle = attr.Style; - } - else - { - sbSinceLastAttrChange.Append (cell.Rune); - } - } - - // write trailing bits - if (sbSinceLastAttrChange.Length > 0) - { - AppendOrWrite (sbSinceLastAttrChange.ToString (), force16Colors, stringBuilder, _consoleBuffer); - sbSinceLastAttrChange.Clear (); - } - } - - AppendOrWriteCursorPosition(new (originalCursorPosition.X, originalCursorPosition.Y),force16Colors,stringBuilder, _consoleBuffer); - - if (force16Colors && !_isVirtualTerminal) - { - SetConsoleActiveScreenBuffer (_consoleBuffer); - return; - } - - var span = stringBuilder.ToString ().AsSpan (); // still allocates the string - result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero); - - foreach (SixelToRender sixel in Application.Sixel) - { - SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y); - WriteConsole (_consoleBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero); - } - - */ if (_force16Colors && !_isVirtualTerminal) { From a4eb3eb253c2b75c5b4acc5c0adc0058bd3155d5 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sat, 2 Aug 2025 11:48:33 +0100 Subject: [PATCH 028/104] Fixes legacy output mode --- Terminal.Gui/Drivers/V2/OutputBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index 1624ae0e0b..8c725d8a8f 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -133,7 +133,7 @@ public virtual void Write (IOutputBuffer buffer) if (output.Length > 0) { SetCursorPositionImpl (lastCol, row); - Console.Out.Write (output); + Write (output); } } From 89079d2c0a25891b55d83b4eb33b0a5e95ae91f3 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 3 Aug 2025 18:35:39 +0100 Subject: [PATCH 029/104] Fixes size with no alt buffer supported on VTS and size restore after maximized. --- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 246 ++++++++++++++++------- 1 file changed, 173 insertions(+), 73 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index d7afd2022f..2aa269ffaa 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -18,14 +18,15 @@ private static partial bool WriteConsole ( nint lpReserved ); - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint GetStdHandle (int nStdHandle); + [LibraryImport ("kernel32.dll", SetLastError = true)] + private static partial nint GetStdHandle (int nStdHandle); - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle (nint handle); + [LibraryImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool CloseHandle (nint handle); - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern nint CreateConsoleScreenBuffer ( + [LibraryImport ("kernel32.dll", SetLastError = true)] + private static partial nint CreateConsoleScreenBuffer ( DesiredAccess dwDesiredAccess, ShareMode dwShareMode, nint secutiryAttributes, @@ -34,6 +35,7 @@ nint screenBufferData ); [DllImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi); [Flags] @@ -52,27 +54,53 @@ private enum DesiredAccess : uint internal static nint INVALID_HANDLE_VALUE = new (-1); - [DllImport ("kernel32.dll", SetLastError = true)] - private static extern bool SetConsoleActiveScreenBuffer (nint handle); + [LibraryImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool SetConsoleActiveScreenBuffer (nint handle); - [DllImport ("kernel32.dll")] - private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition); + [LibraryImport ("kernel32.dll")] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool SetConsoleCursorPosition (nint hConsoleOutput, WindowsConsole.Coord dwCursorPosition); [DllImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo); + [LibraryImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + public static partial bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes); + + [LibraryImport ("kernel32.dll")] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + + [LibraryImport ("kernel32.dll")] + [return: MarshalAs (UnmanagedType.Bool)] + private static partial bool SetConsoleMode (nint hConsoleHandle, uint dwMode); + + [LibraryImport ("kernel32.dll", SetLastError = true)] + private static partial WindowsConsole.Coord GetLargestConsoleWindowSize ( + nint hConsoleOutput + ); + [DllImport ("kernel32.dll", SetLastError = true)] - public static extern bool SetConsoleTextAttribute (nint hConsoleOutput, ushort wAttributes); + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo); - [DllImport ("kernel32.dll")] - private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode); + [DllImport ("kernel32.dll", SetLastError = true)] + [return: MarshalAs (UnmanagedType.Bool)] + private static extern bool SetConsoleWindowInfo ( + nint hConsoleOutput, + bool bAbsolute, + [In] ref WindowsConsole.SmallRect lpConsoleWindow + ); private const int STD_OUTPUT_HANDLE = -11; private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; private readonly nint _outputHandle; private nint _screenBuffer; private readonly bool _isVirtualTerminal; - + public WindowsOutput () { Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}"); @@ -82,8 +110,10 @@ public WindowsOutput () return; } + // Get the standard output handle which is the current screen buffer. _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE); - _isVirtualTerminal = GetConsoleMode (_outputHandle, out uint mode) && (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; + GetConsoleMode (_outputHandle, out uint mode); + _isVirtualTerminal = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; if (_isVirtualTerminal) { @@ -92,14 +122,20 @@ public WindowsOutput () } else { - _screenBuffer = CreateScreenBuffer (); + CreateScreenBuffer (); - var backBuffer = CreateScreenBuffer (); - _doubleBuffer = [_screenBuffer, backBuffer]; + if (!GetConsoleMode (_screenBuffer, out mode)) + { + throw new ApplicationException ($"Failed to get screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}."); + } + + const uint ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002; + + mode &= ~ENABLE_WRAP_AT_EOL_OUTPUT; // Disable wrap - if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + if (!SetConsoleMode (_screenBuffer, mode)) { - throw new Win32Exception (Marshal.GetLastWin32Error ()); + throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}."); } // Force 16 colors if not in virtual terminal mode. @@ -107,12 +143,9 @@ public WindowsOutput () } } - private int _activeDoubleBuffer = 0; - private nint [] _doubleBuffer = new nint[2]; - - private nint CreateScreenBuffer () + private void CreateScreenBuffer () { - var buff = CreateConsoleScreenBuffer ( + _screenBuffer = CreateConsoleScreenBuffer ( DesiredAccess.GenericRead | DesiredAccess.GenericWrite, ShareMode.FileShareRead | ShareMode.FileShareWrite, nint.Zero, @@ -120,7 +153,7 @@ private nint CreateScreenBuffer () nint.Zero ); - if (buff == INVALID_HANDLE_VALUE) + if (_screenBuffer == INVALID_HANDLE_VALUE) { int err = Marshal.GetLastWin32Error (); @@ -130,7 +163,10 @@ private nint CreateScreenBuffer () } } - return buff; + if (!SetConsoleActiveScreenBuffer (_screenBuffer)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } } public void Write (ReadOnlySpan str) @@ -141,23 +177,57 @@ public void Write (ReadOnlySpan str) } } - public void RecreateBackBuffer () + public Size ResizeBuffer (Size size) { - int idx = (_activeDoubleBuffer + 1) % 2; - var inactiveBuffer = _doubleBuffer [idx]; + Size newSize = SetConsoleWindow ( + (short)Math.Max (size.Width, 0), + (short)Math.Max (size.Height, 0)); - DisposeBuffer (inactiveBuffer); - _doubleBuffer [idx] = CreateScreenBuffer (); + return newSize; } - private void DisposeBuffer (nint buffer) + internal Size SetConsoleWindow (short cols, short rows) { - if (buffer != 0 && buffer != INVALID_HANDLE_VALUE) + var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); + + if (!GetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) { - if (!CloseHandle (buffer)) - { - throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to close screen buffer handle."); - } + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer); + short newCols = Math.Min (cols, maxWinSize.X); + short newRows = Math.Min (rows, maxWinSize.Y); + csbi.dwSize = new (newCols, Math.Max (newRows, (short)1)); + csbi.srWindow = new (0, 0, newCols, newRows); + csbi.dwMaximumWindowSize = new (newCols, newRows); + + if (!SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); + } + + var winRect = new WindowsConsole.SmallRect (0, 0, (short)(newCols - 1), (short)Math.Max (newRows - 1, 0)); + + if (!SetConsoleWindowInfo (_outputHandle, true, ref winRect)) + { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + return new (cols, rows); + } + + SetConsoleOutputWindow (csbi); + + return new (winRect.Right + 1, newRows - 1 < 0 ? 0 : winRect.Bottom + 1); + } + + private void SetConsoleOutputWindow (WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX csbi) + { + if ((_isVirtualTerminal + ? _outputHandle + : _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (_isVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi)) + { + throw new Win32Exception (Marshal.GetLastWin32Error ()); } } @@ -176,8 +246,7 @@ public override void Write (IOutputBuffer outputBuffer) } else { - _consoleBuffer = _doubleBuffer [_activeDoubleBuffer = (_activeDoubleBuffer + 1) % 2]; - _screenBuffer = _consoleBuffer; + _consoleBuffer = _screenBuffer; } } else @@ -187,25 +256,37 @@ public override void Write (IOutputBuffer outputBuffer) base.Write (outputBuffer); - if (_force16Colors && !_isVirtualTerminal) - { - SetConsoleActiveScreenBuffer (_consoleBuffer); - } - else + try { - var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string - - var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero); - if (!result) + if (_force16Colors && !_isVirtualTerminal) + { + SetConsoleActiveScreenBuffer (_consoleBuffer); + } + else { - int err = Marshal.GetLastWin32Error (); + var span = _everythingStringBuilder.ToString ().AsSpan (); // still allocates the string - if (err != 0) + var result = WriteConsole (_consoleBuffer, span, (uint)span.Length, out _, nint.Zero); + if (!result) { - throw new Win32Exception (err); + int err = Marshal.GetLastWin32Error (); + + if (err != 0) + { + throw new Win32Exception (err); + } } } } + catch (Exception e) + { + Logging.Logger.LogError ($"Error: {e.Message} in {nameof (WindowsOutput)}"); + + if (!ConsoleDriver.RunningUnitTests) + { + throw; + } + } } /// protected override void Write (StringBuilder output) @@ -256,27 +337,49 @@ protected override void AppendOrWriteAttribute (StringBuilder output, Attribute } - private Size? _lastSize = null; + private Size? _lastSize; + private Size? _lastWindowSizeBeforeMaximized; + private bool _lockResize; + public Size GetWindowSize () { + if (_lockResize) + { + return _lastSize!.Value; + } var newSize = GetWindowSize (out _); + Size largestWindowSize = GetLargestConsoleWindowSize (); - if (_lastSize == null || _lastSize != newSize) + if (_lastWindowSizeBeforeMaximized is null && newSize == largestWindowSize) { - _lastSize ??= newSize; - - // Back buffers only apply to 16 color mode so if not in that just ignore - if (_isVirtualTerminal) + _lastWindowSizeBeforeMaximized = _lastSize; + } + else if (_lastWindowSizeBeforeMaximized is { } && newSize != largestWindowSize) + { + if (newSize != _lastWindowSizeBeforeMaximized) { - return newSize; + newSize = _lastWindowSizeBeforeMaximized.Value; } + _lastWindowSizeBeforeMaximized = null; + } + + if (_lastSize == null || _lastSize != newSize) + { // User is resizing the screen, they can only ever resize the active // buffer since. We now however have issue because background offscreen // buffer will be wrong size, recreate it to ensure it doesn't result in // differing active and back buffer sizes (which causes flickering of window size) - RecreateBackBuffer (); + Size? bufSize = null; + while (bufSize != newSize) + { + _lockResize = true; + bufSize = ResizeBuffer (newSize); + } + + _lockResize = false; + _lastSize = newSize; } return newSize; @@ -302,6 +405,12 @@ public Size GetWindowSize (out WindowsConsole.Coord cursorPosition) return sz; } + private Size GetLargestConsoleWindowSize () + { + WindowsConsole.Coord maxWinSize = GetLargestConsoleWindowSize (_isVirtualTerminal ? _outputHandle : _screenBuffer); + + return new (maxWinSize.X, maxWinSize.Y); + } /// protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY) @@ -383,21 +492,12 @@ public void Dispose () } else { - for (int i = 0; i < 2; i++) + if (_screenBuffer != nint.Zero) { - var buffer = _doubleBuffer [i]; - if (buffer != nint.Zero) - { - try - { - CloseHandle (buffer); - } - catch (Exception e) - { - Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method"); - } - } + CloseHandle (_screenBuffer); } + + _screenBuffer = nint.Zero; } _isDisposed = true; From 46a419dc2c5f46bafb5964867292b14ebfda1c1e Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 3 Aug 2025 22:30:14 +0100 Subject: [PATCH 030/104] Fix set cursor which also fixes the broken surrogate pairs --- Terminal.Gui/Drivers/V2/IConsoleOutput.cs | 3 ++- Terminal.Gui/Drivers/V2/MainLoop.cs | 12 ++++++++---- Terminal.Gui/Drivers/V2/NetOutput.cs | 6 +++--- Terminal.Gui/Drivers/V2/OutputBase.cs | 2 +- Terminal.Gui/Drivers/V2/WindowsOutput.cs | 17 +++++++++++++---- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/IConsoleOutput.cs b/Terminal.Gui/Drivers/V2/IConsoleOutput.cs index aa35c06eef..acd1e5c5f5 100644 --- a/Terminal.Gui/Drivers/V2/IConsoleOutput.cs +++ b/Terminal.Gui/Drivers/V2/IConsoleOutput.cs @@ -38,5 +38,6 @@ public interface IConsoleOutput : IDisposable /// /// /// - void SetCursorPosition (int col, int row); + /// Force setting cursor position if true, even with the same position. + void SetCursorPosition (int col, int row, bool force = false); } diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 5b6d9fdde7..35bac4a381 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -136,9 +136,13 @@ internal void IterationImpl () Out.Write (OutputBuffer); Out.SetCursorVisibility (CursorVisibility.Default); - } - SetCursor (); + SetCursor (true); + } + else + { + SetCursor (); + } } var swCallbacks = Stopwatch.StartNew (); @@ -148,7 +152,7 @@ internal void IterationImpl () Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds); } - private void SetCursor () + private void SetCursor (bool force = false) { View? mostFocused = Application.Top!.MostFocused; @@ -164,7 +168,7 @@ private void SetCursor () // Translate to screen coordinates to = mostFocused.ViewportToScreen (to.Value); - Out.SetCursorPosition (to.Value.X, to.Value.Y); + Out.SetCursorPosition (to.Value.X, to.Value.Y, force); Out.SetCursorVisibility (mostFocused.CursorVisibility); } else diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 511422311d..4caa427ac4 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -53,7 +53,7 @@ public Size GetWindowSize () /// - public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); } + public void SetCursorPosition (int col, int row, bool force = false) { SetCursorPositionImpl (col, row, force); } private Point _lastCursorPosition; @@ -83,9 +83,9 @@ protected override void Write (StringBuilder output) Console.Out.Write (output); } - protected override bool SetCursorPositionImpl (int col, int row) + protected override bool SetCursorPositionImpl (int col, int row, bool force = false) { - if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row) + if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row && !force) { return true; } diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index 8c725d8a8f..1de5996dfc 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -163,7 +163,7 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref protected abstract void Write (StringBuilder output); - protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY); + protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY, bool force = false); public abstract void SetCursorVisibility (CursorVisibility visibility); } diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 2aa269ffaa..78bcb7bf1c 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -413,7 +413,7 @@ private Size GetLargestConsoleWindowSize () } /// - protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY) + protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY, bool force = false) { if (_force16Colors && !_isVirtualTerminal) @@ -460,16 +460,25 @@ public override void SetCursorVisibility (CursorVisibility visibility) private Point _lastCursorPosition; /// - public void SetCursorPosition (int col, int row) + public void SetCursorPosition (int col, int row, bool force = false) { - if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row) + if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row && !force) { return; } _lastCursorPosition = new (col, row); - SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row)); + if (_isVirtualTerminal) + { + var sb = new StringBuilder (); + EscSeqUtils.CSI_AppendCursorPosition (sb, row + 1, col + 1); + Write (sb.ToString ()); + } + else + { + SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row)); + } } private bool _isDisposed; From dae2b26897151c13e9ef09051910530699545ef0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 3 Aug 2025 22:56:25 +0100 Subject: [PATCH 031/104] Add force parameter --- Tests/TerminalGuiFluentTesting/FakeOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TerminalGuiFluentTesting/FakeOutput.cs b/Tests/TerminalGuiFluentTesting/FakeOutput.cs index 1b73d9ba1a..e948a97fb9 100644 --- a/Tests/TerminalGuiFluentTesting/FakeOutput.cs +++ b/Tests/TerminalGuiFluentTesting/FakeOutput.cs @@ -23,7 +23,7 @@ public void Write (ReadOnlySpan text) { } public void SetCursorVisibility (CursorVisibility visibility) { } /// - public void SetCursorPosition (int col, int row) { CursorPosition = new Point (col, row); } + public void SetCursorPosition (int col, int row, bool force = false) { CursorPosition = new Point (col, row); } /// /// The last value set by calling From 556b1d5cb593d6c146787758bc2cacbca6ad181e Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 3 Aug 2025 23:24:16 +0100 Subject: [PATCH 032/104] Fixes an issue that only happens with Windows Terminal when paste surrogate pairs by press Ctrl+V --- Terminal.Gui/Drivers/V2/InputProcessor.cs | 39 +++++++++++++++++++ Terminal.Gui/Drivers/V2/NetInputProcessor.cs | 9 ++++- .../Drivers/V2/WindowsInputProcessor.cs | 3 +- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/InputProcessor.cs b/Terminal.Gui/Drivers/V2/InputProcessor.cs index 607ad3a23e..c860ba796e 100644 --- a/Terminal.Gui/Drivers/V2/InputProcessor.cs +++ b/Terminal.Gui/Drivers/V2/InputProcessor.cs @@ -162,4 +162,43 @@ private IEnumerable ReleaseParserHeldKeysIfStale () /// /// protected abstract void ProcessAfterParsing (T input); + + internal char _highSurrogate = '\0'; + + internal bool IsValidInput (Key key, out Key result) + { + result = key; + + if (char.IsHighSurrogate ((char)key)) + { + _highSurrogate = (char)key; + + return false; + } + + if (_highSurrogate > 0 && char.IsLowSurrogate ((char)key)) + { + result = (KeyCode)new Rune (_highSurrogate, (char)key).Value; + _highSurrogate = '\0'; + + return true; + } + + if (char.IsSurrogate ((char)key)) + { + return false; + } + + if (_highSurrogate > 0) + { + _highSurrogate = '\0'; + } + + if (key.KeyCode == 0) + { + return false; + } + + return true; + } } diff --git a/Terminal.Gui/Drivers/V2/NetInputProcessor.cs b/Terminal.Gui/Drivers/V2/NetInputProcessor.cs index 4e11d402a4..36c885e355 100644 --- a/Terminal.Gui/Drivers/V2/NetInputProcessor.cs +++ b/Terminal.Gui/Drivers/V2/NetInputProcessor.cs @@ -41,8 +41,13 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo) protected override void ProcessAfterParsing (ConsoleKeyInfo input) { var key = KeyConverter.ToKey (input); - OnKeyDown (key); - OnKeyUp (key); + + // If the key is not valid, we don't want to raise any events. + if (IsValidInput (key, out key)) + { + OnKeyDown (key); + OnKeyUp (key); + } } /* For building test cases */ diff --git a/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs index cdd2e1dfe4..b51856a565 100644 --- a/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs +++ b/Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs @@ -71,7 +71,8 @@ protected override void ProcessAfterParsing (InputRecord input) { var key = KeyConverter.ToKey (input); - if (key != (Key)0) + // If the key is not valid, we don't want to raise any events. + if (IsValidInput (key, out key)) { OnKeyDown (key!); OnKeyUp (key!); From 4117f4a3fd70d73f9c42d3f446f17e460e08389b Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Aug 2025 01:03:00 +0100 Subject: [PATCH 033/104] In Windows escape sequences must be sent during the lifetime of the console which is created in input handle --- Terminal.Gui/Drivers/V2/NetInput.cs | 16 ++++++++++++++-- Terminal.Gui/Drivers/V2/NetOutput.cs | 15 --------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/NetInput.cs b/Terminal.Gui/Drivers/V2/NetInput.cs index 518c77e557..10b5e1cf65 100644 --- a/Terminal.Gui/Drivers/V2/NetInput.cs +++ b/Terminal.Gui/Drivers/V2/NetInput.cs @@ -40,6 +40,12 @@ public NetInput () } } + //Enable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); + + //Set cursor key to application. + Console.Out.Write (EscSeqUtils.CSI_HideCursor); + Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); Console.TreatControlCAsInput = true; } @@ -68,8 +74,14 @@ protected override IEnumerable Read () public override void Dispose () { base.Dispose (); - _adjustConsole?.Cleanup (); - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + + //Disable alternative screen buffer. + Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); + + //Set cursor key to cursor. + Console.Out.Write (EscSeqUtils.CSI_ShowCursor); + + _adjustConsole?.Cleanup (); } } diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 4caa427ac4..1523dc2c37 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -25,12 +25,6 @@ public NetOutput () { _isWinPlatform = true; } - - //Enable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll); - - //Set cursor key to application. - Console.Out.Write (EscSeqUtils.CSI_HideCursor); } /// @@ -117,15 +111,6 @@ protected override bool SetCursorPositionImpl (int col, int row, bool force = fa /// public void Dispose () { - Console.ResetColor (); - - //Disable alternative screen buffer. - Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll); - - //Set cursor key to cursor. - Console.Out.Write (EscSeqUtils.CSI_ShowCursor); - - Console.Out.Close (); } /// From e2d3cc17ddaa32ae67df099372c5a3ebb649d3c0 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Aug 2025 21:16:29 +0100 Subject: [PATCH 034/104] Ensure flush the input buffer before reset the console --- Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs index 4750a32e87..39dee104cb 100644 --- a/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs +++ b/Terminal.Gui/Drivers/NetDriver/NetWinVTConsole.cs @@ -96,6 +96,11 @@ public NetWinVTConsole () public void Cleanup () { + if (!FlushConsoleInputBuffer (_inputHandle)) + { + throw new ApplicationException ($"Failed to flush input buffer, error code: {GetLastError ()}."); + } + if (!SetConsoleMode (_inputHandle, _originalInputConsoleMode)) { throw new ApplicationException ($"Failed to restore input console mode, error code: {GetLastError ()}."); @@ -123,4 +128,7 @@ public void Cleanup () [DllImport ("kernel32.dll")] private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode); + + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool FlushConsoleInputBuffer (nint hConsoleInput); } From 5306017a61a85df9aa0d090bcb38867a63b64b0a Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Aug 2025 22:03:18 +0100 Subject: [PATCH 035/104] Flush input buffer before reset console in v2win --- Terminal.Gui/Drivers/V2/WindowsInput.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Terminal.Gui/Drivers/V2/WindowsInput.cs b/Terminal.Gui/Drivers/V2/WindowsInput.cs index 11d01cb608..fa9accbad2 100644 --- a/Terminal.Gui/Drivers/V2/WindowsInput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsInput.cs @@ -35,6 +35,9 @@ out uint lpNumberOfEventsRead private readonly uint _originalConsoleMode; + [DllImport ("kernel32.dll", SetLastError = true)] + private static extern bool FlushConsoleInputBuffer (nint hConsoleInput); + public WindowsInput () { Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}"); @@ -123,6 +126,11 @@ public override void Dispose () return; } + if (!FlushConsoleInputBuffer (_inputHandle)) + { + throw new ApplicationException ($"Failed to flush input buffer, error code: {Marshal.GetLastWin32Error ()}."); + } + SetConsoleMode (_inputHandle, _originalConsoleMode); } } From f32aef72dcca6c25f51f2be1ccc2089855f84f3f Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Aug 2025 22:12:48 +0100 Subject: [PATCH 036/104] Fixes issue in v2net not being refreshing the menu bar at start --- Terminal.Gui/Drivers/V2/MainLoop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 35bac4a381..e86a8d702c 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -184,7 +184,7 @@ private bool AnySubViewsNeedDrawn (View? v) return false; } - if (v.NeedsDraw || v.NeedsLayout) + if (v.NeedsDraw || v.NeedsLayout || v.SubViewNeedsDraw) { // Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) "); From 96b3b41187d0072c39a2dff04f8525d8d27bcfe1 Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 4 Aug 2025 22:28:41 +0100 Subject: [PATCH 037/104] Only force layout and draw on size changed. --- Terminal.Gui/Drivers/V2/MainLoop.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index e86a8d702c..c3e93d30bb 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -131,7 +131,7 @@ internal void IterationImpl () { Logging.Redraws.Add (1); - Application.LayoutAndDrawImpl (true); + Application.LayoutAndDrawImpl (sizeChanged); Out.Write (OutputBuffer); From 36cb50dd0be5875f7f34462e40791c02149a3554 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 5 Aug 2025 13:19:08 +0100 Subject: [PATCH 038/104] Fix v2net issue not draw first line by forcing set cursor position --- Terminal.Gui/Drivers/V2/MainLoop.cs | 4 ++-- Terminal.Gui/Drivers/V2/OutputBase.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index c3e93d30bb..35bac4a381 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -131,7 +131,7 @@ internal void IterationImpl () { Logging.Redraws.Add (1); - Application.LayoutAndDrawImpl (sizeChanged); + Application.LayoutAndDrawImpl (true); Out.Write (OutputBuffer); @@ -184,7 +184,7 @@ private bool AnySubViewsNeedDrawn (View? v) return false; } - if (v.NeedsDraw || v.NeedsLayout || v.SubViewNeedsDraw) + if (v.NeedsDraw || v.NeedsLayout) { // Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) "); diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index 1de5996dfc..d0ffe83568 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -49,7 +49,7 @@ public virtual void Write (IOutputBuffer buffer) return; } - if (!SetCursorPositionImpl (0, row)) + if (!SetCursorPositionImpl (0, row, true)) { return; } @@ -123,7 +123,7 @@ public virtual void Write (IOutputBuffer buffer) else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) { WriteToConsole (output, ref lastCol, row, ref outputWidth); - SetCursorPositionImpl (col - 1, row); + SetCursorPositionImpl (col - 1, row, true); } buffer.Contents [row, col].IsDirty = false; @@ -132,7 +132,7 @@ public virtual void Write (IOutputBuffer buffer) if (output.Length > 0) { - SetCursorPositionImpl (lastCol, row); + SetCursorPositionImpl (lastCol, row, true); Write (output); } } @@ -141,7 +141,7 @@ public virtual void Write (IOutputBuffer buffer) { if (!string.IsNullOrWhiteSpace (s.SixelData)) { - SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y); + SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y, true); Console.Out.Write (s.SixelData); } } @@ -154,7 +154,7 @@ public virtual void Write (IOutputBuffer buffer) private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { - SetCursorPositionImpl (lastCol, row); + SetCursorPositionImpl (lastCol, row, true); Write (output); output.Clear (); lastCol += outputWidth; From fd76522dbd8d383d44733e9ab33cafa56bee7280 Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Aug 2025 12:47:03 +0100 Subject: [PATCH 039/104] Set _lastCursorPosition nullable and remove bool force from set cursor position --- Terminal.Gui/Drivers/V2/IConsoleOutput.cs | 3 +-- Terminal.Gui/Drivers/V2/MainLoop.cs | 12 ++++-------- Terminal.Gui/Drivers/V2/NetOutput.cs | 8 ++++---- Terminal.Gui/Drivers/V2/OutputBase.cs | 12 ++++++------ Terminal.Gui/Drivers/V2/WindowsOutput.cs | 11 ++++++----- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/IConsoleOutput.cs b/Terminal.Gui/Drivers/V2/IConsoleOutput.cs index acd1e5c5f5..aa35c06eef 100644 --- a/Terminal.Gui/Drivers/V2/IConsoleOutput.cs +++ b/Terminal.Gui/Drivers/V2/IConsoleOutput.cs @@ -38,6 +38,5 @@ public interface IConsoleOutput : IDisposable /// /// /// - /// Force setting cursor position if true, even with the same position. - void SetCursorPosition (int col, int row, bool force = false); + void SetCursorPosition (int col, int row); } diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 35bac4a381..5b6d9fdde7 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -136,13 +136,9 @@ internal void IterationImpl () Out.Write (OutputBuffer); Out.SetCursorVisibility (CursorVisibility.Default); - - SetCursor (true); - } - else - { - SetCursor (); } + + SetCursor (); } var swCallbacks = Stopwatch.StartNew (); @@ -152,7 +148,7 @@ internal void IterationImpl () Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds); } - private void SetCursor (bool force = false) + private void SetCursor () { View? mostFocused = Application.Top!.MostFocused; @@ -168,7 +164,7 @@ private void SetCursor (bool force = false) // Translate to screen coordinates to = mostFocused.ViewportToScreen (to.Value); - Out.SetCursorPosition (to.Value.X, to.Value.Y, force); + Out.SetCursorPosition (to.Value.X, to.Value.Y); Out.SetCursorVisibility (mostFocused.CursorVisibility); } else diff --git a/Terminal.Gui/Drivers/V2/NetOutput.cs b/Terminal.Gui/Drivers/V2/NetOutput.cs index 1523dc2c37..bfc3e10884 100644 --- a/Terminal.Gui/Drivers/V2/NetOutput.cs +++ b/Terminal.Gui/Drivers/V2/NetOutput.cs @@ -47,9 +47,9 @@ public Size GetWindowSize () /// - public void SetCursorPosition (int col, int row, bool force = false) { SetCursorPositionImpl (col, row, force); } + public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); } - private Point _lastCursorPosition; + private Point? _lastCursorPosition; /// protected override void AppendOrWriteAttribute (StringBuilder output, Attribute attr, TextStyle redrawTextStyle) @@ -77,9 +77,9 @@ protected override void Write (StringBuilder output) Console.Out.Write (output); } - protected override bool SetCursorPositionImpl (int col, int row, bool force = false) + protected override bool SetCursorPositionImpl (int col, int row) { - if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row && !force) + if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row) { return true; } diff --git a/Terminal.Gui/Drivers/V2/OutputBase.cs b/Terminal.Gui/Drivers/V2/OutputBase.cs index d0ffe83568..8c725d8a8f 100644 --- a/Terminal.Gui/Drivers/V2/OutputBase.cs +++ b/Terminal.Gui/Drivers/V2/OutputBase.cs @@ -49,7 +49,7 @@ public virtual void Write (IOutputBuffer buffer) return; } - if (!SetCursorPositionImpl (0, row, true)) + if (!SetCursorPositionImpl (0, row)) { return; } @@ -123,7 +123,7 @@ public virtual void Write (IOutputBuffer buffer) else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) { WriteToConsole (output, ref lastCol, row, ref outputWidth); - SetCursorPositionImpl (col - 1, row, true); + SetCursorPositionImpl (col - 1, row); } buffer.Contents [row, col].IsDirty = false; @@ -132,7 +132,7 @@ public virtual void Write (IOutputBuffer buffer) if (output.Length > 0) { - SetCursorPositionImpl (lastCol, row, true); + SetCursorPositionImpl (lastCol, row); Write (output); } } @@ -141,7 +141,7 @@ public virtual void Write (IOutputBuffer buffer) { if (!string.IsNullOrWhiteSpace (s.SixelData)) { - SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y, true); + SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y); Console.Out.Write (s.SixelData); } } @@ -154,7 +154,7 @@ public virtual void Write (IOutputBuffer buffer) private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth) { - SetCursorPositionImpl (lastCol, row, true); + SetCursorPositionImpl (lastCol, row); Write (output); output.Clear (); lastCol += outputWidth; @@ -163,7 +163,7 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref protected abstract void Write (StringBuilder output); - protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY, bool force = false); + protected abstract bool SetCursorPositionImpl (int screenPositionX, int screenPositionY); public abstract void SetCursorVisibility (CursorVisibility visibility); } diff --git a/Terminal.Gui/Drivers/V2/WindowsOutput.cs b/Terminal.Gui/Drivers/V2/WindowsOutput.cs index 78bcb7bf1c..5152d3a238 100644 --- a/Terminal.Gui/Drivers/V2/WindowsOutput.cs +++ b/Terminal.Gui/Drivers/V2/WindowsOutput.cs @@ -413,9 +413,8 @@ private Size GetLargestConsoleWindowSize () } /// - protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY, bool force = false) + protected override bool SetCursorPositionImpl (int screenPositionX, int screenPositionY) { - if (_force16Colors && !_isVirtualTerminal) { SetConsoleCursorPosition (_screenBuffer, new ((short)screenPositionX, (short)screenPositionY)); @@ -427,6 +426,8 @@ protected override bool SetCursorPositionImpl (int screenPositionX, int screenPo EscSeqUtils.CSI_AppendCursorPosition (_everythingStringBuilder, screenPositionY + 1, screenPositionX + 1); } + _lastCursorPosition = new (screenPositionX, screenPositionY); + return true; } @@ -457,12 +458,12 @@ public override void SetCursorVisibility (CursorVisibility visibility) } } - private Point _lastCursorPosition; + private Point? _lastCursorPosition; /// - public void SetCursorPosition (int col, int row, bool force = false) + public void SetCursorPosition (int col, int row) { - if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row && !force) + if (_lastCursorPosition is { } && _lastCursorPosition.Value.X == col && _lastCursorPosition.Value.Y == row) { return; } From 9c682d8755a3a40e03ee3505947bec2592566f0f Mon Sep 17 00:00:00 2001 From: BDisp Date: Wed, 6 Aug 2025 21:04:01 +0100 Subject: [PATCH 040/104] Remove force parameter --- Tests/TerminalGuiFluentTesting/FakeOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TerminalGuiFluentTesting/FakeOutput.cs b/Tests/TerminalGuiFluentTesting/FakeOutput.cs index e948a97fb9..1b73d9ba1a 100644 --- a/Tests/TerminalGuiFluentTesting/FakeOutput.cs +++ b/Tests/TerminalGuiFluentTesting/FakeOutput.cs @@ -23,7 +23,7 @@ public void Write (ReadOnlySpan text) { } public void SetCursorVisibility (CursorVisibility visibility) { } /// - public void SetCursorPosition (int col, int row, bool force = false) { CursorPosition = new Point (col, row); } + public void SetCursorPosition (int col, int row) { CursorPosition = new Point (col, row); } /// /// The last value set by calling From eaf1f798fa19541d68fb39b64c4fd2ae18859aef Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 03:45:26 +0100 Subject: [PATCH 041/104] Add v2 version of fake driver attribute --- .../Drivers/V2/ConsoleDriverFacade.cs | 2 +- .../TerminalGuiFluentTesting/FakeDriverV2.cs | 85 +++++++++++++++++++ Tests/UnitTests/Application/CursorTests.cs | 29 +++++++ Tests/UnitTests/SetupFakeDriverAttribute.cs | 48 +++++++++++ Tests/UnitTests/UnitTests.csproj | 1 + 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 Tests/TerminalGuiFluentTesting/FakeDriverV2.cs diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index c89c63965c..d624184805 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui.Drivers; -internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade +public class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade { private readonly IConsoleOutput _output; private readonly IOutputBuffer _outputBuffer; diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs new file mode 100644 index 0000000000..695c19840c --- /dev/null +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -0,0 +1,85 @@ +using System.Collections.Concurrent; +using System.Drawing; + +namespace TerminalGuiFluentTesting; + + +public class FakeDriverFactory +{ + /// + /// Creates a new instance of using default options + /// + /// + public FakeDriverV2 Create () + { + return new FakeDriverV2 ( + new ConcurrentQueue (), + new OutputBuffer (), + new FakeOutput (), + () => DateTime.Now, + new FakeSizeMonitor ()); + } +} + +/// +/// Implementation of that uses fake input/output. +/// This is a lightweight alternative to (if you don't +/// need the entire application main loop running). +/// +public class FakeDriverV2 : ConsoleDriverFacade +{ + public ConcurrentQueue InputBuffer { get; } + public FakeSizeMonitor SizeMonitor { get; } + public OutputBuffer OutputBuffer { get; } + + public IConsoleOutput ConsoleOutput { get; } + + private FakeOutput _fakeOutput; + + internal FakeDriverV2 ( + ConcurrentQueue inputBuffer, + OutputBuffer outputBuffer, + FakeOutput fakeOutput, + Func datetimeFunc, + FakeSizeMonitor sizeMonitor) : + base (new NetInputProcessor (inputBuffer), + outputBuffer, + fakeOutput, + new (new AnsiResponseParser (), datetimeFunc), + sizeMonitor) + { + InputBuffer = inputBuffer; + SizeMonitor = sizeMonitor; + OutputBuffer = outputBuffer; + ConsoleOutput = _fakeOutput = fakeOutput; + SizeChanged += (_, e) => + { + if (e.Size != null) + { + _fakeOutput.Size = e.Size.Value; + } + }; + + } +} + +public class FakeSizeMonitor : IWindowSizeMonitor +{ + /// + public event EventHandler? SizeChanging; + + /// + public bool Poll () + { + return false; + } + + /// + /// Raises the event. + /// + /// + public void RaiseSizeChanging (Size newSize) + { + SizeChanging.Invoke (this,new (newSize)); + } +} diff --git a/Tests/UnitTests/Application/CursorTests.cs b/Tests/UnitTests/Application/CursorTests.cs index b2f0c73cb0..6cd86ab37e 100644 --- a/Tests/UnitTests/Application/CursorTests.cs +++ b/Tests/UnitTests/Application/CursorTests.cs @@ -95,6 +95,35 @@ public void PositionCursor_No_IntersectSuperView_Returns_False () Application.ResetState (true); } + + [Fact] + [SetupFakeDriver2] + public void PositionCursor_No_IntersectSuperView_Returns_False_v2 () + { + Application.Navigation = new (); + View superView = new () + { + Width = 1, + Height = 1, + }; + + TestView view = new () + { + CanFocus = false, + X = 1, + Y = 1, + Width = 1, + Height = 1, + }; + superView.Add (view); + + view.CanFocus = true; + view.SetFocus (); + view.TestLocation = new Point (0, 0); + Assert.False (Application.PositionCursor ()); + Application.ResetState (true); + } + [Fact] [SetupFakeDriver] public void PositionCursor_Position_OutSide_SuperView_Returns_False () diff --git a/Tests/UnitTests/SetupFakeDriverAttribute.cs b/Tests/UnitTests/SetupFakeDriverAttribute.cs index 48c07c10c1..68888e1737 100644 --- a/Tests/UnitTests/SetupFakeDriverAttribute.cs +++ b/Tests/UnitTests/SetupFakeDriverAttribute.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Reflection; +using TerminalGuiFluentTesting; using Xunit.Sdk; namespace UnitTests; @@ -39,6 +40,53 @@ public override void Before (MethodInfo methodUnderTest) // Ensures subscribing events, at least for the SizeChanged event Application.SubscribeDriverEvents (); + base.Before (methodUnderTest); + } +} + + + +/// +/// Enables test functions annotated with the [SetupFakeDriver] attribute to set Application.Driver to new +/// FakeDriver(). The driver is set up with 25 rows and columns. +/// +/// +/// On Before, sets Configuration.Locations to ConfigLocations.DefaultOnly. +/// On After, sets Configuration.Locations to ConfigLocations.All. +/// +[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] +public class SetupFakeDriver2Attribute : BeforeAfterTestAttribute +{ + public override void After (MethodInfo methodUnderTest) + { + Debug.WriteLine ($"After: {methodUnderTest.Name}"); + + // Turn off diagnostic flags in case some test left them on + View.Diagnostics = ViewDiagnosticFlags.Off; + + Application.ResetState (true); + Assert.Null (Application.Driver); + Assert.Equal (new (0, 0, 2048, 2048), Application.Screen); + base.After (methodUnderTest); + } + + public override void Before (MethodInfo methodUnderTest) + { + Debug.WriteLine ($"Before: {methodUnderTest.Name}"); + + Application.ResetState (true); + Assert.Null (Application.Driver); + + var ff = new FakeDriverFactory (); + var driver = ff.Create (); + + Application.Driver = driver; + driver.SizeMonitor.RaiseSizeChanging (new (25,25)); + + Assert.Equal (new (0, 0, 25, 25), Application.Screen); + // Ensures subscribing events, at least for the SizeChanged event + Application.SubscribeDriverEvents (); + base.Before (methodUnderTest); } } \ No newline at end of file diff --git a/Tests/UnitTests/UnitTests.csproj b/Tests/UnitTests/UnitTests.csproj index f1558775d2..b2ae72fe1a 100644 --- a/Tests/UnitTests/UnitTests.csproj +++ b/Tests/UnitTests/UnitTests.csproj @@ -46,6 +46,7 @@ + From 0eec334d115c9f35afc08a0a2ae91d0d7c5394ab Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 04:04:13 +0100 Subject: [PATCH 042/104] Make direct replacement and wire up window resizing events --- .../Drivers/V2/ConsoleDriverFacade.cs | 6 --- .../TerminalGuiFluentTesting/FakeDriverV2.cs | 12 +++++- Tests/UnitTests/Application/CursorTests.cs | 29 ------------- Tests/UnitTests/Dialogs/DialogTests.cs | 2 +- Tests/UnitTests/SetupFakeDriverAttribute.cs | 43 +------------------ 5 files changed, 12 insertions(+), 80 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index d624184805..be3c0ed5aa 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -68,12 +68,6 @@ public Rectangle Screen { get { - if (ConsoleDriver.RunningUnitTests) - { - // In unit tests, we don't have a real output, so we return an empty rectangle. - return Rectangle.Empty; - } - return new (new (0, 0), _output.GetWindowSize ()); } } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index 695c19840c..d5e3cf42a9 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -1,7 +1,8 @@ using System.Collections.Concurrent; using System.Drawing; +using TerminalGuiFluentTesting; -namespace TerminalGuiFluentTesting; +namespace Terminal.Gui.Drivers; public class FakeDriverFactory @@ -56,11 +57,18 @@ internal FakeDriverV2 ( { if (e.Size != null) { - _fakeOutput.Size = e.Size.Value; + var s = e.Size.Value; + _fakeOutput.Size = s; + OutputBuffer.SetWindowSize (s.Width,s.Height); } }; } + + public void SetBufferSize (int width, int height) + { + SizeMonitor.RaiseSizeChanging (new Size (width,height)); + } } public class FakeSizeMonitor : IWindowSizeMonitor diff --git a/Tests/UnitTests/Application/CursorTests.cs b/Tests/UnitTests/Application/CursorTests.cs index 6cd86ab37e..b2f0c73cb0 100644 --- a/Tests/UnitTests/Application/CursorTests.cs +++ b/Tests/UnitTests/Application/CursorTests.cs @@ -95,35 +95,6 @@ public void PositionCursor_No_IntersectSuperView_Returns_False () Application.ResetState (true); } - - [Fact] - [SetupFakeDriver2] - public void PositionCursor_No_IntersectSuperView_Returns_False_v2 () - { - Application.Navigation = new (); - View superView = new () - { - Width = 1, - Height = 1, - }; - - TestView view = new () - { - CanFocus = false, - X = 1, - Y = 1, - Width = 1, - Height = 1, - }; - superView.Add (view); - - view.CanFocus = true; - view.SetFocus (); - view.TestLocation = new Point (0, 0); - Assert.False (Application.PositionCursor ()); - Application.ResetState (true); - } - [Fact] [SetupFakeDriver] public void PositionCursor_Position_OutSide_SuperView_Returns_False () diff --git a/Tests/UnitTests/Dialogs/DialogTests.cs b/Tests/UnitTests/Dialogs/DialogTests.cs index 1e3f907e7e..8adb88b8c0 100644 --- a/Tests/UnitTests/Dialogs/DialogTests.cs +++ b/Tests/UnitTests/Dialogs/DialogTests.cs @@ -1321,7 +1321,7 @@ public void Zero_Buttons_Works () { RunState? runState = null; - var d = (FakeDriver)Driver!; + var d = (FakeDriverV2)Driver!; var title = "1234"; diff --git a/Tests/UnitTests/SetupFakeDriverAttribute.cs b/Tests/UnitTests/SetupFakeDriverAttribute.cs index 68888e1737..eac5b074fb 100644 --- a/Tests/UnitTests/SetupFakeDriverAttribute.cs +++ b/Tests/UnitTests/SetupFakeDriverAttribute.cs @@ -29,47 +29,6 @@ public override void After (MethodInfo methodUnderTest) base.After (methodUnderTest); } - public override void Before (MethodInfo methodUnderTest) - { - Debug.WriteLine ($"Before: {methodUnderTest.Name}"); - - Application.ResetState (true); - Assert.Null (Application.Driver); - Application.Driver = new FakeDriver { Rows = 25, Cols = 25 }; - Assert.Equal (new (0, 0, 25, 25), Application.Screen); - // Ensures subscribing events, at least for the SizeChanged event - Application.SubscribeDriverEvents (); - - base.Before (methodUnderTest); - } -} - - - -/// -/// Enables test functions annotated with the [SetupFakeDriver] attribute to set Application.Driver to new -/// FakeDriver(). The driver is set up with 25 rows and columns. -/// -/// -/// On Before, sets Configuration.Locations to ConfigLocations.DefaultOnly. -/// On After, sets Configuration.Locations to ConfigLocations.All. -/// -[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] -public class SetupFakeDriver2Attribute : BeforeAfterTestAttribute -{ - public override void After (MethodInfo methodUnderTest) - { - Debug.WriteLine ($"After: {methodUnderTest.Name}"); - - // Turn off diagnostic flags in case some test left them on - View.Diagnostics = ViewDiagnosticFlags.Off; - - Application.ResetState (true); - Assert.Null (Application.Driver); - Assert.Equal (new (0, 0, 2048, 2048), Application.Screen); - base.After (methodUnderTest); - } - public override void Before (MethodInfo methodUnderTest) { Debug.WriteLine ($"Before: {methodUnderTest.Name}"); @@ -81,7 +40,7 @@ public override void Before (MethodInfo methodUnderTest) var driver = ff.Create (); Application.Driver = driver; - driver.SizeMonitor.RaiseSizeChanging (new (25,25)); + driver.SizeMonitor.RaiseSizeChanging (new (25, 25)); Assert.Equal (new (0, 0, 25, 25), Application.Screen); // Ensures subscribing events, at least for the SizeChanged event From 21d44c6f5f1f5b454d92ce4fee6ece7d0a9238b4 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 04:08:13 +0100 Subject: [PATCH 043/104] Update casts to use V2 fake driver instead --- Tests/UnitTests/Text/TextFormatterTests.cs | 4 ++-- Tests/UnitTests/View/Adornment/MarginTests.cs | 4 ++-- Tests/UnitTests/View/Adornment/PaddingTests.cs | 2 +- Tests/UnitTests/View/Adornment/ShadowStyleTests.cs | 4 ++-- Tests/UnitTests/View/Draw/ClipTests.cs | 2 +- Tests/UnitTests/View/TextTests.cs | 2 +- Tests/UnitTests/Views/LabelTests.cs | 2 +- Tests/UnitTests/Views/TableViewTests.cs | 2 +- Tests/UnitTests/Views/ToplevelTests.cs | 4 ++-- Tests/UnitTests/Views/TreeTableSourceTests.cs | 4 ++-- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Tests/UnitTests/Text/TextFormatterTests.cs b/Tests/UnitTests/Text/TextFormatterTests.cs index d4367bdd34..2cd0f95d97 100644 --- a/Tests/UnitTests/Text/TextFormatterTests.cs +++ b/Tests/UnitTests/Text/TextFormatterTests.cs @@ -3935,7 +3935,7 @@ public void Draw_With_Combining_Runes (int width, int height, TextDirection text [SetupFakeDriver] public void FillRemaining_True_False () { - ((FakeDriver)Application.Driver!).SetBufferSize (22, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (22, 5); Attribute [] attrs = { @@ -4157,7 +4157,7 @@ public void UICatalog_AboutBox_Text () Size tfSize = tf.FormatAndGetSize (); Assert.Equal (new (59, 13), tfSize); - ((FakeDriver)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); + ((FakeDriverV2)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); Application.Driver.FillRect (Application.Screen, (Rune)'*'); tf.Draw (Application.Screen, Attribute.Default, Attribute.Default); diff --git a/Tests/UnitTests/View/Adornment/MarginTests.cs b/Tests/UnitTests/View/Adornment/MarginTests.cs index 64615ada59..e5d93c1710 100644 --- a/Tests/UnitTests/View/Adornment/MarginTests.cs +++ b/Tests/UnitTests/View/Adornment/MarginTests.cs @@ -9,7 +9,7 @@ public class MarginTests (ITestOutputHelper output) [SetupFakeDriver] public void Margin_Is_Transparent () { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness; @@ -44,7 +44,7 @@ public void Margin_Is_Transparent () [SetupFakeDriver] public void Margin_ViewPortSettings_Not_Transparent_Is_NotTransparent () { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness; diff --git a/Tests/UnitTests/View/Adornment/PaddingTests.cs b/Tests/UnitTests/View/Adornment/PaddingTests.cs index 6e0ebb0983..67a752c744 100644 --- a/Tests/UnitTests/View/Adornment/PaddingTests.cs +++ b/Tests/UnitTests/View/Adornment/PaddingTests.cs @@ -9,7 +9,7 @@ public class PaddingTests (ITestOutputHelper output) [SetupFakeDriver] public void Padding_Uses_Parent_Scheme () { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Padding!.Thickness = new (1); view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness; diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index 6d45ac9b75..6969fbd4d8 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -30,7 +30,7 @@ public class ShadowStyleTests (ITestOutputHelper output) [SetupFakeDriver] public void ShadowView_Colors (ShadowStyle style, string expectedAttrs) { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); Color fg = Color.Red; Color bg = Color.Green; @@ -100,7 +100,7 @@ public void ShadowView_Colors (ShadowStyle style, string expectedAttrs) [SetupFakeDriver] public void Visual_Test (ShadowStyle style, string expected) { - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var superView = new Toplevel { diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs index 893900e59b..238c487867 100644 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ b/Tests/UnitTests/View/Draw/ClipTests.cs @@ -171,7 +171,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) [Trait ("Category", "Unicode")] public void Clipping_Wide_Runes () { - ((FakeDriver)Application.Driver!).SetBufferSize (30, 1); + ((FakeDriverV2)Application.Driver!).SetBufferSize (30, 1); var top = new View { diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index 4a75be09a0..df32e3ebb2 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -998,7 +998,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) [SetupFakeDriver] public void Narrow_Wide_Runes () { - ((FakeDriver)Application.Driver!).SetBufferSize (32, 32); + ((FakeDriverV2)Application.Driver!).SetBufferSize (32, 32); var top = new View { Width = 32, Height = 32 }; var text = $"First line{Environment.NewLine}Second line"; diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 374750f2bc..d6fb73ae49 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -1039,7 +1039,7 @@ public void Dim_Subtract_Operator_With_Text () [SetupFakeDriver] public void Label_Height_Zero_Stays_Zero () { - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + ((FakeDriverV2)Application.Driver!).SetBufferSize (10, 4); var text = "Label"; var label = new Label diff --git a/Tests/UnitTests/Views/TableViewTests.cs b/Tests/UnitTests/Views/TableViewTests.cs index 530dc08a23..7cac65a8ff 100644 --- a/Tests/UnitTests/Views/TableViewTests.cs +++ b/Tests/UnitTests/Views/TableViewTests.cs @@ -2206,7 +2206,7 @@ public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect () [SetupFakeDriver] public void TestEnumerableDataSource_BasicTypes () { - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); var tv = new TableView (); tv.SchemeName = "TopLevel"; tv.Viewport = new (0, 0, 50, 6); diff --git a/Tests/UnitTests/Views/ToplevelTests.cs b/Tests/UnitTests/Views/ToplevelTests.cs index 78b2ba3a0b..81289fcb13 100644 --- a/Tests/UnitTests/Views/ToplevelTests.cs +++ b/Tests/UnitTests/Views/ToplevelTests.cs @@ -507,10 +507,10 @@ public void GetLocationThatFits_With_Border_Null_Not_Throws () top.BeginInit (); top.EndInit (); - Exception exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (0, 10)); + Exception exception = Record.Exception (() => ((FakeDriverV2)Application.Driver!).SetBufferSize (0, 10)); Assert.Null (exception); - exception = Record.Exception (() => ((FakeDriver)Application.Driver!).SetBufferSize (10, 0)); + exception = Record.Exception (() => ((FakeDriverV2)Application.Driver!).SetBufferSize (10, 0)); Assert.Null (exception); } diff --git a/Tests/UnitTests/Views/TreeTableSourceTests.cs b/Tests/UnitTests/Views/TreeTableSourceTests.cs index 68c4cfc05d..332f545eee 100644 --- a/Tests/UnitTests/Views/TreeTableSourceTests.cs +++ b/Tests/UnitTests/Views/TreeTableSourceTests.cs @@ -30,7 +30,7 @@ public void Dispose () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithKeyboard () { - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; @@ -91,7 +91,7 @@ public void TestTreeTableSource_BasicExpanding_WithKeyboard () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithMouse () { - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); From f872cc5f46f8f952468e7f49b89bd704f2cbda38 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 04:47:03 +0100 Subject: [PATCH 044/104] Adjust interfaces to expose less internals --- .../Drivers/V2/ConsoleDriverFacade.cs | 3 +- .../Drivers/V2/IConsoleDriverFacade.cs | 5 ++ .../TerminalGuiFluentTesting/FakeDriverV2.cs | 60 ++++++++++++++++++- Tests/UnitTests/AutoInitShutdownAttribute.cs | 15 ++++- Tests/UnitTests/Dialogs/DialogTests.cs | 2 +- Tests/UnitTests/Dialogs/WizardTests.cs | 6 +- Tests/UnitTests/SetupFakeDriverAttribute.cs | 2 +- Tests/UnitTests/Text/TextFormatterTests.cs | 4 +- Tests/UnitTests/View/Adornment/MarginTests.cs | 4 +- .../UnitTests/View/Adornment/PaddingTests.cs | 2 +- .../View/Adornment/ShadowStyleTests.cs | 4 +- Tests/UnitTests/View/Draw/ClipTests.cs | 2 +- Tests/UnitTests/View/TextTests.cs | 2 +- Tests/UnitTests/Views/LabelTests.cs | 2 +- Tests/UnitTests/Views/TableViewTests.cs | 2 +- Tests/UnitTests/Views/ToplevelTests.cs | 4 +- Tests/UnitTests/Views/TreeTableSourceTests.cs | 4 +- 17 files changed, 98 insertions(+), 25 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index be3c0ed5aa..96cc31f265 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -3,7 +3,7 @@ namespace Terminal.Gui.Drivers; -public class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade +internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade { private readonly IConsoleOutput _output; private readonly IOutputBuffer _outputBuffer; @@ -14,6 +14,7 @@ public class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade public event EventHandler SizeChanged; public IInputProcessor InputProcessor { get; } + public IOutputBuffer OutputBuffer => _outputBuffer; public ConsoleDriverFacade ( IInputProcessor inputProcessor, diff --git a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs index 2bebf3c9bd..8a5fd600da 100644 --- a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs @@ -11,4 +11,9 @@ public interface IConsoleDriverFacade /// and detecting and processing ansi escape sequences. /// public IInputProcessor InputProcessor { get; } + + /// + /// Describes the desired screen state. Data source for . + /// + public IOutputBuffer OutputBuffer { get; } } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index d5e3cf42a9..a4bd0a0da4 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -4,6 +4,57 @@ namespace Terminal.Gui.Drivers; +public class FakeApplicationFactory +{ + /// + /// Creates an initialized fake application which will be cleaned up when result object + /// is disposed. + /// + /// + public IDisposable SetupFakeApplication () + { + var cts = new CancellationTokenSource (); + var fakeInput = new FakeNetInput (cts.Token); + FakeOutput _output = new (); + _output.Size = new (25, 25); + + + IApplication origApp = ApplicationImpl.Instance; + + + var v2 = new ApplicationV2 ( + () => fakeInput, + () => _output, + () => throw new NotSupportedException("Only net input should be created"), + () => _output); + + + ApplicationImpl.ChangeInstance (v2); + v2.Init (null,"v2net"); + + + return new FakeApplicationLifecycle (origApp,cts); + } +} + +class FakeApplicationLifecycle : IDisposable +{ + private readonly IApplication _origApp; + private readonly CancellationTokenSource _hardStop; + + public FakeApplicationLifecycle (IApplication origApp, CancellationTokenSource hardStop) + { + _origApp = origApp; + _hardStop = hardStop; + } + /// + public void Dispose () + { + _hardStop.Cancel(); + + ApplicationImpl.ChangeInstance (_origApp); + } +} public class FakeDriverFactory { @@ -11,7 +62,7 @@ public class FakeDriverFactory /// Creates a new instance of using default options /// /// - public FakeDriverV2 Create () + public IFakeDriverV2 Create () { return new FakeDriverV2 ( new ConcurrentQueue (), @@ -22,12 +73,17 @@ public FakeDriverV2 Create () } } +public interface IFakeDriverV2 : IConsoleDriver, IConsoleDriverFacade +{ + void SetBufferSize (int width, int height); +} + /// /// Implementation of that uses fake input/output. /// This is a lightweight alternative to (if you don't /// need the entire application main loop running). /// -public class FakeDriverV2 : ConsoleDriverFacade +class FakeDriverV2 : ConsoleDriverFacade, IFakeDriverV2 { public ConcurrentQueue InputBuffer { get; } public FakeSizeMonitor SizeMonitor { get; } diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests/AutoInitShutdownAttribute.cs index ea91dff313..2f20a74cbe 100644 --- a/Tests/UnitTests/AutoInitShutdownAttribute.cs +++ b/Tests/UnitTests/AutoInitShutdownAttribute.cs @@ -49,7 +49,7 @@ public AutoInitShutdownAttribute ( { AutoInit = autoInit; CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); - _driverType = consoleDriverType ?? typeof (FakeDriver); + _driverType = consoleDriverType; FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard; FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException = fakeClipboardAlwaysThrowsNotSupportedException; @@ -59,6 +59,7 @@ public AutoInitShutdownAttribute ( private readonly bool _verifyShutdown; private readonly Type _driverType; + private IDisposable _v2Cleanup; public override void After (MethodInfo methodUnderTest) { @@ -67,6 +68,8 @@ public override void After (MethodInfo methodUnderTest) // Turn off diagnostic flags in case some test left them on View.Diagnostics = ViewDiagnosticFlags.Off; + _v2Cleanup?.Dispose (); + if (AutoInit) { // try @@ -131,7 +134,15 @@ public override void Before (MethodInfo methodUnderTest) View.Instances.Clear (); } #endif - Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType)); + if (_driverType == null) + { + var fa = new FakeApplicationFactory (); + _v2Cleanup = fa.SetupFakeApplication (); + } + else + { + Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType)); + } } } diff --git a/Tests/UnitTests/Dialogs/DialogTests.cs b/Tests/UnitTests/Dialogs/DialogTests.cs index 8adb88b8c0..74dfc271c8 100644 --- a/Tests/UnitTests/Dialogs/DialogTests.cs +++ b/Tests/UnitTests/Dialogs/DialogTests.cs @@ -1321,7 +1321,7 @@ public void Zero_Buttons_Works () { RunState? runState = null; - var d = (FakeDriverV2)Driver!; + var d = (IFakeDriverV2)Driver!; var title = "1234"; diff --git a/Tests/UnitTests/Dialogs/WizardTests.cs b/Tests/UnitTests/Dialogs/WizardTests.cs index a45e1803e0..945042e571 100644 --- a/Tests/UnitTests/Dialogs/WizardTests.cs +++ b/Tests/UnitTests/Dialogs/WizardTests.cs @@ -482,14 +482,14 @@ public void OneStepWizard_Shows () // this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle") public void Setting_Title_Works () { - var d = (FakeDriver)Application.Driver; - + var d = (IConsoleDriverFacade)Application.Driver; + var title = "1234"; var stepTitle = " - ABCD"; var width = 40; var height = 4; - d.SetBufferSize (width, height); + d.OutputBuffer.SetWindowSize (width,height); var btnNextText = "Finish"; diff --git a/Tests/UnitTests/SetupFakeDriverAttribute.cs b/Tests/UnitTests/SetupFakeDriverAttribute.cs index eac5b074fb..9d1cadeb45 100644 --- a/Tests/UnitTests/SetupFakeDriverAttribute.cs +++ b/Tests/UnitTests/SetupFakeDriverAttribute.cs @@ -40,7 +40,7 @@ public override void Before (MethodInfo methodUnderTest) var driver = ff.Create (); Application.Driver = driver; - driver.SizeMonitor.RaiseSizeChanging (new (25, 25)); + driver.SetBufferSize (25, 25); Assert.Equal (new (0, 0, 25, 25), Application.Screen); // Ensures subscribing events, at least for the SizeChanged event diff --git a/Tests/UnitTests/Text/TextFormatterTests.cs b/Tests/UnitTests/Text/TextFormatterTests.cs index 2cd0f95d97..453c65f26b 100644 --- a/Tests/UnitTests/Text/TextFormatterTests.cs +++ b/Tests/UnitTests/Text/TextFormatterTests.cs @@ -3935,7 +3935,7 @@ public void Draw_With_Combining_Runes (int width, int height, TextDirection text [SetupFakeDriver] public void FillRemaining_True_False () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (22, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (22, 5); Attribute [] attrs = { @@ -4157,7 +4157,7 @@ public void UICatalog_AboutBox_Text () Size tfSize = tf.FormatAndGetSize (); Assert.Equal (new (59, 13), tfSize); - ((FakeDriverV2)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); + ((IFakeDriverV2)Application.Driver).SetBufferSize (tfSize.Width, tfSize.Height); Application.Driver.FillRect (Application.Screen, (Rune)'*'); tf.Draw (Application.Screen, Attribute.Default, Attribute.Default); diff --git a/Tests/UnitTests/View/Adornment/MarginTests.cs b/Tests/UnitTests/View/Adornment/MarginTests.cs index e5d93c1710..62deb135bc 100644 --- a/Tests/UnitTests/View/Adornment/MarginTests.cs +++ b/Tests/UnitTests/View/Adornment/MarginTests.cs @@ -9,7 +9,7 @@ public class MarginTests (ITestOutputHelper output) [SetupFakeDriver] public void Margin_Is_Transparent () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness; @@ -44,7 +44,7 @@ public void Margin_Is_Transparent () [SetupFakeDriver] public void Margin_ViewPortSettings_Not_Transparent_Is_NotTransparent () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Margin!.Diagnostics = ViewDiagnosticFlags.Thickness; diff --git a/Tests/UnitTests/View/Adornment/PaddingTests.cs b/Tests/UnitTests/View/Adornment/PaddingTests.cs index 67a752c744..7dc217cecb 100644 --- a/Tests/UnitTests/View/Adornment/PaddingTests.cs +++ b/Tests/UnitTests/View/Adornment/PaddingTests.cs @@ -9,7 +9,7 @@ public class PaddingTests (ITestOutputHelper output) [SetupFakeDriver] public void Padding_Uses_Parent_Scheme () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; view.Padding!.Thickness = new (1); view.Padding.Diagnostics = ViewDiagnosticFlags.Thickness; diff --git a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs index 6969fbd4d8..cbac824bf7 100644 --- a/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs +++ b/Tests/UnitTests/View/Adornment/ShadowStyleTests.cs @@ -30,7 +30,7 @@ public class ShadowStyleTests (ITestOutputHelper output) [SetupFakeDriver] public void ShadowView_Colors (ShadowStyle style, string expectedAttrs) { - ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5); Color fg = Color.Red; Color bg = Color.Green; @@ -100,7 +100,7 @@ public void ShadowView_Colors (ShadowStyle style, string expectedAttrs) [SetupFakeDriver] public void Visual_Test (ShadowStyle style, string expected) { - ((FakeDriverV2)Application.Driver!).SetBufferSize (5, 5); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (5, 5); var superView = new Toplevel { diff --git a/Tests/UnitTests/View/Draw/ClipTests.cs b/Tests/UnitTests/View/Draw/ClipTests.cs index 238c487867..017c510887 100644 --- a/Tests/UnitTests/View/Draw/ClipTests.cs +++ b/Tests/UnitTests/View/Draw/ClipTests.cs @@ -171,7 +171,7 @@ public void FillRect_Fills_HonorsClip (int x, int y, int width, int height) [Trait ("Category", "Unicode")] public void Clipping_Wide_Runes () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (30, 1); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (30, 1); var top = new View { diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index df32e3ebb2..40139372e7 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -998,7 +998,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) [SetupFakeDriver] public void Narrow_Wide_Runes () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (32, 32); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (32, 32); var top = new View { Width = 32, Height = 32 }; var text = $"First line{Environment.NewLine}Second line"; diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index d6fb73ae49..754e8b9e49 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -1039,7 +1039,7 @@ public void Dim_Subtract_Operator_With_Text () [SetupFakeDriver] public void Label_Height_Zero_Stays_Zero () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (10, 4); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (10, 4); var text = "Label"; var label = new Label diff --git a/Tests/UnitTests/Views/TableViewTests.cs b/Tests/UnitTests/Views/TableViewTests.cs index 7cac65a8ff..c27f9b1696 100644 --- a/Tests/UnitTests/Views/TableViewTests.cs +++ b/Tests/UnitTests/Views/TableViewTests.cs @@ -2206,7 +2206,7 @@ public void TestControlClick_MultiSelect_ThreeRowTable_FullRowSelect () [SetupFakeDriver] public void TestEnumerableDataSource_BasicTypes () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (100, 100); var tv = new TableView (); tv.SchemeName = "TopLevel"; tv.Viewport = new (0, 0, 50, 6); diff --git a/Tests/UnitTests/Views/ToplevelTests.cs b/Tests/UnitTests/Views/ToplevelTests.cs index 81289fcb13..5ebbdc8a95 100644 --- a/Tests/UnitTests/Views/ToplevelTests.cs +++ b/Tests/UnitTests/Views/ToplevelTests.cs @@ -507,10 +507,10 @@ public void GetLocationThatFits_With_Border_Null_Not_Throws () top.BeginInit (); top.EndInit (); - Exception exception = Record.Exception (() => ((FakeDriverV2)Application.Driver!).SetBufferSize (0, 10)); + Exception exception = Record.Exception (() => ((IFakeDriverV2)Application.Driver!).SetBufferSize (0, 10)); Assert.Null (exception); - exception = Record.Exception (() => ((FakeDriverV2)Application.Driver!).SetBufferSize (10, 0)); + exception = Record.Exception (() => ((IFakeDriverV2)Application.Driver!).SetBufferSize (10, 0)); Assert.Null (exception); } diff --git a/Tests/UnitTests/Views/TreeTableSourceTests.cs b/Tests/UnitTests/Views/TreeTableSourceTests.cs index 332f545eee..16e669a214 100644 --- a/Tests/UnitTests/Views/TreeTableSourceTests.cs +++ b/Tests/UnitTests/Views/TreeTableSourceTests.cs @@ -30,7 +30,7 @@ public void Dispose () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithKeyboard () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); tv.Style.GetOrCreateColumnStyle (1).MinAcceptableWidth = 1; @@ -91,7 +91,7 @@ public void TestTreeTableSource_BasicExpanding_WithKeyboard () [SetupFakeDriver] public void TestTreeTableSource_BasicExpanding_WithMouse () { - ((FakeDriverV2)Application.Driver!).SetBufferSize (100, 100); + ((IFakeDriverV2)Application.Driver!).SetBufferSize (100, 100); TableView tv = GetTreeTable (out _); From cbdd0bd4f4cf08d0018bc284f9977a70921a240f Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 05:04:54 +0100 Subject: [PATCH 045/104] Fix not raising iteration event in v2 --- Terminal.Gui/App/Application.Run.cs | 6 +++++- Terminal.Gui/Drivers/V2/MainLoop.cs | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/App/Application.Run.cs b/Terminal.Gui/App/Application.Run.cs index 30c1384a3e..7adf559d72 100644 --- a/Terminal.Gui/App/Application.Run.cs +++ b/Terminal.Gui/App/Application.Run.cs @@ -461,7 +461,7 @@ internal static void LayoutAndDrawImpl (bool forceDraw = false) /// This event is raised on each iteration of the main loop. /// See also public static event EventHandler? Iteration; - + /// The driver for the application /// The main loop. internal static MainLoop? MainLoop { get; set; } @@ -619,4 +619,8 @@ public static void End (RunState runState) LayoutAndDraw (true); } + internal static void RaiseIteration () + { + Iteration?.Invoke (null, new ()); + } } diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 5b6d9fdde7..1519b62c32 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -98,6 +98,9 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer /// public void Iteration () { + + Application.RaiseIteration (); + DateTime dt = Now (); IterationImpl (); From deaf7912d7daeae503e666169d026c147390c7a8 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 05:33:01 +0100 Subject: [PATCH 046/104] WIP investigate what it takes to do resize and redraw using TextAlignment_Centered as example --- .../Drivers/V2/ConsoleDriverFacade.cs | 27 ++++++++++++++++++- .../Drivers/V2/IConsoleDriverFacade.cs | 10 +++++-- .../TerminalGuiFluentTesting/FakeDriverV2.cs | 14 +++++++++- Tests/UnitTests/Views/CheckBoxTests.cs | 5 ++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index 96cc31f265..999f3088bb 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -16,6 +16,31 @@ internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade public IInputProcessor InputProcessor { get; } public IOutputBuffer OutputBuffer => _outputBuffer; + private IWindowSizeMonitor _windowSizeMonitor; + + public IWindowSizeMonitor WindowSizeMonitor + { + get => _windowSizeMonitor; + + // TODO: Should really not be able to do this, we are making it settable so we can hack in a fake one in tests + set + { + // Remove any old event delegates + if (_windowSizeMonitor != null) + { + _windowSizeMonitor.SizeChanging -= RaiseSizeChanged; + } + + _windowSizeMonitor = value; + _windowSizeMonitor.SizeChanging += RaiseSizeChanged; + } + } + + private void RaiseSizeChanged (object sender, SizeChangedEventArgs e) + { + SizeChanged?.Invoke (this, e); + } + public ConsoleDriverFacade ( IInputProcessor inputProcessor, IOutputBuffer outputBuffer, @@ -37,7 +62,7 @@ IWindowSizeMonitor windowSizeMonitor MouseEvent?.Invoke (s, e); }; - windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e); + WindowSizeMonitor = windowSizeMonitor; CreateClipboard (); } diff --git a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs index 8a5fd600da..b670a196d3 100644 --- a/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs @@ -10,10 +10,16 @@ public interface IConsoleDriverFacade /// e.g. into events /// and detecting and processing ansi escape sequences. /// - public IInputProcessor InputProcessor { get; } + IInputProcessor InputProcessor { get; } /// /// Describes the desired screen state. Data source for . /// - public IOutputBuffer OutputBuffer { get; } + IOutputBuffer OutputBuffer { get; } + + /// + /// Interface for classes responsible for reporting the current + /// size of the terminal window. + /// + IWindowSizeMonitor WindowSizeMonitor { get; } } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index a4bd0a0da4..ca24faee5c 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -31,7 +31,19 @@ public IDisposable SetupFakeApplication () ApplicationImpl.ChangeInstance (v2); v2.Init (null,"v2net"); + + var d = (ConsoleDriverFacade)Application.Driver; + d.WindowSizeMonitor = new FakeSizeMonitor (); + d.WindowSizeMonitor.SizeChanging += (_, e) => + { + if (e.Size != null) + { + var s = e.Size.Value; + _output.Size = s; + d.OutputBuffer.SetWindowSize (s.Width, s.Height); + } + }; return new FakeApplicationLifecycle (origApp,cts); } @@ -51,7 +63,7 @@ public FakeApplicationLifecycle (IApplication origApp, CancellationTokenSource h public void Dispose () { _hardStop.Cancel(); - + Application.Shutdown (); ApplicationImpl.ChangeInstance (_origApp); } } diff --git a/Tests/UnitTests/Views/CheckBoxTests.cs b/Tests/UnitTests/Views/CheckBoxTests.cs index a5ae20d6aa..5ab6b87db6 100644 --- a/Tests/UnitTests/Views/CheckBoxTests.cs +++ b/Tests/UnitTests/Views/CheckBoxTests.cs @@ -351,7 +351,8 @@ public void TextAlignment_Centered () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + ((FakeSizeMonitor)((IConsoleDriverFacade)Application.Driver!).WindowSizeMonitor).RaiseSizeChanging (new Size (30, 5)); + Application.LayoutAndDrawImpl (); Assert.Equal (Alignment.Center, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); @@ -369,7 +370,7 @@ public void TextAlignment_Centered () Assert.Equal (new (0, 0, 30, 5), pos); checkBox.CheckedState = CheckState.Checked; - Application.LayoutAndDraw (); + Application.LayoutAndDrawImpl (); expected = @$" ┌┤Test Demo 你├──────────────┐ From 866cc732b459d9a58c7fbc34a504d3005cc67f6f Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 09:09:03 +0100 Subject: [PATCH 047/104] Sketch adding component factory --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 97 ++++++++++++++----- Terminal.Gui/Drivers/V2/IWindowsInput.cs | 2 +- .../Drivers/V2/MainLoopCoordinator.cs | 20 ++-- .../Drivers/WindowsDriver/WindowsConsole.cs | 2 +- 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index ca94ebe575..47714afa96 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -1,5 +1,6 @@ #nullable enable using System.Collections.Concurrent; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; @@ -12,10 +13,7 @@ namespace Terminal.Gui.Drivers; /// public class ApplicationV2 : ApplicationImpl { - private readonly Func _netInputFactory; - private readonly Func _netOutputFactory; - private readonly Func _winInputFactory; - private readonly Func _winOutputFactory; + private readonly IComponentFactory _componentFactory; private IMainLoopCoordinator? _coordinator; private string? _driverName; @@ -28,25 +26,12 @@ public class ApplicationV2 : ApplicationImpl /// Creates anew instance of the Application backend. The provided /// factory methods will be used on Init calls to get things booted. /// - public ApplicationV2 () : this ( - () => new NetInput (), - () => new NetOutput (), - () => new WindowsInput (), - () => new WindowsOutput () - ) + public ApplicationV2 () { } - internal ApplicationV2 ( - Func netInputFactory, - Func netOutputFactory, - Func winInputFactory, - Func winOutputFactory - ) + internal ApplicationV2 (IComponentFactory componentFactory) { - _netInputFactory = netInputFactory; - _netOutputFactory = netOutputFactory; - _winInputFactory = winInputFactory; - _winOutputFactory = winOutputFactory; + _componentFactory = componentFactory; IsLegacy = false; } @@ -125,12 +110,20 @@ private IMainLoopCoordinator CreateWindowsSubcomponents () ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); - return new MainLoopCoordinator ( - _timedEvents, - _winInputFactory, + IComponentFactory cf; + + if (_componentFactory != null) + { + cf = (IComponentFactory)_componentFactory; + } + else + { + cf = new WindowsComponentFactory (); + } + + return new MainLoopCoordinator (_timedEvents, inputBuffer, - new WindowsInputProcessor (inputBuffer), - _winOutputFactory, + cf loop); } @@ -259,3 +252,57 @@ public override void LayoutAndDraw (bool forceDraw) Application.Top?.SetNeedsLayout (); } } + + +public class NetComponentFactory : IComponentFactory +{ + public IConsoleInput CreateInput () + { + return new NetInput (); + } + + /// + public IConsoleOutput CreateOutput () + { + return new NetOutput (); + } + + /// + public IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + { + return new NetInputProcessor (inputBuffer); + } +} + +public class WindowsComponentFactory : IComponentFactory +{ + /// + public IConsoleInput CreateInput () + { + return new WindowsInput (); + } + + /// + public IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + { + return new WindowsInputProcessor (inputBuffer); + } + + /// + public IConsoleOutput CreateOutput () + { + return new WindowsOutput (); + } +} + + +public interface IComponentFactory : IComponentFactory +{ + IConsoleInput CreateInput (); + IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); +} + +public interface IComponentFactory +{ + IConsoleOutput CreateOutput (); +} diff --git a/Terminal.Gui/Drivers/V2/IWindowsInput.cs b/Terminal.Gui/Drivers/V2/IWindowsInput.cs index d8431b22fe..3bc47fe7d2 100644 --- a/Terminal.Gui/Drivers/V2/IWindowsInput.cs +++ b/Terminal.Gui/Drivers/V2/IWindowsInput.cs @@ -1,4 +1,4 @@ namespace Terminal.Gui.Drivers; -internal interface IWindowsInput : IConsoleInput +public interface IWindowsInput : IConsoleInput { } diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index a2cc34c497..7e9f77ef0b 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -13,12 +13,11 @@ namespace Terminal.Gui.Drivers; /// internal class MainLoopCoordinator : IMainLoopCoordinator { - private readonly Func> _inputFactory; private readonly ConcurrentQueue _inputBuffer; private readonly IInputProcessor _inputProcessor; private readonly IMainLoop _loop; + private readonly IComponentFactory _componentFactory; private readonly CancellationTokenSource _tokenSource = new (); - private readonly Func _outputFactory; private IConsoleInput _input; private IConsoleOutput _output; private readonly object _oLockInitialization = new (); @@ -48,19 +47,18 @@ internal class MainLoopCoordinator : IMainLoopCoordinator /// public MainLoopCoordinator ( ITimedEvents timedEvents, - Func> inputFactory, ConcurrentQueue inputBuffer, - IInputProcessor inputProcessor, - Func outputFactory, - IMainLoop loop + IMainLoop loop, + IComponentFactory componentFactory ) { + componentFactory.CreateInput (); + ; _timedEvents = timedEvents; - _inputFactory = inputFactory; _inputBuffer = inputBuffer; - _inputProcessor = inputProcessor; - _outputFactory = outputFactory; + _inputProcessor = componentFactory.CreateInputProcessor (_inputBuffer); _loop = loop; + _componentFactory = componentFactory; _isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null; } @@ -104,7 +102,7 @@ private void RunInput () lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. - _input = _inputFactory.Invoke (); + _input = _componentFactory.CreateInput (); _input.Initialize (_inputBuffer); BuildFacadeIfPossible (); @@ -144,7 +142,7 @@ private void BootMainLoop () lock (_oLockInitialization) { // Instance must be constructed on the thread in which it is used. - _output = _outputFactory.Invoke (); + _output = _componentFactory.CreateOutput (); _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output); BuildFacadeIfPossible (); diff --git a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs index 445ba1410e..ba3dee5993 100644 --- a/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/Drivers/WindowsDriver/WindowsConsole.cs @@ -5,7 +5,7 @@ namespace Terminal.Gui.Drivers; -internal partial class WindowsConsole +public partial class WindowsConsole { private CancellationTokenSource? _inputReadyCancellationTokenSource; private readonly BlockingCollection _inputQueue = new (new ConcurrentQueue ()); From 5493181e02d504403d242d404bbc3822223e3f14 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 09:17:14 +0100 Subject: [PATCH 048/104] Create relevant fake component factories --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 36 +++++++----- .../TerminalGuiFluentTesting/FakeDriverV2.cs | 8 +-- .../GuiTestContext.cs | 58 +++++++++++++++++-- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 47714afa96..991ac18ad2 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -13,7 +13,7 @@ namespace Terminal.Gui.Drivers; /// public class ApplicationV2 : ApplicationImpl { - private readonly IComponentFactory _componentFactory; + private readonly IComponentFactory? _componentFactory; private IMainLoopCoordinator? _coordinator; private string? _driverName; @@ -123,8 +123,8 @@ private IMainLoopCoordinator CreateWindowsSubcomponents () return new MainLoopCoordinator (_timedEvents, inputBuffer, - cf - loop); + loop, + cf); } private IMainLoopCoordinator CreateNetSubcomponents () @@ -132,13 +132,22 @@ private IMainLoopCoordinator CreateNetSubcomponents () ConcurrentQueue inputBuffer = new (); MainLoop loop = new (); + IComponentFactory cf; + + if (_componentFactory != null) + { + cf = (IComponentFactory)_componentFactory; + } + else + { + cf = new NetComponentFactory (); + } + return new MainLoopCoordinator ( _timedEvents, - _netInputFactory, inputBuffer, - new NetInputProcessor (inputBuffer), - _netOutputFactory, - loop); + loop, + cf); } /// @@ -253,22 +262,21 @@ public override void LayoutAndDraw (bool forceDraw) } } - public class NetComponentFactory : IComponentFactory { - public IConsoleInput CreateInput () + public virtual IConsoleInput CreateInput () { return new NetInput (); } /// - public IConsoleOutput CreateOutput () + public virtual IConsoleOutput CreateOutput () { return new NetOutput (); } /// - public IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + public virtual IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) { return new NetInputProcessor (inputBuffer); } @@ -277,19 +285,19 @@ public IInputProcessor CreateInputProcessor (ConcurrentQueue inp public class WindowsComponentFactory : IComponentFactory { /// - public IConsoleInput CreateInput () + public virtual IConsoleInput CreateInput () { return new WindowsInput (); } /// - public IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + public virtual IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) { return new WindowsInputProcessor (inputBuffer); } /// - public IConsoleOutput CreateOutput () + public virtual IConsoleOutput CreateOutput () { return new WindowsOutput (); } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index ca24faee5c..5a3394a287 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -21,13 +21,7 @@ public IDisposable SetupFakeApplication () IApplication origApp = ApplicationImpl.Instance; - - var v2 = new ApplicationV2 ( - () => fakeInput, - () => _output, - () => throw new NotSupportedException("Only net input should be created"), - () => _output); - + var v2 = new ApplicationV2 (new FakeNetComponentFactory (fakeInput, _output)); ApplicationImpl.ChangeInstance (v2); v2.Init (null,"v2net"); diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index 6321a86cbc..bb818dca8c 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -35,11 +35,11 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, _output.Size = new (width, height); - var v2 = new ApplicationV2 ( - () => _netInput, - () => _output, - () => _winInput, - () => _output); + var cf = driver == V2TestDriver.V2Net + ? new FakeNetComponentFactory (_netInput, _output): + (IComponentFactory)new FakeWindowsComponentFactory(_winInput,_output); + + var v2 = new ApplicationV2 (cf); var booting = new SemaphoreSlim (0, 1); @@ -832,3 +832,51 @@ public Point GetCursorPosition () return _output.CursorPosition; } } + +internal class FakeWindowsComponentFactory : WindowsComponentFactory +{ + private readonly FakeWindowsInput _winInput; + private readonly FakeOutput _output; + + public FakeWindowsComponentFactory (FakeWindowsInput winInput, FakeOutput output) + { + _winInput = winInput; + _output = output; + } + + /// + public override IConsoleInput CreateInput () + { + return _winInput; + } + + /// + public override IConsoleOutput CreateOutput () + { + return _output; + } +} + +internal class FakeNetComponentFactory : NetComponentFactory +{ + private readonly FakeNetInput _netInput; + private readonly FakeOutput _output; + + public FakeNetComponentFactory (FakeNetInput netInput, FakeOutput output) + { + _netInput = netInput; + _output = output; + } + + /// + public override IConsoleInput CreateInput () + { + return _netInput; + } + + /// + public override IConsoleOutput CreateOutput () + { + return _output; + } +} From 391df6fe38795211e524c28b1f54737ec44a914b Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 12:18:20 +0100 Subject: [PATCH 049/104] Add window size monitor into factory --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 33 +++++++++++---- Terminal.Gui/Drivers/V2/IMainLoop.cs | 9 +++- Terminal.Gui/Drivers/V2/MainLoop.cs | 11 ++++- .../Drivers/V2/MainLoopCoordinator.cs | 2 +- .../ConsoleDrivers/V2/ApplicationV2Tests.cs | 41 ++++++++++++------- .../V2/MainLoopCoordinatorTests.cs | 12 +++--- .../ConsoleDrivers/V2/MainLoopTTests.cs | 3 +- 7 files changed, 78 insertions(+), 33 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 991ac18ad2..a4d7887dae 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -262,52 +262,69 @@ public override void LayoutAndDraw (bool forceDraw) } } -public class NetComponentFactory : IComponentFactory +public class NetComponentFactory : ComponentFactory { - public virtual IConsoleInput CreateInput () + public override IConsoleInput CreateInput () { return new NetInput (); } /// - public virtual IConsoleOutput CreateOutput () + public override IConsoleOutput CreateOutput () { return new NetOutput (); } /// - public virtual IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + public override IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) { return new NetInputProcessor (inputBuffer); } } -public class WindowsComponentFactory : IComponentFactory +public class WindowsComponentFactory : ComponentFactory { /// - public virtual IConsoleInput CreateInput () + public override IConsoleInput CreateInput () { return new WindowsInput (); } /// - public virtual IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) + public override IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer) { return new WindowsInputProcessor (inputBuffer); } /// - public virtual IConsoleOutput CreateOutput () + public override IConsoleOutput CreateOutput () { return new WindowsOutput (); } } +public abstract class ComponentFactory : IComponentFactory +{ + /// + public abstract IConsoleInput CreateInput (); + + /// + public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); + + /// + public IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) + { + return new WindowSizeMonitor (consoleOutput, outputBuffer); + } + /// + public abstract IConsoleOutput CreateOutput (); +} public interface IComponentFactory : IComponentFactory { IConsoleInput CreateInput (); IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); + IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer); } public interface IComponentFactory diff --git a/Terminal.Gui/Drivers/V2/IMainLoop.cs b/Terminal.Gui/Drivers/V2/IMainLoop.cs index 647776cbe3..aee2e381fa 100644 --- a/Terminal.Gui/Drivers/V2/IMainLoop.cs +++ b/Terminal.Gui/Drivers/V2/IMainLoop.cs @@ -48,7 +48,14 @@ public interface IMainLoop : IDisposable /// /// /// - void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput); + /// + void Initialize ( + ITimedEvents timedEvents, + ConcurrentQueue inputBuffer, + IInputProcessor inputProcessor, + IConsoleOutput consoleOutput, + IComponentFactory componentFactory + ); /// /// Perform a single iteration of the main loop then blocks for a fixed length diff --git a/Terminal.Gui/Drivers/V2/MainLoop.cs b/Terminal.Gui/Drivers/V2/MainLoop.cs index 1519b62c32..43086784ef 100644 --- a/Terminal.Gui/Drivers/V2/MainLoop.cs +++ b/Terminal.Gui/Drivers/V2/MainLoop.cs @@ -83,7 +83,14 @@ public IWindowSizeMonitor WindowSizeMonitor /// /// /// - public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput) + /// + public void Initialize ( + ITimedEvents timedEvents, + ConcurrentQueue inputBuffer, + IInputProcessor inputProcessor, + IConsoleOutput consoleOutput, + IComponentFactory componentFactory + ) { InputBuffer = inputBuffer; Out = consoleOutput; @@ -92,7 +99,7 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue inputBuffer TimedEvents = timedEvents; AnsiRequestScheduler = new (InputProcessor.GetParser ()); - WindowSizeMonitor = new WindowSizeMonitor (Out, OutputBuffer); + WindowSizeMonitor = componentFactory.CreateWindowSizeMonitor (Out, OutputBuffer); } /// diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index 7e9f77ef0b..a706782f41 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -143,7 +143,7 @@ private void BootMainLoop () { // Instance must be constructed on the thread in which it is used. _output = _componentFactory.CreateOutput (); - _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output); + _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output,_componentFactory); BuildFacadeIfPossible (); } diff --git a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs index d0165a45ae..e97fd963d5 100644 --- a/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs +++ b/Tests/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using Moq; +using TerminalGuiFluentTesting; namespace UnitTests.ConsoleDrivers.V2; public class ApplicationV2Tests @@ -12,18 +13,29 @@ public ApplicationV2Tests () ConsoleDriver.RunningUnitTests = true; } - private ApplicationV2 NewApplicationV2 () + private ApplicationV2 NewApplicationV2 (V2TestDriver driver = V2TestDriver.V2Net) { - var netInput = new Mock (); - SetupRunInputMockMethodToBlock (netInput); - var winInput = new Mock (); - SetupRunInputMockMethodToBlock (winInput); - return new ( - () => netInput.Object, - Mock.Of, - () => winInput.Object, - Mock.Of); + if (driver == V2TestDriver.V2Net) + { + var netInput = new Mock (); + SetupRunInputMockMethodToBlock (netInput); + + var m = new Mock> (); + m.Setup (f => f.CreateInput ()).Returns (netInput.Object); + + return new (m.Object); + } + else + { + + var winInput = new Mock> (); + SetupRunInputMockMethodToBlock (winInput); + var m = new Mock> (); + m.Setup (f => f.CreateInput ()).Returns (winInput.Object); + + return new (m.Object); + } } [Fact] @@ -68,7 +80,7 @@ public void Init_DriverIsFacade () ApplicationImpl.ChangeInstance (orig); } - + /* [Fact] public void Init_ExplicitlyRequestWin () { @@ -150,8 +162,8 @@ public void Init_ExplicitlyRequestNet () ApplicationImpl.ChangeInstance (orig); } - - private void SetupRunInputMockMethodToBlock (Mock winInput) +*/ + private void SetupRunInputMockMethodToBlock (Mock> winInput) { winInput.Setup (r => r.Run (It.IsAny ())) .Callback (token => @@ -473,7 +485,7 @@ private bool IdleExit () return true; } - + /* [Fact] public void Shutdown_Called_Repeatedly_DoNotDuplicateDisposeOutput () { @@ -500,6 +512,7 @@ public void Shutdown_Called_Repeatedly_DoNotDuplicateDisposeOutput () ApplicationImpl.ChangeInstance (orig); } + */ [Fact] public void Init_Called_Repeatedly_WarnsAndIgnores () diff --git a/Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs b/Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs index 4c2a32e6fc..d3ddaceef7 100644 --- a/Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs @@ -14,15 +14,15 @@ public async Task TestMainLoopCoordinator_InputCrashes_ExceptionSurfacesMainThre var beforeLogger = Logging.Logger; Logging.Logger = mockLogger.Object; - var c = new MainLoopCoordinator (new TimedEvents (), - // Runs on a separate thread (input thread) - () => throw new Exception ("Crash on boot"), + var m = new Mock> (); + // Runs on a separate thread (input thread) + m.Setup (f => f.CreateInput ()).Throws (new Exception ("Crash on boot")); + var c = new MainLoopCoordinator (new TimedEvents (), // Rest runs on main thread new ConcurrentQueue (), - Mock.Of (), - ()=>Mock.Of(), - Mock.Of>()); + Mock.Of>(), + m.Object); // StartAsync boots the main loop and the input thread. But if the input class bombs // on startup it is important that the exception surface at the call site and not lost diff --git a/Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs b/Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs index 727c105536..439498b872 100644 --- a/Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs @@ -21,7 +21,8 @@ public void MainLoopT_NotInitialized_Throws() m.Initialize (new TimedEvents (), new ConcurrentQueue (), Mock.Of (), - Mock.Of()); + Mock.Of(), + Mock.Of>()); Assert.NotNull (m.TimedEvents); Assert.NotNull (m.InputBuffer); From 985ec4074832afde09513056fa89e66510868142 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 12:25:40 +0100 Subject: [PATCH 050/104] Fake size monitor injecting --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 2 +- .../Drivers/V2/ConsoleDriverFacade.cs | 25 ++---------------- .../TerminalGuiFluentTesting/FakeDriverV2.cs | 8 +++--- .../GuiTestContext.cs | 26 +++++++++++++++---- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index a4d7887dae..1c136b2c4d 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -312,7 +312,7 @@ public abstract class ComponentFactory : IComponentFactory public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue inputBuffer); /// - public IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) + public virtual IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) { return new WindowSizeMonitor (consoleOutput, outputBuffer); } diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index 999f3088bb..c817918a00 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -16,30 +16,8 @@ internal class ConsoleDriverFacade : IConsoleDriver, IConsoleDriverFacade public IInputProcessor InputProcessor { get; } public IOutputBuffer OutputBuffer => _outputBuffer; - private IWindowSizeMonitor _windowSizeMonitor; + public IWindowSizeMonitor WindowSizeMonitor { get; } - public IWindowSizeMonitor WindowSizeMonitor - { - get => _windowSizeMonitor; - - // TODO: Should really not be able to do this, we are making it settable so we can hack in a fake one in tests - set - { - // Remove any old event delegates - if (_windowSizeMonitor != null) - { - _windowSizeMonitor.SizeChanging -= RaiseSizeChanged; - } - - _windowSizeMonitor = value; - _windowSizeMonitor.SizeChanging += RaiseSizeChanged; - } - } - - private void RaiseSizeChanged (object sender, SizeChangedEventArgs e) - { - SizeChanged?.Invoke (this, e); - } public ConsoleDriverFacade ( IInputProcessor inputProcessor, @@ -63,6 +41,7 @@ IWindowSizeMonitor windowSizeMonitor }; WindowSizeMonitor = windowSizeMonitor; + windowSizeMonitor.SizeChanging += (_,e) => SizeChanged?.Invoke (this, e); CreateClipboard (); } diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index 5a3394a287..00c93e838f 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -21,15 +21,15 @@ public IDisposable SetupFakeApplication () IApplication origApp = ApplicationImpl.Instance; - var v2 = new ApplicationV2 (new FakeNetComponentFactory (fakeInput, _output)); + var sizeMonitor = new FakeSizeMonitor (); + + var v2 = new ApplicationV2 (new FakeNetComponentFactory (fakeInput, _output, sizeMonitor)); ApplicationImpl.ChangeInstance (v2); v2.Init (null,"v2net"); var d = (ConsoleDriverFacade)Application.Driver; - d.WindowSizeMonitor = new FakeSizeMonitor (); - - d.WindowSizeMonitor.SizeChanging += (_, e) => + sizeMonitor.SizeChanging += (_, e) => { if (e.Size != null) { diff --git a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs index bb818dca8c..7cbdc8c613 100644 --- a/Tests/TerminalGuiFluentTesting/GuiTestContext.cs +++ b/Tests/TerminalGuiFluentTesting/GuiTestContext.cs @@ -34,10 +34,10 @@ internal GuiTestContext (Func topLevelBuilder, int width, int height, _winInput = new (_cts.Token); _output.Size = new (width, height); - + var cf = driver == V2TestDriver.V2Net - ? new FakeNetComponentFactory (_netInput, _output): - (IComponentFactory)new FakeWindowsComponentFactory(_winInput,_output); + ? new FakeNetComponentFactory (_netInput, _output, new FakeSizeMonitor ()): + (IComponentFactory)new FakeWindowsComponentFactory(_winInput,_output, new FakeSizeMonitor ()); var v2 = new ApplicationV2 (cf); @@ -837,11 +837,13 @@ internal class FakeWindowsComponentFactory : WindowsComponentFactory { private readonly FakeWindowsInput _winInput; private readonly FakeOutput _output; + private readonly FakeSizeMonitor _fakeSizeMonitor; - public FakeWindowsComponentFactory (FakeWindowsInput winInput, FakeOutput output) + public FakeWindowsComponentFactory (FakeWindowsInput winInput, FakeOutput output, FakeSizeMonitor fakeSizeMonitor) { _winInput = winInput; _output = output; + _fakeSizeMonitor = fakeSizeMonitor; } /// @@ -855,17 +857,25 @@ public override IConsoleOutput CreateOutput () { return _output; } + + /// + public override IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) + { + return _fakeSizeMonitor; + } } internal class FakeNetComponentFactory : NetComponentFactory { private readonly FakeNetInput _netInput; private readonly FakeOutput _output; + private readonly FakeSizeMonitor _fakeSizeMonitor; - public FakeNetComponentFactory (FakeNetInput netInput, FakeOutput output) + public FakeNetComponentFactory (FakeNetInput netInput, FakeOutput output,FakeSizeMonitor fakeSizeMonitor) { _netInput = netInput; _output = output; + _fakeSizeMonitor = fakeSizeMonitor; } /// @@ -879,4 +889,10 @@ public override IConsoleOutput CreateOutput () { return _output; } + + /// + public override IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer) + { + return _fakeSizeMonitor; + } } From af4fecb48e3450c354538cd2ae747af6e3f545ef Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 12:29:07 +0100 Subject: [PATCH 051/104] Add helper for faking console resize in AutoInitShutdown tests --- Tests/UnitTests/AutoInitShutdownAttribute.cs | 10 ++++++++++ Tests/UnitTests/Views/CheckBoxTests.cs | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests/AutoInitShutdownAttribute.cs index 2f20a74cbe..c84d5b11fe 100644 --- a/Tests/UnitTests/AutoInitShutdownAttribute.cs +++ b/Tests/UnitTests/AutoInitShutdownAttribute.cs @@ -147,4 +147,14 @@ public override void Before (MethodInfo methodUnderTest) } private bool AutoInit { get; } + + /// + /// 'Resizes' the application and forces layout. Only works if your test uses + /// + /// + public static void FakeResize (Size size) + { + ((FakeSizeMonitor)((IConsoleDriverFacade)Application.Driver!).WindowSizeMonitor).RaiseSizeChanging (new Size (30, 5)); + Application.LayoutAndDrawImpl (); + } } \ No newline at end of file diff --git a/Tests/UnitTests/Views/CheckBoxTests.cs b/Tests/UnitTests/Views/CheckBoxTests.cs index 5ab6b87db6..4eac82f6b4 100644 --- a/Tests/UnitTests/Views/CheckBoxTests.cs +++ b/Tests/UnitTests/Views/CheckBoxTests.cs @@ -351,8 +351,7 @@ public void TextAlignment_Centered () top.Add (win); Application.Begin (top); - ((FakeSizeMonitor)((IConsoleDriverFacade)Application.Driver!).WindowSizeMonitor).RaiseSizeChanging (new Size (30, 5)); - Application.LayoutAndDrawImpl (); + AutoInitShutdownAttribute.FakeResize (new Size (30, 5)); Assert.Equal (Alignment.Center, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); From 9cbf4fe812a1dd3c007df89d8c8bb396e5711db6 Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 12:34:25 +0100 Subject: [PATCH 052/104] Fix size setting in FakeDriverV2 --- Tests/TerminalGuiFluentTesting/FakeDriverV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs index 00c93e838f..46dc131e6d 100644 --- a/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs +++ b/Tests/TerminalGuiFluentTesting/FakeDriverV2.cs @@ -130,6 +130,7 @@ internal FakeDriverV2 ( public void SetBufferSize (int width, int height) { SizeMonitor.RaiseSizeChanging (new Size (width,height)); + OutputBuffer.SetWindowSize (width,height); } } From d1ba176525871aa33fc6f6926df4e208ab8160fb Mon Sep 17 00:00:00 2001 From: tznind Date: Sat, 9 Aug 2025 12:45:03 +0100 Subject: [PATCH 053/104] Switch to new method --- .../UnitTests/Application/ApplicationTests.cs | 4 ++-- Tests/UnitTests/AutoInitShutdownAttribute.cs | 2 +- .../ConsoleDrivers/ConsoleDriverTests.cs | 2 +- Tests/UnitTests/Dialogs/MessageBoxTests.cs | 12 +++++----- Tests/UnitTests/Drawing/RulerTests.cs | 2 +- Tests/UnitTests/Drawing/ThicknessTests.cs | 4 ++-- Tests/UnitTests/View/Adornment/BorderTests.cs | 16 ++++++------- .../UnitTests/View/Draw/ClearViewportTests.cs | 4 ++-- Tests/UnitTests/View/Draw/DrawTests.cs | 6 ++--- .../UnitTests/View/Layout/Pos.CenterTests.cs | 4 ++-- Tests/UnitTests/View/Layout/SetLayoutTests.cs | 2 +- Tests/UnitTests/View/TextTests.cs | 10 ++++---- Tests/UnitTests/View/ViewTests.cs | 2 +- Tests/UnitTests/Views/ButtonTests.cs | 2 +- Tests/UnitTests/Views/CheckBoxTests.cs | 6 ++--- Tests/UnitTests/Views/FrameViewTests.cs | 2 +- Tests/UnitTests/Views/LabelTests.cs | 20 ++++++++-------- Tests/UnitTests/Views/ListViewTests.cs | 2 +- .../UnitTests/Views/Menuv1/MenuBarv1Tests.cs | 24 +++++++++---------- Tests/UnitTests/Views/RadioGroupTests.cs | 2 +- Tests/UnitTests/Views/TextViewTests.cs | 4 ++-- Tests/UnitTests/Views/ToplevelTests.cs | 10 ++++---- Tests/UnitTests/Views/WindowTests.cs | 6 ++--- 23 files changed, 74 insertions(+), 74 deletions(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index b504b94846..494818d340 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -166,7 +166,7 @@ public void Begin_Sets_Application_Top_To_Console_Size () Toplevel top = new (); Application.Begin (top); Assert.Equal (new (0, 0, 80, 25), Application.Top!.Frame); - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + AutoInitShutdownAttribute.FakeResize(new Size(5, 5)); Assert.Equal (new (0, 0, 5, 5), Application.Top!.Frame); top.Dispose (); } @@ -943,7 +943,7 @@ public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () Width = 5, Height = 5, Arrangement = ViewArrangement.Movable }; - ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); + AutoInitShutdownAttribute.FakeResize(new Size(10, 10)); RunState rs = Application.Begin (w); // Don't use visuals to test as style of border can change over time. diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests/AutoInitShutdownAttribute.cs index c84d5b11fe..22cd9895c0 100644 --- a/Tests/UnitTests/AutoInitShutdownAttribute.cs +++ b/Tests/UnitTests/AutoInitShutdownAttribute.cs @@ -154,7 +154,7 @@ public override void Before (MethodInfo methodUnderTest) /// public static void FakeResize (Size size) { - ((FakeSizeMonitor)((IConsoleDriverFacade)Application.Driver!).WindowSizeMonitor).RaiseSizeChanging (new Size (30, 5)); + ((FakeSizeMonitor)((IConsoleDriverFacade)Application.Driver!).WindowSizeMonitor).RaiseSizeChanging (size); Application.LayoutAndDrawImpl (); } } \ No newline at end of file diff --git a/Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs index 7e5b914d2b..4ee596d345 100644 --- a/Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs +++ b/Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs @@ -235,7 +235,7 @@ public void TerminalResized_Simulation (Type driverType) // { // var win = new Window (); // Application.Begin (win); - // ((FakeDriver)Application.Driver!).SetBufferSize (20, 8); + // AutoInitShutdownAttribute.FakeResize(new Size ( (20, 8); // System.Threading.Tasks.Task.Run (() => { // System.Threading.Tasks.Task.Delay (500).Wait (); diff --git a/Tests/UnitTests/Dialogs/MessageBoxTests.cs b/Tests/UnitTests/Dialogs/MessageBoxTests.cs index 7777806a85..dbcf4a8666 100644 --- a/Tests/UnitTests/Dialogs/MessageBoxTests.cs +++ b/Tests/UnitTests/Dialogs/MessageBoxTests.cs @@ -155,7 +155,7 @@ public void Location_And_Size_Correct (string message, bool wrapMessage, bool ha { int iterations = -1; - ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); // 15 x 15 gives us enough room for a button with one char (9x1) + AutoInitShutdownAttribute.FakeResize(new Size(15, 15)); // 15 x 15 gives us enough room for a button with one char (9x1) Dialog.DefaultShadow = ShadowStyle.None; Button.DefaultShadow = ShadowStyle.None; @@ -189,7 +189,7 @@ public void Message_With_Spaces_WrapMessage_False () int iterations = -1; var top = new Toplevel (); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); var btn = $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}"; @@ -261,7 +261,7 @@ public void Message_With_Spaces_WrapMessage_True () int iterations = -1; var top = new Toplevel (); top.BorderStyle = LineStyle.None; - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); var btn = $"{Glyphs.LeftBracket}{Glyphs.LeftDefaultIndicator} btn {Glyphs.RightDefaultIndicator}{Glyphs.RightBracket}"; @@ -347,7 +347,7 @@ public void Message_With_Spaces_WrapMessage_True () public void Size_Not_Default_Message (int height, int width, string message) { int iterations = -1; - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + AutoInitShutdownAttribute.FakeResize(new Size(100, 100)); Application.Iteration += (s, a) => { @@ -384,7 +384,7 @@ public void Size_Not_Default_Message (int height, int width, string message) public void Size_Not_Default_Message_Button (int height, int width, string message) { int iterations = -1; - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + AutoInitShutdownAttribute.FakeResize(new Size(100, 100)); Application.Iteration += (s, a) => { @@ -417,7 +417,7 @@ public void Size_Not_Default_Message_Button (int height, int width, string messa public void Size_Not_Default_No_Message (int height, int width) { int iterations = -1; - ((FakeDriver)Application.Driver!).SetBufferSize (100, 100); + AutoInitShutdownAttribute.FakeResize(new Size(100, 100)); Application.Iteration += (s, a) => { diff --git a/Tests/UnitTests/Drawing/RulerTests.cs b/Tests/UnitTests/Drawing/RulerTests.cs index b30dfce8e7..0859489d48 100644 --- a/Tests/UnitTests/Drawing/RulerTests.cs +++ b/Tests/UnitTests/Drawing/RulerTests.cs @@ -30,7 +30,7 @@ public void Constructor_Defaults () [AutoInitShutdown] public void Draw_Default () { - ((FakeDriver)Application.Driver!).SetBufferSize (25, 25); + AutoInitShutdownAttribute.FakeResize(new Size(25, 25)); var r = new Ruler (); r.Draw (Point.Empty); diff --git a/Tests/UnitTests/Drawing/ThicknessTests.cs b/Tests/UnitTests/Drawing/ThicknessTests.cs index 24f7394087..6f24881fd9 100644 --- a/Tests/UnitTests/Drawing/ThicknessTests.cs +++ b/Tests/UnitTests/Drawing/ThicknessTests.cs @@ -10,7 +10,7 @@ public class ThicknessTests (ITestOutputHelper output) [AutoInitShutdown] public void DrawTests () { - ((FakeDriver)Application.Driver!).SetBufferSize (60, 60); + AutoInitShutdownAttribute.FakeResize(new Size(60, 60)); var t = new Thickness (0, 0, 0, 0); var r = new Rectangle (5, 5, 40, 15); @@ -125,7 +125,7 @@ public void DrawTests_Ruler () top.Add (f); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (45, 20); + AutoInitShutdownAttribute.FakeResize(new Size(45, 20)); var t = new Thickness (0, 0, 0, 0); var r = new Rectangle (2, 2, 40, 15); Application.RunIteration (ref rs); diff --git a/Tests/UnitTests/View/Adornment/BorderTests.cs b/Tests/UnitTests/View/Adornment/BorderTests.cs index 406a8dec31..7c32f9b71c 100644 --- a/Tests/UnitTests/View/Adornment/BorderTests.cs +++ b/Tests/UnitTests/View/Adornment/BorderTests.cs @@ -97,7 +97,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int w RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (width, 5); + AutoInitShutdownAttribute.FakeResize(new Size(width, 5)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; @@ -230,7 +230,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int RunState rs = Application.Begin (win); - ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); + AutoInitShutdownAttribute.FakeResize(new Size(width, 4)); Application.RunIteration (ref rs, false); var expected = string.Empty; @@ -364,7 +364,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int wi RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (width, 4); + AutoInitShutdownAttribute.FakeResize(new Size(width, 4)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; @@ -487,7 +487,7 @@ public void Border_With_Title_Size_Height (int height) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (20, height); + AutoInitShutdownAttribute.FakeResize(new Size(20, height)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; @@ -549,7 +549,7 @@ public void Border_With_Title_Size_Width (int width) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (width, 3); + AutoInitShutdownAttribute.FakeResize(new Size(width, 3)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; @@ -733,7 +733,7 @@ public void HasSuperView () RunState rs = Application.Begin (top); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (5, 5); + AutoInitShutdownAttribute.FakeResize(new Size(5, 5)); Application.RunIteration (ref rs, firstIteration); var expected = @" @@ -761,7 +761,7 @@ public void HasSuperView_Title () RunState rs = Application.Begin (top); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + AutoInitShutdownAttribute.FakeResize(new Size(10, 4)); Application.RunIteration (ref rs, firstIteration); var expected = @" @@ -784,7 +784,7 @@ public void NoSuperView () RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (3, 3); + AutoInitShutdownAttribute.FakeResize(new Size(3, 3)); Application.RunIteration (ref rs, firstIteration); var expected = @" diff --git a/Tests/UnitTests/View/Draw/ClearViewportTests.cs b/Tests/UnitTests/View/Draw/ClearViewportTests.cs index 5aa69954fe..06f5e291c2 100644 --- a/Tests/UnitTests/View/Draw/ClearViewportTests.cs +++ b/Tests/UnitTests/View/Draw/ClearViewportTests.cs @@ -210,7 +210,7 @@ public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods () var top = new Toplevel (); top.Add (view); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); var expected = @" ┌──────────────────┐ @@ -275,7 +275,7 @@ public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods () var top = new Toplevel (); top.Add (view); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); var expected = @" ┌──────────────────┐ diff --git a/Tests/UnitTests/View/Draw/DrawTests.cs b/Tests/UnitTests/View/Draw/DrawTests.cs index f39e54719e..a4c3049fe4 100644 --- a/Tests/UnitTests/View/Draw/DrawTests.cs +++ b/Tests/UnitTests/View/Draw/DrawTests.cs @@ -32,7 +32,7 @@ public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + AutoInitShutdownAttribute.FakeResize(new Size(10, 4)) ; const string expectedOutput = """ @@ -75,7 +75,7 @@ public void Colors_On_TextAlignment_Right_And_Bottom () top.Add (viewRight, viewBottom); var rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (7, 7); + AutoInitShutdownAttribute.FakeResize(new Size(7, 7)); Application.RunIteration (ref rs); DriverAssert.AssertDriverContentsWithFrameAre ( @@ -615,7 +615,7 @@ public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + AutoInitShutdownAttribute.FakeResize(new Size(10, 4)); var expected = """ diff --git a/Tests/UnitTests/View/Layout/Pos.CenterTests.cs b/Tests/UnitTests/View/Layout/Pos.CenterTests.cs index 26529fc503..6b1046236d 100644 --- a/Tests/UnitTests/View/Layout/Pos.CenterTests.cs +++ b/Tests/UnitTests/View/Layout/Pos.CenterTests.cs @@ -35,7 +35,7 @@ public void PosCenter_SubView_85_Percent_Height (int height) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (20, height); + AutoInitShutdownAttribute.FakeResize(new Size(20, height)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; @@ -182,7 +182,7 @@ public void PosCenter_SubView_85_Percent_Width (int width) RunState rs = Application.Begin (win); var firstIteration = false; - ((FakeDriver)Application.Driver!).SetBufferSize (width, 7); + AutoInitShutdownAttribute.FakeResize(new Size(width, 7)); Application.RunIteration (ref rs, firstIteration); var expected = string.Empty; diff --git a/Tests/UnitTests/View/Layout/SetLayoutTests.cs b/Tests/UnitTests/View/Layout/SetLayoutTests.cs index 19ba990507..68af8f7663 100644 --- a/Tests/UnitTests/View/Layout/SetLayoutTests.cs +++ b/Tests/UnitTests/View/Layout/SetLayoutTests.cs @@ -30,7 +30,7 @@ public void Screen_Size_Change_Causes_Layout () Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame); Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)) ; Assert.Equal (new (0, 0, Application.Screen.Width, Application.Screen.Height), Application.Top.Frame); Assert.Equal (new (0, 0, 20, 10), Application.Top.Frame); diff --git a/Tests/UnitTests/View/TextTests.cs b/Tests/UnitTests/View/TextTests.cs index 40139372e7..ab6d666420 100644 --- a/Tests/UnitTests/View/TextTests.cs +++ b/Tests/UnitTests/View/TextTests.cs @@ -121,7 +121,7 @@ public void TextDirection_Toggle () top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); + AutoInitShutdownAttribute.FakeResize(new Size(15, 15)) ; Assert.Equal (new (0, 0, 15, 15), win.Frame); Assert.Equal (new (0, 0, 15, 15), win.Margin.Frame); @@ -386,7 +386,7 @@ public void View_IsEmpty_False_Minimum_Width () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (4, 10); + AutoInitShutdownAttribute.FakeResize(new Size(4, 10)); Assert.Equal (5, text.Length); @@ -511,7 +511,7 @@ public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 20); + AutoInitShutdownAttribute.FakeResize(new Size(20, 20)); Assert.Equal (new (0, 0, 11, 2), horizontalView.Frame); Assert.Equal (new (0, 3, 2, 11), verticalView.Frame); @@ -599,7 +599,7 @@ public void Width_Height_Stay_True_If_TextFormatter_Size_Fit () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (22, 22); + AutoInitShutdownAttribute.FakeResize(new Size(22, 22)); Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.ConstrainToSize); Assert.Equal (new (2, 8), verticalView.TextFormatter.ConstrainToSize); @@ -913,7 +913,7 @@ public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize) var top = new Toplevel (); top.Add (frame); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (9, height + 2); + AutoInitShutdownAttribute.FakeResize(new Size(9, height + 2)); if (autoSize) { diff --git a/Tests/UnitTests/View/ViewTests.cs b/Tests/UnitTests/View/ViewTests.cs index 67c52e133c..dc28d9a3fa 100644 --- a/Tests/UnitTests/View/ViewTests.cs +++ b/Tests/UnitTests/View/ViewTests.cs @@ -456,7 +456,7 @@ public void Visible_Clear_The_View_Output () Application.RunIteration (ref rs); Assert.Equal ("Testing visibility.".Length, view.Frame.Width); Assert.True (view.Visible); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); DriverAssert.AssertDriverContentsWithFrameAre ( @" diff --git a/Tests/UnitTests/Views/ButtonTests.cs b/Tests/UnitTests/Views/ButtonTests.cs index 2e84f96ab7..f83a6d4fbe 100644 --- a/Tests/UnitTests/Views/ButtonTests.cs +++ b/Tests/UnitTests/Views/ButtonTests.cs @@ -571,7 +571,7 @@ public void Update_Parameterless_Only_On_Or_After_Initialize () Assert.False (btn.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.True (btn.IsInitialized); Assert.Equal ("Say Hello 你", btn.Text); diff --git a/Tests/UnitTests/Views/CheckBoxTests.cs b/Tests/UnitTests/Views/CheckBoxTests.cs index 4eac82f6b4..318ac496e4 100644 --- a/Tests/UnitTests/Views/CheckBoxTests.cs +++ b/Tests/UnitTests/Views/CheckBoxTests.cs @@ -411,7 +411,7 @@ public void TextAlignment_Justified () top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 6); + AutoInitShutdownAttribute.FakeResize(new Size(30, 6)); Assert.Equal (Alignment.Fill, checkBox1.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox1.Frame); @@ -472,7 +472,7 @@ public void TextAlignment_Left () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.Equal (Alignment.Start, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); @@ -523,7 +523,7 @@ public void TextAlignment_Right () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.Equal (Alignment.End, checkBox.TextAlignment); Assert.Equal (new (1, 1, 25, 1), checkBox.Frame); diff --git a/Tests/UnitTests/Views/FrameViewTests.cs b/Tests/UnitTests/Views/FrameViewTests.cs index 04bd0147ec..d7969f4023 100644 --- a/Tests/UnitTests/Views/FrameViewTests.cs +++ b/Tests/UnitTests/Views/FrameViewTests.cs @@ -38,7 +38,7 @@ public void Constructors_Defaults () [AutoInitShutdown] public void Draw_Defaults () { - ((FakeDriver)Application.Driver!).SetBufferSize (10, 10); + AutoInitShutdownAttribute.FakeResize(new Size(10, 10)); var fv = new FrameView () { BorderStyle = LineStyle.Single }; Assert.Equal (string.Empty, fv.Title); Assert.Equal (string.Empty, fv.Text); diff --git a/Tests/UnitTests/Views/LabelTests.cs b/Tests/UnitTests/Views/LabelTests.cs index 754e8b9e49..3e0b16d635 100644 --- a/Tests/UnitTests/Views/LabelTests.cs +++ b/Tests/UnitTests/Views/LabelTests.cs @@ -102,7 +102,7 @@ public void Text_Set_With_AnchorEnd_Works () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); var expected = @" ┌────────────────────────────┐ @@ -142,7 +142,7 @@ public void Set_Text_With_Center () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); var expected = @" ┌────────────────────────────┐ @@ -392,7 +392,7 @@ public void Update_Only_On_Or_After_Initialize () Assert.False (label.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.True (label.IsInitialized); Assert.Equal ("Say Hello 你", label.Text); @@ -424,7 +424,7 @@ public void Update_Parameterless_Only_On_Or_After_Initialize () Assert.False (label.IsInitialized); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.True (label.IsInitialized); Assert.Equal ("Say Hello 你", label.Text); @@ -856,7 +856,7 @@ public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window () Toplevel top = new (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); + AutoInitShutdownAttribute.FakeResize (new Size (40,10)); Assert.Equal (29, label.Text.Length); Assert.Equal (new (0, 0, 40, 10), top.Frame); @@ -903,7 +903,7 @@ public void Bottom_Equal_Inside_Window () Toplevel top = new (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); + AutoInitShutdownAttribute.FakeResize(new Size(40, 10)); Assert.Equal (new (0, 0, 40, 10), top.Frame); Assert.Equal (new (0, 0, 40, 10), win.Frame); @@ -975,7 +975,7 @@ public void Dim_Subtract_Operator_With_Text () { if (k.KeyCode == KeyCode.Enter) { - ((FakeDriver)Application.Driver!).SetBufferSize (22, count + 4); + AutoInitShutdownAttribute.FakeResize(new Size(22, count + 4)); Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (_expecteds [count], output); Assert.Equal (new (0, 0, 22, count + 4), pos); @@ -1126,7 +1126,7 @@ public void Dim_Add_Operator_With_Text () { if (k.KeyCode == KeyCode.Enter) { - ((FakeDriver)Application.Driver!).SetBufferSize (22, count + 4); + AutoInitShutdownAttribute.FakeResize(new Size(22, count + 4)); Rectangle pos = DriverAssert.AssertDriverContentsWithFrameAre (_expecteds [count], output); Assert.Equal (new (0, 0, 22, count + 4), pos); @@ -1201,7 +1201,7 @@ public void Label_IsEmpty_False_Minimum_Height () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + AutoInitShutdownAttribute.FakeResize(new Size(10, 4)); Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); @@ -1260,7 +1260,7 @@ public void Label_IsEmpty_False_Never_Return_Null_Lines () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (10, 4); + AutoInitShutdownAttribute.FakeResize(new Size(10, 4)); Assert.Equal (5, text.Length); Assert.Equal (new (0, 0, 5, 1), label.Frame); diff --git a/Tests/UnitTests/Views/ListViewTests.cs b/Tests/UnitTests/Views/ListViewTests.cs index 465d88f3cd..2b5bcbeb9a 100644 --- a/Tests/UnitTests/Views/ListViewTests.cs +++ b/Tests/UnitTests/Views/ListViewTests.cs @@ -56,7 +56,7 @@ public void Ensures_Visibility_SelectedItem_On_MoveDown_And_MoveUp () var top = new Toplevel (); top.Add (win); RunState rs = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (12, 12); + AutoInitShutdownAttribute.FakeResize(new Size(12, 12)); Application.LayoutAndDraw (); Assert.Equal (-1, lv.SelectedItem); diff --git a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs index 087823380e..819b2ee6ba 100644 --- a/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs +++ b/Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs @@ -468,7 +468,7 @@ public void Draw_A_Menu_Over_A_Dialog () var win = new Window (); top.Add (win); RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 15); + AutoInitShutdownAttribute.FakeResize(new Size(40, 15)) ; Assert.Equal (new (0, 0, 40, 15), win.Frame); @@ -656,7 +656,7 @@ void ChangeMenuTitle (string title) Assert.Equal (items [i], menu.Menus [0].Title); } - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); + AutoInitShutdownAttribute.FakeResize(new Size(20, 15)); menu.OpenMenu (); Application.RunIteration (ref rsDialog); @@ -837,7 +837,7 @@ void ChangeMenuTitle (string title) Assert.Equal (items [i], menu.Menus [0].Title); } - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); + AutoInitShutdownAttribute.FakeResize(new Size(20, 15)); menu.OpenMenu (); Application.RunIteration (ref rs); @@ -908,7 +908,7 @@ public void DrawFrame_With_Negative_Positions () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver!).SetBufferSize (7, 5); + AutoInitShutdownAttribute.FakeResize(new Size(7, 5)); menu.OpenMenu (); Application.LayoutAndDraw (); @@ -924,7 +924,7 @@ public void DrawFrame_With_Negative_Positions () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver!).SetBufferSize (7, 3); + AutoInitShutdownAttribute.FakeResize(new Size(7, 3)); menu.OpenMenu (); Application.LayoutAndDraw (); @@ -982,7 +982,7 @@ public void DrawFrame_With_Negative_Positions_Disabled_Border () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver!).SetBufferSize (3, 2); + AutoInitShutdownAttribute.FakeResize(new Size(3, 2)); menu.OpenMenu (); Application.LayoutAndDraw (); @@ -995,7 +995,7 @@ public void DrawFrame_With_Negative_Positions_Disabled_Border () menu.CloseAllMenus (); menu.Frame = new (0, 0, menu.Frame.Width, menu.Frame.Height); - ((FakeDriver)Application.Driver!).SetBufferSize (3, 1); + AutoInitShutdownAttribute.FakeResize(new Size(3, 1)); menu.OpenMenu (); Application.LayoutAndDraw (); @@ -1629,7 +1629,7 @@ public void MenuBar_In_Window_Without_Other_Views_With_Top_Init () Toplevel top = new (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); + AutoInitShutdownAttribute.FakeResize(new Size(40, 8)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -1741,7 +1741,7 @@ public void MenuBar_In_Window_Without_Other_Views_With_Top_Init_With_Parameterle Application.Iteration += (s, a) => { - ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); + AutoInitShutdownAttribute.FakeResize(new Size (40, 8)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -1853,7 +1853,7 @@ public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init () ] }; win.Add (menu); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); + AutoInitShutdownAttribute.FakeResize(new Size(40, 8)); RunState rs = Application.Begin (win); Application.RunIteration (ref rs); @@ -1940,7 +1940,7 @@ public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init () [AutoInitShutdown] public void MenuBar_In_Window_Without_Other_Views_Without_Top_Init_With_Run_T () { - ((FakeDriver)Application.Driver!).SetBufferSize (40, 8); + AutoInitShutdownAttribute.FakeResize(new Size(40, 8)); Application.Iteration += (s, a) => { @@ -2893,7 +2893,7 @@ public void Resizing_Close_Menus () output ); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 15); + AutoInitShutdownAttribute.FakeResize(new Size(20, 15)); firstIteration = false; Application.RunIteration (ref rs, firstIteration); diff --git a/Tests/UnitTests/Views/RadioGroupTests.cs b/Tests/UnitTests/Views/RadioGroupTests.cs index ba11583050..6aa7d342a2 100644 --- a/Tests/UnitTests/Views/RadioGroupTests.cs +++ b/Tests/UnitTests/Views/RadioGroupTests.cs @@ -533,7 +533,7 @@ public void Orientation_Width_Height_Vertical_Horizontal_Space () top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (30, 5); + AutoInitShutdownAttribute.FakeResize(new Size(30, 5)); Assert.Equal (Orientation.Vertical, rg.Orientation); Assert.Equal (2, rg.RadioLabels.Length); diff --git a/Tests/UnitTests/Views/TextViewTests.cs b/Tests/UnitTests/Views/TextViewTests.cs index b0b0bbed41..4f6db53fe4 100644 --- a/Tests/UnitTests/Views/TextViewTests.cs +++ b/Tests/UnitTests/Views/TextViewTests.cs @@ -5262,7 +5262,7 @@ public void TextView_InsertText_Newline_CRLF () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); + AutoInitShutdownAttribute.FakeResize(new Size(15, 15)); Application.LayoutAndDraw (); //this passes @@ -5340,7 +5340,7 @@ public void TextView_InsertText_Newline_LF () var top = new Toplevel (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (15, 15); + AutoInitShutdownAttribute.FakeResize(new Size (15, 15)); Application.LayoutAndDraw (); //this passes diff --git a/Tests/UnitTests/Views/ToplevelTests.cs b/Tests/UnitTests/Views/ToplevelTests.cs index 5ebbdc8a95..ec695a68c5 100644 --- a/Tests/UnitTests/Views/ToplevelTests.cs +++ b/Tests/UnitTests/Views/ToplevelTests.cs @@ -285,7 +285,7 @@ public void Mouse_Drag_On_Top_With_Superview_Null () if (iterations == 0) { - ((FakeDriver)Application.Driver!).SetBufferSize (15, 7); + AutoInitShutdownAttribute.FakeResize(new Size(15, 7)); // Don't use MessageBox here; it's too complicated for this unit test; just use Window testWindow = new () @@ -405,7 +405,7 @@ public void Mouse_Drag_On_Top_With_Superview_Not_Null () if (iterations == 0) { - ((FakeDriver)Application.Driver!).SetBufferSize (30, 10); + AutoInitShutdownAttribute.FakeResize(new Size(30, 10)); } else if (iterations == 1) { @@ -596,7 +596,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Toplevel top = new (); var window = new Window { Width = 20, Height = 3, Arrangement = ViewArrangement.Movable }; RunState rsTop = Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 10); + AutoInitShutdownAttribute.FakeResize(new Size(40, 10)); RunState rsWindow = Application.Begin (window); Application.LayoutAndDraw (); Assert.Equal (new (0, 0, 40, 10), top.Frame); @@ -619,7 +619,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Assert.Equal (new (-11, -4, 20, 3), window.Frame); // Changes Top size to same size as Dialog more menu and scroll bar - ((FakeDriver)Application.Driver!).SetBufferSize (20, 3); + AutoInitShutdownAttribute.FakeResize(new Size(20, 3)); Application.RaiseMouseEvent ( new () @@ -632,7 +632,7 @@ public void Window_Viewport_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_L Assert.Equal (new (-1, -1, 20, 3), window.Frame); // Changes Top size smaller than Dialog size - ((FakeDriver)Application.Driver!).SetBufferSize (19, 2); + AutoInitShutdownAttribute.FakeResize(new Size(19, 2)); Application.RaiseMouseEvent ( new () diff --git a/Tests/UnitTests/Views/WindowTests.cs b/Tests/UnitTests/Views/WindowTests.cs index f2557d86c1..27206f8f7c 100644 --- a/Tests/UnitTests/Views/WindowTests.cs +++ b/Tests/UnitTests/Views/WindowTests.cs @@ -51,7 +51,7 @@ public void MenuBar_And_StatusBar_Inside_Window () Toplevel top = new (); top.Add (win); Application.Begin (top); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -68,7 +68,7 @@ public void MenuBar_And_StatusBar_Inside_Window () output ); - ((FakeDriver)Application.Driver!).SetBufferSize (40, 20); + AutoInitShutdownAttribute.FakeResize(new Size(40, 20)); DriverAssert.AssertDriverContentsWithFrameAre ( @" @@ -95,7 +95,7 @@ public void MenuBar_And_StatusBar_Inside_Window () output ); - ((FakeDriver)Application.Driver!).SetBufferSize (20, 10); + AutoInitShutdownAttribute.FakeResize(new Size(20, 10)); DriverAssert.AssertDriverContentsWithFrameAre ( @" From 637d9840820dde32fda84649f75eb88f62163260 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 11 Aug 2025 11:17:36 +0100 Subject: [PATCH 054/104] Fix IsLegacy becoming false when using blank constructor --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 1c136b2c4d..80ce60f995 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -27,7 +27,9 @@ public class ApplicationV2 : ApplicationImpl /// factory methods will be used on Init calls to get things booted. /// public ApplicationV2 () - { } + { + IsLegacy = false; + } internal ApplicationV2 (IComponentFactory componentFactory) { From 3bed0c43a67c9e2a35f4746ed29399f2ea36b6a1 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 11 Aug 2025 11:18:57 +0100 Subject: [PATCH 055/104] Fix for Ready not being raised when showing same top twice also fixes garbage collection issue if running millions of top levels --- .../Drivers/V2/MainLoopCoordinator.cs | 2 +- .../Drivers/V2/ToplevelTransitionManager.cs | 3 + .../UnitTests/Application/ApplicationTests.cs | 117 +++++++----------- Tests/UnitTests/AutoInitShutdownAttribute.cs | 4 +- 4 files changed, 48 insertions(+), 78 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs index 3e985a2483..7c11798d17 100644 --- a/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs +++ b/Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs @@ -87,7 +87,7 @@ public async Task StartAsync () throw _inputTask.Exception; } - throw new ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)"); + Logging.Logger.LogCritical("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)"); } Logging.Logger.LogInformation ("Main Loop Coordinator booting complete"); diff --git a/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs b/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs index 6a12f0861c..4e5937ac32 100644 --- a/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs +++ b/Terminal.Gui/Drivers/V2/ToplevelTransitionManager.cs @@ -20,6 +20,9 @@ public void RaiseReadyEventIfNeeded () { top.OnReady (); _readiedTopLevels.Add (top); + + // Views can be closed and opened and run again multiple times, see End_Does_Not_Dispose + top.Closed += (s, e) => _readiedTopLevels.Remove (top); } } diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index 494818d340..aac9f90753 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Reflection; using UnitTests; using Xunit.Abstractions; using static Terminal.Gui.Configuration.ConfigurationManager; @@ -40,7 +41,9 @@ public void AddTimeout_Fires () Application.InitializedChanged += OnApplicationOnInitializedChanged; - Application.Init (new FakeDriver ()); + var a = new AutoInitShutdownAttribute (); + a.Before (null); + Assert.True (initialized); Assert.False (shutdown); @@ -76,6 +79,8 @@ public void AddTimeout_Fires () _timeoutLock = null; } + + a.After (null); return; void OnApplicationOnInitializedChanged (object s, EventArgs a) @@ -143,15 +148,13 @@ void OnApplicationOnIteration (object s, IterationEventArgs a) } [Fact] + [AutoInitShutdown] public void Begin_Null_Toplevel_Throws () { - // Setup Mock driver - Init (); - // Test null Toplevel Assert.Throws (() => Application.Begin (null)); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -163,6 +166,7 @@ public void Begin_Null_Toplevel_Throws () public void Begin_Sets_Application_Top_To_Console_Size () { Assert.Null (Application.Top); + AutoInitShutdownAttribute.FakeResize (new Size (80,25)); Toplevel top = new (); Application.Begin (top); Assert.Equal (new (0, 0, 80, 25), Application.Top!.Frame); @@ -172,12 +176,11 @@ public void Begin_Sets_Application_Top_To_Console_Size () } [Fact] + [AutoInitShutdown] public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop () { Assert.Null (Application.Top); - Init (); - RunState rs = Application.Begin (new ()); Application.Top!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop"; Assert.Equal (rs.Toplevel, Application.Top); @@ -193,25 +196,24 @@ public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop () Toplevel top = Application.Top; #if DEBUG_IDISPOSABLE - Exception exception = Record.Exception (() => Shutdown ()); + Exception exception = Record.Exception (Application.Shutdown); Assert.NotNull (exception); Assert.False (top.WasDisposed); top.Dispose (); Assert.True (top.WasDisposed); #endif - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); } [Fact] + [AutoInitShutdown] public void Init_Begin_End_Cleans_Up () { // Start stopwatch Stopwatch stopwatch = new Stopwatch (); stopwatch.Start (); - Init (); - // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests // if we don't stop Application.Iteration += (s, a) => { Application.RequestStop (); }; @@ -241,7 +243,7 @@ public void Init_Begin_End_Cleans_Up () Assert.NotNull (Application.Driver); topLevel.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -268,7 +270,7 @@ public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType) Assert.NotNull (Application.Driver); Assert.NotEqual (driver, Application.Driver); Assert.Equal (driverType, Application.Driver?.GetType ()); - Shutdown (); + Application.Shutdown (); } [Fact] @@ -278,7 +280,7 @@ public void Init_Null_Driver_Should_Pick_A_Driver () Assert.NotNull (Application.Driver); - Shutdown (); + Application.Shutdown (); } [Theory] @@ -468,7 +470,7 @@ public void Init_Unbalanced_Throws () new FakeDriver () ) ); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -478,7 +480,7 @@ public void Init_Unbalanced_Throws () Application.InternalInit (new FakeDriver ()); Assert.Throws (() => Application.Init (new FakeDriver ())); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -521,7 +523,7 @@ public void Init_WithoutTopLevelFactory_Begin_End_Cleans_Up () Assert.NotNull (Application.Driver); topLevel.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -645,34 +647,13 @@ public void InitState_Throws_If_Driver_Is_Null () Assert.Throws (static () => Application.SubscribeDriverEvents ()); } - private void Init () - { - Application.Init (new FakeDriver ()); - Assert.NotNull (Application.Driver); - Assert.NotNull (Application.MainLoop); - Assert.NotNull (SynchronizationContext.Current); - } - - private void Shutdown () - { - if (ApplicationImpl.Instance is ApplicationV2) - { - ApplicationImpl.Instance.Shutdown (); - } - else - { - Application.Shutdown (); - } - } #region RunTests [Fact] + [AutoInitShutdown] public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws () { - // Setup Mock driver - Init (); - Application.Iteration += (s, e) => Application.RequestStop (); // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) @@ -681,7 +662,7 @@ public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws () Assert.True (Application.Top is Window); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -689,11 +670,9 @@ public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws () } [Fact] + [AutoInitShutdown] public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws () { - // Setup Mock driver - Init (); - Application.Iteration += (s, e) => Application.RequestStop (); // Run when already initialized or not with a Driver will not throw (because Window is derived from Toplevel) @@ -708,7 +687,7 @@ public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws Assert.True (Application.Top is Dialog); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -716,10 +695,10 @@ public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws } [Fact] + [AutoInitShutdown] [TestRespondersDisposed] public void Run_T_After_Init_Does_Not_Disposes_Application_Top () { - Init (); // Init doesn't create a Toplevel and assigned it to Application.Top // but Begin does @@ -742,7 +721,7 @@ public void Run_T_After_Init_Does_Not_Disposes_Application_Top () Assert.True (initTop.WasDisposed); #endif Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -751,18 +730,16 @@ public void Run_T_After_Init_Does_Not_Disposes_Application_Top () [Fact] [TestRespondersDisposed] + [AutoInitShutdown] public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow () { - // Setup Mock driver - Init (); - Application.Iteration += (s, a) => { Application.RequestStop (); }; // Init has been called and we're passing no driver to Run. This is ok. Application.Run (); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -784,7 +761,7 @@ public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () Application.Run (); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -793,16 +770,15 @@ public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () [Fact] [TestRespondersDisposed] + [AutoInitShutdown] public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws () { - Init (); - Application.Driver = null; // Init has been called, but Driver has been set to null. Bad. Assert.Throws (() => Application.Run ()); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -821,7 +797,7 @@ public void Run_T_NoInit_DoesNotThrow () Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -838,7 +814,7 @@ public void Run_T_NoInit_WithDriver_DoesNotThrow () Application.Run (null, new FakeDriver ()); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.Null (Application.Top); Assert.Null (Application.MainLoop); @@ -847,10 +823,11 @@ public void Run_T_NoInit_WithDriver_DoesNotThrow () [Fact] [TestRespondersDisposed] + [AutoInitShutdown] public void Run_RequestStop_Stops () { // Setup Mock driver - Init (); + Application.Init (); var top = new Toplevel (); RunState rs = Application.Begin (top); @@ -868,11 +845,9 @@ public void Run_RequestStop_Stops () } [Fact] + [AutoInitShutdown] public void Run_Sets_Running_True () { - // Setup Mock driver - Init (); - var top = new Toplevel (); RunState rs = Application.Begin (top); Assert.NotNull (rs); @@ -894,11 +869,9 @@ public void Run_Sets_Running_True () [Fact] [TestRespondersDisposed] + [AutoInitShutdown] public void Run_RunningFalse_Stops () { - // Setup Mock driver - Init (); - var top = new Toplevel (); RunState rs = Application.Begin (top); Assert.NotNull (rs); @@ -915,10 +888,10 @@ public void Run_RunningFalse_Stops () } [Fact] + [AutoInitShutdown] [TestRespondersDisposed] public void Run_Loaded_Ready_Unloaded_Events () { - Init (); Toplevel top = new (); var count = 0; top.Loaded += (s, e) => count++; @@ -933,10 +906,9 @@ public void Run_Loaded_Ready_Unloaded_Events () // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs [Fact] + [AutoInitShutdown] public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () { - Init (); - // Don't use Dialog here as it has more layout logic. Use Window instead. var w = new Window { @@ -963,19 +935,16 @@ public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving () } [Fact] + [AutoInitShutdown] public void End_Does_Not_Dispose () { - Init (); - var top = new Toplevel (); Window w = new (); w.Ready += (s, e) => Application.RequestStop (); // Causes `End` to be called Application.Run (w); -#if DEBUG_IDISPOSABLE Assert.False (w.WasDisposed); -#endif Assert.NotNull (w); Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again @@ -1160,7 +1129,7 @@ public void Run_T_Call_Init_ForceDriver_Should_Pick_Correct_Driver (string drive Assert.NotNull (Application.Top); Assert.False (Application.Top!.Running); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); Assert.True (result); } @@ -1176,7 +1145,7 @@ public void Run_T_With_Legacy_Driver_Does_Not_Call_ResetState_After_Init () Assert.NotNull (Application.Top); Assert.False (Application.Top!.Running); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); } [Fact] @@ -1203,7 +1172,7 @@ public void Run_T_With_V2_Driver_Does_Not_Call_ResetState_After_Init () Assert.NotNull (Application.Top); Assert.False (Application.Top!.Running); Application.Top!.Dispose (); - Shutdown (); + Application.Shutdown (); } // TODO: Add tests for Run that test errorHandler @@ -1225,7 +1194,6 @@ async Task TaskWithAsyncContinuation () isCompletedSuccessfully = true; } - Init (); Application.Shutdown (); Assert.False (isCompletedSuccessfully); @@ -1237,7 +1205,6 @@ async Task TaskWithAsyncContinuation () [Fact] public void Shutdown_Resets_SyncContext () { - Init (); Application.Shutdown (); Assert.Null (SynchronizationContext.Current); } diff --git a/Tests/UnitTests/AutoInitShutdownAttribute.cs b/Tests/UnitTests/AutoInitShutdownAttribute.cs index 22cd9895c0..3a77f2d3f1 100644 --- a/Tests/UnitTests/AutoInitShutdownAttribute.cs +++ b/Tests/UnitTests/AutoInitShutdownAttribute.cs @@ -63,7 +63,7 @@ public AutoInitShutdownAttribute ( public override void After (MethodInfo methodUnderTest) { - Debug.WriteLine ($"After: {methodUnderTest.Name}"); + Debug.WriteLine ($"After: {methodUnderTest?.Name ?? "Unknown Test"}"); // Turn off diagnostic flags in case some test left them on View.Diagnostics = ViewDiagnosticFlags.Off; @@ -112,7 +112,7 @@ public override void After (MethodInfo methodUnderTest) public override void Before (MethodInfo methodUnderTest) { - Debug.WriteLine ($"Before: {methodUnderTest.Name}"); + Debug.WriteLine ($"Before: {methodUnderTest?.Name ?? "Unknown Test"}"); // Disable & force the ConfigurationManager to reset to its hardcoded defaults CM.Disable (true); From 82a279a83db326a0af99b99636b7f80c245f9b2e Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 11 Aug 2025 11:32:48 +0100 Subject: [PATCH 056/104] Fix tests --- Terminal.Gui/Drivers/V2/ApplicationV2.cs | 6 ++++++ Tests/UnitTests/Application/ApplicationTests.cs | 17 ++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Terminal.Gui/Drivers/V2/ApplicationV2.cs b/Terminal.Gui/Drivers/V2/ApplicationV2.cs index 80ce60f995..aedb5067cf 100644 --- a/Terminal.Gui/Drivers/V2/ApplicationV2.cs +++ b/Terminal.Gui/Drivers/V2/ApplicationV2.cs @@ -170,6 +170,12 @@ public override void Run (Toplevel view, Func? errorHandler = n Logging.Information ($"Run '{view}'"); ArgumentNullException.ThrowIfNull (view); + if (Application.Driver == null) + { + // See Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws + throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view"); + } + if (!Application.Initialized) { throw new NotInitializedException (nameof (Run)); diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index aac9f90753..c55176d199 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -239,14 +239,12 @@ public void Init_Begin_End_Cleans_Up () Application.End (runstate); Assert.NotNull (Application.Top); - Assert.NotNull (Application.MainLoop); Assert.NotNull (Application.Driver); topLevel.Dispose (); Application.Shutdown (); Assert.Null (Application.Top); - Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); // Stop stopwatch @@ -257,7 +255,6 @@ public void Init_Begin_End_Cleans_Up () } [Theory] - [InlineData (typeof (FakeDriver))] [InlineData (typeof (NetDriver))] //[InlineData (typeof (ANSIDriver))] @@ -752,8 +749,8 @@ public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () { Application.ForceDriver = "FakeDriver"; - Application.Init (); - Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ()); + var a = new AutoInitShutdownAttribute (); + a.Before (null); Application.Iteration += (s, a) => { Application.RequestStop (); }; @@ -766,6 +763,8 @@ public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow () Assert.Null (Application.Top); Assert.Null (Application.MainLoop); Assert.Null (Application.Driver); + + a.After (null); } [Fact] @@ -1086,8 +1085,12 @@ private class TestToplevel : Toplevel { } private readonly object _forceDriverLock = new (); [Theory] - [InlineData ("v2win", typeof (ConsoleDriverFacade))] - [InlineData ("v2net", typeof (ConsoleDriverFacade))] + + // This test wants to Run which results in console handle errors, it wants to rely non drivers checking ConsoleDriver.RunningUnitTests + // And suppressing things that might fail, this is anti pattern, instead we should test this kind of thing with Mocking + // [InlineData ("v2win", typeof (ConsoleDriverFacade))] + // [InlineData ("v2net", typeof (ConsoleDriverFacade))] + [InlineData ("FakeDriver", typeof (FakeDriver))] [InlineData ("NetDriver", typeof (NetDriver))] [InlineData ("WindowsDriver", typeof (WindowsDriver))] From f0d5794e55fb6762bab26193689ddeec4c32e778 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 11 Aug 2025 11:39:08 +0100 Subject: [PATCH 057/104] Remove auto init --- Tests/UnitTests/Application/ApplicationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index c55176d199..579477b565 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -667,7 +667,6 @@ public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws () } [Fact] - [AutoInitShutdown] public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws () { Application.Iteration += (s, e) => Application.RequestStop (); From 2a68279410f006e1e49232fc5b5ca170b3ac36b6 Mon Sep 17 00:00:00 2001 From: tznind Date: Mon, 11 Aug 2025 13:48:45 +0100 Subject: [PATCH 058/104] Restore conditional compilation stuff --- Tests/UnitTests/Application/ApplicationTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/UnitTests/Application/ApplicationTests.cs b/Tests/UnitTests/Application/ApplicationTests.cs index 579477b565..f2384778f8 100644 --- a/Tests/UnitTests/Application/ApplicationTests.cs +++ b/Tests/UnitTests/Application/ApplicationTests.cs @@ -942,7 +942,9 @@ public void End_Does_Not_Dispose () w.Ready += (s, e) => Application.RequestStop (); // Causes `End` to be called Application.Run (w); +#if DEBUG_IDISPOSABLE Assert.False (w.WasDisposed); +#endif Assert.NotNull (w); Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again From 295a81f4bfb29f421b39ca39be0ac95415eba471 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 14 Aug 2025 12:57:27 +0100 Subject: [PATCH 059/104] Restore 'if running unit tests' logic --- Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index c817918a00..6f30c7cbc5 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -73,6 +73,12 @@ public Rectangle Screen { get { + if (ConsoleDriver.RunningUnitTests) + { + // In unit tests, we don't have a real output, so we return an empty rectangle. + return Rectangle.Empty; + } + return new (new (0, 0), _output.GetWindowSize ()); } } From 382156f4e992e6db4202567660794eca347d3076 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 14 Aug 2025 12:58:19 +0100 Subject: [PATCH 060/104] Check only for the output being specific classes for the suppression --- Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs index 6f30c7cbc5..5f6a391fa1 100644 --- a/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs +++ b/Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs @@ -73,7 +73,7 @@ public Rectangle Screen { get { - if (ConsoleDriver.RunningUnitTests) + if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput) { // In unit tests, we don't have a real output, so we return an empty rectangle. return Rectangle.Empty; From ab9ac775051cc75c0e9c2fe934bf0173c8bb23e5 Mon Sep 17 00:00:00 2001 From: tznind Date: Thu, 14 Aug 2025 14:11:47 +0100 Subject: [PATCH 061/104] Fix ShadowView blowing up with index out of bounds error --- Terminal.Gui/ViewBase/Adornment/ShadowView.cs | 7 ++ .../FluentTests/FileDialogFluentTests.cs | 86 +++++++++++-------- .../GuiTestContext.cs | 8 +- Tests/TerminalGuiFluentTesting/With.cs | 9 +- .../View/Layout/Pos.AnchorEndTests.cs | 2 + 5 files changed, 70 insertions(+), 42 deletions(-) diff --git a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs index 12f2e08d93..a2d2eb5773 100644 --- a/Terminal.Gui/ViewBase/Adornment/ShadowView.cs +++ b/Terminal.Gui/ViewBase/Adornment/ShadowView.cs @@ -151,6 +151,13 @@ private Attribute GetAttributeUnderLocation (Point location) return Attribute.Default; } + if (Driver?.Contents == null || + location.Y < 0 || location.Y >= Driver.Contents.GetLength (0) || + location.X < 0 || location.X >= Driver.Contents.GetLength (1)) + { + return Attribute.Default; + } + Attribute attr = Driver!.Contents! [location.Y, location.X].Attribute!.Value; var newAttribute = diff --git a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs index 47a819fc78..460313a33c 100644 --- a/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs +++ b/Tests/IntegrationTests/FluentTests/FileDialogFluentTests.cs @@ -41,15 +41,28 @@ private MockFileSystem CreateExampleFileSystem () return mockFileSystem; } + private Toplevel NewSaveDialog (out SaveDialog sd, bool modal = true) + { + return NewSaveDialog (out sd, out _, modal); + } + + private Toplevel NewSaveDialog (out SaveDialog sd, out MockFileSystem fs,bool modal = true) + { + fs = CreateExampleFileSystem (); + sd = new SaveDialog (fs) { Modal = modal }; + return sd; + } + + [Theory] [ClassData (typeof (V2TestDrivers))] public void CancelFileDialog_UsingEscape (V2TestDriver d) { - var sd = new SaveDialog (CreateExampleFileSystem ()); - using var c = With.A (sd, 100, 20, d) + SaveDialog? sd = null; + using var c = With.A (()=>NewSaveDialog(out sd), 100, 20, d) .ScreenShot ("Save dialog", _out) .Escape () - .Then (() => Assert.True (sd.Canceled)) + .Then (() => Assert.True (sd!.Canceled)) .Stop (); } @@ -57,8 +70,9 @@ public void CancelFileDialog_UsingEscape (V2TestDriver d) [ClassData (typeof (V2TestDrivers))] public void CancelFileDialog_UsingCancelButton_TabThenEnter (V2TestDriver d) { - var sd = new SaveDialog (CreateExampleFileSystem ()) { Modal = false }; - using var c = With.A (sd, 100, 20, d) + + SaveDialog? sd = null; + using var c = With.A (() => NewSaveDialog (out sd,modal:false), 100, 20, d) .ScreenShot ("Save dialog", _out) .Focus