Fixes #4233 - CharMap rendering (#4255)

* Fixed almost all issues

* code comments

* fixed copilot suggestion

* Add Unicode filtering and improve context menu handling

Enabled nullable reference types for better null safety. Added a
Unicode category filter to `CharacterMap` via the new
`ShowUnicodeCategory` property and `OptionSelector`. Updated
rendering logic to dynamically manage visible rows based on the
filter, improving performance and usability.

Refactored menu items to include the Unicode category selector.
Enhanced `TextView` context menu handling to support mouse-based
positioning. Performed miscellaneous code cleanup and added
comments for improved readability and maintainability.

* Fix Unicode rendering and simplify CombiningMarks

Updated `RuneExtensions.GetColumns` to handle specific Unicode glyphs (I Ching symbols) rendered as double-width in Windows Terminal, despite being single-width per Unicode. Added a workaround to return `2` for these glyphs and fallback to `UnicodeCalculator.GetWidth` for others.

Simplified `CombiningMarks` by removing examples for Unicode characters `\u0600` and `\u0301`, streamlining the scenario.

Referenced PR #4255 for context on the workaround.

* Update RuneTests with new Unicode test cases and fixes

Added new test cases for Unicode characters U+d7b0 (ힰ) and
U+f61e () with expected parameters. Updated the test case
for U+4dc0 (䷀) to adjust the second parameter from 1 to 2
and added references to the Microsoft Terminal Unicode
width overrides file and GitHub issue #19389. Existing test
cases for other Unicode characters remain unchanged.

* Update Terminal.Gui/Views/CharMap/CharMap.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update comments in GetColumns method for clarity

Updated comments in the `GetColumns` method of the `RuneExtensions` class to replace "HACK" with "TODO" and reference issue #4259 instead of pull request #4255. This change clarifies that the code is a temporary measure and should be removed once the issue is resolved. No functional changes were made to the code logic.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Tig
2025-09-27 20:26:44 +01:00
committed by GitHub
parent b3aa9c5717
commit 4c6145cee9
7 changed files with 725 additions and 376 deletions

View File

@@ -1,9 +1,6 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace UICatalog.Scenarios;
@@ -24,6 +21,33 @@ public class CharacterMap : Scenario
private Label? _errorLabel;
private TableView? _categoryList;
private CharMap? _charMap;
private OptionSelector? _unicodeCategorySelector;
public override List<Key> GetDemoKeyStrokes ()
{
List<Key> keys = new ();
for (var i = 0; i < 200; i++)
{
keys.Add (Key.CursorDown);
}
// Category table
keys.Add (Key.Tab.WithShift);
// Block elements
keys.Add (Key.B);
keys.Add (Key.L);
keys.Add (Key.Tab);
for (var i = 0; i < 200; i++)
{
keys.Add (Key.CursorLeft);
}
return keys;
}
// Don't create a Window, just return the top-level view
public override void Main ()
@@ -39,9 +63,9 @@ public class CharacterMap : Scenario
{
X = 0,
Y = 1,
Height = Dim.Fill (),
// SchemeName = "Base"
Height = Dim.Fill ()
// SchemeName = "Base"
};
top.Add (_charMap);
@@ -50,7 +74,8 @@ public class CharacterMap : Scenario
X = Pos.Right (_charMap) + 1,
Y = Pos.Y (_charMap),
HotKeySpecifier = (Rune)'_',
Text = "_Jump To:",
Text = "_Jump To:"
//SchemeName = "Dialog"
};
top.Add (jumpLabel);
@@ -60,7 +85,8 @@ public class CharacterMap : Scenario
X = Pos.Right (jumpLabel) + 1,
Y = Pos.Y (_charMap),
Width = 17,
Caption = "e.g. 01BE3 or ✈",
Caption = "e.g. 01BE3 or ✈"
//SchemeName = "Dialog"
};
top.Add (jumpEdit);
@@ -89,10 +115,12 @@ public class CharacterMap : Scenario
jumpEdit.Accepting += JumpEditOnAccept;
_categoryList = new () {
X = Pos.Right (_charMap),
Y = Pos.Bottom (jumpLabel),
Height = Dim.Fill (),
_categoryList = new ()
{
X = Pos.Right (_charMap),
Y = Pos.Bottom (jumpLabel),
Height = Dim.Fill ()
//SchemeName = "Dialog"
};
_categoryList.FullRowSelect = true;
@@ -165,7 +193,7 @@ public class CharacterMap : Scenario
),
new (
"_Options",
new MenuItemv2 [] { CreateMenuShowWidth () }
[CreateMenuShowWidth (), CreateMenuUnicodeCategorySelector ()]
)
]
};
@@ -317,6 +345,7 @@ public class CharacterMap : Scenario
CheckedState = _charMap!.ShowGlyphWidths ? CheckState.Checked : CheckState.None
};
var item = new MenuItemv2 { CommandView = cb };
item.Action += () =>
{
if (_charMap is { })
@@ -328,29 +357,48 @@ public class CharacterMap : Scenario
return item;
}
public override List<Key> GetDemoKeyStrokes ()
private MenuItemv2 CreateMenuUnicodeCategorySelector ()
{
List<Key> keys = new ();
// First option is "All" (no filter), followed by all UnicodeCategory names
string [] allCategoryNames = Enum.GetNames<UnicodeCategory> ();
var options = new string [allCategoryNames.Length + 1];
options [0] = "All";
Array.Copy (allCategoryNames, 0, options, 1, allCategoryNames.Length);
for (var i = 0; i < 200; i++)
// TODO: When #4126 is merged update this to use OptionSelector<UnicodeCategory?>
var selector = new OptionSelector
{
keys.Add (Key.CursorDown);
}
AssignHotKeysToCheckBoxes = true,
Options = options
};
// Category table
keys.Add (Key.Tab.WithShift);
_unicodeCategorySelector = selector;
// Block elements
keys.Add (Key.B);
keys.Add (Key.L);
// Default to "All"
selector.SelectedItem = 0;
_charMap!.ShowUnicodeCategory = null;
keys.Add (Key.Tab);
selector.SelectedItemChanged += (s, e) =>
{
int? idx = selector.SelectedItem;
for (var i = 0; i < 200; i++)
{
keys.Add (Key.CursorLeft);
}
if (idx is null)
{
return;
}
return keys;
if (idx.Value == 0)
{
_charMap.ShowUnicodeCategory = null;
}
else
{
// Map index to UnicodeCategory (offset by 1 because 0 is "All")
UnicodeCategory cat = Enum.GetValues<UnicodeCategory> () [idx.Value - 1];
_charMap.ShowUnicodeCategory = cat;
}
};
return new() { CommandView = selector };
}
}

View File

@@ -11,22 +11,78 @@ public class CombiningMarks : Scenario
var top = new Toplevel ();
top.DrawComplete += (s, e) =>
{
top.Move (0, 0);
top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
top.Move (0, 2);
top.AddStr ("\u0301\u0301\u0328<- \"\\u301\\u301\\u328]\" using AddStr.");
top.Move (0, 3);
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u301\\u301\\u328]\" using AddStr.");
top.Move (0, 4);
top.AddRune ('[');
top.AddRune ('a');
top.AddRune ('\u0301');
top.AddRune ('\u0301');
top.AddRune ('\u0328');
top.AddRune (']');
top.AddStr ("<- \"[a\\u301\\u301\\u328]\" using AddRune for each.");
};
{
// Forces reset _lineColsOffset because we're dealing with direct draw
Application.ClearScreenNextIteration = true;
var i = -1;
top.AddStr ("Terminal.Gui only supports combining marks that normalize. See Issue #2616.");
top.Move (0, ++i);
top.AddStr ("\u0301<- \"\\u0301\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\u0301]<- \"[\\u0301]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[ \u0301]<- \"[ \\u0301]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\u0301 ]<- \"[\\u0301 ]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("\u0301\u0301\u0328<- \"\\u0301\\u0301\\u0328\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\u0301\u0301\u0328]<- \"[\\u0301\\u0301\\u0328]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
top.Move (0, ++i);
top.AddRune ('[');
top.AddRune ('a');
top.AddRune ('\u0301');
top.AddRune ('\u0301');
top.AddRune ('\u0328');
top.AddRune (']');
top.AddStr ("<- \"[a\\u0301\\u0301\\u0328]\" using AddRune for each.");
top.Move (0, ++i);
top.AddStr ("[a\u0301\u0301\u0328]<- \"[a\\u0301\\u0301\\u0328]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("\u00ad<- \"\\u00ad\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\u00ad]<- \"[\\u00ad]\" using AddStr.");
top.Move (0, ++i);
top.AddRune ('[');
top.AddRune ('\u00ad');
top.AddRune (']');
top.AddStr ("<- \"[\\u00ad]\" using AddRune for each.");
i++;
top.Move (0, ++i);
top.AddStr ("From now on we are using TextFormatter");
TextFormatter tf = new () { Text = "[e\u0301\u0301\u0328]<- \"[e\\u0301\\u0301\\u0328]\" using TextFormatter." };
tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal));
tf.Text = "[e\u0328\u0301]<- \"[e\\u0328\\u0301]\" using TextFormatter.";
tf.Draw (new (0, ++i, tf.Text.Length, 1), top.GetAttributeForRole (VisualRole.Normal), top.GetAttributeForRole (VisualRole.Normal));
i++;
top.Move (0, ++i);
top.AddStr ("From now on we are using Surrogate pairs with combining diacritics");
top.Move (0, ++i);
top.AddStr ("[\ud835\udc4b\u0302]<- \"[\\ud835\\udc4b\\u0302]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\ud83d\udc68\ud83e\uddd2]<- \"[\\ud83d\\udc68\\ud83e\\uddd2]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("\u200d<- \"\\u200d\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\u200d]<- \"[\\u200d]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\ud83d\udc68\u200d\ud83e\uddd2]<- \"[\\ud83d\\udc68\\u200d\\ud83e\\uddd2]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\U0001F469\U0001F9D2]<- \"[\\U0001F469\\U0001F9D2]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\U0001F468\U0001F469\U0001F9D2]<- \"[\\U0001F468\\U0001F469\\U0001F9D2]\" using AddStr.");
top.Move (0, ++i);
top.AddStr ("[\U0001F468\u200D\U0001F469\u200D\U0001F9D2]<- \"[\\U0001F468\\u200D\\U0001F469\\u200D\\U0001F9D2]\" using AddStr.");
};
Application.Run (top);
top.Dispose ();