/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; namespace Microsoft.PowerShell { public partial class PSConsoleReadLine { /// /// Remembers last history search direction. /// private bool _searchHistoryBackward = true; private class ViCharacterSearcher { private char searchChar = '\0'; private bool wasBackward = false; private bool wasBackoff = false; public static ViCharacterSearcher instance = new ViCharacterSearcher(); public static bool IsRepeatable { get { return instance.searchChar != '\0'; } } public static char SearchChar { get { return instance.searchChar; } } public static bool WasBackward { get { return instance.wasBackward; } } public static bool WasBackoff { get { return instance.wasBackoff; } } public static void Set(char theChar, bool isBackward = false, bool isBackoff = false) { instance.searchChar = theChar; instance.wasBackward = isBackward; instance.wasBackoff = isBackoff; } public static void Search(char keyChar, object arg, bool backoff) { int qty = (arg is int) ? (int) arg : 1; for (int i = _singleton._current + 1; i < _singleton._buffer.Length; i++) { if (_singleton._buffer[i] == keyChar) { qty -= 1; if (qty == 0) { _singleton._current = backoff ? i - 1 : i; _singleton.PlaceCursor(); return; } } } Ding(); } public static bool SearchDelete(char keyChar, object arg, bool backoff, Action instigator) { int qty = (arg is int) ? (int) arg : 1; for (int i = _singleton._current + 1; i < _singleton._buffer.Length; i++) { if (_singleton._buffer[i] == keyChar) { qty -= 1; if (qty == 0) { DeleteToEndPoint(arg, backoff ? i : i + 1, instigator); return true; } } } Ding(); return false; } public static void SearchBackward(char keyChar, object arg, bool backoff) { Set(keyChar, isBackward: true, isBackoff: backoff); int qty = (arg is int) ? (int) arg : 1; for (int i = _singleton._current - 1; i >= 0; i--) { if (_singleton._buffer[i] == keyChar) { qty -= 1; if (qty == 0) { _singleton._current = backoff ? i + 1 : i; _singleton.PlaceCursor(); return; } } } Ding(); } public static bool SearchBackwardDelete(char keyChar, object arg, bool backoff, Action instigator) { Set(keyChar, isBackward: true, isBackoff: backoff); int qty = (arg is int) ? (int) arg : 1; for (int i = _singleton._current - 1; i >= 0; i--) { if (_singleton._buffer[i] == keyChar) { qty -= 1; if (qty == 0) { DeleteBackwardToEndPoint(arg, backoff ? i + 1 : i, instigator); return true; } } } Ding(); return false; } } /// /// Repeat the last recorded character search. /// public static void RepeatLastCharSearch(ConsoleKeyInfo? key = null, object arg = null) { if (!ViCharacterSearcher.IsRepeatable) { Ding(); return; } if (ViCharacterSearcher.WasBackward) { ViCharacterSearcher.SearchBackward(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); } else { ViCharacterSearcher.Search(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); } } /// /// Repeat the last recorded character search, but in the opposite direction. /// public static void RepeatLastCharSearchBackwards(ConsoleKeyInfo? key = null, object arg = null) { if (!ViCharacterSearcher.IsRepeatable) { Ding(); return; } if (ViCharacterSearcher.WasBackward) { ViCharacterSearcher.Search(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); } else { ViCharacterSearcher.SearchBackward(ViCharacterSearcher.SearchChar, null, ViCharacterSearcher.WasBackoff); } } /// /// Read the next character and then find it, going forward, and then back off a character. /// This is for 't' functionality. /// public static void SearchChar(ConsoleKeyInfo? key = null, object arg = null) { char keyChar = ReadKey().KeyChar; ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: false); ViCharacterSearcher.Search(keyChar, arg, backoff: false); } /// /// Read the next character and then find it, going backard, and then back off a character. /// This is for 'T' functionality. /// public static void SearchCharBackward(ConsoleKeyInfo? key = null, object arg = null) { char keyChar = ReadKey().KeyChar; ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: false); ViCharacterSearcher.SearchBackward(keyChar, arg, backoff: false); } /// /// Read the next character and then find it, going forward, and then back off a character. /// This is for 't' functionality. /// public static void SearchCharWithBackoff(ConsoleKeyInfo? key = null, object arg = null) { char keyChar = ReadKey().KeyChar; ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: true); ViCharacterSearcher.Search(keyChar, arg, backoff: true); } /// /// Read the next character and then find it, going backard, and then back off a character. /// This is for 'T' functionality. /// public static void SearchCharBackwardWithBackoff(ConsoleKeyInfo? key = null, object arg = null) { char keyChar = ReadKey().KeyChar; ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: true); ViCharacterSearcher.SearchBackward(keyChar, arg, backoff: true); } /// /// Exits the shell. /// public static void ViExit(ConsoleKeyInfo? key = null, object arg = null) { throw new ExitException(); } /// /// Delete to the end of the line. /// public static void DeleteToEnd(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current >= _singleton._buffer.Length) { Ding(); return; } _singleton._clipboard = _singleton._buffer.ToString(_singleton._current, _singleton._buffer.Length - _singleton._current); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, _singleton._current, DeleteToEnd, arg )); _singleton._buffer.Remove(_singleton._current, _singleton._buffer.Length - _singleton._current); _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); _singleton.Render(); } /// /// Delete the next word. /// public static void DeleteWord(ConsoleKeyInfo? key = null, object arg = null) { int qty = (arg is int) ? (int)arg : 1; int endPoint = _singleton._current; for (int i = 0; i < qty; i++) { endPoint = _singleton.ViFindNextWordPoint(endPoint, _singleton.Options.WordDelimiters); } if (endPoint <= _singleton._current) { Ding(); return; } DeleteToEndPoint(arg, endPoint, DeleteWord); } private static void DeleteToEndPoint(object arg, int endPoint, Action instigator) { _singleton.SaveToClipboard(_singleton._current, endPoint - _singleton._current); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, _singleton._current, instigator, arg )); _singleton._buffer.Remove(_singleton._current, endPoint - _singleton._current); if (_singleton._current >= _singleton._buffer.Length) { _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); } _singleton.Render(); } private static void DeleteBackwardToEndPoint(object arg, int endPoint, Action instigator) { int deleteLength = _singleton._current - endPoint + 1; _singleton.SaveToClipboard(endPoint, deleteLength); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, endPoint, instigator, arg )); _singleton._buffer.Remove(endPoint, deleteLength); _singleton._current = endPoint; _singleton.Render(); } /// /// Delete the next glob (white space delimited word). /// public static void ViDeleteGlob(ConsoleKeyInfo? key = null, object arg = null) { int qty = (arg is int) ? (int)arg : 1; int endPoint = _singleton._current; while (qty-- > 0) { endPoint = _singleton.ViFindNextGlob(endPoint); } int length = endPoint - _singleton._current; _singleton.SaveToClipboard(_singleton._current, length); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, _singleton._current, ViDeleteGlob, arg )); _singleton._buffer.Remove(_singleton._current, length); if (_singleton._current >= _singleton._buffer.Length) { _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); } _singleton.Render(); } /// /// Delete to the end of the word. /// public static void DeleteEndOfWord(ConsoleKeyInfo? key = null, object arg = null) { int qty = (arg is int) ? (int)arg : 1; int endPoint = _singleton._current; for (int i = 0; i < qty; i++) { endPoint = _singleton.ViFindNextWordEnd(endPoint, _singleton.Options.WordDelimiters); } if (endPoint <= _singleton._current) { Ding(); return; } _singleton.SaveToClipboard(_singleton._current, 1 + endPoint - _singleton._current); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, _singleton._current, DeleteEndOfWord, arg )); _singleton._buffer.Remove(_singleton._current, 1 + endPoint - _singleton._current); if (_singleton._current >= _singleton._buffer.Length) { _singleton._current = Math.Max(0,_singleton._buffer.Length - 1); } _singleton.Render(); } /// /// Delete to the end of the word. /// public static void ViDeleteEndOfGlob(ConsoleKeyInfo? key = null, object arg = null) { int qty = (arg is int) ? (int)arg : 1; int endPoint = _singleton._current; for (int i = 0; i < qty; i++) { endPoint = _singleton.ViFindGlobEnd(endPoint); } _singleton.SaveToClipboard(_singleton._current, 1 + endPoint - _singleton._current); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, _singleton._current, ViDeleteEndOfGlob, arg )); _singleton._buffer.Remove(_singleton._current, 1 + endPoint - _singleton._current); if (_singleton._current >= _singleton._buffer.Length) { _singleton._current = Math.Max(0, _singleton._buffer.Length - 1); } _singleton.Render(); } /// /// Deletes until given character /// /// /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] private static void ViDeleteToChar(ConsoleKeyInfo? key = null, object arg = null) { var keyChar = ReadKey().KeyChar; ViDeleteToChar(keyChar, key, arg); } private static void ViDeleteToChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) { ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: false); ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViDeleteToChar(keyChar, _key, _arg)); } /// /// Deletes until given character /// /// /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] private static void ViDeleteToCharBackward(ConsoleKeyInfo? key = null, object arg = null) { var keyChar = ReadKey().KeyChar; ViDeleteToCharBack(keyChar, key, arg); } private static void ViDeleteToCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) { ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: false, instigator: (_key, _arg) => ViDeleteToCharBack(keyChar, _key, _arg)); } /// /// Deletes until given character /// /// /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] private static void ViDeleteToBeforeChar(ConsoleKeyInfo? key = null, object arg = null) { var keyChar = ReadKey().KeyChar; ViDeleteToBeforeChar(keyChar, key, arg); } private static void ViDeleteToBeforeChar(char keyChar, ConsoleKeyInfo? key = null, object arg = null) { ViCharacterSearcher.Set(keyChar, isBackward: false, isBackoff: true); ViCharacterSearcher.SearchDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViDeleteToBeforeChar(keyChar, _key, _arg)); } /// /// Deletes until given character /// /// /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] private static void ViDeleteToBeforeCharBackward(ConsoleKeyInfo? key = null, object arg = null) { var keyChar = ReadKey().KeyChar; ViDeleteToBeforeCharBack(keyChar, key, arg); } private static void ViDeleteToBeforeCharBack(char keyChar, ConsoleKeyInfo? key = null, object arg = null) { ViCharacterSearcher.Set(keyChar, isBackward: true, isBackoff: true); ViCharacterSearcher.SearchBackwardDelete(keyChar, arg, backoff: true, instigator: (_key, _arg) => ViDeleteToBeforeCharBack(keyChar, _key, _arg)); } /// /// Ring the bell. /// private static void Ding(ConsoleKeyInfo? key = null, object arg = null) { Ding(); } /// /// Switch the current operating mode from Vi-Insert to Vi-Command. /// public static void ViCommandMode(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._editGroupStart >= 0) { _singleton._groupUndoHelper.EndGroup(); } _singleton._dispatchTable = _viCmdKeyMap; _singleton._chordDispatchTable = _viCmdChordTable; BackwardChar(); _singleton.PlaceCursor(); _singleton.ViIndicateCommandMode(); } /// /// Switch to Insert mode. /// public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null) { _singleton._dispatchTable = _viInsKeyMap; _singleton._chordDispatchTable = _viInsChordTable; _singleton.ViIndicateInsertMode(); } /// /// Temporarily swap in Vi-Command dispatch tables. Used for setting handlers. /// internal static IDisposable UseViCommandModeTables() { var oldDispatchTable = _singleton._dispatchTable; var oldChordDispatchTable = _singleton._chordDispatchTable; _singleton._dispatchTable = _viCmdKeyMap; _singleton._chordDispatchTable = _viCmdChordTable; return new Disposable( () => { _singleton._dispatchTable = oldDispatchTable; _singleton._chordDispatchTable = oldChordDispatchTable; } ); } /// /// Temporarily swap in Vi-Insert dispatch tables. Used for setting handlers. /// internal static IDisposable UseViInsertModeTables() { var oldDispatchTable = _singleton._dispatchTable; var oldChordDispatchTable = _singleton._chordDispatchTable; _singleton._dispatchTable = _viInsKeyMap; _singleton._chordDispatchTable = _viInsChordTable; return new Disposable( () => { _singleton._dispatchTable = oldDispatchTable; _singleton._chordDispatchTable = oldChordDispatchTable; } ); } private void ViIndicateCommandMode() { if (_options.ViModeIndicator == ViModeStyle.Cursor) { _console.CursorSize = _normalCursorSize < 50 ? 100 : 25; } else if (_options.ViModeIndicator == ViModeStyle.Prompt) { ConsoleColor savedBackground = _console.BackgroundColor; _console.BackgroundColor = AlternateBackground(_console.BackgroundColor); InvokePrompt(); _console.BackgroundColor = savedBackground; } } private void ViIndicateInsertMode() { if (_options.ViModeIndicator == ViModeStyle.Cursor) { _console.CursorSize = _normalCursorSize; } else if (_options.ViModeIndicator == ViModeStyle.Prompt) { InvokePrompt(); } } /// /// Switch to Insert mode and position the cursor at the begining of the line. /// public static void ViInsertAtBegining(ConsoleKeyInfo? key = null, object arg = null) { ViInsertMode(key, arg); BeginningOfLine(key, arg); } /// /// Switch to Insert mode and position the cursor at the end of the line. /// public static void ViInsertAtEnd(ConsoleKeyInfo? key = null, object arg = null) { ViInsertMode(key, arg); EndOfLine(key, arg); } /// /// Append from the current line position. /// public static void ViInsertWithAppend(ConsoleKeyInfo? key = null, object arg = null) { ViInsertMode(key, arg); ForwardChar(key, arg); } /// /// Delete the current character and switch to Insert mode. /// public static void ViInsertWithDelete(ConsoleKeyInfo? key = null, object arg = null) { _singleton._groupUndoHelper.StartGroup(ViInsertWithDelete, arg); DeleteChar(key, arg); ViInsertMode(key, arg); } /// /// Accept the line and switch to Insert mode. /// public static void ViAcceptLine(ConsoleKeyInfo? key = null, object arg = null) { ViInsertMode(key, arg); AcceptLine(key, arg); } /// /// Prepend a '#' and accept the line. /// public static void PrependAndAccept(ConsoleKeyInfo? key = null, object arg = null) { BeginningOfLine(key, arg); SelfInsert(key, arg); ViAcceptLine(key, arg); } /// /// Invert the case of the current character and move to the next one. /// public static void InvertCase(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current >= _singleton._buffer.Length) { Ding(); return; } int qty = (arg is int) ? (int) arg : 1; for (; qty > 0 && _singleton._current < _singleton._buffer.Length; qty--) { char c = _singleton._buffer[_singleton._current]; if (Char.IsLetter(c)) { char newChar = Char.IsUpper(c) ? Char.ToLower(c) : char.ToUpper(c); EditItem delEditItem = EditItemDelete.Create(c.ToString(), _singleton._current); EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current); _singleton.SaveEditItem(GroupedEdit.Create(new List { delEditItem, insEditItem }, InvertCase, arg )); _singleton._buffer[_singleton._current] = newChar; } _singleton._current = Math.Min(_singleton._current + 1, _singleton._buffer.Length); _singleton.PlaceCursor(); } _singleton.Render(); } /// /// Swap the current character and the one before it. /// public static void SwapCharacters(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current <= 0 || _singleton._current >= _singleton._buffer.Length) { Ding(); return; } char current = _singleton._buffer[_singleton._current]; char previous = _singleton._buffer[_singleton._current - 1]; _singleton.StartEditGroup(); _singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer.ToString(_singleton._current - 1, 2), _singleton._current - 1)); _singleton.SaveEditItem(EditItemInsertChar.Create(current, _singleton._current - 1)); _singleton.SaveEditItem(EditItemInsertChar.Create(previous, _singleton._current)); _singleton.EndEditGroup(); _singleton._buffer[_singleton._current] = previous; _singleton._buffer[_singleton._current - 1] = current; _singleton._current = Math.Min(_singleton._current + 1, _singleton._buffer.Length - 1); _singleton.PlaceCursor(); _singleton.Render(); } /// /// Deletes text from the cursor to the first non-blank character of the line, /// public static void DeleteLineToFirstChar(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current > 0) { int i = 0; for (; i < _singleton._current; i++) { if (!Char.IsWhiteSpace(_singleton._buffer[i])) { break; } } _singleton.SaveToClipboard(i, _singleton._current - i); _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, i, DeleteLineToFirstChar)); _singleton._buffer.Remove(i, _singleton._current - i); _singleton._current = i; _singleton.Render(); } else { Ding(); } } /// /// Deletes the current line, enabling undo. /// public static void DeleteLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton._clipboard = _singleton._buffer.ToString(); _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, 0)); _singleton._current = 0; _singleton._buffer.Remove(0, _singleton._buffer.Length); _singleton.Render(); } /// /// Deletes the previous word. /// public static void BackwardDeleteWord(ConsoleKeyInfo? key = null, object arg = null) { int qty = (arg is int) ? (int) arg : 1; int deletePoint = _singleton._current; for (int i = 0; i < qty; i++) { deletePoint = _singleton.ViFindPreviousWordPoint(deletePoint, _singleton.Options.WordDelimiters); } if (deletePoint == _singleton._current) { Ding(); return; } _singleton._clipboard = _singleton._buffer.ToString(deletePoint, _singleton._current - deletePoint); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, deletePoint, BackwardDeleteWord, arg )); _singleton._buffer.Remove(deletePoint, _singleton._current - deletePoint); _singleton._current = deletePoint; _singleton.Render(); } /// /// Deletes the previous word, using only white space as the word delimiter. /// public static void ViBackwardDeleteGlob(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._current == 0) { Ding(); return; } int qty = (arg is int) ? (int)arg : 1; int deletePoint = _singleton._current; for (int i = 0; i < qty && deletePoint > 0; i++) { deletePoint = _singleton.ViFindPreviousGlob(deletePoint - 1); } if (deletePoint == _singleton._current) { Ding(); return; } _singleton._clipboard = _singleton._buffer.ToString(deletePoint, _singleton._current - deletePoint); _singleton.SaveEditItem(EditItemDelete.Create( _singleton._clipboard, deletePoint, BackwardDeleteWord, arg )); _singleton._buffer.Remove(deletePoint, _singleton._current - deletePoint); _singleton._current = deletePoint; _singleton.Render(); } /// /// Find the matching brace, paren, or square bracket and delete all contents within, including the brace. /// public static void ViDeleteBrace(ConsoleKeyInfo? key = null, object arg = null) { int newCursor = _singleton.ViFindBrace(_singleton._current); if (_singleton._current < newCursor) { DeleteRange(_singleton._current, newCursor, ViDeleteBrace); } else if (newCursor < _singleton._current) { DeleteRange(newCursor, _singleton._current, ViDeleteBrace); } else { Ding(); } } /// /// Delete all characters included in the supplied range. /// /// Index of where to begin the delete. /// Index of where to end the delete. /// Action that generated this request, used for repeat command ('.'). private static void DeleteRange(int first, int last, Action action) { int length = last - first + 1; _singleton.SaveToClipboard(first, length); _singleton.SaveEditItem(EditItemDelete.Create(_singleton._clipboard, first, action)); _singleton._current = first; _singleton._buffer.Remove(first, length); _singleton.Render(); } /// /// Prompts for a search string and initiates search upon AcceptLine. /// public static void ViSearchHistoryBackward(ConsoleKeyInfo? key = null, object arg = null) { if (!key.HasValue || char.IsControl(key.Value.KeyChar)) { Ding(); return; } _singleton.StartSearch(backward: true); } /// /// Prompts for a search string and initiates search upon AcceptLine. /// public static void SearchForward(ConsoleKeyInfo? key = null, object arg = null) { if (!key.HasValue || char.IsControl(key.Value.KeyChar)) { Ding(); return; } _singleton.StartSearch(backward: false); } /// /// Repeat the last search in the same direction as before. /// public static void RepeatSearch(ConsoleKeyInfo? key = null, object arg = null) { if (string.IsNullOrEmpty(_singleton._searchHistoryPrefix)) { Ding(); return; } _singleton.HistorySearch(); } /// /// Repeat the last search in the same direction as before. /// public static void RepeatSearchBackward(ConsoleKeyInfo? key = null, object arg = null) { _singleton._searchHistoryBackward = !_singleton._searchHistoryBackward; RepeatSearch(); _singleton._searchHistoryBackward = !_singleton._searchHistoryBackward; } /// /// Prompts for a string for history searching. /// /// True for seaching backward in the history. private void StartSearch(bool backward) { _statusLinePrompt = "find: "; var argBuffer = _statusBuffer; Render(); // Render prompt while (true) { var nextKey = ReadKey(); if (nextKey.Key == Keys.Enter.Key || nextKey.Key == Keys.Tab.Key) { _searchHistoryPrefix = argBuffer.ToString(); _searchHistoryBackward = backward; HistorySearch(); break; } if (nextKey.Key == Keys.Escape.Key) { break; } if (nextKey.Key == Keys.Backspace.Key) { if (argBuffer.Length > 0) { argBuffer.Remove(argBuffer.Length - 1, 1); Render(); // Render prompt continue; } break; } argBuffer.Append(nextKey.KeyChar); Render(); // Render prompt } // Remove our status line argBuffer.Clear(); _statusLinePrompt = null; Render(); // Render prompt } /// /// Searches line history. /// private void HistorySearch() { _searchHistoryCommandCount++; int incr = _searchHistoryBackward ? -1 : +1; for (int i = _currentHistoryIndex + incr; i >= 0 && i < _history.Count; i += incr) { if (Options.HistoryStringComparison.HasFlag(StringComparison.OrdinalIgnoreCase)) { if (_history[i]._line.ToLower().Contains(_searchHistoryPrefix.ToLower())) { _currentHistoryIndex = i; UpdateFromHistory(moveCursor: Options.HistorySearchCursorMovesToEnd); return; } } else { if (_history[i]._line.Contains(_searchHistoryPrefix)) { _currentHistoryIndex = i; UpdateFromHistory(moveCursor: Options.HistorySearchCursorMovesToEnd); return; } } } Ding(); } /// /// Repeat the last text modification. /// public static void RepeatLastCommand(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._undoEditIndex > 0) { EditItem editItem = _singleton._edits[_singleton._undoEditIndex - 1]; if (editItem._instigator != null) { editItem._instigator(key, editItem._instigatorArg); return; } } Ding(); } /// /// Chords in vi needs special handling because a numeric argument can be input between the 1st and 2nd key. /// /// /// private static void ViChord(ConsoleKeyInfo? key = null, object arg = null) { if (!key.HasValue) { throw new ArgumentNullException("key"); } if (arg != null) { Chord(key, arg); return; } Dictionary secondKeyDispatchTable; if (_singleton._chordDispatchTable.TryGetValue(key.Value, out secondKeyDispatchTable)) { //if (_singleton._demoMode) //{ // // Render so the first key of the chord appears in the demo window // _singleton.Render(); //} var secondKey = ReadKey(); KeyHandler handler; if (secondKeyDispatchTable.TryGetValue(secondKey, out handler)) { _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); } else if (!IsNumberic(secondKey)) { _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: arg); } else { var argBuffer = _singleton._statusBuffer; argBuffer.Clear(); _singleton._statusLinePrompt = "digit-argument: "; while (IsNumberic(secondKey)) { argBuffer.Append(secondKey.KeyChar); _singleton.Render(); secondKey = ReadKey(); } int numericArg = int.Parse(argBuffer.ToString()); if (secondKeyDispatchTable.TryGetValue(secondKey, out handler)) { _singleton.ProcessOneKey(secondKey, secondKeyDispatchTable, ignoreIfNoAction: true, arg: numericArg); } else { Ding(); } argBuffer.Clear(); _singleton.ClearStatusMessage(render: true); } } } private static bool IsNumberic(ConsoleKeyInfo key) { return char.IsNumber(key.KeyChar); } /// /// Start a new digit argument to pass to other functions while in one of vi's chords. /// public static void ViDigitArgumentInChord(ConsoleKeyInfo? key = null, object arg = null) { if (!key.HasValue || char.IsControl(key.Value.KeyChar)) { Ding(); return; } #region VI special case if (_singleton._options.EditMode == EditMode.Vi && key.Value.KeyChar == '0') { BeginningOfLine(); return; } #endregion VI special case bool sawDigit = false; _singleton._statusLinePrompt = "digit-argument: "; var argBuffer = _singleton._statusBuffer; argBuffer.Append(key.Value.KeyChar); if (key.Value.KeyChar == '-') { argBuffer.Append('1'); } else { sawDigit = true; } _singleton.Render(); // Render prompt while (true) { var nextKey = ReadKey(); KeyHandler handler; if (_singleton._dispatchTable.TryGetValue(nextKey, out handler) && handler.Action == DigitArgument) { if (nextKey.KeyChar == '-') { if (argBuffer[0] == '-') { argBuffer.Remove(0, 1); } else { argBuffer.Insert(0, '-'); } _singleton.Render(); // Render prompt continue; } if (nextKey.KeyChar >= '0' && nextKey.KeyChar <= '9') { if (!sawDigit && argBuffer.Length > 0) { // Buffer is either '-1' or '1' from one or more Alt+- keys // but no digits yet. Remove the '1'. argBuffer.Length -= 1; } sawDigit = true; argBuffer.Append(nextKey.KeyChar); _singleton.Render(); // Render prompt continue; } } int intArg; if (int.TryParse(argBuffer.ToString(), out intArg)) { _singleton.ProcessOneKey(nextKey, _singleton._dispatchTable, ignoreIfNoAction: false, arg: intArg); } else { Ding(); } break; } } /// /// Like DeleteCharOrExit in Emacs mode, but accepts the line instead of deleting a character. /// public static void ViAcceptLineOrExit(ConsoleKeyInfo? key = null, object arg = null) { if (_singleton._buffer.Length > 0) { _singleton.AcceptLineImpl(false); } else { ViExit(key, arg); } } /// /// A new line is inserted above the current line. /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void ViInsertLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton._groupUndoHelper.StartGroup(ViInsertLine, arg); _singleton.MoveToBeginningOfPhrase(); _singleton._buffer.Insert(_singleton._current, '\n'); //_singleton._current = Math.Max(0, _singleton._current - 1); _singleton.SaveEditItem(EditItemInsertChar.Create( '\n', _singleton._current)); _singleton.Render(); ViInsertMode(); } private void MoveToBeginningOfPhrase() { while (!IsAtBeginningOfPhrase()) { _current--; } } private bool IsAtBeginningOfPhrase() { if (_current == 0) { return true; } if (_buffer[_current - 1] == '\n') { return true; } return false; } /// /// A new line is inserted below the current line. /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void ViAppendLine(ConsoleKeyInfo? key = null, object arg = null) { _singleton._groupUndoHelper.StartGroup(ViInsertLine, arg); _singleton.MoveToEndOfPhrase(); int insertPoint = 0; if (_singleton.IsAtEndOfLine(_singleton._current)) { insertPoint = _singleton._buffer.Length; _singleton._buffer.Append('\n'); _singleton._current = insertPoint; } else { insertPoint = _singleton._current + 1; _singleton._buffer.Insert(insertPoint, '\n'); } _singleton.SaveEditItem(EditItemInsertChar.Create('\n', insertPoint)); _singleton.Render(); ViInsertWithAppend(); } private void MoveToEndOfPhrase() { while (!IsAtEndOfPhrase()) { _current++; } } private bool IsAtEndOfPhrase() { if (_buffer.Length == 0 || _current == _buffer.Length + ViEndOfLineFactor) { return true; } if (_buffer[_current] == '\n') { return true; } return false; } /// /// Joins 2 lines. /// [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static void ViJoinLines(ConsoleKeyInfo? key = null, object arg = null) { _singleton.MoveToEndOfPhrase(); if (_singleton.IsAtEndOfLine(_singleton._current)) { Ding(); } else { _singleton._buffer[_singleton._current] = ' '; _singleton._groupUndoHelper.StartGroup(ViJoinLines, arg); _singleton.SaveEditItem(EditItemDelete.Create("\n", _singleton._current)); _singleton.SaveEditItem(EditItemInsertChar.Create(' ', _singleton._current)); _singleton._groupUndoHelper.EndGroup(); _singleton.Render(); } } } }