diff --git a/PSReadLine/Cmdlets.cs b/PSReadLine/Cmdlets.cs index 2917f6a..aef51c9 100644 --- a/PSReadLine/Cmdlets.cs +++ b/PSReadLine/Cmdlets.cs @@ -443,14 +443,16 @@ namespace PSConsoleUtilities // ValidateSet attribute is generated by the script GenerateBuiltinList [ValidateSet("Abort", "AcceptLine", "AddLine", "BackwardChar", "BackwardDeleteChar", - "BackwardDeleteLine", "BackwardKillLine", "BeginningOfHistory", "BeginningOfLine", "CancelLine", - "ClearScreen", "Complete", "DeleteChar", "DisableDemoMode", "EnableDemoMode", - "EndOfHistory", "EndOfLine", "ExchangePointAndMark", "ForwardChar", "ForwardDeleteLine", - "ForwardSearchHistory", "GotoBrace", "HistorySearchBackward", "HistorySearchForward", "ShellBackwardKillWord", - "KillLine", "ShellKillWord", "NextHistory", "Paste", "PossibleCompletions", - "PreviousHistory", "Redo", "ReverseSearchHistory", "RevertLine", "SetKeyHandler", - "SetMark", "ShellBackwardWord", "ShellForwardWord", "TabCompleteNext", "TabCompletePrevious", - "Undo", "Yank", "YankPop")] + "BackwardDeleteLine", "BackwardKillLine", "BackwardKillWord", "BackwardWord", "BeginningOfHistory", + "BeginningOfLine", "CancelLine", "ClearScreen", "Complete", "DeleteChar", + "DigitArgument", "DisableDemoMode", "EnableDemoMode", "EndOfHistory", "EndOfLine", + "ExchangePointAndMark", "ForwardChar", "ForwardDeleteLine", "ForwardSearchHistory", "ForwardWord", + "GotoBrace", "HistorySearchBackward", "HistorySearchForward", "KillLine", "KillWord", + "NextHistory", "NextWord", "Paste", "PossibleCompletions", "PreviousHistory", + "Redo", "ReverseSearchHistory", "RevertLine", "SelfInsert", "SetKeyHandler", + "SetMark", "ShellBackwardKillWord", "ShellBackwardWord", "ShellForwardWord", "ShellKillWord", + "TabCompleteNext", "TabCompletePrevious", "Undo", "UnixWordRubout", "Yank", + "YankLastArg", "YankNthArg", "YankPop")] [Parameter(Position = 1, Mandatory = true, ParameterSetName = "Function")] public string Function { get; set; } diff --git a/PSReadLine/Keys.cs b/PSReadLine/Keys.cs index ffd9c59..609dfb0 100644 --- a/PSReadLine/Keys.cs +++ b/PSReadLine/Keys.cs @@ -109,6 +109,7 @@ namespace PSConsoleUtilities public static ConsoleKeyInfo Equals = new ConsoleKeyInfo('=', ConsoleKey.OemPlus, false, false, false); public static ConsoleKeyInfo CtrlAt = new ConsoleKeyInfo((char)0, ConsoleKey.D2, true, false, true); + public static ConsoleKeyInfo AltUnderbar = new ConsoleKeyInfo('_', ConsoleKey.OemMinus, true, true, false); public static ConsoleKeyInfo CtrlUnderbar = new ConsoleKeyInfo((char)31, ConsoleKey.OemMinus, true, false, true); public static ConsoleKeyInfo AltEquals = new ConsoleKeyInfo('=', ConsoleKey.OemPlus, false, true, false); public static ConsoleKeyInfo Space = new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false); @@ -118,6 +119,7 @@ namespace PSConsoleUtilities public static ConsoleKeyInfo AltLess = new ConsoleKeyInfo('<', ConsoleKey.OemComma, true, true, false); public static ConsoleKeyInfo AltGreater = new ConsoleKeyInfo('>', ConsoleKey.OemPeriod, true, true, false); public static ConsoleKeyInfo CtrlRBracket = new ConsoleKeyInfo((char)29, ConsoleKey.Oem6, false, false, true); + public static ConsoleKeyInfo AltPeriod = new ConsoleKeyInfo('.', ConsoleKey.OemPeriod, false, true, false); public static ConsoleKeyInfo Alt0 = new ConsoleKeyInfo('0', ConsoleKey.D0, false, true, false); public static ConsoleKeyInfo Alt1 = new ConsoleKeyInfo('1', ConsoleKey.D1, false, true, false); @@ -184,6 +186,8 @@ namespace PSConsoleUtilities public static ConsoleKeyInfo CtrlY = new ConsoleKeyInfo((char)25, ConsoleKey.Y, false, false, true); public static ConsoleKeyInfo CtrlZ = new ConsoleKeyInfo((char)26, ConsoleKey.Z, false, false, true); + public static ConsoleKeyInfo AltCtrlY = new ConsoleKeyInfo((char)0, ConsoleKey.Y, false, true, true); + public static ConsoleKeyInfo Backspace = new ConsoleKeyInfo((char)8, ConsoleKey.Backspace, false, false, false); public static ConsoleKeyInfo CtrlBackspace = new ConsoleKeyInfo((char)0x7f, ConsoleKey.Backspace, false, false, true); public static ConsoleKeyInfo AltBackspace = new ConsoleKeyInfo((char)8, ConsoleKey.Backspace, false, true, false); diff --git a/PSReadLine/PSReadLineResources.Designer.cs b/PSReadLine/PSReadLineResources.Designer.cs index 59e87b1..426b4eb 100644 --- a/PSReadLine/PSReadLineResources.Designer.cs +++ b/PSReadLine/PSReadLineResources.Designer.cs @@ -537,6 +537,24 @@ namespace PSConsoleUtilities { } } + /// + /// Looks up a localized string similar to Copy the text of the last argument to the input. + /// + internal static string YankLastArgDescription { + get { + return ResourceManager.GetString("YankLastArgDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Copy the text of the first argument to the input. + /// + internal static string YankNthArgDescription { + get { + return ResourceManager.GetString("YankNthArgDescription", resourceCulture); + } + } + /// /// Looks up a localized string similar to Replace the previously yanked text with the text from the next kill ring position. /// diff --git a/PSReadLine/PSReadLineResources.resx b/PSReadLine/PSReadLineResources.resx index 599cda3..d628f97 100644 --- a/PSReadLine/PSReadLineResources.resx +++ b/PSReadLine/PSReadLineResources.resx @@ -279,4 +279,10 @@ Move the text from the cursor to the start of the current or previous whitespace delimited word to the kill ring + + Copy the text of the last argument to the input + + + Copy the text of the first argument to the input + \ No newline at end of file diff --git a/PSReadLine/ReadLine.cs b/PSReadLine/ReadLine.cs index aaf0ab1..0107e7b 100644 --- a/PSReadLine/ReadLine.cs +++ b/PSReadLine/ReadLine.cs @@ -169,6 +169,15 @@ namespace PSConsoleUtilities private int _killCommandCount; private int _yankCommandCount; private int _yankStartPoint; + private int _yankLastArgCommandCount; + class YankLastArgState + { + internal int argument; + internal int historyIndex; + internal int historyIncrement; + internal int startPoint = -1; + } + private YankLastArgState _yankLastArgState; // Tab completion state private int _tabCommandCount; @@ -313,6 +322,7 @@ namespace PSConsoleUtilities var yankCommandCount = _yankCommandCount; var tabCommandCount = _tabCommandCount; var searchHistoryCommandCount = _searchHistoryCommandCount; + var yankLastArgCommandCount = _yankLastArgCommandCount; var key = ReadKey(); ProcessOneKey(key, _dispatchTable, ignoreIfNoAction: false, arg: null); @@ -331,6 +341,12 @@ namespace PSConsoleUtilities // Reset yank command count if it didn't change _yankCommandCount = 0; } + if (yankLastArgCommandCount == _yankLastArgCommandCount) + { + // Reset yank last arg command count if it didn't change + _yankLastArgCommandCount = 0; + _yankLastArgState = null; + } if (tabCommandCount == _tabCommandCount) { // Reset tab command count if it didn't change @@ -514,6 +530,9 @@ namespace PSConsoleUtilities { Keys.AltBackspace, MakeKeyHandler(BackwardKillWord, "BackwardKillWord") }, { Keys.AltEquals, MakeKeyHandler(PossibleCompletions, "PossibleCompletions") }, { 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") }, { Keys.VolumeDown, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeUp, MakeKeyHandler(Ignore, "Ignore") }, { Keys.VolumeMute, MakeKeyHandler(Ignore, "Ignore") }, @@ -526,7 +545,10 @@ namespace PSConsoleUtilities { 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") }, }; _emacsCtrlXMap = new Dictionary(new ConsoleKeyInfoComparer()) @@ -1657,12 +1679,14 @@ namespace PSConsoleUtilities return; } + bool firstKeyAfterNegative = false; _singleton._statusLinePrompt = "digit-argument: "; var argBuffer = _singleton._statusBuffer; argBuffer.Append(key.Value.KeyChar); if (key.Value.KeyChar == '-') { argBuffer.Append('1'); + firstKeyAfterNegative = true; } _singleton.Render(); // Render prompt @@ -1682,11 +1706,17 @@ namespace PSConsoleUtilities { argBuffer.Insert(0, '-'); } + _singleton.Render(); // Render prompt continue; } if (nextKey.KeyChar >= '0' && nextKey.KeyChar <= '9') { + if (firstKeyAfterNegative) + { + argBuffer.Remove(1, 1); + firstKeyAfterNegative = false; + } argBuffer.Append(nextKey.KeyChar); _singleton.Render(); // Render prompt continue; @@ -1907,6 +1937,109 @@ namespace PSConsoleUtilities _singleton.YankPopImpl(); } + void YankArgImpl(YankLastArgState yankLastArgState) + { + Debug.Assert(yankLastArgState.historyIndex >= 0 && yankLastArgState.historyIndex < _history.Count); + + Token[] tokens; + ParseError[] errors; + var buffer = _history[yankLastArgState.historyIndex]; + Parser.ParseInput(buffer._line, out tokens, out errors); + + int arg = (yankLastArgState.argument < 0) + ? tokens.Length + yankLastArgState.argument - 1 + : yankLastArgState.argument; + if (arg < 0 || arg >= tokens.Length) + { + Ding(); + return; + } + + var argText = tokens[arg].Text; + if (yankLastArgState.startPoint < 0) + { + yankLastArgState.startPoint = _current; + Insert(argText); + } + else + { + Replace(yankLastArgState.startPoint, _current - yankLastArgState.startPoint, argText); + } + } + + /// + /// Yank the first argument (after the command) from the previous history line. + /// With an argument, yank the nth argument (starting from 0), if the argument + /// is negative, start from the last argument. + /// + public static void YankNthArg(ConsoleKeyInfo? key = null, object arg = null) + { + var yankLastArgState = new YankLastArgState + { + argument = (arg is int) ? (int)arg : 1, + historyIndex = _singleton._currentHistoryIndex - 1, + }; + _singleton.YankArgImpl(yankLastArgState); + } + + /// + /// Yank the last argument from the previous history line. With an argument, + /// the first time it is invoked, behaves just like YankNthArg. If invoked + /// multiple times, instead it iterates through history and arg sets the direction + /// (negative reverses the direction.) + /// + public static void YankLastArg(ConsoleKeyInfo? key = null, object arg = null) + { + _singleton._yankLastArgCommandCount += 1; + + if (_singleton._yankLastArgCommandCount == 1) + { + _singleton._yankLastArgState = new YankLastArgState + { + argument = (arg is int) ? (int)arg : -1, + historyIncrement = -1, + historyIndex = _singleton._currentHistoryIndex - 1 + }; + + _singleton.YankArgImpl(_singleton._yankLastArgState); + return; + } + + var yankLastArgState = _singleton._yankLastArgState; + + if (arg != null) + { + if (!(arg is int)) + { + Ding(); + return; + } + + if ((int)arg < 0) + { + yankLastArgState.historyIncrement = -yankLastArgState.historyIncrement; + } + } + + yankLastArgState.historyIndex += yankLastArgState.historyIncrement; + + // Don't increment more than 1 out of range so it's quick to get back to being in range. + if (yankLastArgState.historyIndex < 0) + { + Ding(); + yankLastArgState.historyIndex = 0; + } + else if (yankLastArgState.historyIndex >= _singleton._history.Count) + { + Ding(); + yankLastArgState.historyIndex = _singleton._history.Count - 1; + } + else + { + _singleton.YankArgImpl(yankLastArgState); + } + } + #endregion Kill/Yank private enum FindTokenMode