aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKeith Cirkel <keithamus@users.noreply.github.com>2025-10-01 02:50:46 +0100
committerGitHub <noreply@github.com>2025-09-30 21:50:46 -0400
commit7137e4f2972a5456dc37e302795d08f71894a568 (patch)
tree3dbd15d006aba7eb7d25f61914c452f6876752ca
parentfix: fish up binding (#2902) (diff)
downloadatuin-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.toml4
-rw-r--r--crates/atuin-client/src/settings.rs4
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs120
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;
+ }
}