Skip to content

Fixes #4200. ExtendedCharInfo needs be enhanced to properly deal with all codepoints #4202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: v2_develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2d1ab6f
Fixes #4196. Application.Begin doesn't refresh the screen at start
BDisp Jul 21, 2025
a470275
Fixes #4198. Application.Invoke isn't wakeup the driver if idle
BDisp Jul 21, 2025
ef639c1
Reformatting to run CI again
BDisp Jul 21, 2025
d2acdf6
Revert "Reformatting to run CI again"
BDisp Jul 21, 2025
e48466f
Trying fix an issue where sometimes subview variable is null running …
BDisp Jul 21, 2025
0a52c49
Merge branch 'v2_4198_application-invoke-wakeup-fix' into v2_4200_wid…
BDisp Jul 21, 2025
fea1d4f
Replace ExtendedCharInfo.Char with char array
BDisp Jul 21, 2025
7097c0b
Replace IsWindowsTerminal with IsVirtualTerminal
BDisp Jul 22, 2025
8ef7116
Add a lastSize parameter to process resize automatically
BDisp Jul 22, 2025
1a2fb66
Handling surrogate pairs in input
BDisp Jul 22, 2025
9c25a7c
Implement SetConsoleTextAttribute
BDisp Jul 22, 2025
69e1986
Prevent select true color is not supported
BDisp Jul 22, 2025
600eba4
Fix null exception
BDisp Jul 22, 2025
0cc4c60
Revert GetWindowSize and add SetWindowSize
BDisp Jul 22, 2025
265c30e
Resolving merge conflicts
BDisp Jul 22, 2025
e061af0
Fix unit tests
BDisp Jul 22, 2025
15d4ac0
Revert all v2 changes except the one related with the ExtendedCharInfo
BDisp Jul 23, 2025
2f7b3c9
Revert newlines and FakeOutput
BDisp Jul 23, 2025
45d1710
Merge branch 'v2_develop' into v2_4200_wider-surrogatepair-force16col…
BDisp Jul 29, 2025
5be8d60
Prevents null reference
BDisp Jul 30, 2025
50cfa71
Add gnome-terminal to launch settings
BDisp Jul 30, 2025
88bf87b
Fixes issue on restore window size after maximize causing width shrin…
BDisp Jul 30, 2025
1ce2788
Add ; exec bash to stay in terminal
BDisp Jul 31, 2025
e5edad7
Fixes issue on restore window size after maximize causing width shrin…
BDisp Jul 31, 2025
8ead150
Tidying up input and output console modes
BDisp Jul 31, 2025
742ba26
Fixes uninitialized screen buffer.
BDisp Jul 31, 2025
3ad0859
Revert "Fixes issue on restore window size after maximize causing wid…
BDisp Aug 4, 2025
18d85e2
Merge branch 'v2_develop' into v2_4200_wider-surrogatepair-force16col…
BDisp Aug 4, 2025
3f5bdd2
Reset console after sending escape sequences
BDisp Aug 4, 2025
785a1ed
Remove unnecessary code only for buggy VSDebugConsole
BDisp Aug 4, 2025
30ff632
Fix more annoying exceptions
BDisp Aug 4, 2025
b0df97e
Ensure flush the input buffer before reset the console
BDisp Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions Examples/UICatalog/UICatalogTop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,23 @@ View [] CreateThemeMenuItems ()
CheckedState = Application.Force16Colors ? CheckState.Checked : CheckState.UnChecked
};

_force16ColorsMenuItemCb.CheckedStateChanging += (sender, args) =>
{
if (Application.Force16Colors
&& args.Result == CheckState.UnChecked
&& !Application.Driver!.SupportsTrueColor)
{
args.Handled = true;
}
};

_force16ColorsMenuItemCb.CheckedStateChanged += (sender, args) =>
{
Application.Force16Colors = args.Value == CheckState.Checked;
{
Application.Force16Colors = args.Value == CheckState.Checked;

_force16ColorsShortcutCb!.CheckedState = args.Value;
Application.LayoutAndDraw ();
};
_force16ColorsShortcutCb!.CheckedState = args.Value;
Application.LayoutAndDraw ();
};

menuItems.Add (
new MenuItemv2
Expand Down Expand Up @@ -633,11 +643,22 @@ private StatusBar CreateStatusBar ()
};

_force16ColorsShortcutCb.CheckedStateChanging += (sender, args) =>
{
Application.Force16Colors = args.Result == CheckState.Checked;
_force16ColorsMenuItemCb!.CheckedState = args.Result;
Application.LayoutAndDraw ();
};
{
if (Application.Force16Colors
&& args.Result == CheckState.UnChecked
&& !Application.Driver!.SupportsTrueColor)
{
// If the driver does not support TrueColor, we cannot disable 16 colors
args.Handled = true;
}
};

_force16ColorsShortcutCb.CheckedStateChanged += (sender, args) =>
{
Application.Force16Colors = args.Value == CheckState.Checked;
_force16ColorsMenuItemCb!.CheckedState = args.Value;
Application.LayoutAndDraw ();
};

statusBar.Add (
_shQuit,
Expand Down
11 changes: 9 additions & 2 deletions Terminal.Gui/App/Application.Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,15 @@ public static RunState Begin (Toplevel toplevel)

NotifyNewRunState?.Invoke (toplevel, new (rs));

// Force an Idle event so that an Iteration (and Refresh) happen.
Invoke (() => { });
if (!ConsoleDriver.RunningUnitTests)
{
// Force an Idle event so that an Iteration (and Refresh) happen.
Task.Run (() =>
{
Invoke (() => { });
Task.Delay (10).Wait ();
});
}

return rs;
}
Expand Down
4 changes: 4 additions & 0 deletions Terminal.Gui/App/ApplicationImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ public virtual void Invoke (Action action)
return false;
}
);

// Ensure the action is executed in the main loop
// Wakeup mainloop if it's waiting for events
Application.MainLoop.Wakeup ();
}

/// <inheritdoc />
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Drawing/Sixel/SixelSupportDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class SixelSupportDetector
public void Detect (Action<SixelSupportResult> resultCallback)
{
var result = new SixelSupportResult ();
result.SupportsTransparency = IsWindowsTerminal () || IsXtermWithTransparency ();
result.SupportsTransparency = IsVirtualTerminal () || IsXtermWithTransparency ();
IsSixelSupportedByDar (result, resultCallback);
}

Expand Down Expand Up @@ -142,7 +142,7 @@ private static void QueueRequest (AnsiEscapeSequence req, Action<string> respons

private static bool ResponseIndicatesSupport (string response) { return response.Split (';').Contains ("4"); }

private static bool IsWindowsTerminal ()
private static bool IsVirtualTerminal ()
{
return !string.IsNullOrWhiteSpace (Environment.GetEnvironmentVariable ("WT_SESSION"));

Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Drivers/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public void AddRune (Rune rune)
if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
{
// Invalidate cell to left
Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
Contents [Row, Col - 1].Rune = (Rune)'\0';
Contents [Row, Col - 1].IsDirty = true;
}
}
Expand Down Expand Up @@ -308,7 +308,7 @@ public void AddRune (Rune rune)
{
// 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.ReplacementChar;
Contents [Row, Col + 1].Rune = (Rune)'\0';
Contents [Row, Col + 1].IsDirty = true;
}
}
Expand Down
11 changes: 10 additions & 1 deletion Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ IWindowSizeMonitor windowSizeMonitor
_outputBuffer = outputBuffer;
_ansiRequestScheduler = ansiRequestScheduler;

if (InputProcessor is WindowsInputProcessor)
{
SupportsTrueColor = new WindowsInput ().IsVirtualTerminal ();
}
else if (InputProcessor is NetInputProcessor)
{
SupportsTrueColor = Application.Driver?.SupportsTrueColor == true;
}

InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
InputProcessor.MouseEvent += (s, e) =>
Expand Down Expand Up @@ -145,7 +154,7 @@ public int Top
// TODO: Probably not everyone right?

/// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
public bool SupportsTrueColor => true;
public bool SupportsTrueColor { get; init; } = true;

// TODO: Currently ignored
/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions Terminal.Gui/Drivers/V2/IConsoleOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ 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.
Expand Down
19 changes: 16 additions & 3 deletions Terminal.Gui/Drivers/V2/MainLoopCoordinator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ 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);

Expand Down Expand Up @@ -61,7 +60,6 @@ IMainLoop<T> loop
_inputProcessor = inputProcessor;
_outputFactory = outputFactory;
_loop = loop;
_isWindowsTerminal = Environment.GetEnvironmentVariable ("WT_SESSION") is { } || Environment.GetEnvironmentVariable ("VSAPPIDNAME") != null;
}

/// <summary>
Expand Down Expand Up @@ -162,7 +160,15 @@ private void BuildFacadeIfPossible ()
_loop.AnsiRequestScheduler,
_loop.WindowSizeMonitor);

if (!_isWindowsTerminal)
if (_facade.SupportsTrueColor)
{
if (!ConsoleDriver.RunningUnitTests)
{
// Enable alternative screen buffer.
Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
}
}
else
{
Application.Force16Colors = _facade.Force16Colors = true;
}
Expand All @@ -187,6 +193,13 @@ public void Stop ()
_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
Expand Down
6 changes: 6 additions & 0 deletions Terminal.Gui/Drivers/V2/NetOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ public Size GetWindowSize ()
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);
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Drivers/V2/OutputBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ public void AddRune (Rune rune)
{
// 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.ReplacementChar;
Contents [Row, Col + 1].Rune = (Rune)'\0';
Contents [Row, Col + 1].IsDirty = true;
}
}
Expand Down
13 changes: 10 additions & 3 deletions Terminal.Gui/Drivers/V2/WindowSizeMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,16 @@ public bool Poll ()
if (size != _lastSize)
{
Logging.Logger.LogInformation ($"Console size changes from '{_lastSize}' to {size}");
_outputBuffer.SetWindowSize (size.Width, size.Height);
_lastSize = size;
SizeChanging?.Invoke (this, new (size));
Size newSize = size;

if (_consoleOut.GetType().Name == "WindowsOutput")
{
newSize = _consoleOut.SetWindowSize (size);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resizing code looks confusing... can you explain why it is necessary to detect change in size and immediately order the output to resize itself again?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When windows is resized the buffer isn't correctly resized. So the code force resize the buffer correctly and thus there is no scroll buffer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid this.

devenv_ubd46dG3Un

Copy link
Collaborator Author

@BDisp BDisp Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I recognize that isn't good as is. So, feel free to change all you want.

}

_outputBuffer.SetWindowSize (newSize.Width, newSize.Height);
_lastSize = newSize;
SizeChanging?.Invoke (this, new (newSize));

return true;
}
Expand Down
6 changes: 6 additions & 0 deletions Terminal.Gui/Drivers/V2/WindowsInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public WindowsInput ()
SetConsoleMode (_inputHandle, newConsoleMode);
}

internal bool IsVirtualTerminal ()
{
nint outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
return GetConsoleMode (outputHandle, out uint mode) && (mode & (uint)ConsoleModes.EnableVirtualTerminalProcessing) != 0;
}

protected override bool Peek ()
{
const int bufferSize = 1; // We only need to check if there's at least one event
Expand Down
41 changes: 40 additions & 1 deletion Terminal.Gui/Drivers/V2/WindowsInputProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,45 @@ 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)
{
Expand Down Expand Up @@ -71,7 +110,7 @@ protected override void ProcessAfterParsing (InputRecord input)
{
var key = KeyConverter.ToKey (input);

if (key != (Key)0)
if (IsValidInput (key, out key))
{
OnKeyDown (key!);
OnKeyUp (key!);
Expand Down
Loading
Loading