Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[vi-mode] Undo now leaves the cursor under the position at the start of the deletion #2045

Merged
merged 9 commits into from
Sep 22, 2021
26 changes: 17 additions & 9 deletions PSReadLine/BasicEditing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public static void CancelLine(ConsoleKeyInfo? key = null, object arg = null)
/// </summary>
public static void ForwardDeleteInput(ConsoleKeyInfo? key = null, object arg = null)
{
ForwardDeleteImpl(_singleton._buffer.Length);
ForwardDeleteImpl(_singleton._buffer.Length, ForwardDeleteInput);
}

/// <summary>
Expand All @@ -111,15 +111,15 @@ public static void ForwardDeleteInput(ConsoleKeyInfo? key = null, object arg = n
/// </summary>
public static void ForwardDeleteLine(ConsoleKeyInfo? key = null, object arg = null)
{
ForwardDeleteImpl(GetEndOfLogicalLinePos(_singleton._current) + 1);
ForwardDeleteImpl(GetEndOfLogicalLinePos(_singleton._current) + 1, ForwardDeleteLine);
}

/// <summary>
/// Deletes text from the cursor position to the specified end position
/// but does not put the deleted text in the kill ring.
/// </summary>
/// <param name="endPosition">0-based offset to one character past the end of the text.</param>
private static void ForwardDeleteImpl(int endPosition)
private static void ForwardDeleteImpl(int endPosition, Action<ConsoleKeyInfo?, object> instigator)
{
var current = _singleton._current;
var buffer = _singleton._buffer;
Expand All @@ -128,7 +128,15 @@ private static void ForwardDeleteImpl(int endPosition)
{
int length = endPosition - current;
var str = buffer.ToString(current, length);
_singleton.SaveEditItem(EditItemDelete.Create(str, current));

_singleton.SaveEditItem(
EditItemDelete.Create(
str,
current,
instigator,
instigatorArg: null,
!InViEditMode()));

buffer.Remove(current, length);
_singleton.Render();
}
Expand All @@ -152,13 +160,13 @@ public static void BackwardDeleteLine(ConsoleKeyInfo? key = null, object arg = n
BackwardDeleteSubstring(position, BackwardDeleteLine);
}

private static void BackwardDeleteSubstring(int position, Action<ConsoleKeyInfo?, object> instigator = null)
private static void BackwardDeleteSubstring(int position, Action<ConsoleKeyInfo?, object> instigator)
{
if (_singleton._current > position)
{
var count = _singleton._current - position;
_singleton.RemoveTextToViRegister(position, count, instigator);

_singleton.RemoveTextToViRegister(position, count, instigator, arg: null, !InViEditMode());
_singleton._current = position;
_singleton.Render();
}
Expand All @@ -184,7 +192,7 @@ public static void BackwardDeleteChar(ConsoleKeyInfo? key = null, object arg = n

int startDeleteIndex = _singleton._current - qty;

_singleton.RemoveTextToViRegister(startDeleteIndex, qty, BackwardDeleteChar, arg);
_singleton.RemoveTextToViRegister(startDeleteIndex, qty, BackwardDeleteChar, arg, !InViEditMode());
_singleton._current = startDeleteIndex;
_singleton.Render();
}
Expand All @@ -205,7 +213,7 @@ private void DeleteCharImpl(int qty, bool orExit)
{
qty = Math.Min(qty, _singleton._buffer.Length - _singleton._current);

RemoveTextToViRegister(_current, qty, DeleteChar, qty);
RemoveTextToViRegister(_current, qty, DeleteChar, qty, !InViEditMode());
if (_current >= _buffer.Length)
{
_current = Math.Max(0, _buffer.Length + ViEndOfLineFactor);
Expand Down
11 changes: 8 additions & 3 deletions PSReadLine/Prediction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ public static void AcceptSuggestion(ConsoleKeyInfo? key = null, object arg = nul
inlineView.OnSuggestionAccepted();

using var _ = prediction.DisableScoped();
Replace(0, _singleton._buffer.Length, inlineView.SuggestionText);

_singleton._current = _singleton._buffer.Length;
Insert(inlineView.SuggestionText.Substring(_singleton._current));
}
}

Expand Down Expand Up @@ -105,14 +107,17 @@ private static void AcceptNextSuggestionWord(int numericArg)
// Ignore the visual selection.
_singleton._visualSelectionCommandCount = 0;

int index = _singleton._buffer.Length;
int start = _singleton._buffer.Length;
int index = start;
while (numericArg-- > 0 && index < inlineView.SuggestionText.Length)
{
index = inlineView.FindForwardSuggestionWordPoint(index, _singleton.Options.WordDelimiters);
}

inlineView.OnSuggestionAccepted();
Replace(0, _singleton._buffer.Length, inlineView.SuggestionText.Substring(0, index));

_singleton._current = start;
Insert(inlineView.SuggestionText.Substring(start, index - start));
}
}

Expand Down
42 changes: 30 additions & 12 deletions PSReadLine/ReadLine.vi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
_singleton.ViIndicateInsertMode();
}

/// <summary>
/// Returns true if in Vi edit mode, otherwise false.
/// </summary>
internal static bool InViEditMode() => _singleton.Options.EditMode == EditMode.Vi;

/// <summary>
/// Returns true if in Vi Command mode, otherwise false.
/// </summary>
Expand Down Expand Up @@ -621,7 +626,13 @@ public static void InvertCase(ConsoleKeyInfo? key = null, object arg = null)
if (Char.IsLetter(c))
{
char newChar = Char.IsUpper(c) ? Char.ToLower(c, CultureInfo.CurrentCulture) : char.ToUpper(c, CultureInfo.CurrentCulture);
EditItem delEditItem = EditItemDelete.Create(c.ToString(), _singleton._current);
EditItem delEditItem = EditItemDelete.Create(
c.ToString(),
_singleton._current,
InvertCase,
arg,
moveCursorToEndWhenUndo: false);

EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current);
_singleton.SaveEditItem(GroupedEdit.Create(new List<EditItem>
{
Expand Down Expand Up @@ -657,21 +668,22 @@ public static void SwapCharacters(ConsoleKeyInfo? key = null, object arg = null)
if (cursor == bufferLength)
--cursor; // if at end of line, swap previous two chars

char current = _singleton._buffer[cursor];
char previous = _singleton._buffer[cursor - 1];

_singleton.StartEditGroup();
_singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer.ToString(cursor - 1, 2), cursor - 1));
_singleton.SaveEditItem(EditItemInsertChar.Create(current, cursor - 1));
_singleton.SaveEditItem(EditItemInsertChar.Create(previous, cursor));
_singleton.EndEditGroup();
_singleton.SaveEditItem(EditItemSwapCharacters.Create(cursor));
_singleton.SwapCharactersImpl(cursor);

_singleton._buffer[cursor] = previous;
_singleton._buffer[cursor - 1] = current;
_singleton.MoveCursor(Math.Min(cursor + 1, cursorRightLimit));
_singleton.Render();
}

private void SwapCharactersImpl(int cursor)
{
char current = _buffer[cursor];
char previous = _buffer[cursor - 1];

_buffer[cursor] = previous;
_buffer[cursor - 1] = current;
}

/// <summary>
/// Deletes text from the cursor to the first non-blank character of the line.
/// </summary>
Expand Down Expand Up @@ -1334,7 +1346,13 @@ public static void ViJoinLines(ConsoleKeyInfo? key = null, object arg = null)
{
_singleton._buffer[_singleton._current] = ' ';
_singleton._groupUndoHelper.StartGroup(ViJoinLines, arg);
_singleton.SaveEditItem(EditItemDelete.Create("\n", _singleton._current));
_singleton.SaveEditItem(EditItemDelete.Create(
"\n",
_singleton._current,
ViJoinLines,
arg,
moveCursorToEndWhenUndo: false));

_singleton.SaveEditItem(EditItemInsertChar.Create(' ', _singleton._current));
_singleton._groupUndoHelper.EndGroup();
_singleton.Render();
Expand Down
16 changes: 14 additions & 2 deletions PSReadLine/Replace.vi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ private static void ViReplaceUntilEsc(ConsoleKeyInfo? key, object arg)
{
_singleton.StartEditGroup();
string insStr = _singleton._buffer.ToString(startingCursor, _singleton._current - startingCursor);
_singleton.SaveEditItem(EditItemDelete.Create(deletedStr.ToString(), startingCursor));
_singleton.SaveEditItem(EditItemDelete.Create(
deletedStr.ToString(),
startingCursor,
ViReplaceUntilEsc,
arg,
moveCursorToEndWhenUndo: false));

_singleton.SaveEditItem(EditItemInsertString.Create(insStr, startingCursor));
_singleton.EndEditGroup();
}
Expand Down Expand Up @@ -226,7 +232,13 @@ private static void ReplaceCharInPlace(ConsoleKeyInfo? key, object arg)
if (_singleton._buffer.Length > 0 && nextKey.KeyStr.Length == 1)
{
_singleton.StartEditGroup();
_singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer[_singleton._current].ToString(), _singleton._current));
_singleton.SaveEditItem(EditItemDelete.Create(
_singleton._buffer[_singleton._current].ToString(),
_singleton._current,
ReplaceCharInPlace,
arg,
moveCursorToEndWhenUndo: false));

_singleton.SaveEditItem(EditItemInsertString.Create(nextKey.KeyStr, _singleton._current));
_singleton.EndEditGroup();

Expand Down
49 changes: 44 additions & 5 deletions PSReadLine/UndoRedo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,27 +258,40 @@ class EditItemDelete : EditItem
private readonly string _deletedString;
private readonly int _deleteStartPosition;

protected EditItemDelete(string str, int position, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg)
// The undo-delete operation will insert some text starting from the '_deleteStartPosition'.
// The '_moveCursorToEndWhenUndo' flag specifies whether the cursor should be moved to the end of the inserted text.
private readonly bool _moveCursorToEndWhenUndo;

protected EditItemDelete(string str, int position, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg, bool moveCursorToEndWhenUndo)
{
_deletedString = str;
_deleteStartPosition = position;
_instigator = instigator;
_instigatorArg = instigatorArg;
_moveCursorToEndWhenUndo = moveCursorToEndWhenUndo;
}

public static EditItem Create(string str, int position, Action<ConsoleKeyInfo?, object> instigator = null, object instigatorArg = null)
public static EditItem Create(
string str,
int position,
Action<ConsoleKeyInfo?, object> instigator = null,
object instigatorArg = null,
bool moveCursorToEndWhenUndo = true)
{
return new EditItemDelete(
str,
position,
instigator,
instigatorArg);
instigatorArg,
moveCursorToEndWhenUndo);
}

public override void Undo()
{
_singleton._buffer.Insert(_deleteStartPosition, _deletedString);
_singleton._current = _deleteStartPosition + _deletedString.Length;
_singleton._current = _moveCursorToEndWhenUndo
? _deleteStartPosition + _deletedString.Length
: _deleteStartPosition;
}

public override void Redo()
Expand All @@ -297,7 +310,7 @@ class EditItemDeleteLines : EditItemDelete
private readonly int _deleteAnchor;

private EditItemDeleteLines(string str, int position, int anchor, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg)
: base(str, position, instigator, instigatorArg)
: base(str, position, instigator, instigatorArg, moveCursorToEndWhenUndo: false)
{
_deleteAnchor = anchor;
}
Expand All @@ -314,6 +327,32 @@ public override void Undo()
}
}

[DebuggerDisplay("SwapCharacters (position: {_swapPosition})")]
class EditItemSwapCharacters : EditItem
{
private readonly int _swapPosition;

private EditItemSwapCharacters(int swapPosition)
{
_swapPosition = swapPosition;
}

public static EditItem Create(int swapPosition)
{
return new EditItemSwapCharacters(swapPosition);
}

public override void Redo()
{
_singleton.SwapCharactersImpl(_swapPosition);
}

public override void Undo()
{
_singleton.SwapCharactersImpl(_swapPosition);
}
}

class GroupedEdit : EditItem
{
internal List<EditItem> _groupedEditItems;
Expand Down
31 changes: 21 additions & 10 deletions PSReadLine/YankPaste.vi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,34 @@ private void SaveLinesToClipboard(int lineIndex, int lineCount)
_viRegister.LinewiseRecord(_buffer.ToString(range.Offset, range.Count));
}

/// <summary>
/// <summary>
/// Remove a portion of text from the buffer, save it to the vi register
/// and also save it to the edit list to support undo.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <param name="instigator"></param>
/// and also save it to the edit list to support undo.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <param name="instigator"></param>
/// <param name="arg"></param>
private void RemoveTextToViRegister(int start, int count, Action<ConsoleKeyInfo?, object> instigator = null, object arg = null)
{
/// <param name="moveCursorToEndWhenUndoDelete">
/// Use 'false' as the default value because this method is used a lot by VI operations,
/// and for VI opeartions, we do NOT want to move the cursor to the end when undoing a
/// deletion.
/// </param>
private void RemoveTextToViRegister(
int start,
int count,
Action<ConsoleKeyInfo?, object> instigator = null,
object arg = null,
bool moveCursorToEndWhenUndoDelete = false)
{
_singleton.SaveToClipboard(start, count);
_singleton.SaveEditItem(EditItemDelete.Create(
_viRegister.RawText,
start,
instigator,
arg));
_singleton._buffer.Remove(start, count);
arg,
moveCursorToEndWhenUndoDelete));
_singleton._buffer.Remove(start, count);
}

/// <summary>
Expand Down
Loading