@@ -15,6 +15,7 @@ namespace SharpConsoleUI.Helpers
1515 public static class TerminalCapabilities
1616 {
1717 private static bool ? _supportsVS16Widening ;
18+ private static bool ? _supportsUnicode16Widths ;
1819
1920 /// <summary>
2021 /// Whether the terminal renders emoji+VS16 (U+FE0F) as 2 columns.
@@ -27,8 +28,19 @@ public static bool SupportsVS16Widening
2728 }
2829
2930 /// <summary>
30- /// Probes the terminal to determine if VS16 (U+FE0F) widens emoji.
31- /// Writes a test character, queries cursor position via DSR, and compares.
31+ /// Whether the terminal renders Unicode 16.0 newly-widened characters
32+ /// (e.g. U+2630 ☰ trigrams) as 2 columns.
33+ /// When false, these characters are treated as width 1 (Unicode 15.0 behavior).
34+ /// Defaults to false (most terminals haven't adopted Unicode 16.0 widths yet).
35+ /// </summary>
36+ public static bool SupportsUnicode16Widths
37+ {
38+ get => _supportsUnicode16Widths ?? false ;
39+ }
40+
41+ /// <summary>
42+ /// Probes the terminal to determine rendering capabilities.
43+ /// Tests VS16 emoji widening and Unicode 16.0 width changes.
3244 /// Must be called after raw mode is entered and before input loops start.
3345 /// </summary>
3446 /// <param name="write">Action to write escape sequences to the terminal.</param>
@@ -45,6 +57,16 @@ public static void Probe(Action<string> write, Func<int> readByte)
4557 // If probing fails, assume modern terminal
4658 _supportsVS16Widening = true ;
4759 }
60+
61+ try
62+ {
63+ _supportsUnicode16Widths = ProbeUnicode16Width ( write , readByte ) ;
64+ }
65+ catch
66+ {
67+ // If probing fails, assume terminal hasn't adopted Unicode 16.0 widths
68+ _supportsUnicode16Widths = false ;
69+ }
4870 }
4971
5072 /// <summary>
@@ -56,12 +78,22 @@ public static void SetVS16Widening(bool supported)
5678 _supportsVS16Widening = supported ;
5779 }
5880
81+ /// <summary>
82+ /// Allows manual override of the Unicode 16.0 width capability.
83+ /// Useful for testing or when the terminal is known ahead of time.
84+ /// </summary>
85+ public static void SetUnicode16Widths ( bool supported )
86+ {
87+ _supportsUnicode16Widths = supported ;
88+ }
89+
5990 /// <summary>
6091 /// Resets all cached capabilities (for testing).
6192 /// </summary>
6293 internal static void Reset ( )
6394 {
6495 _supportsVS16Widening = null ;
96+ _supportsUnicode16Widths = null ;
6597 }
6698
6799 private static bool ProbeVS16 ( Action < string > write , Func < int > readByte )
@@ -90,6 +122,28 @@ private static bool ProbeVS16(Action<string> write, Func<int> readByte)
90122 return col >= 3 ; // col is 1-based; 3 means cursor at column 3 → char was 2 wide
91123 }
92124
125+ /// <summary>
126+ /// Probes whether the terminal renders Unicode 16.0 newly-widened characters as 2 columns.
127+ /// Tests U+2630 (☰ TRIGRAM FOR HEAVEN), which changed from width 1 to 2 in Unicode 16.0.
128+ /// </summary>
129+ private static bool ProbeUnicode16Width ( Action < string > write , Func < int > readByte )
130+ {
131+ // Write ☰ (U+2630) and query cursor position.
132+ // Unicode 15.0: width 1 → cursor at column 2
133+ // Unicode 16.0: width 2 → cursor at column 3
134+ write ( "\r \u2630 \x1b [6n" ) ;
135+
136+ int col = ReadDSRColumn ( readByte ) ;
137+
138+ // Clean up probe output
139+ write ( "\r \x1b [K" ) ;
140+
141+ if ( col < 0 )
142+ return false ; // Timeout/error → assume pre-Unicode 16.0
143+
144+ return col >= 3 ; // col 3 means 2-wide rendering (Unicode 16.0)
145+ }
146+
93147 /// <summary>
94148 /// Reads a DSR (Device Status Report) response and extracts the column number.
95149 /// Expected format: ESC [ row ; col R
0 commit comments