mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 04:36:39 +00:00
Merge pull request #1566 from PowerShell/andschwa/chars
Fix PSReadLine custom key binding on Linux
This commit is contained in:
commit
0513425ff9
4 files changed
+122
-42
No files matched your search
@@ -6,9 +6,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if UNIX
|
|
||||||
using System.Reflection;
|
|
||||||
#endif
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Microsoft.PowerShell.Internal;
|
using Microsoft.PowerShell.Internal;
|
||||||
|
|
||||||
@@ -56,6 +53,7 @@ namespace Microsoft.PowerShell
|
|||||||
Stack<string> tokens = null;
|
Stack<string> tokens = null;
|
||||||
ConsoleModifiers modifiers = 0;
|
ConsoleModifiers modifiers = 0;
|
||||||
ConsoleKey key = 0;
|
ConsoleKey key = 0;
|
||||||
|
char keyChar = '\u0000';
|
||||||
|
|
||||||
bool valid = !String.IsNullOrEmpty(sequence);
|
bool valid = !String.IsNullOrEmpty(sequence);
|
||||||
|
|
||||||
@@ -81,6 +79,9 @@ namespace Microsoft.PowerShell
|
|||||||
// key should be first token to be popped
|
// key should be first token to be popped
|
||||||
if (key == 0)
|
if (key == 0)
|
||||||
{
|
{
|
||||||
|
// the keyChar is this token
|
||||||
|
keyChar = token[0];
|
||||||
|
|
||||||
// Enum.TryParse accepts arbitrary integers. We shouldn't,
|
// Enum.TryParse accepts arbitrary integers. We shouldn't,
|
||||||
// but single digits need to map to the correct key, e.g.
|
// but single digits need to map to the correct key, e.g.
|
||||||
// ConsoleKey.D1
|
// ConsoleKey.D1
|
||||||
@@ -152,8 +153,6 @@ namespace Microsoft.PowerShell
|
|||||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, PSReadLineResources.InvalidSequence, sequence));
|
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, PSReadLineResources.InvalidSequence, sequence));
|
||||||
}
|
}
|
||||||
|
|
||||||
char keyChar = GetCharFromConsoleKey(key, modifiers);
|
|
||||||
|
|
||||||
return new ConsoleKeyInfo(keyChar, key,
|
return new ConsoleKeyInfo(keyChar, key,
|
||||||
shift: ((modifiers & ConsoleModifiers.Shift) != 0),
|
shift: ((modifiers & ConsoleModifiers.Shift) != 0),
|
||||||
alt: ((modifiers & ConsoleModifiers.Alt) != 0),
|
alt: ((modifiers & ConsoleModifiers.Alt) != 0),
|
||||||
@@ -164,6 +163,27 @@ namespace Microsoft.PowerShell
|
|||||||
{
|
{
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
|
||||||
|
#if UNIX
|
||||||
|
bool isShift;
|
||||||
|
bool isCtrl;
|
||||||
|
key = GetKeyFromCharValue(literal, out isShift, out isCtrl);
|
||||||
|
|
||||||
|
// Failure to get a key for the char just means that the key is not
|
||||||
|
// special (in the ConsoleKey enum), so we return a default key.
|
||||||
|
// Thus this never fails.
|
||||||
|
valid = true;
|
||||||
|
failReason = null;
|
||||||
|
|
||||||
|
if (isShift)
|
||||||
|
{
|
||||||
|
modifiers |= ConsoleModifiers.Shift;
|
||||||
|
}
|
||||||
|
if (isCtrl)
|
||||||
|
{
|
||||||
|
modifiers |= ConsoleModifiers.Control;
|
||||||
|
}
|
||||||
|
// alt is not possible to get
|
||||||
|
#else
|
||||||
// shift state will be in MSB
|
// shift state will be in MSB
|
||||||
short virtualKey = NativeMethods.VkKeyScan(literal);
|
short virtualKey = NativeMethods.VkKeyScan(literal);
|
||||||
int hresult = Marshal.GetLastWin32Error();
|
int hresult = Marshal.GetLastWin32Error();
|
||||||
@@ -206,30 +226,89 @@ namespace Microsoft.PowerShell
|
|||||||
Exception e = Marshal.GetExceptionForHR(hresult);
|
Exception e = Marshal.GetExceptionForHR(hresult);
|
||||||
failReason = e.Message;
|
failReason = e.Message;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNIX
|
||||||
|
// this is borrowed from the CoreFX internal System.IO.StdInReader class
|
||||||
|
// https://github.com/dotnet/corefx/blob/5b2ae6aa485773cd5569f56f446698633c9ad945/src/System.Console/src/System/IO/StdInReader.cs#L222
|
||||||
|
private static ConsoleKey GetKeyFromCharValue(char x, out bool isShift, out bool isCtrl)
|
||||||
|
{
|
||||||
|
isShift = false;
|
||||||
|
isCtrl = false;
|
||||||
|
|
||||||
|
switch (x)
|
||||||
|
{
|
||||||
|
case '\b':
|
||||||
|
return ConsoleKey.Backspace;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
return ConsoleKey.Tab;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
return ConsoleKey.Enter;
|
||||||
|
|
||||||
|
case (char)(0x1B):
|
||||||
|
return ConsoleKey.Escape;
|
||||||
|
|
||||||
|
case '*':
|
||||||
|
return ConsoleKey.Multiply;
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
return ConsoleKey.Add;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
return ConsoleKey.Subtract;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
return ConsoleKey.Divide;
|
||||||
|
|
||||||
|
case (char)(0x7F):
|
||||||
|
return ConsoleKey.Delete;
|
||||||
|
|
||||||
|
case ' ':
|
||||||
|
return ConsoleKey.Spacebar;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 1. Ctrl A to Ctrl Z.
|
||||||
|
if (x >= 1 && x <= 26)
|
||||||
|
{
|
||||||
|
isCtrl = true;
|
||||||
|
return ConsoleKey.A + x - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Numbers from 0 to 9.
|
||||||
|
if (x >= '0' && x <= '9')
|
||||||
|
{
|
||||||
|
return ConsoleKey.D0 + x - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
//3. A to Z
|
||||||
|
if (x >= 'A' && x <= 'Z')
|
||||||
|
{
|
||||||
|
isShift = true;
|
||||||
|
return ConsoleKey.A + (x - 'A');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. a to z.
|
||||||
|
if (x >= 'a' && x <= 'z')
|
||||||
|
{
|
||||||
|
return ConsoleKey.A + (x - 'a');
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default(ConsoleKey);
|
||||||
|
}
|
||||||
|
#else
|
||||||
internal static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modifiers)
|
internal static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modifiers)
|
||||||
{
|
{
|
||||||
// default for unprintables and unhandled
|
// default for unprintables and unhandled
|
||||||
char keyChar = '\u0000';
|
char keyChar = '\u0000';
|
||||||
#if UNIX
|
|
||||||
Type keyType = typeof (Keys);
|
|
||||||
FieldInfo[] keyFields = keyType.GetFields();
|
|
||||||
|
|
||||||
foreach (FieldInfo field in keyFields)
|
|
||||||
{
|
|
||||||
if (field.FieldType == typeof(ConsoleKeyInfo))
|
|
||||||
{
|
|
||||||
ConsoleKeyInfo info = (ConsoleKeyInfo)field.GetValue(null);
|
|
||||||
if (info.Key == key && info.Modifiers == modifiers)
|
|
||||||
{
|
|
||||||
return info.KeyChar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// emulate GetKeyboardState bitmap - set high order bit for relevant modifier virtual keys
|
// emulate GetKeyboardState bitmap - set high order bit for relevant modifier virtual keys
|
||||||
var state = new byte[256];
|
var state = new byte[256];
|
||||||
state[NativeMethods.VK_SHIFT] = (byte)(((modifiers & ConsoleModifiers.Shift) != 0) ? 0x80 : 0);
|
state[NativeMethods.VK_SHIFT] = (byte)(((modifiers & ConsoleModifiers.Shift) != 0) ? 0x80 : 0);
|
||||||
@@ -252,8 +331,8 @@ namespace Microsoft.PowerShell
|
|||||||
{
|
{
|
||||||
keyChar = chars[0];
|
keyChar = chars[0];
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return keyChar;
|
return keyChar;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,25 +387,12 @@ namespace Microsoft.PowerShell.Internal
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if UNIX
|
#if UNIX
|
||||||
if (sb.Length > 0)
|
char c = key.KeyChar;
|
||||||
sb.Append("+");
|
|
||||||
// TODO: find better way to map these characters to something more friendly
|
|
||||||
if ((key.Key >= ConsoleKey.D0 && key.Key <= ConsoleKey.D9)
|
|
||||||
|| (key.Key >= ConsoleKey.Oem1 && key.Key <= ConsoleKey.Oem8))
|
|
||||||
{
|
|
||||||
sb.Append(key.KeyChar);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
|
|
||||||
{
|
|
||||||
sb.Append("Shift+");
|
|
||||||
}
|
|
||||||
sb.Append(key.Key);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
|
// Windows cannot use KeyChar as some chords (like Ctrl+[) show up as control characters.
|
||||||
char c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key,
|
char c = ConsoleKeyChordConverter.GetCharFromConsoleKey(key.Key,
|
||||||
(mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0);
|
(mods & ConsoleModifiers.Shift) != 0 ? ConsoleModifiers.Shift : 0);
|
||||||
|
#endif
|
||||||
if (char.IsControl(c) || char.IsWhiteSpace(c))
|
if (char.IsControl(c) || char.IsWhiteSpace(c))
|
||||||
{
|
{
|
||||||
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
|
if (key.Modifiers.HasFlag(ConsoleModifiers.Shift))
|
||||||
@@ -424,7 +411,6 @@ namespace Microsoft.PowerShell.Internal
|
|||||||
sb.Append("+");
|
sb.Append("+");
|
||||||
sb.Append(c);
|
sb.Append(c);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,9 +93,13 @@ namespace Microsoft.PowerShell
|
|||||||
{
|
{
|
||||||
// Because a comparison of two ConsoleKeyInfo objects is a comparison of the
|
// Because a comparison of two ConsoleKeyInfo objects is a comparison of the
|
||||||
// combination of the ConsoleKey and Modifiers, we must combine their hashes.
|
// combination of the ConsoleKey and Modifiers, we must combine their hashes.
|
||||||
// This is based on Tuple.GetHashCode
|
// Note that if the ConsoleKey is default, we must fall back to the KeyChar,
|
||||||
int h1 = obj.Key.GetHashCode();
|
// otherwise every non-special key will compare as the same.
|
||||||
|
int h1 = obj.Key == default(ConsoleKey)
|
||||||
|
? obj.KeyChar.GetHashCode()
|
||||||
|
: obj.Key.GetHashCode();
|
||||||
int h2 = obj.Modifiers.GetHashCode();
|
int h2 = obj.Modifiers.GetHashCode();
|
||||||
|
// This is based on Tuple.GetHashCode
|
||||||
return unchecked(((h1 << 5) + h1) ^ h2);
|
return unchecked(((h1 << 5) + h1) ^ h2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Describe "PSReadLine" -tags "CI" {
|
|||||||
|
|
||||||
It "Should use Windows Bindings on Windows" -skip:(-not $IsWindows) {
|
It "Should use Windows Bindings on Windows" -skip:(-not $IsWindows) {
|
||||||
(Get-PSReadLineOption).EditMode | Should Be Windows
|
(Get-PSReadLineOption).EditMode | Should Be Windows
|
||||||
(Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be SelectAll
|
(Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+a" }).Function | Should Be SelectAll
|
||||||
}
|
}
|
||||||
|
|
||||||
It "Should set the edit mode" {
|
It "Should set the edit mode" {
|
||||||
@@ -31,6 +31,17 @@ Describe "PSReadLine" -tags "CI" {
|
|||||||
(Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be BeginningOfLine
|
(Get-PSReadlineKeyHandler | where { $_.Key -eq "Ctrl+A" }).Function | Should Be BeginningOfLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It "Should allow custom bindings for plain keys" {
|
||||||
|
Set-PSReadlineKeyHandler -Key '"' -Function SelfInsert
|
||||||
|
(Get-PSReadLineKeyHandler | where { $_.Key -eq '"' }).Function | Should Be SelfInsert
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should report Capitalized bindings correctly" {
|
||||||
|
Set-PSReadlineOption -EditMode Emacs
|
||||||
|
(Get-PSReadLineKeyHandler | where { $_.Key -ceq "Alt+b" }).Function | Should Be BackwardWord
|
||||||
|
(Get-PSReadLineKeyHandler | where { $_.Key -ceq "Alt+B" }).Function | Should Be SelectBackwardWord
|
||||||
|
}
|
||||||
|
|
||||||
AfterAll {
|
AfterAll {
|
||||||
Remove-Module PSReadLine
|
Remove-Module PSReadLine
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user