#nullable enable
using System.Buffers;
namespace Terminal.Gui;
/// Extensions to to support TUI text manipulation.
public static class StringExtensions
{
/// Unpacks the last UTF-8 encoding in the string.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to decode.
/// Index in string to stop at; if -1, use the buffer length.
///
public static (Rune rune, int size) DecodeLastRune (this string str, int end = -1)
{
Rune rune = str.EnumerateRunes ().ToArray () [end == -1 ? ^1 : end];
byte [] bytes = Encoding.UTF8.GetBytes (rune.ToString ());
OperationStatus operationStatus = Rune.DecodeFromUtf8 (bytes, out rune, out int bytesConsumed);
if (operationStatus == OperationStatus.Done)
{
return (rune, bytesConsumed);
}
return (Rune.ReplacementChar, 1);
}
/// Unpacks the first UTF-8 encoding in the string and returns the rune and its width in bytes.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to decode.
/// Starting offset.
/// Number of bytes in the buffer, or -1 to make it the length of the buffer.
///
public static (Rune Rune, int Size) DecodeRune (this string str, int start = 0, int count = -1)
{
Rune rune = str.EnumerateRunes ().ToArray () [start];
byte [] bytes = Encoding.UTF8.GetBytes (rune.ToString ());
if (count == -1)
{
count = bytes.Length;
}
OperationStatus operationStatus = Rune.DecodeFromUtf8 (bytes, out rune, out int bytesConsumed);
if (operationStatus == OperationStatus.Done && bytesConsumed >= count)
{
return (rune, bytesConsumed);
}
return (Rune.ReplacementChar, 1);
}
/// Gets the number of columns the string occupies in the terminal.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to measure.
///
public static int GetColumns (this string str) { return str is null ? 0 : str.EnumerateRunes ().Sum (r => Math.Max (r.GetColumns (), 0)); }
/// Gets the number of runes in the string.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to count.
///
public static int GetRuneCount (this string str) { return str.EnumerateRunes ().Count (); }
///
/// Determines if this of is composed entirely of ASCII
/// digits.
///
/// A of to check.
///
/// A indicating if all elements of the are ASCII digits (
/// ) or not (
///
public static bool IsAllAsciiDigits (this ReadOnlySpan stringSpan) { return stringSpan.ToString ().All (char.IsAsciiDigit); }
///
/// Determines if this of is composed entirely of ASCII
/// digits.
///
/// A of to check.
///
/// A indicating if all elements of the are ASCII digits (
/// ) or not (
///
public static bool IsAllAsciiHexDigits (this ReadOnlySpan stringSpan) { return stringSpan.ToString ().All (char.IsAsciiHexDigit); }
/// Repeats the string times.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The text to repeat.
/// Number of times to repeat the text.
/// The text repeated if is greater than zero, otherwise .
public static string? Repeat (this string str, int n)
{
if (n <= 0)
{
return null;
}
if (string.IsNullOrEmpty (str) || n == 1)
{
return str;
}
return new StringBuilder (str.Length * n)
.Insert (0, str, n)
.ToString ();
}
/// Converts the string into a .
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to convert.
///
public static List ToRuneList (this string str) { return str.EnumerateRunes ().ToList (); }
/// Converts the string into a array.
/// This is a Terminal.Gui extension method to to support TUI text manipulation.
/// The string to convert.
///
public static Rune [] ToRunes (this string str) { return str.EnumerateRunes ().ToArray (); }
/// Converts a generic collection into a string.
/// The enumerable rune to convert.
///
public static string ToString (IEnumerable runes)
{
const int maxCharsPerRune = 2;
// Max stackalloc ~2 kB
const int maxStackallocTextBufferSize = 1048;
Span runeBuffer = stackalloc char[maxCharsPerRune];
// Use stackalloc buffer if rune count is easily available and the count is reasonable.
if (runes.TryGetNonEnumeratedCount (out int count))
{
if (count == 0)
{
return string.Empty;
}
int maxRequiredTextBufferSize = count * maxCharsPerRune;
if (maxRequiredTextBufferSize <= maxStackallocTextBufferSize)
{
Span textBuffer = stackalloc char[maxRequiredTextBufferSize];
Span remainingBuffer = textBuffer;
foreach (Rune rune in runes)
{
int charsWritten = rune.EncodeToUtf16 (runeBuffer);
ReadOnlySpan runeChars = runeBuffer [..charsWritten];
runeChars.CopyTo (remainingBuffer);
remainingBuffer = remainingBuffer [runeChars.Length..];
}
ReadOnlySpan text = textBuffer[..^remainingBuffer.Length];
return text.ToString ();
}
}
// Fallback to StringBuilder append.
StringBuilder stringBuilder = new();
foreach (Rune rune in runes)
{
int charsWritten = rune.EncodeToUtf16 (runeBuffer);
ReadOnlySpan runeChars = runeBuffer [..charsWritten];
stringBuilder.Append (runeChars);
}
return stringBuilder.ToString ();
}
/// Converts a byte generic collection into a string in the provided encoding (default is UTF8)
/// The enumerable byte to convert.
/// The encoding to be used.
///
public static string ToString (IEnumerable bytes, Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
return encoding.GetString (bytes.ToArray ());
}
}