Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e50396c
Initial plan
Copilot Sep 27, 2025
0e9068d
Add documentation for TextFormatter architectural issues and future r…
Copilot Sep 27, 2025
87c761c
Implement new TextFormatter architecture with separated formatter and…
Copilot Sep 27, 2025
688c27f
Fix Draw method to use new architecture - addresses @tig's feedback
Copilot Sep 27, 2025
7c68e49
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Sep 27, 2025
2d27f38
Fix test failures by making new architecture opt-in with UseNewArchit…
Copilot Sep 27, 2025
ae980c3
Make new architecture the default and validate all tests pass
Copilot Sep 27, 2025
78e319b
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Oct 15, 2025
08a3c19
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Oct 19, 2025
fa0b173
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Oct 19, 2025
40b28de
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Oct 25, 2025
2fbfb31
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Oct 26, 2025
98ae631
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Dec 1, 2025
5a4600e
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Dec 6, 2025
32b3444
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Dec 17, 2025
e0b3e72
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Mar 9, 2026
1a928b7
Merge branch 'v2_develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54…
tig Mar 30, 2026
e744b2c
Merge branch 'develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54ed8…
tig Apr 12, 2026
2039187
Merge branch 'develop' into copilot/fix-e6dde989-9ea1-4d83-8522-54ed8…
tig Apr 17, 2026
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
92 changes: 92 additions & 0 deletions Terminal.Gui/Text/FormattedText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Drawing;

namespace Terminal.Gui.Text;

/// <summary>
/// Represents the result of text formatting, containing formatted lines, size requirements, and metadata.
/// </summary>
public sealed class FormattedText
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedText"/> class.
/// </summary>
/// <param name="lines">The formatted text lines.</param>
/// <param name="requiredSize">The size required to display the text.</param>
/// <param name="hotKey">The HotKey found in the text, if any.</param>
/// <param name="hotKeyPosition">The position of the HotKey in the original text.</param>
public FormattedText(
IReadOnlyList<FormattedLine> lines,
Size requiredSize,
Key hotKey = default,

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.
int hotKeyPosition = -1)
{
Lines = lines ?? throw new ArgumentNullException(nameof(lines));
RequiredSize = requiredSize;
HotKey = hotKey;
HotKeyPosition = hotKeyPosition;
}

/// <summary>Gets the formatted text lines.</summary>
public IReadOnlyList<FormattedLine> Lines { get; }

/// <summary>Gets the size required to display the formatted text.</summary>
public Size RequiredSize { get; }

/// <summary>Gets the HotKey found in the text, if any.</summary>
public Key HotKey { get; }

/// <summary>Gets the position of the HotKey in the original text (-1 if no HotKey).</summary>
public int HotKeyPosition { get; }

/// <summary>Gets a value indicating whether the text contains a HotKey.</summary>
public bool HasHotKey => HotKeyPosition >= 0;
}

/// <summary>
/// Represents a single formatted line of text.
/// </summary>
public sealed class FormattedLine
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedLine"/> class.
/// </summary>
/// <param name="runs">The text runs that make up this line.</param>
/// <param name="width">The display width of this line.</param>
public FormattedLine(IReadOnlyList<FormattedRun> runs, int width)
{
Runs = runs;
Width = width;
}

/// <summary>Gets the text runs that make up this line.</summary>
public IReadOnlyList<FormattedRun> Runs { get; }

/// <summary>Gets the display width of this line.</summary>
public int Width { get; }
}

/// <summary>
/// Represents a run of text with consistent formatting.
/// </summary>
public sealed class FormattedRun
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedRun"/> class.
/// </summary>
/// <param name="text">The text content of this run.</param>
/// <param name="isHotKey">Whether this run represents a HotKey.</param>
public FormattedRun(string text, bool isHotKey = false)
{
Text = text;
IsHotKey = isHotKey;
}

/// <summary>Gets the text content of this run.</summary>
public string Text { get; }

/// <summary>Gets a value indicating whether this run represents a HotKey.</summary>
public bool IsHotKey { get; }
}
52 changes: 52 additions & 0 deletions Terminal.Gui/Text/ITextFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#nullable enable
using System.Drawing;

namespace Terminal.Gui.Text;

/// <summary>
/// Interface for text formatting. Separates formatting concerns from rendering.
/// </summary>
public interface ITextFormatter
{
/// <summary>Gets or sets the text to be formatted.</summary>
string Text { get; set; }

/// <summary>Gets or sets the size constraint for formatting.</summary>
Size? ConstrainToSize { get; set; }

/// <summary>Gets or sets the horizontal text alignment.</summary>
Alignment Alignment { get; set; }

/// <summary>Gets or sets the vertical text alignment.</summary>
Alignment VerticalAlignment { get; set; }

/// <summary>Gets or sets the text direction.</summary>
TextDirection Direction { get; set; }

/// <summary>Gets or sets whether word wrap is enabled.</summary>
bool WordWrap { get; set; }

/// <summary>Gets or sets whether multi-line text is allowed.</summary>
bool MultiLine { get; set; }

/// <summary>Gets or sets the HotKey specifier character.</summary>
Rune HotKeySpecifier { get; set; }

/// <summary>Gets or sets the tab width.</summary>
int TabWidth { get; set; }

/// <summary>Gets or sets whether trailing spaces are preserved in word-wrapped lines.</summary>
bool PreserveTrailingSpaces { get; set; }

/// <summary>
/// Formats the text and returns the formatted result.
/// </summary>
/// <returns>The formatted text result containing lines, size, and metadata.</returns>
FormattedText Format();

/// <summary>
/// Gets the size required to display the formatted text.
/// </summary>
/// <returns>The size required for the formatted text.</returns>
Size GetFormattedSize();
}
40 changes: 40 additions & 0 deletions Terminal.Gui/Text/ITextRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#nullable enable

namespace Terminal.Gui.Text;

/// <summary>
/// Interface for rendering formatted text to the console.
/// </summary>
public interface ITextRenderer
{
/// <summary>
/// Draws the formatted text to the console driver.
/// </summary>
/// <param name="formattedText">The formatted text to draw.</param>
/// <param name="screen">The screen bounds for drawing.</param>
/// <param name="normalColor">The color for normal text.</param>
/// <param name="hotColor">The color for HotKey text.</param>
/// <param name="fillRemaining">Whether to fill remaining area with spaces.</param>
/// <param name="maximum">The maximum container bounds.</param>
/// <param name="driver">The console driver to use for drawing.</param>
void Draw(
FormattedText formattedText,
Rectangle screen,
Attribute normalColor,
Attribute hotColor,
bool fillRemaining = false,
Rectangle maximum = default,
IConsoleDriver? driver = null);

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Integration Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Build All Configurations

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 27 in Terminal.Gui/Text/ITextRenderer.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (windows-latest)

The type or namespace name 'IConsoleDriver' could not be found (are you missing a using directive or an assembly reference?)

/// <summary>
/// Gets the region that would be drawn by the formatted text.
/// </summary>
/// <param name="formattedText">The formatted text.</param>
/// <param name="screen">The screen bounds.</param>
/// <param name="maximum">The maximum container bounds.</param>
/// <returns>A region representing the areas that would be drawn.</returns>
Region GetDrawRegion(
FormattedText formattedText,
Rectangle screen,
Rectangle maximum = default);
}
43 changes: 43 additions & 0 deletions Terminal.Gui/Text/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Text Formatting in Terminal.Gui

This directory contains text formatting and processing classes for Terminal.Gui.

## Classes

### TextFormatter

The main text formatting class that handles:
- Text alignment (horizontal and vertical)
- Text direction support (left-to-right, right-to-left, top-to-bottom, etc.)
- Word wrapping
- Multi-line text support
- HotKey processing
- Wide character (Unicode) support

**Known Issues**: The current `TextFormatter` implementation has several architectural problems that are planned to be addressed in a future rewrite:

1. **Format/Draw Coupling**: The `Draw()` method does significant formatting work, making `FormatAndGetSize()` unreliable
2. **Performance**: `Format()` is called multiple times during layout operations
3. **Complex Alignment**: Alignment logic is embedded in drawing code instead of using the `Aligner` engine
4. **Poor Extensibility**: Adding new features requires modifying the monolithic class
5. **No Interface**: Prevents multiple text formatter implementations

See [TextFormatter Rewrite Issue](https://github.com/gui-cs/Terminal.Gui/issues/3469) for details.

### Other Classes

- `TextDirection`: Enumeration for text direction support
- `StringExtensions`: Extension methods for string processing
- `RuneExtensions`: Extension methods for Unicode Rune processing
- `NerdFonts`: Support for Nerd Fonts icons

## Future Plans

A complete rewrite of `TextFormatter` is planned that will:
- Separate formatting from rendering concerns
- Provide an interface-based architecture for extensibility
- Improve performance with better caching
- Support multiple text formats (HTML, Attributed Text, etc.)
- Use the `Aligner` engine for proper alignment

This is a major architectural change planned for a future release.
Loading
Loading