diff --git a/src/app.rs b/src/app.rs index 1fa1a18..5cf5521 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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 { @@ -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();