Fixes #4259. Our wcwidth library is out of date (#4281)

* Update package versions and remove hack code from RuneExtensions

Updated several package versions in `Directory.Packages.props`, including `JetBrains.Annotations`, `Microsoft.Extensions.Logging.Abstractions`, `System.IO.Abstractions`, and `Wcwidth`.

Refactored methods in `RuneExtensions.cs`:
- Simplified `GetColumns` by removing special Unicode handling.
- Renamed constants to follow naming conventions.
- Improved logic and readability in `DecodeSurrogatePair`, `Encode`, and `GetEncodingLength`.
- Streamlined `IsSurrogatePair` and `MakePrintable` for clarity and efficiency.

* Update package version ranges for flexibility

Updated the `JetBrains.Annotations` package to use a version range
starting from `2025.2.2` to allow future updates. Adjusted the
`Microsoft.Extensions.Logging.Abstractions` package to a version
range `[9.0.0,10)` for compatibility. Changed `System.IO.Abstractions`
to a range `[22.0.16,23)` and `Wcwidth` to `[3.0.0,)` to enable
future updates within specified ranges.
This commit is contained in:
Tig
2025-10-15 11:54:21 -06:00
committed by GitHub
parent c8147416b2
commit b83bcc2fdb
2 changed files with 34 additions and 37 deletions

View File

@@ -11,14 +11,14 @@
<PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" /> <PackageVersion Include="Microsoft.Net.Compilers.Toolset" Version="4.11.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="[8,9)" />
<PackageVersion Include="ColorHelper" Version="[1.8.1,2)" /> <PackageVersion Include="ColorHelper" Version="[1.8.1,2)" />
<PackageVersion Include="JetBrains.Annotations" Version="[2024.3.0,)" /> <PackageVersion Include="JetBrains.Annotations" Version="[2025.2.2,)" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" /> <PackageVersion Include="Microsoft.CodeAnalysis" Version="4.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.11.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.2,10)" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="[9.0.0,10)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageVersion Include="System.IO.Abstractions" Version="[22.0.11,23)" /> <PackageVersion Include="System.IO.Abstractions" Version="[22.0.16,23)" />
<PackageVersion Include="Wcwidth" Version="[2,3)" /> <PackageVersion Include="Wcwidth" Version="[3.0.0,)" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" /> <PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21.2,2)" />
<PackageVersion Include="Serilog" Version="4.2.0" /> <PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" /> <PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.0" />

View File

@@ -37,22 +37,27 @@ public static class RuneExtensions
public static bool DecodeSurrogatePair (this Rune rune, out char []? chars) public static bool DecodeSurrogatePair (this Rune rune, out char []? chars)
{ {
bool isSingleUtf16CodeUnit = rune.IsBmp; bool isSingleUtf16CodeUnit = rune.IsBmp;
if (isSingleUtf16CodeUnit) if (isSingleUtf16CodeUnit)
{ {
chars = null; chars = null;
return false; return false;
} }
const int maxCharsPerRune = 2; const int MAX_CHARS_PER_RUNE = 2;
Span<char> charBuffer = stackalloc char[maxCharsPerRune]; Span<char> charBuffer = stackalloc char [MAX_CHARS_PER_RUNE];
int charsWritten = rune.EncodeToUtf16 (charBuffer); int charsWritten = rune.EncodeToUtf16 (charBuffer);
if (charsWritten >= 2 && char.IsSurrogatePair (charBuffer [0], charBuffer [1])) if (charsWritten >= 2 && char.IsSurrogatePair (charBuffer [0], charBuffer [1]))
{ {
chars = charBuffer [..charsWritten].ToArray (); chars = charBuffer [..charsWritten].ToArray ();
return true; return true;
} }
chars = null; chars = null;
return false; return false;
} }
@@ -65,23 +70,26 @@ public static class RuneExtensions
/// <returns>he number of bytes written into the destination buffer.</returns> /// <returns>he number of bytes written into the destination buffer.</returns>
public static int Encode (this Rune rune, byte [] dest, int start = 0, int count = -1) public static int Encode (this Rune rune, byte [] dest, int start = 0, int count = -1)
{ {
const int maxUtf8BytesPerRune = 4; const int MAX_UTF8_BYTES_PER_RUNE = 4;
Span<byte> bytes = stackalloc byte[maxUtf8BytesPerRune]; Span<byte> bytes = stackalloc byte [MAX_UTF8_BYTES_PER_RUNE];
int writtenBytes = rune.EncodeToUtf8 (bytes); int writtenBytes = rune.EncodeToUtf8 (bytes);
int bytesToCopy = count == -1 int bytesToCopy = count == -1
? writtenBytes ? writtenBytes
: Math.Min (count, writtenBytes); : Math.Min (count, writtenBytes);
int bytesWritten = 0; var bytesWritten = 0;
for (int i = 0; i < bytesToCopy; i++)
for (var i = 0; i < bytesToCopy; i++)
{ {
if (bytes [i] == '\0') if (bytes [i] == '\0')
{ {
break; break;
} }
dest [start + i] = bytes [i]; dest [start + i] = bytes [i];
bytesWritten++; bytesWritten++;
} }
return bytesWritten; return bytesWritten;
} }
@@ -111,22 +119,7 @@ public static class RuneExtensions
/// The number of columns required to fit the rune, 0 if the argument is the null character, or -1 if the value is /// The number of columns required to fit the rune, 0 if the argument is the null character, or -1 if the value is
/// not printable, otherwise the number of columns that the rune occupies. /// not printable, otherwise the number of columns that the rune occupies.
/// </returns> /// </returns>
public static int GetColumns (this Rune rune) public static int GetColumns (this Rune rune) { return UnicodeCalculator.GetWidth (rune); }
{
int value = rune.Value;
// TODO: Remove this code when #4259 is fixed
// TODO: See https://github.com/gui-cs/Terminal.Gui/issues/4259
if (value is >= 0x2630 and <= 0x2637 || // Trigrams
value is >= 0x268A and <= 0x268F || // Monograms/Digrams
value is >= 0x4DC0 and <= 0x4DFF) // Hexagrams
{
return 2; // Assume double-width due to Windows Terminal font rendering
}
// Fallback to original GetWidth for other code points
return UnicodeCalculator.GetWidth (rune);
}
/// <summary>Get number of bytes required to encode the rune, based on the provided encoding.</summary> /// <summary>Get number of bytes required to encode the rune, based on the provided encoding.</summary>
/// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks> /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
@@ -137,21 +130,23 @@ public static class RuneExtensions
{ {
encoding ??= Encoding.UTF8; encoding ??= Encoding.UTF8;
const int maxCharsPerRune = 2; const int MAX_CHARS_PER_RUNE = 2;
// Get characters with UTF16 to keep that part independent of selected encoding. // Get characters with UTF16 to keep that part independent of selected encoding.
Span<char> charBuffer = stackalloc char[maxCharsPerRune]; Span<char> charBuffer = stackalloc char [MAX_CHARS_PER_RUNE];
int charsWritten = rune.EncodeToUtf16(charBuffer); int charsWritten = rune.EncodeToUtf16 (charBuffer);
Span<char> chars = charBuffer[..charsWritten]; Span<char> chars = charBuffer [..charsWritten];
int maxEncodedLength = encoding.GetMaxByteCount (charsWritten); int maxEncodedLength = encoding.GetMaxByteCount (charsWritten);
Span<byte> byteBuffer = stackalloc byte[maxEncodedLength]; Span<byte> byteBuffer = stackalloc byte [maxEncodedLength];
int bytesEncoded = encoding.GetBytes (chars, byteBuffer); int bytesEncoded = encoding.GetBytes (chars, byteBuffer);
ReadOnlySpan<byte> encodedBytes = byteBuffer[..bytesEncoded]; ReadOnlySpan<byte> encodedBytes = byteBuffer [..bytesEncoded];
if (encodedBytes [^1] == '\0') if (encodedBytes [^1] == '\0')
{ {
return encodedBytes.Length - 1; return encodedBytes.Length - 1;
} }
return encodedBytes.Length; return encodedBytes.Length;
} }
@@ -175,14 +170,16 @@ public static class RuneExtensions
public static bool IsSurrogatePair (this Rune rune) public static bool IsSurrogatePair (this Rune rune)
{ {
bool isSingleUtf16CodeUnit = rune.IsBmp; bool isSingleUtf16CodeUnit = rune.IsBmp;
if (isSingleUtf16CodeUnit) if (isSingleUtf16CodeUnit)
{ {
return false; return false;
} }
const int maxCharsPerRune = 2; const int MAX_CHARS_PER_RUNE = 2;
Span<char> charBuffer = stackalloc char[maxCharsPerRune]; Span<char> charBuffer = stackalloc char [MAX_CHARS_PER_RUNE];
int charsWritten = rune.EncodeToUtf16 (charBuffer); int charsWritten = rune.EncodeToUtf16 (charBuffer);
return charsWritten >= 2 && char.IsSurrogatePair (charBuffer [0], charBuffer [1]); return charsWritten >= 2 && char.IsSurrogatePair (charBuffer [0], charBuffer [1]);
} }
@@ -193,5 +190,5 @@ public static class RuneExtensions
/// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks> /// <remarks>This is a Terminal.Gui extension method to <see cref="System.Text.Rune"/> to support TUI text manipulation.</remarks>
/// <param name="rune"></param> /// <param name="rune"></param>
/// <returns></returns> /// <returns></returns>
public static Rune MakePrintable (this Rune rune) { return Rune.IsControl (rune) ? new Rune (rune.Value + 0x2400) : rune; } public static Rune MakePrintable (this Rune rune) { return Rune.IsControl (rune) ? new (rune.Value + 0x2400) : rune; }
} }