diff options
| author | Keith Cirkel <keithamus@users.noreply.github.com> | 2025-10-01 02:50:46 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-30 21:50:46 -0400 |
| commit | 7137e4f2972a5456dc37e302795d08f71894a568 (patch) | |
| tree | 3dbd15d006aba7eb7d25f61914c452f6876752ca | |
| parent | fix: fish up binding (#2902) (diff) | |
| download | atuin-7137e4f2972a5456dc37e302795d08f71894a568.zip | |
feat: add various acceptance keys (#2928)
This change introduces (optional) acceptance keys of Backspace and Left
Arrow, when at the start of a line. These two are common muscle memory
actions for users.
The configuration defaults to false so as not to disrupt existing user
patterns.
This also adds a test that exercises the various acceptance modes, which
as it turns out was quite easy to do.
I discussed this on discord where [Ellie suggested I raised an
issue](https://discord.com/channels/954121165239115808/1421180955657244703/1422642337481228400),
but I felt like a PR would be more tangiable. I've tested this locally
and I'm very happy with how these keys work, it fits my needs well.
`exit_past_line_start` and `accept_past_line_start` can technically
co-exist. When this happens `accept_past_line_start` takes precedence.
Is this okay, or should we reconsider the config? Perhaps
`acceptance_keys = []` would be better here? I'm very open to changes
here.
<!-- Thank you for making a PR! Bug fixes are always welcome, but if
you're adding a new feature or changing an existing one, we'd really
appreciate if you open an issue, post on the forum, or drop in on
Discord -->
## Checks
- [x] I am happy for maintainers to push small adjustments to this PR,
to speed up the review cycle
- [x] I have checked that there are no existing pull requests for the
same thing
| -rw-r--r-- | crates/atuin-client/config.toml | 4 | ||||
| -rw-r--r-- | crates/atuin-client/src/settings.rs | 4 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/search/interactive.rs | 120 |
3 files changed, 128 insertions, 0 deletions
diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml index c143d88f..476a95db 100644 --- a/crates/atuin-client/config.toml +++ b/crates/atuin-client/config.toml @@ -227,6 +227,10 @@ enter_accept = true # exit_past_line_start = true # Defaults to true. The right arrow key performs the same functionality as Tab and copies the selected line to the command line to be modified. # accept_past_line_end = true +# Defaults to false. The left arrow key performs the same functionality as Tab and copies the selected line to the command line to be modified. +# accept_past_line_start = false +# Defaults to false. The backspace key performs the same functionality as Tab and copies the selected line to the command line to be modified when at the start of the line. +# accept_with_backspace = false [sync] # Enable sync v2 by default diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index 71eb490f..489c1a83 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -342,6 +342,8 @@ pub struct Keys { pub scroll_exits: bool, pub exit_past_line_start: bool, pub accept_past_line_end: bool, + pub accept_past_line_start: bool, + pub accept_with_backspace: bool, pub prefix: String, } @@ -804,6 +806,8 @@ impl Settings { .set_default("keys.scroll_exits", true)? .set_default("keys.accept_past_line_end", true)? .set_default("keys.exit_past_line_start", true)? + .set_default("keys.accept_past_line_start", false)? + .set_default("keys.accept_with_backspace", false)? .set_default("keys.prefix", "a")? .set_default("keymap_mode", "emacs")? .set_default("keymap_mode_shell", "auto")? diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs index cb0c195a..2f577292 100644 --- a/crates/atuin/src/command/client/search/interactive.rs +++ b/crates/atuin/src/command/client/search/interactive.rs @@ -231,9 +231,17 @@ impl State { KeyCode::Right if cursor_at_end_of_line && settings.keys.accept_past_line_end => { Some(InputAction::Accept(self.results_state.selected())) } + KeyCode::Left if cursor_at_start_of_line && settings.keys.accept_past_line_start => { + Some(InputAction::Accept(self.results_state.selected())) + } KeyCode::Left if cursor_at_start_of_line && settings.keys.exit_past_line_start => { Some(Self::handle_key_exit(settings)) } + KeyCode::Backspace + if cursor_at_start_of_line && settings.keys.accept_with_backspace => + { + Some(InputAction::Accept(self.results_state.selected())) + } KeyCode::Char('o') if ctrl => { self.tab_index = (self.tab_index + 1) % TAB_TITLES.len(); Some(InputAction::Continue) @@ -1522,4 +1530,116 @@ mod tests { state.scroll_up(1); state.scroll_down(1); } + + #[test] + fn test_accept_keybindings() { + use atuin_client::settings::Keys; + use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + + let mut settings = Settings::utc(); + settings.keys = Keys { + scroll_exits: true, + exit_past_line_start: false, + accept_past_line_end: true, + accept_past_line_start: false, + accept_with_backspace: false, + prefix: "a".to_string(), + }; + + let mut state = State { + history_count: 1, + update_needed: None, + results_state: ListState::default(), + switched_search_mode: false, + search_mode: SearchMode::Fuzzy, + results_len: 1, + accept: false, + keymap_mode: KeymapMode::Emacs, + prefix: false, + current_cursor: None, + tab_index: 0, + search: SearchState { + input: String::new().into(), + filter_mode: FilterMode::Global, + context: Context { + session: String::new(), + cwd: String::new(), + hostname: String::new(), + host_id: String::new(), + git_root: None, + }, + }, + engine: engines::engine(SearchMode::Fuzzy), + now: Box::new(OffsetDateTime::now_utc), + }; + + let tab_event = KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &tab_event); + assert!( + matches!(result, super::InputAction::Accept(_)), + "Tab should always accept" + ); + + // Test left arrow with accept_past_line_start disabled (should continue) + let left_event = KeyEvent::new(KeyCode::Left, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &left_event); + assert!( + matches!(result, super::InputAction::Continue), + "Left arrow should continue when disabled" + ); + + // Test left arrow with accept_past_line_start enabled (should accept at start of line) + settings.keys.accept_past_line_start = true; + let result = state.handle_key_input(&settings, &left_event); + assert!( + matches!(result, super::InputAction::Accept(_)), + "Left arrow should accept at start of line when enabled" + ); + settings.keys.accept_past_line_start = false; + + let backspace_event = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &backspace_event); + assert!( + matches!(result, super::InputAction::Continue), + "Backspace should continue when disabled" + ); + + settings.keys.accept_with_backspace = true; + let result = state.handle_key_input(&settings, &backspace_event); + assert!( + matches!(result, super::InputAction::Accept(_)), + "Backspace should accept at start of line when enabled" + ); + + state.search.input.insert('t'); + state.search.input.insert('e'); + state.search.input.insert('s'); + state.search.input.insert('t'); + state.search.input.end(); + + let right_event = KeyEvent::new(KeyCode::Right, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &right_event); + assert!( + matches!(result, super::InputAction::Accept(_)), + "Right arrow should accept at end of line when enabled" + ); + + settings.keys.accept_past_line_start = true; + let left_event = KeyEvent::new(KeyCode::Left, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &left_event); + assert!( + matches!(result, super::InputAction::Continue), + "Left arrow should continue and end of line, even when enabled" + ); + settings.keys.accept_past_line_start = false; + + settings.keys.accept_with_backspace = true; + let backspace_event = KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE); + let result = state.handle_key_input(&settings, &backspace_event); + assert!( + matches!(result, super::InputAction::Continue), + "Backspace should continue at end of line, even when enabled" + ); + settings.keys.accept_with_backspace = false; + } } |
