Skip to content

Commit fbdf7fa

Browse files
authored
Fix a few VI key handlers to close edit group properly (#3845)
1 parent bfd88e4 commit fbdf7fa

File tree

4 files changed

+112
-9
lines changed

4 files changed

+112
-9
lines changed

PSReadLine/Completion.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ private void CompleteImpl(bool menuSelect)
205205
if (InViInsertMode()) // must close out the current edit group before engaging menu completion
206206
{
207207
ViCommandMode();
208-
ViInsertWithAppend();
208+
ViInsertWithAppendImpl();
209209
}
210210

211211
// Do not show suggestion text during tab completion.

PSReadLine/ReadLine.vi.cs

+7-1
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,12 @@ public static void ViInsertAtEnd(ConsoleKeyInfo? key = null, object arg = null)
572572
/// Append from the current line position.
573573
/// </summary>
574574
public static void ViInsertWithAppend(ConsoleKeyInfo? key = null, object arg = null)
575+
{
576+
_singleton._groupUndoHelper.StartGroup(ViInsertWithAppend, arg);
577+
ViInsertWithAppendImpl(key, arg);
578+
}
579+
580+
private static void ViInsertWithAppendImpl(ConsoleKeyInfo? key = null, object arg = null)
575581
{
576582
ViInsertMode(key, arg);
577583
ForwardChar(key, arg);
@@ -1304,7 +1310,7 @@ public static void ViAppendLine(ConsoleKeyInfo? key = null, object arg = null)
13041310
}
13051311
_singleton.SaveEditItem(EditItemInsertChar.Create('\n', insertPoint));
13061312
_singleton.Render();
1307-
ViInsertWithAppend();
1313+
ViInsertWithAppendImpl();
13081314
}
13091315

13101316
private void MoveToEndOfPhrase()

PSReadLine/Replace.vi.cs

+21-7
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ private static void ViReplaceWord(ConsoleKeyInfo? key, object arg)
161161
&& !_singleton.IsDelimiter(_singleton._lastWordDelimiter, _singleton.Options.WordDelimiters)
162162
&& _singleton._shouldAppend)
163163
{
164-
ViInsertWithAppend(key, arg);
164+
ViInsertWithAppendImpl(key, arg);
165165
}
166166
else
167167
{
@@ -180,7 +180,7 @@ private static void ViReplaceGlob(ConsoleKeyInfo? key, object arg)
180180
}
181181
if (_singleton._current == _singleton._buffer.Length - 1)
182182
{
183-
ViInsertWithAppend(key, arg);
183+
ViInsertWithAppendImpl(key, arg);
184184
}
185185
else
186186
{
@@ -194,7 +194,7 @@ private static void ViReplaceEndOfWord(ConsoleKeyInfo? key, object arg)
194194
DeleteEndOfWord(key, arg);
195195
if (_singleton._current == _singleton._buffer.Length - 1)
196196
{
197-
ViInsertWithAppend(key, arg);
197+
ViInsertWithAppendImpl(key, arg);
198198
}
199199
else
200200
{
@@ -208,7 +208,7 @@ private static void ViReplaceEndOfGlob(ConsoleKeyInfo? key, object arg)
208208
ViDeleteEndOfGlob(key, arg);
209209
if (_singleton._current == _singleton._buffer.Length - 1)
210210
{
211-
ViInsertWithAppend(key, arg);
211+
ViInsertWithAppendImpl(key, arg);
212212
}
213213
else
214214
{
@@ -270,13 +270,17 @@ private static void ViReplaceToChar(char keyChar, ConsoleKeyInfo? key = null, ob
270270
{
271271
if (_singleton._current < initialCurrent || _singleton._current >= _singleton._buffer.Length)
272272
{
273-
ViInsertWithAppend(key, arg);
273+
ViInsertWithAppendImpl(key, arg);
274274
}
275275
else
276276
{
277277
ViInsertMode(key, arg);
278278
}
279279
}
280+
else
281+
{
282+
_singleton._groupUndoHelper.EndGroup();
283+
}
280284
}
281285

282286
/// <summary>
@@ -295,6 +299,10 @@ private static void ViReplaceToCharBack(char keyChar, ConsoleKeyInfo? key = null
295299
{
296300
ViInsertMode(key, arg);
297301
}
302+
else
303+
{
304+
_singleton._groupUndoHelper.EndGroup();
305+
}
298306
}
299307

300308
/// <summary>
@@ -314,6 +322,10 @@ private static void ViReplaceToBeforeChar(char keyChar, ConsoleKeyInfo? key = nu
314322
{
315323
ViInsertMode(key, arg);
316324
}
325+
else
326+
{
327+
_singleton._groupUndoHelper.EndGroup();
328+
}
317329
}
318330

319331
/// <summary>
@@ -332,8 +344,10 @@ private static void ViReplaceToBeforeCharBack(char keyChar, ConsoleKeyInfo? key
332344
{
333345
ViInsertMode(key, arg);
334346
}
347+
else
348+
{
349+
_singleton._groupUndoHelper.EndGroup();
350+
}
335351
}
336-
337-
338352
}
339353
}

test/BasicEditingTest.VI.cs

+83
Original file line numberDiff line numberDiff line change
@@ -1121,5 +1121,88 @@ public void ViInsertModeMoveCursor()
11211121
_.RightArrow, // 'RightArrow' again does nothing, but doesn't crash
11221122
"c"));
11231123
}
1124+
1125+
[SkippableFact]
1126+
public void ViDefect1281_1()
1127+
{
1128+
TestSetup(KeyMode.Vi);
1129+
1130+
Test("bcd", Keys(
1131+
"abcdabcd", _.Escape,
1132+
1133+
// return to the [B]eginning of the word,
1134+
// then [c]hange text un[t]il just before the [2]nd [b] character
1135+
// this leaves the cursor at the current position (0) but erases
1136+
// the "abcda" text portion, / switches to edit mode and
1137+
// positions the cursor just before the "bcd" text portion.
1138+
1139+
"Bc2tb",
1140+
1141+
// going back to normal mode again without having modified the buffer further
1142+
// even though the [c] command started an edit group, going back to normal
1143+
// mode closes the pending edit group.
1144+
1145+
_.Escape, CheckThat(() => AssertCursorLeftIs(0)),
1146+
1147+
// attempt to [c]hange text un[t]il just before the [2]nd [b] character again
1148+
// because the [b] character only appears once further down in the buffer
1149+
// relative to where the cursor position is – currently set to 0 - the command
1150+
// fails. Therefore, we are still in normal mode.
1151+
//
1152+
// as the command failed, the current edit group is now correctly closed.
1153+
1154+
"c2tb", CheckThat(() => AssertLineIs("bcd")),
1155+
1156+
// attempt to [c]hange text un[t]il just before the [2]nd [b] character a third time.
1157+
// this exercises a code path where starting a edit group while another
1158+
// pending edit group was previously started crashed PSRL.
1159+
//
1160+
// this should no longer crash as any started pending group is now properly closed if
1161+
// the command fails
1162+
"c2tb"
1163+
));
1164+
}
1165+
1166+
[SkippableFact]
1167+
public void ViDefect1281_2()
1168+
{
1169+
TestSetup(KeyMode.Vi);
1170+
1171+
Test("abc", Keys(
1172+
"abc", _.Escape,
1173+
1174+
// 'cff' triggers `ViReplaceToChar` to delete until the character 'f'. But 'abc' doesn't
1175+
// have the letter 'f', so the started edit group should be ended and cursor is not moved.
1176+
"cff",
1177+
CheckThat(() => AssertCursorLeftIs(2)),
1178+
1179+
// the subsequent 'cc' calls `ViReplaceLine` to replace the current line with 'i', which
1180+
// starts a new edit group.
1181+
"ccip",
1182+
CheckThat(() => AssertLineIs("ip")),
1183+
1184+
// now we undo the 'cci' step, and accept the current command line, which should be 'abc'.
1185+
_.Escape, "u"
1186+
));
1187+
}
1188+
1189+
[SkippableFact]
1190+
public void ViDefect1281_3()
1191+
{
1192+
TestSetup(KeyMode.Vi);
1193+
1194+
Test("bcd", Keys(
1195+
"bcd", _.Escape, _.LeftArrow, _.LeftArrow,
1196+
1197+
// 'a' triggers `ViInsertWithAppend` and we append 'iii' after 'b'.
1198+
"aiii",
1199+
CheckThat(() => AssertCursorLeftIs(4)),
1200+
CheckThat(() => AssertLineIs("biiicd")),
1201+
1202+
// now we undo the 'aiii' step, and accept the current command line,
1203+
// which should be 'bcd'.
1204+
_.Escape, "u"
1205+
));
1206+
}
11241207
}
11251208
}

0 commit comments

Comments
 (0)