/********************************************************************++ 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(); } } }