Skip to content

Commit 653b492

Browse files
committed
Add new functions WhatIsKey and ShowKeyBindings
WhatIsKey prompts for a key and then shows what it is bound to. ShowKeyBindings is like running Get-PSReadlineKeyBindings, but does so without needing to run a command and preserves the current input line.
1 parent 1e2dee3 commit 653b492

6 files changed

+203
-57
lines changed

PSReadLine/ConsoleLib.cs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -206,27 +206,26 @@ public static string ToGestureString(this ConsoleKeyInfo key)
206206
{
207207
sb.Append((char)('0' + key.Key - ConsoleKey.D0));
208208
}
209+
else if (char.IsLetterOrDigit(key.KeyChar))
210+
{
211+
sb.Append(key.KeyChar);
212+
}
209213
else switch (key.Key)
210214
{
211-
case ConsoleKey.Escape: sb.Append("Esc"); break;
212-
case ConsoleKey.PageUp: sb.Append("PgUp"); break;
213-
case ConsoleKey.PageDown: sb.Append("PgDn"); break;
214-
case ConsoleKey.Delete: sb.Append("Del"); break;
215-
case ConsoleKey.Insert: sb.Append("Ins"); break;
216-
case ConsoleKey.PrintScreen: sb.Append("PrtSc"); break;
217-
case ConsoleKey.Backspace: sb.Append("BS"); break;
218-
case ConsoleKey.Oem1: sb.Append("Semicolon"); break;
219-
case ConsoleKey.OemPlus: sb.Append("Plus"); break;
220-
case ConsoleKey.OemComma: sb.Append("Comma"); break;
221-
case ConsoleKey.OemMinus: sb.Append("Minus"); break;
222-
case ConsoleKey.OemPeriod: sb.Append("Period"); break;
223-
case ConsoleKey.Oem2: sb.Append("Question"); break;
224-
case ConsoleKey.Oem3: sb.Append("Tilde"); break;
225-
case ConsoleKey.Oem4: sb.Append("Openbrackets"); break;
226-
case ConsoleKey.Oem5: sb.Append("Pipe"); break;
227-
case ConsoleKey.Oem6: sb.Append("Closebrackets"); break;
228-
case ConsoleKey.Oem7: sb.Append("Quotes"); break;
229-
case ConsoleKey.Oem102: sb.Append("Backslash"); break;
215+
case ConsoleKey.Oem1: sb.Append(';'); break;
216+
case ConsoleKey.Oem2: sb.Append('/'); break;
217+
case ConsoleKey.Oem3: sb.Append('`'); break;
218+
case ConsoleKey.Oem4: sb.Append('['); break;
219+
case ConsoleKey.Oem6: sb.Append(']'); break;
220+
case ConsoleKey.Oem7: sb.Append('`'); break;
221+
case ConsoleKey.Oem8: sb.Append('`'); break;
222+
case ConsoleKey.OemComma: sb.Append(','); break;
223+
case ConsoleKey.OemPeriod: sb.Append('.'); break;
224+
case ConsoleKey.OemMinus: sb.Append('-'); break;
225+
case ConsoleKey.OemPlus: sb.Append('+'); break;
226+
//case ConsoleKey.Oem102:
227+
//case ConsoleKey.OemClear:
228+
//case ConsoleKey.Oem5:
230229
default:
231230
sb.Append(key.Key);
232231
break;

PSReadLine/Keys.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ public class Keys
121121
public static ConsoleKeyInfo CtrlRBracket = new ConsoleKeyInfo((char)29, ConsoleKey.Oem6, false, false, true);
122122
public static ConsoleKeyInfo AltCtrlRBracket = new ConsoleKeyInfo((char)0, ConsoleKey.Oem6, false, true, true);
123123
public static ConsoleKeyInfo AltPeriod = new ConsoleKeyInfo('.', ConsoleKey.OemPeriod, false, true, false);
124+
public static ConsoleKeyInfo CtrlAltQuestion = new ConsoleKeyInfo((char)0, ConsoleKey.Oem2, true, true, true);
125+
public static ConsoleKeyInfo AltQuestion = new ConsoleKeyInfo('?', ConsoleKey.Oem2, true, true, false);
124126

125127
public static ConsoleKeyInfo Alt0 = new ConsoleKeyInfo('0', ConsoleKey.D0, false, true, false);
126128
public static ConsoleKeyInfo Alt1 = new ConsoleKeyInfo('1', ConsoleKey.D1, false, true, false);

PSReadLine/PSReadLineResources.Designer.cs

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

PSReadLine/PSReadLineResources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,16 @@
303303
<data name="EnableDemoModeDescription" xml:space="preserve">
304304
<value>Displays a window below the input line that shows which keys are typed</value>
305305
</data>
306+
<data name="KeyIsUnbound" xml:space="preserve">
307+
<value>Key is unbound</value>
308+
</data>
306309
<data name="SelfInsertDescription" xml:space="preserve">
307310
<value>Insert the key typed</value>
308311
</data>
312+
<data name="ShowKeyBindingsDescription" xml:space="preserve">
313+
<value>Show all key bindings</value>
314+
</data>
315+
<data name="WhatIsKeyDescription" xml:space="preserve">
316+
<value>Show the key binding for the next chord entered</value>
317+
</data>
309318
</root>

PSReadLine/ReadLine.cs

Lines changed: 139 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ class HistoryItem
110110

111111
static KeyHandler MakeKeyHandler(Action<ConsoleKeyInfo?, object> action, string briefDescription, string longDescription = null)
112112
{
113+
if (string.IsNullOrWhiteSpace(longDescription))
114+
longDescription = PSReadLineResources.ResourceManager.GetString(briefDescription + "Description");
115+
113116
return new KeyHandler
114117
{
115118
Action = action,
@@ -476,6 +479,8 @@ static PSConsoleReadLine()
476479
{ Keys.CtrlEnd, MakeKeyHandler(ForwardDeleteLine, "ForwardDeleteLine") },
477480
{ Keys.CtrlHome, MakeKeyHandler(BackwardDeleteLine, "BackwardDeleteLine") },
478481
{ Keys.CtrlRBracket, MakeKeyHandler(GotoBrace, "GotoBrace") },
482+
{ Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings, "ShowKeyBindings") },
483+
{ Keys.AltQuestion, MakeKeyHandler(WhatIsKey, "WhatIsKey") },
479484
{ Keys.F3, MakeKeyHandler(CharacterSearch, "CharacterSearch") },
480485
{ Keys.ShiftF3, MakeKeyHandler(CharacterSearchBackward,"CharacterSearchBackward") },
481486
};
@@ -536,6 +541,8 @@ static PSConsoleReadLine()
536541
{ Keys.AltY, MakeKeyHandler(YankPop, "YankPop") },
537542
{ Keys.AltBackspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") },
538543
{ Keys.AltEquals, MakeKeyHandler(PossibleCompletions, "PossibleCompletions") },
544+
{ Keys.CtrlAltQuestion, MakeKeyHandler(ShowKeyBindings, "ShowKeyBindings") },
545+
{ Keys.AltQuestion, MakeKeyHandler(WhatIsKey, "WhatIsKey") },
539546
{ Keys.AltSpace, MakeKeyHandler(SetMark, "SetMark") }, // useless entry here for completeness - brings up system menu on Windows
540547
{ Keys.AltPeriod, MakeKeyHandler(YankLastArg, "YankLastArg") },
541548
{ Keys.AltUnderbar, MakeKeyHandler(YankLastArg, "YankLastArg") },
@@ -3066,55 +3073,57 @@ public static void Ding()
30663073
// The unit test framework redirects stdout - so it would see Console.WriteLine calls.
30673074
// Unfortunately, we are testing exact placement of characters on the screen, so redirection
30683075
// doesn't work for us.
3069-
static private void WriteImpl(string s)
3076+
static private void WriteLine(string s)
30703077
{
3071-
Debug.Assert(s.Length <= Console.BufferWidth);
3072-
30733078
var handle = NativeMethods.GetStdHandle((uint) StandardHandleId.Output);
30743079

3075-
var buffer = new CHAR_INFO[s.Length];
3076-
for (int i = 0; i < s.Length; i++)
3080+
var buffer = new CHAR_INFO[Console.BufferWidth];
3081+
int i = 0;
3082+
int linesWritten = 0;
3083+
int startLine = Console.CursorTop;
3084+
var space = new CHAR_INFO(' ', Console.ForegroundColor, Console.BackgroundColor);
3085+
while (i < s.Length)
30773086
{
3078-
Debug.Assert(s[i] != '\n');
3079-
buffer[i] = new CHAR_INFO(s[i], Console.ForegroundColor, Console.BackgroundColor);
3080-
}
3087+
int j;
3088+
for (j = 0; j < buffer.Length && i < s.Length; j++, i++)
3089+
{
3090+
if (s[i] == '\n')
3091+
{
3092+
break;
3093+
}
3094+
buffer[j] = new CHAR_INFO(s[i], Console.ForegroundColor, Console.BackgroundColor);
3095+
}
30813096

3082-
var bufferSize = new COORD
3083-
{
3084-
X = (short) s.Length,
3085-
Y = 1
3086-
};
3087-
var bufferCoord = new COORD {X = 0, Y = 0};
3088-
var writeRegion = new SMALL_RECT
3089-
{
3090-
Top = (short) Console.CursorTop,
3091-
Left = 0,
3092-
Bottom = (short) Console.CursorTop,
3093-
Right = (short) s.Length
3094-
};
3095-
NativeMethods.WriteConsoleOutput(handle, buffer, bufferSize, bufferCoord, ref writeRegion);
3096-
}
3097+
if (i < s.Length && s[i] == '\n')
3098+
{
3099+
i++;
3100+
}
30973101

3098-
static private void WriteLine(string s)
3099-
{
3100-
Debug.Assert(s.Length <= Console.BufferWidth);
3102+
while (j < buffer.Length)
3103+
{
3104+
buffer[j++] = space;
3105+
}
31013106

3102-
var spaces = Console.BufferWidth - s.Length;
3103-
if (spaces > 0)
3104-
{
3105-
s = s + new string(' ', spaces);
3107+
var bufferSize = new COORD
3108+
{
3109+
X = (short) Console.BufferWidth,
3110+
Y = 1
3111+
};
3112+
var bufferCoord = new COORD {X = 0, Y = 0};
3113+
var writeRegion = new SMALL_RECT
3114+
{
3115+
Top = (short) (startLine + linesWritten),
3116+
Left = 0,
3117+
Bottom = (short) (startLine + linesWritten),
3118+
Right = (short) Console.BufferWidth
3119+
};
3120+
NativeMethods.WriteConsoleOutput(handle, buffer, bufferSize, bufferCoord, ref writeRegion);
3121+
linesWritten += 1;
31063122
}
3107-
WriteImpl(s);
31083123

3109-
_singleton.PlaceCursor(0, Console.CursorTop + 1);
3124+
_singleton.PlaceCursor(0, Console.CursorTop + linesWritten);
31103125
}
31113126

3112-
static private void Write(string s)
3113-
{
3114-
WriteImpl(s);
3115-
3116-
_singleton.PlaceCursor(s.Length, Console.CursorTop);
3117-
}
31183127

31193128
private bool PromptYesOrNo(string s)
31203129
{
@@ -3224,6 +3233,96 @@ private void ClearDemoWindow()
32243233

32253234
#endregion Rendering
32263235

3236+
#region Miscellaneous bindable functions
3237+
3238+
/// <summary>
3239+
/// Show all bound keys
3240+
/// </summary>
3241+
public static void ShowKeyBindings(ConsoleKeyInfo? key = null, object arg = null)
3242+
{
3243+
var buffer = new StringBuilder();
3244+
buffer.AppendFormat("{0,-20} {1,-24} {2}\n", "Key", "Function", "Description");
3245+
buffer.AppendFormat("{0,-20} {1,-24} {2}\n", "---", "--------", "-----------");
3246+
var boundKeys = GetKeyHandlers(includeBound: true, includeUnbound: false);
3247+
var maxDescriptionLength = Console.WindowWidth - 20 - 24 - 2;
3248+
foreach (var boundKey in boundKeys)
3249+
{
3250+
var description = boundKey.Description;
3251+
if (description.Length >= maxDescriptionLength)
3252+
{
3253+
description = description.Substring(0, maxDescriptionLength - 3) + "...";
3254+
}
3255+
buffer.AppendFormat("{0,-20} {1,-24} {2}\n", boundKey.Key, boundKey.Function, description);
3256+
}
3257+
3258+
// Don't overwrite any of the line - so move to first line after the end of our buffer.
3259+
var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length);
3260+
_singleton.PlaceCursor(0, coords.Y + 1);
3261+
3262+
WriteLine(buffer.ToString());
3263+
_singleton._initialY = Console.CursorTop;
3264+
_singleton.Render();
3265+
}
3266+
3267+
/// <summary>
3268+
/// Read a key and tell me what the key is bound to.
3269+
/// </summary>
3270+
public static void WhatIsKey(ConsoleKeyInfo? key = null, object arg = null)
3271+
{
3272+
_singleton._statusLinePrompt = "what-is-key: ";
3273+
_singleton.Render();
3274+
var toLookup = ReadKey();
3275+
KeyHandler keyHandler;
3276+
var buffer = new StringBuilder();
3277+
_singleton._dispatchTable.TryGetValue(toLookup, out keyHandler);
3278+
buffer.Append(toLookup.ToGestureString());
3279+
if (keyHandler != null)
3280+
{
3281+
if (keyHandler.BriefDescription == "ChordFirstKey")
3282+
{
3283+
Dictionary<ConsoleKeyInfo, KeyHandler> secondKeyDispatchTable;
3284+
if (_singleton._chordDispatchTable.TryGetValue(toLookup, out secondKeyDispatchTable))
3285+
{
3286+
toLookup = ReadKey();
3287+
secondKeyDispatchTable.TryGetValue(toLookup, out keyHandler);
3288+
buffer.Append(",");
3289+
buffer.Append(toLookup.ToGestureString());
3290+
}
3291+
}
3292+
}
3293+
buffer.Append(": ");
3294+
if (keyHandler != null)
3295+
{
3296+
buffer.Append(keyHandler.BriefDescription);
3297+
if (!string.IsNullOrWhiteSpace(keyHandler.LongDescription))
3298+
{
3299+
buffer.Append(" - ");
3300+
buffer.Append(keyHandler.LongDescription);
3301+
}
3302+
}
3303+
else if (toLookup.KeyChar != 0)
3304+
{
3305+
buffer.Append("SelfInsert");
3306+
buffer.Append(" - ");
3307+
buffer.Append(PSReadLineResources.SelfInsertDescription);
3308+
}
3309+
else
3310+
{
3311+
buffer.Append(PSReadLineResources.KeyIsUnbound);
3312+
}
3313+
3314+
_singleton._statusLinePrompt = null;
3315+
_singleton.Render();
3316+
3317+
// Don't overwrite any of the line - so move to first line after the end of our buffer.
3318+
var coords = _singleton.ConvertOffsetToCoordinates(_singleton._buffer.Length);
3319+
_singleton.PlaceCursor(0, coords.Y + 1);
3320+
3321+
WriteLine(buffer.ToString());
3322+
_singleton._initialY = Console.CursorTop;
3323+
_singleton.Render();
3324+
}
3325+
32273326
/// <summary>
32283327
/// Turn on demo mode (display events like keys pressed)
32293328
/// </summary>
@@ -3253,6 +3352,8 @@ public static void DisableDemoMode(ConsoleKeyInfo? key = null, object arg = null
32533352
_singleton.ClearDemoWindow();
32543353
}
32553354

3355+
#endregion Miscellaneous bindable functions
3356+
32563357
private void SetOptionsInternal(SetPSReadlineOption options)
32573358
{
32583359
if (options.ContinuationPrompt != null)

PSReadLine/en-US/about_PSReadline.help.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,14 @@ LONG DESCRIPTION
337337
Turn on demo mode. Displays a window below the input line that shows which
338338
keys are typed.
339339

340+
WhatIsKey (Cmd: <Alt+?> Emacs: <Alt+?>)
341+
342+
Read a key or chord and display the key binding.
343+
344+
ShowKeyBindings (Cmd: <Ctrl+Alt+?> Emacs: <Ctrl+Alt+?>)
345+
346+
Show all of the currently bound keys.
347+
340348
Custom Key Binding Support
341349
--------------------------
342350

0 commit comments

Comments
 (0)