Skip to content

Commit c884ee5

Browse files
authored
fix(engine): move to line start in multi-line history entries (#1078)
Re-applies #584 (reverted in #704); also fixes completions on a recalled history line being erased on the next key-press. Adds regression tests. Closes #1075
1 parent dfd800b commit c884ee5

1 file changed

Lines changed: 166 additions & 15 deletions

File tree

src/engine.rs

Lines changed: 166 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1717,20 +1717,6 @@ impl Reedline {
17171717
/// Executes [`EditCommand`] actions by modifying the internal state appropriately. Does not output itself.
17181718
pub fn run_edit_commands(&mut self, commands: &[EditCommand]) {
17191719
if self.input_mode == InputMode::HistoryTraversal {
1720-
if matches!(
1721-
self.history_cursor.get_navigation(),
1722-
HistoryNavigationQuery::Normal(_)
1723-
) {
1724-
if let Some(string) = self.history_cursor.string_at_cursor() {
1725-
// NOTE: `set_buffer` resets the insertion point,
1726-
// which we should avoid during history navigation through the same buffer
1727-
// https://github.com/nushell/reedline/pull/899
1728-
if string != self.editor.get_buffer() {
1729-
self.editor
1730-
.set_buffer(string, UndoBehavior::HistoryNavigation);
1731-
}
1732-
}
1733-
}
17341720
self.input_mode = InputMode::Regular;
17351721
}
17361722

@@ -2300,7 +2286,7 @@ impl Reedline {
23002286
mod tests {
23012287
use super::*;
23022288
use crate::terminal_extensions::semantic_prompt::PromptKind;
2303-
use crate::DefaultPrompt;
2289+
use crate::{ColumnarMenu, DefaultPrompt, MenuBuilder};
23042290
use rstest::rstest;
23052291

23062292
#[test]
@@ -2685,4 +2671,169 @@ mod tests {
26852671
"must expand when highlighter does not override should_expand_abbr"
26862672
);
26872673
}
2674+
2675+
#[rstest]
2676+
#[case("")]
2677+
#[case("line of text")]
2678+
#[case(
2679+
"longgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line of text"
2680+
)]
2681+
fn test_move_to_line_start(#[case] input: &str) {
2682+
let mut reedline = Reedline::create();
2683+
2684+
// Write the string, and then move to the start of the line.
2685+
let insertion = EditCommand::InsertString(String::from(input));
2686+
reedline.run_edit_commands(&[insertion]);
2687+
2688+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2689+
reedline.run_edit_commands(&[move_to_start]);
2690+
2691+
assert_eq!(reedline.editor.line_buffer().insertion_point(), 0);
2692+
}
2693+
2694+
#[rstest]
2695+
#[case("")]
2696+
#[case("line of text")]
2697+
#[case(
2698+
"longgggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line of text"
2699+
)]
2700+
fn test_move_to_line_start_history(#[case] input: &str) {
2701+
let mut reedline = Reedline::create();
2702+
2703+
// Enter the string into history, then scroll back up and move to the start of the line.
2704+
let history = HistoryItem::from_command_line(input);
2705+
reedline.history.save(history).unwrap();
2706+
2707+
reedline.previous_history();
2708+
2709+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2710+
reedline.run_edit_commands(&[move_to_start]);
2711+
2712+
assert_eq!(reedline.editor.line_buffer().insertion_point(), 0);
2713+
}
2714+
2715+
#[rstest]
2716+
#[case("a\nb", 2)]
2717+
#[case("123456789\n123456789\n123456789", 20)]
2718+
#[case("0\n1\n2\n3\n4\n5\n6\n7\n8\n9", 18)]
2719+
fn test_move_to_line_start_multiline(#[case] input: &str, #[case] last_line_start: usize) {
2720+
let mut reedline = Reedline::create();
2721+
2722+
// Write the string, and then move to the start of the last line.
2723+
let insertion = EditCommand::InsertString(String::from(input));
2724+
reedline.run_edit_commands(&[insertion]);
2725+
2726+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2727+
reedline.run_edit_commands(&[move_to_start]);
2728+
2729+
assert_eq!(
2730+
reedline.editor.line_buffer().insertion_point(),
2731+
last_line_start
2732+
);
2733+
}
2734+
2735+
#[rstest]
2736+
#[case("a\nb")]
2737+
#[case("123456789\n123456789\n123456789")]
2738+
#[case("0\n1\n2\n3\n4\n5\n6\n7\n8\n9")]
2739+
fn test_move_to_line_start_multiline_history_up_start(#[case] input: &str) {
2740+
let mut reedline = Reedline::create();
2741+
2742+
// Enter the string into history, then scroll back up and move to the start of the line.
2743+
let history = HistoryItem::from_command_line(input);
2744+
reedline.history.save(history).unwrap();
2745+
2746+
reedline.previous_history();
2747+
2748+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2749+
reedline.run_edit_commands(&[move_to_start]);
2750+
2751+
assert_eq!(reedline.editor.line_buffer().insertion_point(), 0);
2752+
}
2753+
2754+
#[rstest]
2755+
#[case("a\nb", 2)]
2756+
#[case("123456789\n123456789\n123456789", 10)]
2757+
#[case("0\n1\n2\n3\n4\n5\n6\n7\n8\n9", 2)]
2758+
fn test_move_to_line_start_multiline_history_up_down_start(
2759+
#[case] input: &str,
2760+
#[case] second_line_start: usize,
2761+
) {
2762+
let mut reedline = Reedline::create();
2763+
2764+
// Enter the string again, then scroll up in history, move down one line,
2765+
// and move to the start of the second line.
2766+
let history = HistoryItem::from_command_line(input);
2767+
reedline.history.save(history).unwrap();
2768+
2769+
reedline.previous_history();
2770+
2771+
reedline.down_command();
2772+
2773+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2774+
reedline.run_edit_commands(&[move_to_start]);
2775+
2776+
assert_eq!(
2777+
reedline.editor.line_buffer().insertion_point(),
2778+
second_line_start
2779+
);
2780+
}
2781+
2782+
#[rstest]
2783+
#[case("a\nb", 2)]
2784+
#[case("123456789\n123456789\n123456789", 20)]
2785+
#[case("0\n1\n2\n3\n4\n5\n6\n7\n8\n9", 18)]
2786+
fn test_move_to_line_start_multiline_history_up_end_start(
2787+
#[case] input: &str,
2788+
#[case] last_line_start: usize,
2789+
) {
2790+
let mut reedline = Reedline::create();
2791+
2792+
// Enter the string again, then scroll up in history, move to the end of the text,
2793+
// and move to the start of the last line.
2794+
let history = HistoryItem::from_command_line(input);
2795+
reedline.history.save(history).unwrap();
2796+
2797+
reedline.previous_history();
2798+
2799+
let move_to_end = EditCommand::MoveToEnd { select: false };
2800+
reedline.run_edit_commands(&[move_to_end]);
2801+
2802+
let move_to_start = EditCommand::MoveToLineStart { select: false };
2803+
reedline.run_edit_commands(&[move_to_start]);
2804+
2805+
assert_eq!(
2806+
reedline.editor.line_buffer().insertion_point(),
2807+
last_line_start
2808+
);
2809+
}
2810+
2811+
#[test]
2812+
fn test_complete_line_from_history() {
2813+
let completer = Box::new(DefaultCompleter::new(Vec::from([String::from("67")])));
2814+
let completion_menu = ReedlineMenu::EngineCompleter(Box::new(
2815+
ColumnarMenu::default().with_name("completion_menu"),
2816+
));
2817+
let mut reedline = Reedline::create()
2818+
.with_quick_completions(true)
2819+
.with_completer(completer)
2820+
.with_menu(completion_menu);
2821+
2822+
// Save "6" to the history and scroll back to it
2823+
let history = HistoryItem::from_command_line("6");
2824+
reedline.history.save(history).unwrap();
2825+
reedline.previous_history();
2826+
assert_eq!(reedline.current_buffer_contents(), "6");
2827+
2828+
// Perform quick completion
2829+
let prompt = DefaultPrompt::default();
2830+
let completion = ReedlineEvent::Menu(String::from("completion_menu"));
2831+
reedline.handle_event(&prompt, completion).unwrap();
2832+
assert_eq!(reedline.current_buffer_contents(), "67");
2833+
2834+
// Insert the "x" to the prompt
2835+
let insertion = EditCommand::InsertString(String::from("x"));
2836+
reedline.run_edit_commands(&[insertion]);
2837+
assert_eq!(reedline.current_buffer_contents(), "67x");
2838+
}
26882839
}

0 commit comments

Comments
 (0)