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