Skip to content

Commit 1966609

Browse files
authored
[vi-mode] Undo now leaves the cursor under the position at the start of the deletion (PowerShell#2045)
1 parent 355bd08 commit 1966609

File tree

11 files changed

+280
-101
lines changed

11 files changed

+280
-101
lines changed

PSReadLine/BasicEditing.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static void CancelLine(ConsoleKeyInfo? key = null, object arg = null)
102102
/// </summary>
103103
public static void ForwardDeleteInput(ConsoleKeyInfo? key = null, object arg = null)
104104
{
105-
ForwardDeleteImpl(_singleton._buffer.Length);
105+
ForwardDeleteImpl(_singleton._buffer.Length, ForwardDeleteInput);
106106
}
107107

108108
/// <summary>
@@ -111,15 +111,15 @@ public static void ForwardDeleteInput(ConsoleKeyInfo? key = null, object arg = n
111111
/// </summary>
112112
public static void ForwardDeleteLine(ConsoleKeyInfo? key = null, object arg = null)
113113
{
114-
ForwardDeleteImpl(GetEndOfLogicalLinePos(_singleton._current) + 1);
114+
ForwardDeleteImpl(GetEndOfLogicalLinePos(_singleton._current) + 1, ForwardDeleteLine);
115115
}
116116

117117
/// <summary>
118118
/// Deletes text from the cursor position to the specified end position
119119
/// but does not put the deleted text in the kill ring.
120120
/// </summary>
121121
/// <param name="endPosition">0-based offset to one character past the end of the text.</param>
122-
private static void ForwardDeleteImpl(int endPosition)
122+
private static void ForwardDeleteImpl(int endPosition, Action<ConsoleKeyInfo?, object> instigator)
123123
{
124124
var current = _singleton._current;
125125
var buffer = _singleton._buffer;
@@ -128,7 +128,15 @@ private static void ForwardDeleteImpl(int endPosition)
128128
{
129129
int length = endPosition - current;
130130
var str = buffer.ToString(current, length);
131-
_singleton.SaveEditItem(EditItemDelete.Create(str, current));
131+
132+
_singleton.SaveEditItem(
133+
EditItemDelete.Create(
134+
str,
135+
current,
136+
instigator,
137+
instigatorArg: null,
138+
!InViEditMode()));
139+
132140
buffer.Remove(current, length);
133141
_singleton.Render();
134142
}
@@ -152,13 +160,13 @@ public static void BackwardDeleteLine(ConsoleKeyInfo? key = null, object arg = n
152160
BackwardDeleteSubstring(position, BackwardDeleteLine);
153161
}
154162

155-
private static void BackwardDeleteSubstring(int position, Action<ConsoleKeyInfo?, object> instigator = null)
163+
private static void BackwardDeleteSubstring(int position, Action<ConsoleKeyInfo?, object> instigator)
156164
{
157165
if (_singleton._current > position)
158166
{
159167
var count = _singleton._current - position;
160-
161-
_singleton.RemoveTextToViRegister(position, count, instigator);
168+
169+
_singleton.RemoveTextToViRegister(position, count, instigator, arg: null, !InViEditMode());
162170
_singleton._current = position;
163171
_singleton.Render();
164172
}
@@ -184,7 +192,7 @@ public static void BackwardDeleteChar(ConsoleKeyInfo? key = null, object arg = n
184192

185193
int startDeleteIndex = _singleton._current - qty;
186194

187-
_singleton.RemoveTextToViRegister(startDeleteIndex, qty, BackwardDeleteChar, arg);
195+
_singleton.RemoveTextToViRegister(startDeleteIndex, qty, BackwardDeleteChar, arg, !InViEditMode());
188196
_singleton._current = startDeleteIndex;
189197
_singleton.Render();
190198
}
@@ -205,7 +213,7 @@ private void DeleteCharImpl(int qty, bool orExit)
205213
{
206214
qty = Math.Min(qty, _singleton._buffer.Length - _singleton._current);
207215

208-
RemoveTextToViRegister(_current, qty, DeleteChar, qty);
216+
RemoveTextToViRegister(_current, qty, DeleteChar, qty, !InViEditMode());
209217
if (_current >= _buffer.Length)
210218
{
211219
_current = Math.Max(0, _buffer.Length + ViEndOfLineFactor);

PSReadLine/Prediction.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ public static void AcceptSuggestion(ConsoleKeyInfo? key = null, object arg = nul
7575
inlineView.OnSuggestionAccepted();
7676

7777
using var _ = prediction.DisableScoped();
78-
Replace(0, _singleton._buffer.Length, inlineView.SuggestionText);
78+
79+
_singleton._current = _singleton._buffer.Length;
80+
Insert(inlineView.SuggestionText.Substring(_singleton._current));
7981
}
8082
}
8183

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

108-
int index = _singleton._buffer.Length;
110+
int start = _singleton._buffer.Length;
111+
int index = start;
109112
while (numericArg-- > 0 && index < inlineView.SuggestionText.Length)
110113
{
111114
index = inlineView.FindForwardSuggestionWordPoint(index, _singleton.Options.WordDelimiters);
112115
}
113116

114117
inlineView.OnSuggestionAccepted();
115-
Replace(0, _singleton._buffer.Length, inlineView.SuggestionText.Substring(0, index));
118+
119+
_singleton._current = start;
120+
Insert(inlineView.SuggestionText.Substring(start, index - start));
116121
}
117122
}
118123

PSReadLine/ReadLine.vi.cs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,11 @@ public static void ViInsertMode(ConsoleKeyInfo? key = null, object arg = null)
458458
_singleton.ViIndicateInsertMode();
459459
}
460460

461+
/// <summary>
462+
/// Returns true if in Vi edit mode, otherwise false.
463+
/// </summary>
464+
internal static bool InViEditMode() => _singleton.Options.EditMode == EditMode.Vi;
465+
461466
/// <summary>
462467
/// Returns true if in Vi Command mode, otherwise false.
463468
/// </summary>
@@ -621,7 +626,13 @@ public static void InvertCase(ConsoleKeyInfo? key = null, object arg = null)
621626
if (Char.IsLetter(c))
622627
{
623628
char newChar = Char.IsUpper(c) ? Char.ToLower(c, CultureInfo.CurrentCulture) : char.ToUpper(c, CultureInfo.CurrentCulture);
624-
EditItem delEditItem = EditItemDelete.Create(c.ToString(), _singleton._current);
629+
EditItem delEditItem = EditItemDelete.Create(
630+
c.ToString(),
631+
_singleton._current,
632+
InvertCase,
633+
arg,
634+
moveCursorToEndWhenUndo: false);
635+
625636
EditItem insEditItem = EditItemInsertChar.Create(newChar, _singleton._current);
626637
_singleton.SaveEditItem(GroupedEdit.Create(new List<EditItem>
627638
{
@@ -657,21 +668,22 @@ public static void SwapCharacters(ConsoleKeyInfo? key = null, object arg = null)
657668
if (cursor == bufferLength)
658669
--cursor; // if at end of line, swap previous two chars
659670

660-
char current = _singleton._buffer[cursor];
661-
char previous = _singleton._buffer[cursor - 1];
662-
663-
_singleton.StartEditGroup();
664-
_singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer.ToString(cursor - 1, 2), cursor - 1));
665-
_singleton.SaveEditItem(EditItemInsertChar.Create(current, cursor - 1));
666-
_singleton.SaveEditItem(EditItemInsertChar.Create(previous, cursor));
667-
_singleton.EndEditGroup();
671+
_singleton.SaveEditItem(EditItemSwapCharacters.Create(cursor));
672+
_singleton.SwapCharactersImpl(cursor);
668673

669-
_singleton._buffer[cursor] = previous;
670-
_singleton._buffer[cursor - 1] = current;
671674
_singleton.MoveCursor(Math.Min(cursor + 1, cursorRightLimit));
672675
_singleton.Render();
673676
}
674677

678+
private void SwapCharactersImpl(int cursor)
679+
{
680+
char current = _buffer[cursor];
681+
char previous = _buffer[cursor - 1];
682+
683+
_buffer[cursor] = previous;
684+
_buffer[cursor - 1] = current;
685+
}
686+
675687
/// <summary>
676688
/// Deletes text from the cursor to the first non-blank character of the line.
677689
/// </summary>
@@ -1334,7 +1346,13 @@ public static void ViJoinLines(ConsoleKeyInfo? key = null, object arg = null)
13341346
{
13351347
_singleton._buffer[_singleton._current] = ' ';
13361348
_singleton._groupUndoHelper.StartGroup(ViJoinLines, arg);
1337-
_singleton.SaveEditItem(EditItemDelete.Create("\n", _singleton._current));
1349+
_singleton.SaveEditItem(EditItemDelete.Create(
1350+
"\n",
1351+
_singleton._current,
1352+
ViJoinLines,
1353+
arg,
1354+
moveCursorToEndWhenUndo: false));
1355+
13381356
_singleton.SaveEditItem(EditItemInsertChar.Create(' ', _singleton._current));
13391357
_singleton._groupUndoHelper.EndGroup();
13401358
_singleton.Render();

PSReadLine/Replace.vi.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ private static void ViReplaceUntilEsc(ConsoleKeyInfo? key, object arg)
6565
{
6666
_singleton.StartEditGroup();
6767
string insStr = _singleton._buffer.ToString(startingCursor, _singleton._current - startingCursor);
68-
_singleton.SaveEditItem(EditItemDelete.Create(deletedStr.ToString(), startingCursor));
68+
_singleton.SaveEditItem(EditItemDelete.Create(
69+
deletedStr.ToString(),
70+
startingCursor,
71+
ViReplaceUntilEsc,
72+
arg,
73+
moveCursorToEndWhenUndo: false));
74+
6975
_singleton.SaveEditItem(EditItemInsertString.Create(insStr, startingCursor));
7076
_singleton.EndEditGroup();
7177
}
@@ -226,7 +232,13 @@ private static void ReplaceCharInPlace(ConsoleKeyInfo? key, object arg)
226232
if (_singleton._buffer.Length > 0 && nextKey.KeyStr.Length == 1)
227233
{
228234
_singleton.StartEditGroup();
229-
_singleton.SaveEditItem(EditItemDelete.Create(_singleton._buffer[_singleton._current].ToString(), _singleton._current));
235+
_singleton.SaveEditItem(EditItemDelete.Create(
236+
_singleton._buffer[_singleton._current].ToString(),
237+
_singleton._current,
238+
ReplaceCharInPlace,
239+
arg,
240+
moveCursorToEndWhenUndo: false));
241+
230242
_singleton.SaveEditItem(EditItemInsertString.Create(nextKey.KeyStr, _singleton._current));
231243
_singleton.EndEditGroup();
232244

PSReadLine/UndoRedo.cs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,27 +258,40 @@ class EditItemDelete : EditItem
258258
private readonly string _deletedString;
259259
private readonly int _deleteStartPosition;
260260

261-
protected EditItemDelete(string str, int position, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg)
261+
// The undo-delete operation will insert some text starting from the '_deleteStartPosition'.
262+
// The '_moveCursorToEndWhenUndo' flag specifies whether the cursor should be moved to the end of the inserted text.
263+
private readonly bool _moveCursorToEndWhenUndo;
264+
265+
protected EditItemDelete(string str, int position, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg, bool moveCursorToEndWhenUndo)
262266
{
263267
_deletedString = str;
264268
_deleteStartPosition = position;
265269
_instigator = instigator;
266270
_instigatorArg = instigatorArg;
271+
_moveCursorToEndWhenUndo = moveCursorToEndWhenUndo;
267272
}
268273

269-
public static EditItem Create(string str, int position, Action<ConsoleKeyInfo?, object> instigator = null, object instigatorArg = null)
274+
public static EditItem Create(
275+
string str,
276+
int position,
277+
Action<ConsoleKeyInfo?, object> instigator = null,
278+
object instigatorArg = null,
279+
bool moveCursorToEndWhenUndo = true)
270280
{
271281
return new EditItemDelete(
272282
str,
273283
position,
274284
instigator,
275-
instigatorArg);
285+
instigatorArg,
286+
moveCursorToEndWhenUndo);
276287
}
277288

278289
public override void Undo()
279290
{
280291
_singleton._buffer.Insert(_deleteStartPosition, _deletedString);
281-
_singleton._current = _deleteStartPosition + _deletedString.Length;
292+
_singleton._current = _moveCursorToEndWhenUndo
293+
? _deleteStartPosition + _deletedString.Length
294+
: _deleteStartPosition;
282295
}
283296

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

299312
private EditItemDeleteLines(string str, int position, int anchor, Action<ConsoleKeyInfo?, object> instigator, object instigatorArg)
300-
: base(str, position, instigator, instigatorArg)
313+
: base(str, position, instigator, instigatorArg, moveCursorToEndWhenUndo: false)
301314
{
302315
_deleteAnchor = anchor;
303316
}
@@ -314,6 +327,32 @@ public override void Undo()
314327
}
315328
}
316329

330+
[DebuggerDisplay("SwapCharacters (position: {_swapPosition})")]
331+
class EditItemSwapCharacters : EditItem
332+
{
333+
private readonly int _swapPosition;
334+
335+
private EditItemSwapCharacters(int swapPosition)
336+
{
337+
_swapPosition = swapPosition;
338+
}
339+
340+
public static EditItem Create(int swapPosition)
341+
{
342+
return new EditItemSwapCharacters(swapPosition);
343+
}
344+
345+
public override void Redo()
346+
{
347+
_singleton.SwapCharactersImpl(_swapPosition);
348+
}
349+
350+
public override void Undo()
351+
{
352+
_singleton.SwapCharactersImpl(_swapPosition);
353+
}
354+
}
355+
317356
class GroupedEdit : EditItem
318357
{
319358
internal List<EditItem> _groupedEditItems;

PSReadLine/YankPaste.vi.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,34 @@ private void SaveLinesToClipboard(int lineIndex, int lineCount)
7070
_viRegister.LinewiseRecord(_buffer.ToString(range.Offset, range.Count));
7171
}
7272

73-
/// <summary>
73+
/// <summary>
7474
/// Remove a portion of text from the buffer, save it to the vi register
75-
/// and also save it to the edit list to support undo.
76-
/// </summary>
77-
/// <param name="start"></param>
78-
/// <param name="count"></param>
79-
/// <param name="instigator"></param>
75+
/// and also save it to the edit list to support undo.
76+
/// </summary>
77+
/// <param name="start"></param>
78+
/// <param name="count"></param>
79+
/// <param name="instigator"></param>
8080
/// <param name="arg"></param>
81-
private void RemoveTextToViRegister(int start, int count, Action<ConsoleKeyInfo?, object> instigator = null, object arg = null)
82-
{
81+
/// <param name="moveCursorToEndWhenUndoDelete">
82+
/// Use 'false' as the default value because this method is used a lot by VI operations,
83+
/// and for VI opeartions, we do NOT want to move the cursor to the end when undoing a
84+
/// deletion.
85+
/// </param>
86+
private void RemoveTextToViRegister(
87+
int start,
88+
int count,
89+
Action<ConsoleKeyInfo?, object> instigator = null,
90+
object arg = null,
91+
bool moveCursorToEndWhenUndoDelete = false)
92+
{
8393
_singleton.SaveToClipboard(start, count);
8494
_singleton.SaveEditItem(EditItemDelete.Create(
8595
_viRegister.RawText,
8696
start,
8797
instigator,
88-
arg));
89-
_singleton._buffer.Remove(start, count);
98+
arg,
99+
moveCursorToEndWhenUndoDelete));
100+
_singleton._buffer.Remove(start, count);
90101
}
91102

92103
/// <summary>

0 commit comments

Comments
 (0)