/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Management.Automation;
using System.Text;
using Microsoft.PowerShell.Internal;
namespace Microsoft.PowerShell
{
///
/// The class is used as the output type for the cmdlet Get-PSReadlineKeyHandler
///
public class KeyHandler
{
///
/// The key that is bound or unbound.
///
public string Key { get; set; }
///
/// The name of the function that a key is bound to, if any.
///
public string Function { get; set; }
///
/// A short description of the behavior of the function.
///
public string Description
{
get
{
var result = _description;
if (string.IsNullOrWhiteSpace(result))
result = PSReadLineResources.ResourceManager.GetString(Function + "Description");
if (string.IsNullOrWhiteSpace(result))
result = Function;
return result;
}
set { _description = value; }
}
private string _description;
}
public partial class PSConsoleReadLine
{
class KeyHandler
{
// Each key handler will be passed 2 arguments. Most will ignore these arguments,
// but having a consistent signature greatly simplifies dispatch. Defaults
// should be included on all handlers that ignore their parameters so they
// can be called from PowerShell without passing anything.
//
// The first arugment is the key that caused the action to be called
// (the second key when it's a 2 key chord). The default is null (it's nullable)
// because PowerShell can't handle default(ConsoleKeyInfo) as a default.
// Most actions will ignore this argument.
//
// The second argument is an arbitrary object. It will usually be either a number
// (e.g. as a repeat count) or a string. Most actions will ignore this argument.
public Action Action;
public string BriefDescription;
public string LongDescription
{
get
{
return _longDescription ??
(_longDescription =
PSReadLineResources.ResourceManager.GetString(BriefDescription + "Description"));
}
set { _longDescription = value; }
}
private string _longDescription;
public ScriptBlock ScriptBlock;
}
internal class ConsoleKeyInfoComparer : IEqualityComparer
{
public bool Equals(ConsoleKeyInfo x, ConsoleKeyInfo y)
{
// We *must not* compare the KeyChar field as its value is platform-dependent.
// We compare exactly the ConsoleKey enum field (which is platform-agnostic)
// and the modifiers.
return x.Key == y.Key && x.Modifiers == y.Modifiers;
}
public int GetHashCode(ConsoleKeyInfo obj)
{
// Because a comparison of two ConsoleKeyInfo objects is a comparison of the
// combination of the ConsoleKey and Modifiers, we must combine their hashes.
// Note that if the ConsoleKey is default, we must fall back to the KeyChar,
// 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();
// This is based on Tuple.GetHashCode
return unchecked(((h1 << 5) + h1) ^ h2);
}
}
static KeyHandler MakeKeyHandler(Action action, string briefDescription, string longDescription = null, ScriptBlock scriptBlock = null)
{
return new KeyHandler
{
Action = action,
BriefDescription = briefDescription,
LongDescription = longDescription,
ScriptBlock = scriptBlock,
};
}
private Dictionary _dispatchTable;
private Dictionary> _chordDispatchTable;
///
/// Helper to set bindings based on EditMode
///
void SetDefaultBindings(EditMode editMode)
{
switch (editMode)
{
case EditMode.Emacs:
SetDefaultEmacsBindings();
break;
case EditMode.Vi:
SetDefaultViBindings();
break;
case EditMode.Windows:
SetDefaultWindowsBindings();
break;
}
}
void SetDefaultWindowsBindings()
{
_dispatchTable = new Dictionary(new ConsoleKeyInfoComparer())
{
{ Keys.Enter, MakeKeyHandler(AcceptLine, "AcceptLine") },
{ Keys.ShiftEnter, MakeKeyHandler(AddLine, "AddLine") },
{ Keys.CtrlEnter, MakeKeyHandler(InsertLineAbove, "InsertLineAbove") },
{ Keys.CtrlShiftEnter, MakeKeyHandler(InsertLineBelow, "InsertLineBelow") },
{ Keys.Escape, MakeKeyHandler(RevertLine, "RevertLine") },
{ Keys.LeftArrow, MakeKeyHandler(BackwardChar, "BackwardChar") },
{ Keys.RightArrow, MakeKeyHandler(ForwardChar, "ForwardChar") },
{ Keys.CtrlLeftArrow, MakeKeyHandler(BackwardWord, "BackwardWord") },
{ Keys.CtrlRightArrow, MakeKeyHandler(NextWord, "NextWord") },
{ Keys.ShiftLeftArrow, MakeKeyHandler(SelectBackwardChar, "SelectBackwardChar") },
{ Keys.ShiftRightArrow, MakeKeyHandler(SelectForwardChar, "SelectForwardChar") },
{ Keys.ShiftCtrlLeftArrow, MakeKeyHandler(SelectBackwardWord, "SelectBackwardWord") },
{ Keys.ShiftCtrlRightArrow, MakeKeyHandler(SelectNextWord, "SelectNextWord") },
{ Keys.UpArrow, MakeKeyHandler(PreviousHistory, "PreviousHistory") },
{ Keys.DownArrow, MakeKeyHandler(NextHistory, "NextHistory") },
{ Keys.Home, MakeKeyHandler(BeginningOfLine, "BeginningOfLine") },
{ Keys.End, MakeKeyHandler(EndOfLine, "EndOfLine") },
{ Keys.ShiftHome, MakeKeyHandler(SelectBackwardsLine, "SelectBackwardsLine") },
{ Keys.ShiftEnd, MakeKeyHandler(SelectLine, "SelectLine") },
{ Keys.Delete, MakeKeyHandler(DeleteChar, "DeleteChar") },
{ Keys.Backspace, MakeKeyHandler(BackwardDeleteChar, "BackwardDeleteChar") },
{ Keys.CtrlSpace, MakeKeyHandler(MenuComplete, "MenuComplete") },
{ Keys.Tab, MakeKeyHandler(TabCompleteNext, "TabCompleteNext") },
{ Keys.ShiftTab, MakeKeyHandler(TabCompletePrevious, "TabCompletePrevious") },
#if !UNIX
{ Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") },
{ Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") },
{ Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") },
#endif
{ Keys.CtrlA, MakeKeyHandler(SelectAll, "SelectAll") },
{ Keys.CtrlC, MakeKeyHandler(CopyOrCancelLine, "CopyOrCancelLine") },
{ Keys.CtrlShiftC, MakeKeyHandler(Copy, "Copy") },
{ Keys.CtrlL, MakeKeyHandler(ClearScreen, "ClearScreen") },
{ Keys.CtrlR, MakeKeyHandler(ReverseSearchHistory, "ReverseSearchHistory") },
{ Keys.CtrlS, MakeKeyHandler(ForwardSearchHistory, "ForwardSearchHistory") },
{ Keys.CtrlV, MakeKeyHandler(Paste, "Paste") },
{ Keys.CtrlX, MakeKeyHandler(Cut, "Cut") },
{ Keys.CtrlY, MakeKeyHandler(Redo, "Redo") },
{ Keys.CtrlZ, MakeKeyHandler(Undo, "Undo") },
{ Keys.CtrlBackspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") },
{ Keys.CtrlDelete, MakeKeyHandler(KillWord, "KillWord") },
{ Keys.CtrlEnd, MakeKeyHandler(ForwardDeleteLine, "ForwardDeleteLine") },
{ Keys.CtrlHome, MakeKeyHandler(BackwardDeleteLine, "BackwardDeleteLine") },
{ Keys.CtrlRBracket, MakeKeyHandler(GotoBrace, "GotoBrace") },
{ Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings, "ShowKeyBindings") },
{ Keys.AltPeriod, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.Alt0, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt1, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt2, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt3, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt4, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt5, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt6, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt7, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt8, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt9, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.AltMinus, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.AltQuestion, MakeKeyHandler(WhatIsKey, "WhatIsKey") },
{ Keys.AltF7, MakeKeyHandler(ClearHistory, "ClearHistory") },
{ Keys.F3, MakeKeyHandler(CharacterSearch, "CharacterSearch") },
{ Keys.ShiftF3, MakeKeyHandler(CharacterSearchBackward, "CharacterSearchBackward") },
{ Keys.F8, MakeKeyHandler(HistorySearchBackward, "HistorySearchBackward") },
{ Keys.ShiftF8, MakeKeyHandler(HistorySearchForward, "HistorySearchForward") },
#if !UNIX
{ Keys.PageUp, MakeKeyHandler(ScrollDisplayUp, "ScrollDisplayUp") },
{ Keys.PageDown, MakeKeyHandler(ScrollDisplayDown, "ScrollDisplayDown") },
{ Keys.CtrlPageUp, MakeKeyHandler(ScrollDisplayUpLine, "ScrollDisplayUpLine") },
{ Keys.CtrlPageDown, MakeKeyHandler(ScrollDisplayDownLine, "ScrollDisplayDownLine") },
#endif
};
_chordDispatchTable = new Dictionary>();
}
void SetDefaultEmacsBindings()
{
_dispatchTable = new Dictionary(new ConsoleKeyInfoComparer())
{
{ Keys.Backspace, MakeKeyHandler(BackwardDeleteChar, "BackwardDeleteChar") },
{ Keys.Enter, MakeKeyHandler(AcceptLine, "AcceptLine") },
{ Keys.ShiftEnter, MakeKeyHandler(AddLine, "AddLine") },
{ Keys.LeftArrow, MakeKeyHandler(BackwardChar, "BackwardChar") },
{ Keys.RightArrow, MakeKeyHandler(ForwardChar, "ForwardChar") },
{ Keys.ShiftLeftArrow, MakeKeyHandler(SelectBackwardChar, "SelectBackwardChar") },
{ Keys.ShiftRightArrow, MakeKeyHandler(SelectForwardChar, "SelectForwardChar") },
{ Keys.UpArrow, MakeKeyHandler(PreviousHistory, "PreviousHistory") },
{ Keys.DownArrow, MakeKeyHandler(NextHistory, "NextHistory") },
{ Keys.AltLess, MakeKeyHandler(BeginningOfHistory, "BeginningOfHistory") },
{ Keys.AltGreater, MakeKeyHandler(EndOfHistory, "EndOfHistory") },
{ Keys.Home, MakeKeyHandler(BeginningOfLine, "BeginningOfLine") },
{ Keys.End, MakeKeyHandler(EndOfLine, "EndOfLine") },
{ Keys.ShiftHome, MakeKeyHandler(SelectBackwardsLine, "SelectBackwardsLine") },
{ Keys.ShiftEnd, MakeKeyHandler(SelectLine, "SelectLine") },
{ Keys.Escape, MakeKeyHandler(Chord, "ChordFirstKey") },
{ Keys.Delete, MakeKeyHandler(DeleteChar, "DeleteChar") },
{ Keys.Tab, MakeKeyHandler(Complete, "Complete") },
{ Keys.CtrlA, MakeKeyHandler(BeginningOfLine, "BeginningOfLine") },
{ Keys.CtrlB, MakeKeyHandler(BackwardChar, "BackwardChar") },
{ Keys.CtrlC, MakeKeyHandler(CopyOrCancelLine, "CopyOrCancelLine") },
{ Keys.CtrlD, MakeKeyHandler(DeleteCharOrExit, "DeleteCharOrExit") },
{ Keys.CtrlE, MakeKeyHandler(EndOfLine, "EndOfLine") },
{ Keys.CtrlF, MakeKeyHandler(ForwardChar, "ForwardChar") },
{ Keys.CtrlG, MakeKeyHandler(Abort, "Abort") },
{ Keys.CtrlH, MakeKeyHandler(BackwardDeleteChar, "BackwardDeleteChar") },
{ Keys.CtrlL, MakeKeyHandler(ClearScreen, "ClearScreen") },
{ Keys.CtrlK, MakeKeyHandler(KillLine, "KillLine") },
{ Keys.CtrlM, MakeKeyHandler(ValidateAndAcceptLine,"ValidateAndAcceptLine") },
{ Keys.CtrlN, MakeKeyHandler(NextHistory, "NextHistory") },
{ Keys.CtrlO, MakeKeyHandler(AcceptAndGetNext, "AcceptAndGetNext") },
{ Keys.CtrlP, MakeKeyHandler(PreviousHistory, "PreviousHistory") },
{ Keys.CtrlR, MakeKeyHandler(ReverseSearchHistory, "ReverseSearchHistory") },
{ Keys.CtrlS, MakeKeyHandler(ForwardSearchHistory, "ForwardSearchHistory") },
{ Keys.CtrlU, MakeKeyHandler(BackwardKillLine, "BackwardKillLine") },
{ Keys.CtrlX, MakeKeyHandler(Chord, "ChordFirstKey") },
{ Keys.CtrlW, MakeKeyHandler(UnixWordRubout, "UnixWordRubout") },
{ Keys.CtrlY, MakeKeyHandler(Yank, "Yank") },
{ Keys.CtrlAt, MakeKeyHandler(SetMark, "SetMark") },
{ Keys.CtrlUnderbar, MakeKeyHandler(Undo, "Undo") },
{ Keys.CtrlRBracket, MakeKeyHandler(CharacterSearch, "CharacterSearch") },
{ Keys.AltCtrlRBracket, MakeKeyHandler(CharacterSearchBackward,"CharacterSearchBackward") },
{ Keys.Alt0, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt1, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt2, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt3, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt4, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt5, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt6, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt7, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt8, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.Alt9, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.AltMinus, MakeKeyHandler(DigitArgument, "DigitArgument") },
{ Keys.AltB, MakeKeyHandler(BackwardWord, "BackwardWord") },
{ Keys.AltShiftB, MakeKeyHandler(SelectBackwardWord, "SelectBackwardWord") },
{ Keys.AltD, MakeKeyHandler(KillWord, "KillWord") },
{ Keys.AltF, MakeKeyHandler(ForwardWord, "ForwardWord") },
{ Keys.AltShiftF, MakeKeyHandler(SelectForwardWord, "SelectForwardWord") },
{ Keys.AltR, MakeKeyHandler(RevertLine, "RevertLine") },
{ Keys.AltY, MakeKeyHandler(YankPop, "YankPop") },
{ Keys.AltBackspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") },
{ Keys.AltEquals, MakeKeyHandler(PossibleCompletions, "PossibleCompletions") },
{ Keys.CtrlSpace, MakeKeyHandler(MenuComplete, "MenuComplete") },
{ Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings, "ShowKeyBindings") },
{ Keys.AltQuestion, MakeKeyHandler(WhatIsKey, "WhatIsKey") },
{ Keys.AltSpace, MakeKeyHandler(SetMark, "SetMark") }, // useless entry here for completeness - brings up system menu on Windows
{ Keys.AltPeriod, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.AltUnderbar, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.AltCtrlY, MakeKeyHandler(YankNthArg, "YankNthArg") },
#if !UNIX
{ Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") },
{ Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") },
{ Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") },
{ Keys.PageUp, MakeKeyHandler(ScrollDisplayUp, "ScrollDisplayUp") },
{ Keys.CtrlPageUp, MakeKeyHandler(ScrollDisplayUpLine, "ScrollDisplayUpLine") },
{ Keys.PageDown, MakeKeyHandler(ScrollDisplayDown, "ScrollDisplayDown") },
{ Keys.CtrlPageDown, MakeKeyHandler(ScrollDisplayDownLine,"ScrollDisplayDownLine") },
{ Keys.CtrlHome, MakeKeyHandler(ScrollDisplayTop, "ScrollDisplayTop") },
{ Keys.CtrlEnd, MakeKeyHandler(ScrollDisplayToCursor,"ScrollDisplayToCursor") },
#endif
};
_chordDispatchTable = new Dictionary>();
// Escape, table (meta key)
_chordDispatchTable[Keys.Escape] = new Dictionary(new ConsoleKeyInfoComparer())
{
{ Keys.B, MakeKeyHandler(BackwardWord, "BackwardWord") },
{ Keys.D, MakeKeyHandler(KillWord, "KillWord") },
{ Keys.F, MakeKeyHandler(ForwardWord, "ForwardWord") },
{ Keys.R, MakeKeyHandler(RevertLine, "RevertLine") },
{ Keys.Y, MakeKeyHandler(YankPop, "YankPop") },
{ Keys.CtrlY, MakeKeyHandler(YankNthArg, "YankNthArg") },
{ Keys.Backspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") },
{ Keys.Period, MakeKeyHandler(YankLastArg, "YankLastArg") },
{ Keys.Underbar, MakeKeyHandler(YankLastArg, "YankLastArg") },
};
// Ctrl+X, table
_chordDispatchTable[Keys.CtrlX] = new Dictionary(new ConsoleKeyInfoComparer())
{
{ Keys.Backspace, MakeKeyHandler(BackwardKillLine, "BackwardKillLine") },
{ Keys.CtrlU, MakeKeyHandler(Undo, "Undo") },
{ Keys.CtrlX, MakeKeyHandler(ExchangePointAndMark, "ExchangePointAndMark") },
};
}
///
/// Show all bound keys
///
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
public static void ShowKeyBindings(ConsoleKeyInfo? key = null, object arg = null)
{
var buffer = new StringBuilder();
buffer.AppendFormat(CultureInfo.InvariantCulture, "{0,-20} {1,-24} {2}\n", "Key", "Function", "Description");
buffer.AppendFormat(CultureInfo.InvariantCulture, "{0,-20} {1,-24} {2}\n", "---", "--------", "-----------");
var boundKeys = GetKeyHandlers(includeBound: true, includeUnbound: false);
var console = _singleton._console;
var maxDescriptionLength = console.WindowWidth - 20 - 24 - 2;
foreach (var boundKey in boundKeys)
{
var description = boundKey.Description;
var newline = "\n";
if (description.Length >= maxDescriptionLength)
{
description = description.Substring(0, maxDescriptionLength - 3) + "...";
newline = "";
}
buffer.AppendFormat(CultureInfo.InvariantCulture, "{0,-20} {1,-24} {2}{3}", boundKey.Key, boundKey.Function, description, newline);
}
// Don't overwrite any of the line - so move to first line after the end of our buffer.
var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length);
var y = coords.Y + 1;
_singleton.PlaceCursor(0, ref y);
console.WriteLine(buffer.ToString());
_singleton._initialY = console.CursorTop;
_singleton.Render();
}
///
/// Read a key and tell me what the key is bound to.
///
[SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
public static void WhatIsKey(ConsoleKeyInfo? key = null, object arg = null)
{
_singleton._statusLinePrompt = "what-is-key: ";
_singleton.Render();
var toLookup = ReadKey();
KeyHandler keyHandler;
var buffer = new StringBuilder();
_singleton._dispatchTable.TryGetValue(toLookup, out keyHandler);
buffer.Append(toLookup.ToGestureString());
if (keyHandler != null)
{
if (keyHandler.BriefDescription == "ChordFirstKey")
{
Dictionary secondKeyDispatchTable;
if (_singleton._chordDispatchTable.TryGetValue(toLookup, out secondKeyDispatchTable))
{
toLookup = ReadKey();
secondKeyDispatchTable.TryGetValue(toLookup, out keyHandler);
buffer.Append(",");
buffer.Append(toLookup.ToGestureString());
}
}
}
buffer.Append(": ");
if (keyHandler != null)
{
buffer.Append(keyHandler.BriefDescription);
if (!string.IsNullOrWhiteSpace(keyHandler.LongDescription))
{
buffer.Append(" - ");
buffer.Append(keyHandler.LongDescription);
}
}
else if (toLookup.KeyChar != 0)
{
buffer.Append("SelfInsert");
buffer.Append(" - ");
buffer.Append(PSReadLineResources.SelfInsertDescription);
}
else
{
buffer.Append(PSReadLineResources.KeyIsUnbound);
}
_singleton.ClearStatusMessage(render: false);
// Don't overwrite any of the line - so move to first line after the end of our buffer.
var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length);
var y = coords.Y + 1;
_singleton.PlaceCursor(0, ref y);
_singleton._console.WriteLine(buffer.ToString());
_singleton._initialY = _singleton._console.CursorTop;
_singleton.Render();
}
}
}