Skip to content

Commit fd13554

Browse files
authored
Preserve and clear the saved current line properly (#1259)
1. Preserve the saved current line and the history index while in a series of history commands. So when switching between different history command, such as from `F8`/`Shift+F8` (`HistorySearchBackward` and `HistorySearchForward`) to `UpArrow`/`DownArrow` (recall history) or from `Ctrl+r`/`Ctrl+s` to `UpArrow`/`DownArrow`, the history command can continue to work as expected. 2. Clear the saved current line when we are out of a series of history commands. So the saved line will not leak to another unrelated history search.
1 parent d648aed commit fd13554

File tree

5 files changed

+211
-19
lines changed

5 files changed

+211
-19
lines changed

PSReadLine/History.cs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public class HistoryItem
9595
private int _getNextHistoryIndex;
9696
private int _searchHistoryCommandCount;
9797
private int _recallHistoryCommandCount;
98+
private int _anyHistoryCommandCount;
9899
private string _searchHistoryPrefix;
99100
// When cycling through history, the current line (not yet added to history)
100101
// is saved here so it can be restored.
@@ -113,6 +114,13 @@ public class HistoryItem
113114
"password|asplaintext|token|key|secret",
114115
RegexOptions.Compiled | RegexOptions.IgnoreCase);
115116

117+
private void ClearSavedCurrentLine()
118+
{
119+
_savedCurrentLine.CommandLine = null;
120+
_savedCurrentLine._edits = null;
121+
_savedCurrentLine._undoEditIndex = 0;
122+
}
123+
116124
private AddToHistoryOption GetAddToHistoryOption(string line)
117125
{
118126
// Whitespace only is useless, never add.
@@ -217,9 +225,7 @@ private string MaybeAddToHistory(
217225
// to recall the saved line.
218226
if (_getNextHistoryIndex == 0)
219227
{
220-
_savedCurrentLine.CommandLine = null;
221-
_savedCurrentLine._edits = null;
222-
_savedCurrentLine._undoEditIndex = 0;
228+
ClearSavedCurrentLine();
223229
}
224230
return result;
225231
}
@@ -469,7 +475,7 @@ private void UpdateFromHistory(HistoryMoveCursor moveCursor)
469475
if (_currentHistoryIndex == _history.Count)
470476
{
471477
line = _savedCurrentLine.CommandLine;
472-
_edits = _savedCurrentLine._edits;
478+
_edits = new List<EditItem>(_savedCurrentLine._edits);
473479
_undoEditIndex = _savedCurrentLine._undoEditIndex;
474480
}
475481
else
@@ -505,6 +511,7 @@ private void SaveCurrentLine()
505511
// to check if we need to load history from another sessions now.
506512
MaybeReadHistoryFile();
507513

514+
_anyHistoryCommandCount += 1;
508515
if (_savedCurrentLine.CommandLine == null)
509516
{
510517
_savedCurrentLine.CommandLine = _buffer.ToString();
@@ -633,7 +640,7 @@ private void HistorySearch(int direction)
633640
continue;
634641
}
635642

636-
var line = newHistoryIndex == _history.Count ? _savedCurrentLine.CommandLine : _history[newHistoryIndex].CommandLine;
643+
var line = _history[newHistoryIndex].CommandLine;
637644
if (line.StartsWith(_searchHistoryPrefix, Options.HistoryStringComparison))
638645
{
639646
if (Options.HistoryNoDuplicates)
@@ -684,6 +691,12 @@ public static void BeginningOfHistory(ConsoleKeyInfo? key = null, object arg = n
684691
/// Move to the last item (the current input) in the history.
685692
/// </summary>
686693
public static void EndOfHistory(ConsoleKeyInfo? key = null, object arg = null)
694+
{
695+
_singleton.SaveCurrentLine();
696+
GoToEndOfHistory();
697+
}
698+
699+
private static void GoToEndOfHistory()
687700
{
688701
_singleton._currentHistoryIndex = _singleton._history.Count;
689702
_singleton.UpdateFromHistory(HistoryMoveCursor.ToEnd);
@@ -842,7 +855,7 @@ private void InteractiveHistorySearchLoop(int direction)
842855
else if (function == Abort)
843856
{
844857
// Abort search
845-
EndOfHistory();
858+
GoToEndOfHistory();
846859
break;
847860
}
848861
else
@@ -885,9 +898,6 @@ private void InteractiveHistorySearch(int direction)
885898
Render(); // Render prompt
886899
InteractiveHistorySearchLoop(direction);
887900

888-
_hashedHistory = null;
889-
_currentHistoryIndex = _history.Count;
890-
891901
_emphasisStart = -1;
892902
_emphasisLength = 0;
893903

PSReadLine/ReadLine.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ private string InputLoop()
474474
var tabCommandCount = _tabCommandCount;
475475
var searchHistoryCommandCount = _searchHistoryCommandCount;
476476
var recallHistoryCommandCount = _recallHistoryCommandCount;
477+
var anyHistoryCommandCount = _anyHistoryCommandCount;
477478
var yankLastArgCommandCount = _yankLastArgCommandCount;
478479
var visualSelectionCommandCount = _visualSelectionCommandCount;
479480
var moveToLineCommandCount = _moveToLineCommandCount;
@@ -515,23 +516,23 @@ private string InputLoop()
515516
_emphasisStart = -1;
516517
_emphasisLength = 0;
517518
Render();
518-
_currentHistoryIndex = _history.Count;
519519
}
520520
_searchHistoryCommandCount = 0;
521521
_searchHistoryPrefix = null;
522522
}
523523
if (recallHistoryCommandCount == _recallHistoryCommandCount)
524524
{
525-
if (_recallHistoryCommandCount > 0)
526-
{
527-
_currentHistoryIndex = _history.Count;
528-
}
529525
_recallHistoryCommandCount = 0;
530526
}
531-
if (searchHistoryCommandCount == _searchHistoryCommandCount &&
532-
recallHistoryCommandCount == _recallHistoryCommandCount)
527+
if (anyHistoryCommandCount == _anyHistoryCommandCount)
533528
{
534-
_hashedHistory = null;
529+
if (_anyHistoryCommandCount > 0)
530+
{
531+
ClearSavedCurrentLine();
532+
_hashedHistory = null;
533+
_currentHistoryIndex = _history.Count;
534+
}
535+
_anyHistoryCommandCount = 0;
535536
}
536537
if (visualSelectionCommandCount == _visualSelectionCommandCount && _visualSelectionCommandCount > 0)
537538
{
@@ -717,6 +718,7 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics)
717718
_yankLastArgCommandCount = 0;
718719
_tabCommandCount = 0;
719720
_recallHistoryCommandCount = 0;
721+
_anyHistoryCommandCount = 0;
720722
_visualSelectionCommandCount = 0;
721723
_hashedHistory = null;
722724

PSReadLine/ReadLine.vi.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,7 @@ private static void DeleteRange(int first, int last, Action<ConsoleKeyInfo?, obj
834834
/// </summary>
835835
public static void ViSearchHistoryBackward(ConsoleKeyInfo? key = null, object arg = null)
836836
{
837+
_singleton.SaveCurrentLine();
837838
_singleton.StartSearch(backward: true);
838839
}
839840

@@ -842,6 +843,7 @@ public static void ViSearchHistoryBackward(ConsoleKeyInfo? key = null, object ar
842843
/// </summary>
843844
public static void SearchForward(ConsoleKeyInfo? key = null, object arg = null)
844845
{
846+
_singleton.SaveCurrentLine();
845847
_singleton.StartSearch(backward: false);
846848
}
847849

@@ -856,6 +858,7 @@ public static void RepeatSearch(ConsoleKeyInfo? key = null, object arg = null)
856858
return;
857859
}
858860

861+
_singleton._anyHistoryCommandCount++;
859862
_singleton.HistorySearch();
860863
}
861864

test/HistoryTest.VI.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,38 @@ public void ViHistoryRepeat()
109109
));
110110
}
111111

112+
[SkippableFact]
113+
public void ViHistoryCommandMix()
114+
{
115+
TestSetup(KeyMode.Vi);
116+
117+
// Clear history in case the above added some history (but it shouldn't)
118+
SetHistory();
119+
Test( " ", Keys( ' ', _.UpArrow, _.DownArrow ) );
120+
121+
// Mix history search, repeat, and recall.
122+
// Mix different history commands to verify that the saved current line and
123+
// the history index stay the same while in a series of history commands.
124+
125+
SetHistory("bar1", "bar2", "bar3", "bar4", "bar5");
126+
Test("first", Keys(
127+
"first", _.Escape, _.Slash, "bar", _.Enter,
128+
CheckThat(() => AssertLineIs("bar5")),
129+
_.DownArrow, CheckThat(() => AssertLineIs("first")),
130+
_.Slash, "bar", _.Enter,
131+
CheckThat(() => AssertLineIs("bar5")),
132+
"nn", CheckThat(() => AssertLineIs("bar3")),
133+
"N", CheckThat(() => AssertLineIs("bar4")),
134+
"N", CheckThat(() => AssertLineIs("bar5")),
135+
"nnn", CheckThat(() => AssertLineIs("bar2")),
136+
_.UpArrow, CheckThat(() => AssertLineIs("bar1")),
137+
_.DownArrow, CheckThat(() => AssertLineIs("bar2")),
138+
_.DownArrow, CheckThat(() => AssertLineIs("bar3")),
139+
_.DownArrow, CheckThat(() => AssertLineIs("bar4")),
140+
_.DownArrow, CheckThat(() => AssertLineIs("bar5")),
141+
_.DownArrow));
142+
}
143+
112144
[SkippableFact]
113145
public void ViMovementAfterHistory()
114146
{

test/HistoryTest.cs

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,40 @@ public void HistoryRecallCurrentLine()
409409
{
410410
TestSetup(KeyMode.Cmd);
411411

412+
// Recall history backward and forward.
412413
SetHistory("echo foo", "echo bar");
413-
Test("ec", Keys("ec", _.UpArrow, _.UpArrow, _.DownArrow, _.DownArrow));
414+
Test("ec", Keys(
415+
"ec",
416+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
417+
_.UpArrow, CheckThat(() => AssertLineIs("echo foo")),
418+
_.DownArrow, CheckThat(() => AssertLineIs("echo bar")),
419+
_.DownArrow));
420+
421+
// Verify that the saved current line gets reset when the line gets edited.
422+
// Recall history, then edit the line, and recall again.
423+
SetHistory("echo foo", "echo bar");
424+
Test("get", Keys(
425+
"ec", _.UpArrow,
426+
_.DownArrow, CheckThat(() => AssertLineIs("ec")),
427+
_.Escape, "get", _.UpArrow, _.DownArrow));
428+
429+
// Recall history, then edit the line, and recall again.
430+
SetHistory("echo foo", "echo bar");
431+
Test("ge", Keys(
432+
"ec", _.UpArrow,
433+
_.DownArrow, CheckThat(() => AssertLineIs("ec")),
434+
_.Backspace, _.Backspace, "ge", CheckThat(() => AssertLineIs("ge")),
435+
_.UpArrow, _.DownArrow));
436+
437+
// Recall history, then edit the line, and recall again.
438+
SetHistory("echo foo", "echo bar");
439+
Test("", Keys(
440+
"ec", _.UpArrow,
441+
_.DownArrow, CheckThat(() => AssertLineIs("ec")),
442+
"h", CheckThat(() => AssertLineIs("ech")),
443+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
444+
_.DownArrow, CheckThat(() => AssertLineIs("ech")),
445+
_.Escape));
414446
}
415447

416448
[SkippableFact]
@@ -420,8 +452,121 @@ public void HistorySearchCurrentLine()
420452
new KeyHandler("UpArrow", PSConsoleReadLine.HistorySearchBackward),
421453
new KeyHandler("DownArrow", PSConsoleReadLine.HistorySearchForward));
422454

455+
// Search history backward and forward.
456+
SetHistory("echo foo", "echo bar");
457+
Test("ec", Keys(
458+
"ec",
459+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
460+
_.UpArrow, CheckThat(() => AssertLineIs("echo foo")),
461+
_.DownArrow, CheckThat(() => AssertLineIs("echo bar")),
462+
_.DownArrow));
463+
464+
// Verify that the saved current line gets reset when the line gets edited.
465+
// Search history, then edit the line, and search again.
466+
SetHistory("echo foo", "echo bar");
467+
Test("echo ", Keys(
468+
"ec", _.UpArrow,
469+
_.DownArrow, CheckThat(() => AssertLineIs("ec")),
470+
_.Escape, "echo ",
471+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
472+
_.DownArrow));
473+
474+
// Search history, then edit the line, and search again.
475+
SetHistory("echo foo", "echo bar");
476+
Test("echo", Keys(
477+
"ec", _.UpArrow, _.DownArrow,
478+
"ho", CheckThat(() => AssertLineIs("echo")),
479+
_.UpArrow, _.DownArrow));
480+
481+
// Search history, then edit the line, and search again.
482+
SetHistory("echo foo", "echo bar");
483+
Test("e", Keys(
484+
"ec", _.UpArrow, _.DownArrow,
485+
_.Backspace, CheckThat(() => AssertLineIs("e")),
486+
_.UpArrow, _.DownArrow));
487+
488+
// Search history, then edit the line, and search again.
489+
SetHistory("echo foo", "echo bar");
490+
Test("", Keys(
491+
"ec", _.UpArrow, _.DownArrow, "ho f",
492+
_.UpArrow, CheckThat(() => AssertLineIs("echo foo")),
493+
_.DownArrow, CheckThat(() => AssertLineIs("echo f")),
494+
_.Escape));
495+
}
496+
497+
[SkippableFact]
498+
public void HistorySavedCurrentLine()
499+
{
500+
TestSetup(KeyMode.Cmd,
501+
new KeyHandler("F3", PSConsoleReadLine.BeginningOfHistory),
502+
new KeyHandler("Shift+F3", PSConsoleReadLine.EndOfHistory));
503+
504+
// Mix different history commands to verify that the saved current line and
505+
// the history index stay the same while in a series of history commands.
506+
423507
SetHistory("echo foo", "echo bar");
424-
Test("ec", Keys("ec", _.UpArrow, _.UpArrow, _.DownArrow, _.DownArrow));
508+
Test("ec", Keys(
509+
"ec",
510+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
511+
_.F3, CheckThat(() => AssertLineIs("echo foo")),
512+
_.DownArrow, CheckThat(() => AssertLineIs("echo bar")),
513+
_.DownArrow));
514+
515+
SetHistory("echo foo", "get zoo", "echo bar");
516+
Test("ec", Keys(
517+
"ec",
518+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
519+
_.F3, CheckThat(() => AssertLineIs("echo foo")),
520+
_.Shift_F3));
521+
522+
SetHistory("echo foo", "get zoo", "echo bar");
523+
Test("e", Keys(
524+
"e",
525+
_.UpArrow, CheckThat(() => AssertLineIs("echo bar")),
526+
_.UpArrow, CheckThat(() => AssertLineIs("get zoo")),
527+
_.Shift_F3));
528+
529+
SetHistory("echo foo", "get zoo", "echo bar");
530+
Test("ech", Keys(
531+
"ech",
532+
_.F8, CheckThat(() => AssertLineIs("echo bar")),
533+
_.F3, CheckThat(() => AssertLineIs("echo foo")),
534+
_.DownArrow, CheckThat(() => AssertLineIs("get zoo")),
535+
_.DownArrow, CheckThat(() => AssertLineIs("echo bar")),
536+
_.DownArrow));
537+
538+
SetHistory("echo foo", "get zoo", "echo bar");
539+
Test("ech", Keys(
540+
"ech",
541+
_.F8, CheckThat(() => AssertLineIs("echo bar")),
542+
_.F8, CheckThat(() => AssertLineIs("echo foo")),
543+
_.Shift_F3));
544+
545+
SetHistory("echo foo", "get bar", "echo f");
546+
Test("ec", Keys(
547+
"ec",
548+
_.UpArrow, CheckThat(() => AssertLineIs("echo f")),
549+
_.F8, CheckThat(() => AssertLineIs("echo foo")),
550+
_.Shift_F8, CheckThat(() => AssertLineIs("echo f")),
551+
_.DownArrow));
552+
553+
SetHistory("echo foo", "get bar", "echo f");
554+
Test("ec", Keys(
555+
"ec", _.UpArrow, _.F8,
556+
_.DownArrow, CheckThat(() => AssertLineIs("get bar")),
557+
_.DownArrow, CheckThat(() => AssertLineIs("echo f")),
558+
_.Shift_F3));
559+
560+
SetHistory("echo kv", "get bar", "echo f");
561+
Test("e", Keys(
562+
"e",
563+
_.UpArrow, CheckThat(() => AssertLineIs("echo f")),
564+
_.Ctrl_r, "v", _.Escape,
565+
CheckThat(() => AssertLineIs("echo kv")),
566+
_.Ctrl_s, "f", _.Escape,
567+
CheckThat(() => AssertLineIs("echo f")),
568+
_.UpArrow, CheckThat(() => AssertLineIs("get bar")),
569+
_.DownArrow, _.DownArrow));
425570
}
426571

427572
[SkippableFact]

0 commit comments

Comments
 (0)