Skip to content

Commit 22ceb03

Browse files
authored
Handle the dead key on Windows properly (#933)
1 parent 49acd1d commit 22ceb03

File tree

4 files changed

+54
-6
lines changed

4 files changed

+54
-6
lines changed

PSReadLine/Keys.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public static extern int ToUnicode(
125125

126126
static readonly ThreadLocal<char[]> toUnicodeBuffer = new ThreadLocal<char[]>(() => new char[2]);
127127
static readonly ThreadLocal<byte[]> toUnicodeStateBuffer = new ThreadLocal<byte[]>(() => new byte[256]);
128-
internal static void TryGetCharFromConsoleKey(ConsoleKeyInfo key, ref char result)
128+
internal static void TryGetCharFromConsoleKey(ConsoleKeyInfo key, ref char result, ref bool isDeadKey)
129129
{
130130
var modifiers = key.Modifiers;
131131
var virtualKey = key.Key;
@@ -149,11 +149,20 @@ internal static void TryGetCharFromConsoleKey(ConsoleKeyInfo key, ref char resul
149149
}
150150
int charCount = ToUnicode(virtualKey, scanCode, state, chars, chars.Length, flags);
151151

152-
// TODO: support diacriticals (charCount == 2)
153152
if (charCount == 1)
154153
{
155154
result = chars[0];
156155
}
156+
else if (charCount == -1 || charCount >=2)
157+
{
158+
// Quoted from https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-tounicode#return-value:
159+
// "Return value -1 --
160+
// The specified virtual key is a dead-key character (accent or diacritic).
161+
// Return value >=2 --
162+
// Two or more characters were written to the buffer specified by pwszBuff. The most common cause for this is that a dead-key character
163+
// (accent or diacritic) stored in the keyboard layout could not be combined with the specified virtual key to form a single character."
164+
isDeadKey = true;
165+
}
157166
}
158167

159168
static readonly ThreadLocal<StringBuilder> keyInfoStringBuilder = new ThreadLocal<StringBuilder>(() => new StringBuilder());
@@ -217,6 +226,7 @@ void AppendPart(string str)
217226
}
218227

219228
var c = key.KeyChar;
229+
var isDeadKey = false;
220230
if (char.IsControl(c) )
221231
{
222232
// We have the virtual key code and Windows has a handy api to map that to the non-control
@@ -226,7 +236,7 @@ void AppendPart(string str)
226236
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
227237
{
228238
var keySansControl = new ConsoleKeyInfo(key.KeyChar, key.Key, isShift, isAlt, control: false);
229-
TryGetCharFromConsoleKey(keySansControl, ref c);
239+
TryGetCharFromConsoleKey(keySansControl, ref c, ref isDeadKey);
230240
}
231241
}
232242
else if (isAlt && isCtrl)
@@ -268,8 +278,13 @@ void AppendPart(string str)
268278
break;
269279

270280
case '\0':
271-
// This is ugly but familiar.
272-
s = "@";
281+
// This could be a dead key for a particular keyboard layout in Windows console.
282+
// The dead key is not an issue when there is tty involved, so on non-Windows, `isDeadKey` is always false.
283+
//
284+
// When we believe it's a dead key, we use the text form of the virtual key so the resulted PSKeyInfo can be
285+
// converted back to ConsoleKeyInfo correctly later on, and be properly ignored during rendering.
286+
// Otherwise, we use `@` in case `key.KeyChar = '\0'`. This is ugly but familiar.
287+
s = isDeadKey ? key.Key.ToString() : "@";
273288
break;
274289

275290
case char _ when (c >= 1 && c <= 26):

test/DeadKeyTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace Test
5+
{
6+
public partial class ReadLine
7+
{
8+
[SkippableFact]
9+
public void DeadKeyShouldBeIgnored()
10+
{
11+
Skip.If(this.Fixture.Lang != "fr-FR", "The dead key test requires Keyboard layout to be set to 'fr-FR'");
12+
TestSetup(KeyMode.Cmd);
13+
14+
Test("aa", Keys("aa", _.DeadKey_Caret));
15+
Test("aab", Keys("aa", _.DeadKey_Caret, 'b'));
16+
Test("aaâ", Keys("aa", _.DeadKey_Caret_A));
17+
}
18+
}
19+
}

test/KeyInfo-fr-FR-windows.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,5 +1636,19 @@
16361636
"ConsoleKey": "Z",
16371637
"Modifiers": "0",
16381638
"Investigate": false
1639+
},
1640+
{
1641+
"Key": "DeadKey_Caret",
1642+
"KeyChar": "\u0000",
1643+
"ConsoleKey": "Oem6",
1644+
"Modifiers": "0",
1645+
"Investigate": false
1646+
},
1647+
{
1648+
"Key": "DeadKey_Caret+A",
1649+
"KeyChar": "\u00e2",
1650+
"ConsoleKey": "A",
1651+
"Modifiers": "0",
1652+
"Investigate": false
16391653
}
16401654
]

test/KeyboardLayouts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public string KeyAsPropertyName()
8787
}
8888
var key = (alt != null)
8989
? Key.Substring(0, Key.Length - 1) + alt
90-
: Key;
90+
: Key;
9191
return key.Replace('+', '_');
9292
}
9393

0 commit comments

Comments
 (0)