Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2014,7 +2014,12 @@ impl Reedline {
if self.input_mode == InputMode::HistoryTraversal {
self.input_mode = InputMode::Regular;
}
self.apply_edit_commands(commands);
}

/// [`run_edit_commands`](Self::run_edit_commands) without ending history
/// traversal, for the engine's own line moves inside a recalled entry.
fn apply_edit_commands(&mut self, commands: &[EditCommand]) {
// Adopt the current edit mode's rest policy so these commands resolve
// under it (e.g. block-caret selection geometry) — but *without*
// committing the cursor first. A commit here would apply the policy's
Expand All @@ -2036,10 +2041,10 @@ impl Reedline {
// If we're at the top, move to previous history
self.previous_history();
} else {
// Through `run_edit_commands` so the cursor settles under the mode's
// Through `apply_edit_commands` so the cursor settles under the mode's
// rest policy — a bare `editor.move_line_up` skips the commit boundary,
// leaving a vi-normal caret past the last grapheme on a short line.
self.run_edit_commands(&[EditCommand::MoveLineUp { select: false }]);
self.apply_edit_commands(&[EditCommand::MoveLineUp { select: false }]);
}
}

Expand All @@ -2050,7 +2055,7 @@ impl Reedline {
self.next_history();
} else {
// See `up_command`: settle under the rest policy via the commit boundary.
self.run_edit_commands(&[EditCommand::MoveLineDown { select: false }]);
self.apply_edit_commands(&[EditCommand::MoveLineDown { select: false }]);
}
}

Expand Down Expand Up @@ -2855,6 +2860,57 @@ mod tests {
assert_eq!(reedline.current_buffer_contents(), multiline_command);
}

// --- history walk across a recalled multi-line entry (#1109 regression) ---

/// History holds `older` then `one\ntwo` (newest).
fn two_entry_history_engine() -> Reedline {
let mut rl = seam_engine(Box::<Emacs>::default());
for cmd in ["older", "one\ntwo"] {
rl.history
.save(HistoryItem::from_command_line(cmd))
.expect("save history");
}
rl
}

#[test]
fn down_inside_recalled_multiline_entry_keeps_walking_forward() {
let mut rl = two_entry_history_engine();
drive(&mut rl, &[key(KeyCode::Up)]);
assert_eq!(rl.editor.get_buffer(), "one\ntwo", "setup");
assert_eq!(rl.editor.insertion_point(), 3, "setup: end of line 1");

drive(&mut rl, &[key(KeyCode::Down)]);
assert_eq!(rl.editor.get_buffer(), "one\ntwo", "moves to line 2 first");
assert!(rl.editor.insertion_point() > 3, "setup: on line 2");

drive(&mut rl, &[key(KeyCode::Down)]);
assert_eq!(
rl.editor.get_buffer(),
"",
"from the last line, Down walks forward to the empty draft"
);
}

#[test]
fn up_inside_recalled_multiline_entry_keeps_walking_back() {
let mut rl = two_entry_history_engine();
drive(&mut rl, &[key(KeyCode::Up), key(KeyCode::Down)]);
assert_eq!(rl.editor.get_buffer(), "one\ntwo", "setup");
assert!(rl.editor.insertion_point() > 3, "setup: on line 2");

drive(&mut rl, &[key(KeyCode::Up)]);
assert_eq!(rl.editor.get_buffer(), "one\ntwo", "moves to line 1 first");
assert!(rl.editor.insertion_point() <= 3, "setup: on line 1");

drive(&mut rl, &[key(KeyCode::Up)]);
assert_eq!(
rl.editor.get_buffer(),
"older",
"from the first line, Up walks back to the older entry"
);
}

#[test]
fn thread_safe() {
fn f<S: Send>(_: S) {}
Expand Down
Loading