Skip to content

fix(TextPrompt): support non-ASCII characters in editable default values - #2159

Open
danjourno-dev wants to merge 2 commits into
spectreconsole:mainfrom
danjourno-dev:fix/2132
Open

fix(TextPrompt): support non-ASCII characters in editable default values#2159
danjourno-dev wants to merge 2 commits into
spectreconsole:mainfrom
danjourno-dev:fix/2132

Conversation

@danjourno-dev

Copy link
Copy Markdown

Fixes #2132

  • I have read the Contribution Guidelines
  • I have checked that there isn't already another pull request that solves the above issue
  • All newly added code is adequately covered by tests
  • All existing tests are still running without errors

Claude Code assisted with designing a proper fix. Once reviewed, I applied the fix and created a local console app to manually test the fix. The results of that test are...

Non-ASCII default (press Enter to accept, or edit): (ㅎ): ㅎ
Got: ㅎ
Wide char default (press Enter to accept, or edit): (中文): 中文
Got: 中文
Mixed default (try backspace): (hello ㅎ): hello ㅎ
Got: hello ㅎ

Note: if you are testing this, you may need to add the following into your console app
Console.OutputEncoding = System.Text.Encoding.UTF8;

Changes

ConsoleKeyInfo constructor throws ArgumentOutOfRangeException when the ConsoleKey value exceeds 255. Non-ASCII chars (e.g. Korean, CJK) have code points above 255, so casting directly to ConsoleKey was invalid.

Also fixes two related wide-character display bugs:

  • Autocomplete erase used char count instead of cell width, leaving ghost characters on screen for double-width glyphs
  • Backspace in secret mode over-erased cells for wide chars; now erases one cell per mask character regardless of source char width
  • Backspace now handles surrogate pairs correctly instead of splitting them

Please upvote 👍 this pull request if you are interested in it.

ConsoleKeyInfo constructor throws ArgumentOutOfRangeException when the
ConsoleKey value exceeds 255. Non-ASCII chars (e.g. Korean, CJK) have
code points above 255, so casting directly to ConsoleKey was invalid.

Also fixes two related wide-character display bugs:
- Autocomplete erase used char count instead of cell width, leaving
  ghost characters on screen for double-width glyphs
- Backspace in secret mode over-erased cells for wide chars; now erases
  one cell per mask character regardless of source char width
- Backspace now handles surrogate pairs correctly instead of splitting
  them

Closes spectreconsole#2132
@danjourno-dev

Copy link
Copy Markdown
Author

@danjourno-dev please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree

Comment thread src/Spectre.Console/Extensions/AnsiConsoleExtensions.Input.cs
@github-actions github-actions Bot added the ⭐ top pull request Top pull request. label Jul 7, 2026
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
[Fact]
public void Should_Backspace_Remove_Surrogate_Pair_As_Single_Rune_Leaving_Preceding_Char()
{
// Given: "a😀" — 'a' then emoji (surrogate pair 😀)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Given: "a😀" — 'a' then emoji (surrogate pair 😀)
// Given

// When
var result = console.Prompt(new TextPrompt<string>("Enter:"));

// Then: both surrogate chars removed as one rune; 'a' remains

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Then: both surrogate chars removed as one rune; 'a' remains
// Then

[Fact]
public void Should_Backspace_Remove_Lone_Emoji_Leaving_Empty_String()
{
// Given: only an emoji (surrogate pair); AllowEmpty so prompt accepts ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Given: only an emoji (surrogate pair); AllowEmpty so prompt accepts ""
// Given

// When
var result = console.Prompt(new TextPrompt<string>("Enter:").AllowEmpty());

// Then: both surrogate chars removed, nothing left

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Then: both surrogate chars removed, nothing left
// Then

[Expectation("SecretValueBackspaceWideChar")]
public Task Should_Erase_One_Mask_Cell_Per_Backspace_For_Wide_Char_In_Secret_Mode()
{
// Given: type '한' (double-width CJK), backspace, enter — in secret mode

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Given: type '한' (double-width CJK), backspace, enter — in secret mode
// Given

[Fact]
public void Should_Backspace_Through_Mixed_Width_Sequence_Peeling_One_Rune_At_A_Time()
{
// Given: "a한😀" — ASCII (1 cell) + CJK (1 char, 2 cells) + emoji (surrogate pair)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Given: "a한😀" — ASCII (1 cell) + CJK (1 char, 2 cells) + emoji (surrogate pair)
// Given

var console = new TestConsole();
console.Input.PushKey(new ConsoleKeyInfo('a', ConsoleKey.A, false, false, false));
console.Input.PushKey(new ConsoleKeyInfo('한', ConsoleKey.Packet, false, false, false));
// 😀 = U+1F600 = surrogate pair 😀

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 😀 = U+1F600 = surrogate pair 😀

// 😀 = U+1F600 = surrogate pair 😀
console.Input.PushKey(new ConsoleKeyInfo('\uD83D', ConsoleKey.Packet, false, false, false));
console.Input.PushKey(new ConsoleKeyInfo('\uDE00', ConsoleKey.Packet, false, false, false));
console.Input.PushKey(ConsoleKey.Backspace); // removes emoji surrogate pair as one rune

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.Input.PushKey(ConsoleKey.Backspace); // removes emoji surrogate pair as one rune
console.Input.PushKey(ConsoleKey.Backspace);

console.Input.PushKey(new ConsoleKeyInfo('\uD83D', ConsoleKey.Packet, false, false, false));
console.Input.PushKey(new ConsoleKeyInfo('\uDE00', ConsoleKey.Packet, false, false, false));
console.Input.PushKey(ConsoleKey.Backspace); // removes emoji surrogate pair as one rune
console.Input.PushKey(ConsoleKey.Backspace); // removes '한'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.Input.PushKey(ConsoleKey.Backspace); // removes '한'
console.Input.PushKey(ConsoleKey.Backspace);

// When
var result = console.Prompt(new TextPrompt<string>("Enter:"));

// Then: 'a' remains; emoji and CJK each removed as single runes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Then: 'a' remains; emoji and CJK each removed as single runes
// Then

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⭐ top pull request Top pull request.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TextPrompt throws ArgumentOutOfRangeException when DefaultValue() contains non-ASCII chars

3 participants