Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 14 additions & 2 deletions PSReadLine/Render.Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ internal static int LengthInBufferCells(string str, int start, int end)
for (var i = start; i < end; i++)
{
var c = str[i];
if (c == 0x1b && (i+1) < end && str[i+1] == '[')
if ((i + 1) < end && char.IsSurrogatePair(c, str[i + 1]))
{
sum++;
i++; // Skip the low surrogate
continue;
}
else if (c == 0x1b && (i + 1) < end && str[i + 1] == '[')
{
// Simple escape sequence skipping
i += 2;
Expand All @@ -77,7 +83,13 @@ internal static int LengthInBufferCells(StringBuilder sb, int start, int end)
for (var i = start; i < end; i++)
{
var c = sb[i];
if (c == 0x1b && (i + 1) < end && sb[i + 1] == '[')
if ((i + 1) < end && char.IsSurrogatePair(c, sb[i + 1]))
{
sum++;
i++; // Skip the low surrogate
continue;
}
else if (c == 0x1b && (i + 1) < end && sb[i + 1] == '[')
{
// Simple escape sequence skipping
i += 2;
Expand Down
68 changes: 68 additions & 0 deletions test/RenderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -340,5 +340,73 @@ function prompt {
Tuple.Create(ConsoleColor.Blue, ConsoleColor.Magenta), "PSREADLINE>",
TokenClassification.Command, "dir"))));
}

[SkippableFact]
public void LengthInBufferCells_SurrogatePair()
{
// 👉 is U+1F449, encoded as surrogate pair \uD83D\uDC49
// A surrogate pair should count as 1 buffer cell.
Assert.Equal(1, PSConsoleReadLine.LengthInBufferCells("\uD83D\uDC49"));
}

[SkippableFact]
public void LengthInBufferCells_MultipleSurrogatePairs()
{
// "👉❌" = two surrogate pairs, should be 2 buffer cells
Assert.Equal(2, PSConsoleReadLine.LengthInBufferCells("👉❌"));
}

[SkippableFact]
public void LengthInBufferCells_SurrogatePairWithText()
{
// "👉 " = surrogate pair + space = 2 buffer cells
Assert.Equal(2, PSConsoleReadLine.LengthInBufferCells("👉 "));
// "❌ " = surrogate pair + space = 2 buffer cells
Assert.Equal(2, PSConsoleReadLine.LengthInBufferCells("❌ "));
// "abc👉def" = 3 + 1 + 3 = 7
Assert.Equal(7, PSConsoleReadLine.LengthInBufferCells("abc👉def"));
}

[SkippableFact]
public void LengthInBufferCells_SurrogatePairWithEscapeSequence()
{
// ESC[31m (red color) + surrogate pair + ESC[0m (reset)
// Only the surrogate pair should count: 1 buffer cell
Assert.Equal(1, PSConsoleReadLine.LengthInBufferCells("\x1b[31m\uD83D\uDC49\x1b[0m"));
}

[SkippableFact]
public void LengthInBufferCells_NerdFontGlyph()
{
// U+F015A (nf-md-home) = \uDB80\uDD5A - a supplementary character in the private use area
// "╰󰅚 " = 1 + 1 + 1 = 3 buffer cells (the issue reported this as 4 before the fix)
Assert.Equal(3, PSConsoleReadLine.LengthInBufferCells("╰\uDB80\uDD5A "));
}

[SkippableFact]
public void LengthInBufferCells_LoneHighSurrogate()
{
// A lone high surrogate without a following low surrogate should still
// be handled without error (falls through to LengthInBufferCells(char))
var str = "a\uD83Db";
int expected = 1 + PSConsoleReadLine.LengthInBufferCells('\uD83D') + 1;
Assert.Equal(expected, PSConsoleReadLine.LengthInBufferCells(str));
}

[SkippableFact]
public void LengthInBufferCells_StringBuilder_SurrogatePair()
{
var sb = new StringBuilder("👉 ");
// surrogate pair + space = 2 buffer cells
Assert.Equal(2, PSConsoleReadLine.LengthInBufferCells(sb, 0, sb.Length));
}

[SkippableFact]
public void LengthInBufferCells_StringBuilder_SurrogatePairSubstring()
{
var sb = new StringBuilder("abc👉def");
// Just the surrogate pair portion (indices 3 and 4) = 1 buffer cell
Assert.Equal(1, PSConsoleReadLine.LengthInBufferCells(sb, 3, 5));
}
}
}