Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/lzybkr/PSReadLine
Browse files Browse the repository at this point in the history
  • Loading branch information
lzybkr committed May 25, 2013
2 parents a17d05d + 4a13417 commit b7fc7bd
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 3 deletions.
14 changes: 11 additions & 3 deletions PSReadLine/Cmdlets.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Policy;

namespace PSConsoleUtilities
{
Expand Down Expand Up @@ -210,11 +214,13 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input
[Cmdlet("Set", "PSReadlineKeyHandler")]
public class SetKeyHandlerCommand : PSCmdlet
{
[Parameter(Mandatory = true)]
//[ConsoleKeyInfoConverter]
[Parameter(Position = 0, Mandatory = true)]
[ValidateNotNull]
[ConsoleKeyInfoConverter]
public ConsoleKeyInfo Key { get; set; }

[Parameter(Mandatory = true)]
[Parameter(Position = 1, Mandatory = true)]
[ValidateNotNull]
public Action Handler { get; set; }

[Parameter(Mandatory = true)]
Expand All @@ -241,5 +247,7 @@ protected override void EndProcessing()
{
WriteObject(PSConsoleReadLine.GetKeyHandlers(), true);
}


}
}
201 changes: 201 additions & 0 deletions PSReadLine/ConsoleKeyInfoConverterAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Text;

namespace PSConsoleUtilities
{
// $c = new-object PSConsoleUtilities.ConsoleKeyInfoConverterAttribute
[AttributeUsage(AttributeTargets.Property)]
public class ConsoleKeyInfoConverterAttribute : ArgumentTransformationAttribute
{
public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
{
if (!(inputData is string))
{
// pass through
return inputData;
}

var sequence = (string)inputData;
Stack<string> tokens = null;
ConsoleModifiers modifiers = 0;
ConsoleKey key = 0;

bool valid = !String.IsNullOrEmpty(sequence);

if (valid)
{
tokens = new Stack<string>(
(sequence.Split(new[] {'+'})
.Select(
part => part.ToLowerInvariant().Trim())));
}

while (valid && tokens.Count > 0)
{
string token = tokens.Pop();

// sequence was something silly like "shift++"
if (token == String.Empty)
{
valid = false;
break;
}

// key should be first token to be popped
if (key == 0)
{
// try simple parse for ConsoleKey enum name
valid = Enum.TryParse(token, ignoreCase: true, result: out key);

// doesn't map to ConsoleKey so convert to virtual key from char
if (!valid && token.Length == 1)
{
string failReason;
valid = TryParseCharLiteral(token[0], ref modifiers, ref key, out failReason);

if (!valid)
{
throw new ArgumentException(String.Format("Unable to translate '{0}' to " +
"virtual key code: {1}.", token[0], failReason));
}
}

if (!valid)
{
throw new ArgumentException("Unrecognized key '" + token + "'. Please use a character literal or a " +
"well-known key name from the System.ConsoleKey enumeration.");
}
}
else
{
// now, parse modifier(s)
ConsoleModifiers modifier;

// courtesy translation
if (token == "ctrl")
{
token = "control";
}

if (Enum.TryParse(token, ignoreCase: true, result: out modifier))
{
// modifier already set?
if ((modifiers & modifier) != 0)
{
// either found duplicate modifier token or shift state
// was already implied from char, e.g. char is "}", which is "shift+]"
throw new ArgumentException(
String.Format("Duplicate or invalid modifier token '{0}' for key '{1}'.", modifier, key));
}
modifiers |= modifier;
}
else
{
throw new ArgumentException("Invalid modifier token '" + token + "'. The supported modifiers are " +
"'alt', 'shift', 'control' or 'ctrl'.");
}
}
}

if (!valid)
{
throw new ArgumentException("Invalid sequence '" + sequence + "'.");
}

char keyChar = GetCharFromConsoleKey(key, modifiers);

return new ConsoleKeyInfo(keyChar, key,
shift: ((modifiers & ConsoleModifiers.Shift) != 0),
alt: ((modifiers & ConsoleModifiers.Alt) != 0),
control: ((modifiers & ConsoleModifiers.Control) != 0));
}

private static bool TryParseCharLiteral(char literal, ref ConsoleModifiers modifiers, ref ConsoleKey key, out string failReason)
{
bool valid = false;

// shift state will be in MSB
short virtualKey = NativeMethods.VkKeyScan(literal);

if (virtualKey != 0)
{
// e.g. "}" = 0x01dd but "]" is 0x00dd, ergo } = shift+].
// shift = 1, control = 2, alt = 4, hankaku = 8 (ignored)
int state = virtualKey >> 8;

if ((state & 1) == 1)
{
modifiers |= ConsoleModifiers.Shift;
}
if ((state & 2) == 2)
{
modifiers |= ConsoleModifiers.Control;
}
if ((state & 4) == 4)
{
modifiers |= ConsoleModifiers.Alt;
}

virtualKey &= 0xff;

if (Enum.IsDefined(typeof (ConsoleKey), (int) virtualKey))
{
failReason = null;
key = (ConsoleKey) virtualKey;
valid = true;
}
else
{
// haven't seen this happen yet, but possible
failReason = String.Format("The virtual key code {0} does not map " +
"to a known System.ConsoleKey enumerated value.", virtualKey);
}
}
else
{
int hresult = Marshal.GetLastWin32Error();
Exception e = Marshal.GetExceptionForHR(hresult);
failReason = e.Message;
}

return valid;
}

private static char GetCharFromConsoleKey(ConsoleKey key, ConsoleModifiers modifiers)
{
// default for unprintables and unhandled
char keyChar = '\u0000';

// emulate GetKeyboardState bitmap - set high order bit for relevant modifier virtual keys
var state = new byte[256];
state[NativeMethods.VK_SHIFT] = (byte)(((modifiers & ConsoleModifiers.Shift) != 0) ? 0x80 : 0);
state[NativeMethods.VK_CONTROL] = (byte)(((modifiers & ConsoleModifiers.Control) != 0) ? 0x80 : 0);
state[NativeMethods.VK_ALT] = (byte)(((modifiers & ConsoleModifiers.Alt) != 0) ? 0x80 : 0);

// a ConsoleKey enum's value is a virtual key code
uint virtualKey = (uint)key;

// get corresponding scan code
uint scanCode = NativeMethods.MapVirtualKey(virtualKey, NativeMethods.MAPVK_VK_TO_VSC);
Debug.Assert(scanCode != 0, "scanCode != 0");

// get corresponding character - maybe be 0, 1 or 2 in length (diacriticals)
var chars = new StringBuilder();
int charCount = NativeMethods.ToAscii(
virtualKey, scanCode, state, chars, NativeMethods.MENU_IS_INACTIVE);

// TODO: support diacriticals (charCount == 2)
if (charCount == 1)
{
keyChar = chars[0];
}

return keyChar;
}
}
}
19 changes: 19 additions & 0 deletions PSReadLine/ConsoleLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ namespace PSConsoleUtilities
{
public static class NativeMethods
{
public const uint MAPVK_VK_TO_VSC = 0x00;
public const uint MAPVK_VSC_TO_VK = 0x01;
public const uint MAPVK_VK_TO_CHAR = 0x02;

public const byte VK_SHIFT = 0x10;
public const byte VK_CONTROL = 0x11;
public const byte VK_ALT = 0x12;
public const uint MENU_IS_ACTIVE = 0x01;
public const uint MENU_IS_INACTIVE = 0x00; // windows key

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr GetStdHandle(uint handleId);

Expand All @@ -29,6 +39,15 @@ public static extern bool ScrollConsoleScreenBuffer(IntPtr hConsoleOutput,

[DllImport("KERNEL32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ReadConsoleOutput(IntPtr consoleOutput, [Out] CHAR_INFO[] buffer, COORD bufferSize, COORD bufferCoord, ref SMALL_RECT readRegion);

[DllImport("user32.dll", SetLastError = true)]
public static extern uint MapVirtualKey(uint uCode, uint uMapType);

[DllImport("user32.dll")]
public static extern int ToAscii(uint uVirtKey, uint uScanCode, byte[] lpKeyState, [Out] StringBuilder lpChar, uint uFlags);

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern short VkKeyScan(char @char);
}

public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);
Expand Down
1 change: 1 addition & 0 deletions PSReadLine/PSReadLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<ItemGroup>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Cmdlets.cs" />
<Compile Include="ConsoleKeyInfoConverterAttribute.cs" />
<Compile Include="ConsoleLib.cs" />
<Compile Include="HistoryQueue.cs" />
<Compile Include="Keys.cs" />
Expand Down
Loading

0 comments on commit b7fc7bd

Please sign in to comment.