Input history: reset the nav cursor on every submit

`push_history` skipped its `history_cursor` / `history_draft`
reset on the consecutive-duplicate early-return path. Recalling
a command with Up and re-submitting it unchanged left the cursor
stranded at that entry, so the next Up stepped backwards from
there instead of restarting at the newest entry.

Move the reset ahead of the early-return guards. Adds a Tier-1
regression test driving the recall/resubmit keystroke sequence.
This commit is contained in:
claude@clouddev1
2026-05-17 09:17:20 +00:00
parent 0dc159fd7e
commit 4e4dbbffe3
+33 -2
View File
@@ -849,6 +849,13 @@ impl App {
}
fn push_history(&mut self, line: &str) {
// Submitting a command always ends history navigation —
// the next Up restarts from the newest entry. Reset here,
// before the early-return guards below, so a recalled
// command re-submitted unchanged (a consecutive duplicate)
// doesn't strand the cursor at its old position.
self.history_cursor = None;
self.history_draft = None;
// Skip empties and consecutive duplicates — the same
// trick most shells use to keep navigation pleasant.
if line.is_empty() {
@@ -861,8 +868,6 @@ impl App {
while self.history.len() > HISTORY_CAPACITY {
self.history.remove(0);
}
self.history_cursor = None;
self.history_draft = None;
}
fn submit(&mut self) -> Vec<Action> {
@@ -2923,6 +2928,32 @@ mod tests {
assert_eq!(app.input, "drop table A");
}
#[test]
fn resubmitting_a_recalled_command_does_not_strand_the_cursor() {
// Regression: a recalled command re-submitted unchanged
// is a consecutive duplicate, so `push_history` skips the
// append — but it must still reset the navigation cursor.
// Otherwise the next Up steps backwards from the stranded
// position instead of restarting at the newest entry.
let mut app = App::new();
type_str(&mut app, "show data Thing");
submit(&mut app);
type_str(&mut app, "insert into Thing values (1)");
submit(&mut app);
// Recall the insert and resubmit it unchanged, repeatedly.
// Every fresh Up must restart at the newest entry.
for round in 0..3 {
app.update(key(KeyCode::Up));
assert_eq!(
app.input, "insert into Thing values (1)",
"Up #{} should recall the newest entry",
round + 1,
);
submit(&mut app);
}
}
#[test]
fn down_arrow_returns_through_history_to_the_draft() {
let mut app = App::new();