Revert all v2 changes except the one related with the ExtendedCharInfo

This commit is contained in:
BDisp
2025-07-23 14:11:44 +01:00
parent e061af00cd
commit 15d4ac0158
9 changed files with 61 additions and 386 deletions

View File

@@ -28,11 +28,6 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
_outputBuffer = outputBuffer;
_ansiRequestScheduler = ansiRequestScheduler;
if (!ConsoleDriver.RunningUnitTests && InputProcessor is WindowsInputProcessor)
{
SupportsTrueColor = new WindowsInput ().IsVirtualTerminal ();
}
InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
InputProcessor.MouseEvent += (s, e) =>
@@ -150,7 +145,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
// TODO: Probably not everyone right?
/// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
public bool SupportsTrueColor { get; init; } = true;
public bool SupportsTrueColor => true;
// TODO: Currently ignored
/// <summary>
@@ -411,4 +406,4 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
{
// No need we will always draw when dirty
}
}
}

View File

@@ -26,13 +26,6 @@ public interface IConsoleOutput : IDisposable
/// <returns></returns>
public Size GetWindowSize ();
/// <summary>
/// Sets the current size of the console window in rows/columns
/// </summary>
/// <param name="newSize"></param>
/// /// <returns></returns>
public Size SetWindowSize (Size newSize);
/// <summary>
/// Updates the console cursor (the blinking underscore) to be hidden,
/// visible etc.
@@ -46,4 +39,4 @@ public interface IConsoleOutput : IDisposable
/// <param name="col"></param>
/// <param name="row"></param>
void SetCursorPosition (int col, int row);
}
}

View File

@@ -25,6 +25,7 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
private ConsoleDriverFacade<T> _facade;
private Task _inputTask;
private readonly ITimedEvents _timedEvents;
private readonly bool _isWindowsTerminal;
private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
@@ -60,6 +61,7 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
_inputProcessor = inputProcessor;
_outputFactory = outputFactory;
_loop = loop;
_isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
}
/// <summary>
@@ -160,15 +162,7 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
_loop.AnsiRequestScheduler,
_loop.WindowSizeMonitor);
if (_facade.SupportsTrueColor)
{
if (!ConsoleDriver.RunningUnitTests)
{
// Enable alternative screen buffer.
Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
}
}
else
if (!_isWindowsTerminal)
{
Application.Force16Colors = _facade.Force16Colors = true;
}
@@ -193,16 +187,9 @@ internal class MainLoopCoordinator<T> : IMainLoopCoordinator
_stopCalled = true;
_tokenSource.Cancel ();
if (!ConsoleDriver.RunningUnitTests && _facade.SupportsTrueColor)
{
// Disable alternative screen buffer.
Console.Out.Write (EscSeqUtils.CSI_RestoreCursorAndRestoreAltBufferWithBackscroll);
}
_output.Dispose ();
// Wait for input infinite loop to exit
_inputTask.Wait ();
}
}
}

View File

@@ -213,12 +213,6 @@ public class NetOutput : IConsoleOutput
return new (Console.WindowWidth, Console.WindowHeight);
}
/// <inheritdoc />
public Size SetWindowSize (Size newSize)
{
return newSize;
}
private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
{
SetCursorPositionImpl (lastCol, row);
@@ -283,4 +277,4 @@ public class NetOutput : IConsoleOutput
{
Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
}
}
}

View File

@@ -256,7 +256,7 @@ public class OutputBuffer : IOutputBuffer
{
// Invalidate cell to right so that it doesn't get drawn
// TODO: Figure out if it is better to show a replacement character or ' '
Contents [Row, Col + 1].Rune = (Rune)'\0';
Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
Contents [Row, Col + 1].IsDirty = true;
}
}
@@ -447,4 +447,4 @@ public class OutputBuffer : IOutputBuffer
Col = col;
Row = row;
}
}
}

View File

@@ -30,20 +30,13 @@ internal class WindowSizeMonitor : IWindowSizeMonitor
if (size != _lastSize)
{
Logging.Logger.LogInformation ($"Console size changes from '{_lastSize}' to {size}");
Size newSize = size;
if (_consoleOut.GetType().Name == "WindowsOutput")
{
newSize = _consoleOut.SetWindowSize (size);
}
_outputBuffer.SetWindowSize (newSize.Width, newSize.Height);
_lastSize = newSize;
SizeChanging?.Invoke (this, new (newSize));
_outputBuffer.SetWindowSize (size.Width, size.Height);
_lastSize = size;
SizeChanging?.Invoke (this, new (size));
return true;
}
return false;
}
}
}

View File

@@ -44,38 +44,16 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
return;
}
try
{
_inputHandle = GetStdHandle (STD_INPUT_HANDLE);
_inputHandle = GetStdHandle (STD_INPUT_HANDLE);
GetConsoleMode (_inputHandle, out uint v);
_originalConsoleMode = v;
GetConsoleMode (_inputHandle, out uint v);
_originalConsoleMode = v;
uint newConsoleMode = _originalConsoleMode;
newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
SetConsoleMode (_inputHandle, newConsoleMode);
}
catch (Exception ex)
{
Logging.Logger.LogInformation ($"Exception {nameof (WindowsInput)}");
}
}
internal bool IsVirtualTerminal ()
{
try
{
nint outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
return GetConsoleMode (outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0;
}
catch (Exception e)
{
Logging.Logger.LogInformation ("Exception IsVirtualTerminal");
return false;
}
uint newConsoleMode = _originalConsoleMode;
newConsoleMode |= (uint)(WindowsConsole.ConsoleModes.EnableMouseInput | WindowsConsole.ConsoleModes.EnableExtendedFlags);
newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableQuickEditMode;
newConsoleMode &= ~(uint)WindowsConsole.ConsoleModes.EnableProcessedInput;
SetConsoleMode (_inputHandle, newConsoleMode);
}
protected override bool Peek ()
@@ -147,4 +125,4 @@ internal class WindowsInput : ConsoleInput<WindowsConsole.InputRecord>, IWindows
SetConsoleMode (_inputHandle, _originalConsoleMode);
}
}
}

View File

@@ -15,45 +15,6 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
/// <inheritdoc/>
public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ()) { }
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;
}
/// <inheritdoc/>
protected override void Process (InputRecord inputEvent)
{
@@ -110,7 +71,7 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
{
var key = KeyConverter.ToKey (input);
if (IsValidInput (key, out key))
if (key != (Key)0)
{
OnKeyDown (key!);
OnKeyUp (key!);
@@ -225,4 +186,4 @@ internal class WindowsInputProcessor : InputProcessor<InputRecord>
return current;
}
}
}

View File

@@ -59,38 +59,7 @@ internal partial class WindowsOutput : IConsoleOutput
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleCursorInfo (nint hConsoleOutput, [In] ref WindowsConsole.ConsoleCursorInfo lpConsoleCursorInfo);
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern nint GetStdHandle (int nStdHandle);
[DllImport ("kernel32.dll")]
private static extern bool GetConsoleMode (nint hConsoleHandle, out uint lpMode);
[DllImport ("kernel32.dll")]
private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleTextAttribute (
nint hConsoleOutput,
ushort wAttributes
);
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern Coord GetLargestConsoleWindowSize (
nint hConsoleOutput
);
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX consoleScreenBufferInfo);
[DllImport ("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleWindowInfo (
nint hConsoleOutput,
bool bAbsolute,
[In] ref SmallRect lpConsoleWindow
);
private nint _screenBuffer;
private nint _outputHandle;
private readonly nint _screenBuffer;
// Last text style used, for updating style with EscSeqUtils.CSI_AppendTextStyleChange().
private TextStyle _redrawTextStyle = TextStyle.None;
@@ -104,61 +73,33 @@ internal partial class WindowsOutput : IConsoleOutput
return;
}
_outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
_screenBuffer = CreateConsoleScreenBuffer (
DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
ShareMode.FileShareRead | ShareMode.FileShareWrite,
nint.Zero,
1,
nint.Zero
);
if (!GetConsoleMode (_outputHandle, out uint mode))
if (_screenBuffer == INVALID_HANDLE_VALUE)
{
throw new ApplicationException ($"Failed to get _outputHandle console mode, error code: {Marshal.GetLastWin32Error ()}.");
int err = Marshal.GetLastWin32Error ();
if (err != 0)
{
throw new Win32Exception (err);
}
}
IsVirtualTerminal = (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0;
if (!IsVirtualTerminal)
if (!SetConsoleActiveScreenBuffer (_screenBuffer))
{
_screenBuffer = CreateConsoleScreenBuffer (
DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
ShareMode.FileShareRead | ShareMode.FileShareWrite,
nint.Zero,
1,
nint.Zero
);
if (_screenBuffer == INVALID_HANDLE_VALUE)
{
int err = Marshal.GetLastWin32Error ();
if (err != 0)
{
throw new Win32Exception (err);
}
}
if (!SetConsoleActiveScreenBuffer (_screenBuffer))
{
throw new Win32Exception (Marshal.GetLastWin32Error ());
}
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 (!SetConsoleMode (_screenBuffer, mode))
{
throw new ApplicationException ($"Failed to set screenBuffer console mode, error code: {Marshal.GetLastWin32Error ()}.");
}
throw new Win32Exception (Marshal.GetLastWin32Error ());
}
}
internal bool IsVirtualTerminal { get; init; }
public void Write (ReadOnlySpan<char> str)
{
if (!WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
{
throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
}
@@ -198,7 +139,7 @@ internal partial class WindowsOutput : IConsoleOutput
if (buffer.Contents [row, col].IsDirty == false)
{
outputBuffer [position].Empty = true;
outputBuffer [position].Char = [(char)buffer.Contents [row, col].Rune.Value];
outputBuffer [position].Char = [(char)Rune.ReplacementChar.Value];
continue;
}
@@ -211,8 +152,8 @@ internal partial class WindowsOutput : IConsoleOutput
}
else
{
outputBuffer [position].Char = [(char)buffer.Contents [row, col].Rune.ToString () [0],
(char)buffer.Contents [row, col].Rune.ToString () [1]];
//outputBuffer [position].Empty = true;
outputBuffer [position].Char = [(char)Rune.ReplacementChar.Value];
if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
{
@@ -220,7 +161,7 @@ internal partial class WindowsOutput : IConsoleOutput
col++;
position = row * buffer.Cols + col;
outputBuffer [position].Empty = false;
outputBuffer [position].Char = ['\0'];
outputBuffer [position].Char = [' '];
}
}
}
@@ -254,18 +195,6 @@ internal partial class WindowsOutput : IConsoleOutput
WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
}
private struct Run
{
public ushort attr;
public string text;
public Run (ushort attr, string text)
{
this.attr = attr;
this.text = text;
}
}
public bool WriteToConsole (Size size, WindowsConsole.ExtendedCharInfo [] charInfoBuffer, WindowsConsole.Coord bufferSize, WindowsConsole.SmallRect window, bool force16Colors)
{
@@ -276,124 +205,24 @@ internal partial class WindowsOutput : IConsoleOutput
// ReadFromConsoleOutput (size, bufferSize, ref window);
//}
Attribute? prev = null;
var result = false;
if (force16Colors)
{
StringBuilder stringBuilder = new ();
var i = 0;
List<Run> runs = [];
Run? current = null;
SetCursorPosition (0, 0);
WindowsConsole.CharInfo [] ci = new WindowsConsole.CharInfo [charInfoBuffer.Length];
foreach (ExtendedCharInfo info in charInfoBuffer)
foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
{
if (IsVirtualTerminal)
ci [i++] = new ()
{
Attribute attr = info.Attribute;
AnsiColorCode fgColor = info.Attribute.Foreground.GetAnsiColorCode ();
AnsiColorCode bgColor = info.Attribute.Background.GetAnsiColorCode ();
if (attr != prev)
{
prev = attr;
stringBuilder.Append (EscSeqUtils.CSI_SetForegroundColor (fgColor));
stringBuilder.Append (EscSeqUtils.CSI_SetBackgroundColor (bgColor));
EscSeqUtils.CSI_AppendTextStyleChange (stringBuilder, _redrawTextStyle, attr.Style);
_redrawTextStyle = attr.Style;
}
if (info.Char [0] != '\x1b')
{
if (!info.Empty)
{
stringBuilder.Append (info.Char);
}
}
else
{
stringBuilder.Append (' ');
}
}
else
{
if (info.Empty)
{
i++;
continue;
}
if (!info.Empty)
{
var attr = (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 ()
| ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4));
// Start new run if needed
if (current == null || attr != current.Value.attr)
{
if (current != null)
{
runs.Add (new (current.Value.attr, stringBuilder.ToString ()));
}
stringBuilder.Clear ();
current = new Run (attr, "");
}
stringBuilder!.Append (info.Char);
}
i++;
if (i > 0 && i <= charInfoBuffer.Length && i % bufferSize.X == 0)
{
if (i < charInfoBuffer.Length)
{
stringBuilder.AppendLine ();
}
runs.Add (new (current!.Value.attr, stringBuilder.ToString ()));
stringBuilder.Clear ();
}
}
Char = new () { UnicodeChar = info.Char [0] },
Attributes =
(ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
};
}
if (IsVirtualTerminal)
{
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<char>.Shared.Rent (minimumLength: stringBuilder.Length);
try
{
Span<char> writeBuffer = rentedWriteArray.AsSpan (0, stringBuilder.Length);
stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
// Supply console with the new content.
result = WriteConsole (_outputHandle, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
}
finally
{
ArrayPool<char>.Shared.Return (rentedWriteArray);
}
foreach (SixelToRender sixel in Application.Sixel)
{
SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
WriteConsole (_outputHandle, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
}
}
else
{
foreach (var run in runs)
{
SetConsoleTextAttribute (_screenBuffer, run.attr);
result = WriteConsole (_screenBuffer, run.text, (uint)run.text.Length, out _, nint.Zero);
}
}
result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window);
}
else
{
@@ -402,6 +231,8 @@ internal partial class WindowsOutput : IConsoleOutput
stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
EscSeqUtils.CSI_AppendCursorPosition (stringBuilder, 0, 0);
Attribute? prev = null;
foreach (WindowsConsole.ExtendedCharInfo info in charInfoBuffer)
{
Attribute attr = info.Attribute;
@@ -439,7 +270,7 @@ internal partial class WindowsOutput : IConsoleOutput
stringBuilder.CopyTo (0, writeBuffer, stringBuilder.Length);
// Supply console with the new content.
result = WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
result = WriteConsole (_screenBuffer, writeBuffer, (uint)writeBuffer.Length, out uint _, nint.Zero);
}
finally
{
@@ -449,7 +280,7 @@ internal partial class WindowsOutput : IConsoleOutput
foreach (SixelToRender sixel in Application.Sixel)
{
SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
WriteConsole (IsVirtualTerminal ? _outputHandle : _screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
}
}
@@ -471,7 +302,7 @@ internal partial class WindowsOutput : IConsoleOutput
var csbi = new WindowsConsole.CONSOLE_SCREEN_BUFFER_INFOEX ();
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi))
{
//throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ());
return Size.Empty;
@@ -484,63 +315,6 @@ internal partial class WindowsOutput : IConsoleOutput
return sz;
}
public Size SetWindowSize (Size newSize)
{
Size resSize = SetConsoleWindow ((short)newSize.Width, (short)newSize.Height);
if (resSize != newSize)
{
return resSize;
}
return newSize;
}
private Size SetConsoleWindow (short cols, short rows)
{
var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
csbi.cbSize = (uint)Marshal.SizeOf (csbi);
if (!GetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
throw new Win32Exception (Marshal.GetLastWin32Error ());
}
Coord maxWinSize = GetLargestConsoleWindowSize (IsVirtualTerminal ? _outputHandle : _screenBuffer);
short newCols = Math.Min (cols, maxWinSize.X);
short newRows = Math.Min (rows, maxWinSize.Y);
csbi.dwSize = new Coord (newCols, Math.Max (newRows, (short)1));
csbi.srWindow = new SmallRect (0, 0, newCols, newRows);
csbi.dwMaximumWindowSize = new Coord (newCols, newRows);
if (!SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
throw new Win32Exception (Marshal.GetLastWin32Error ());
}
var winRect = new 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 (CONSOLE_SCREEN_BUFFER_INFOEX csbi)
{
if ((IsVirtualTerminal
? _outputHandle
: _screenBuffer) != nint.Zero && !SetConsoleScreenBufferInfoEx (IsVirtualTerminal ? _outputHandle : _screenBuffer, ref csbi))
{
throw new Win32Exception (Marshal.GetLastWin32Error ());
}
}
/// <inheritdoc/>
public void SetCursorVisibility (CursorVisibility visibility)
{
@@ -607,4 +381,4 @@ internal partial class WindowsOutput : IConsoleOutput
_isDisposed = true;
}
}
}