aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs68
-rw-r--r--crates/atuin/src/command/client/search/keybindings/conditions.rs35
-rw-r--r--crates/atuin/src/command/client/search/keybindings/defaults.rs55
-rw-r--r--crates/atuin/src/command/client/search/keybindings/keymap.rs1
-rw-r--r--docs/docs/configuration/advanced-key-binding.md1
5 files changed, 160 insertions, 0 deletions
diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs
index 7cddb163..b5186706 100644
--- a/crates/atuin/src/command/client/search/interactive.rs
+++ b/crates/atuin/src/command/client/search/interactive.rs
@@ -105,6 +105,7 @@ pub fn to_compactness(f: &Frame, settings: &Settings) -> Compactness {
}
#[allow(clippy::struct_field_names)]
+#[allow(clippy::struct_excessive_bools)]
pub struct State {
history_count: i64,
update_needed: Option<Version>,
@@ -118,6 +119,7 @@ pub struct State {
current_cursor: Option<CursorStyle>,
tab_index: usize,
pending_vim_key: Option<char>,
+ original_input_empty: bool,
pub inspecting_state: InspectingState,
@@ -301,6 +303,7 @@ impl State {
input_byte_len: self.search.input.as_str().len(),
selected_index: self.results_state.selected(),
results_len: self.results_len,
+ original_input_empty: self.original_input_empty,
};
// Convert KeyEvent to SingleKey
@@ -1416,6 +1419,7 @@ pub async fn history(
},
prefix: false,
pending_vim_key: None,
+ original_input_empty: original_query.is_empty(),
};
app.initialize_keymap_cursor(settings);
@@ -1806,6 +1810,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -1859,6 +1864,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -1976,6 +1982,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2033,6 +2040,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2086,6 +2094,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2135,6 +2144,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2193,6 +2203,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2252,6 +2263,7 @@ mod tests {
current_cursor: None,
tab_index: 0,
pending_vim_key: None,
+ original_input_empty: false,
inspecting_state: InspectingState {
current: None,
next: None,
@@ -2582,4 +2594,60 @@ mod tests {
state.execute_action(&Action::ClearLine, &settings);
assert_eq!(state.search.input.as_str(), "");
}
+
+ #[test]
+ fn keymap_config_return_query() {
+ use atuin_client::settings::KeyBindingConfig;
+ use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
+ use std::collections::HashMap;
+
+ let mut settings = Settings::utc();
+ // Configure tab to return-query
+ settings.keymap.emacs = HashMap::from([(
+ "tab".to_string(),
+ KeyBindingConfig::Simple("return-query".to_string()),
+ )]);
+
+ let mut state = State {
+ history_count: 100,
+ update_needed: None,
+ results_state: ListState::default(),
+ switched_search_mode: false,
+ search_mode: SearchMode::Fuzzy,
+ results_len: 100,
+ accept: false,
+ keymap_mode: KeymapMode::Emacs,
+ prefix: false,
+ current_cursor: None,
+ tab_index: 0,
+ pending_vim_key: None,
+ original_input_empty: false,
+ inspecting_state: InspectingState {
+ current: None,
+ next: None,
+ previous: None,
+ },
+ keymaps: KeymapSet::from_settings(&settings),
+ search: SearchState {
+ input: "test query".to_string().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::ReturnQuery),
+ "Tab configured as return-query should return InputAction::ReturnQuery"
+ );
+ }
}
diff --git a/crates/atuin/src/command/client/search/keybindings/conditions.rs b/crates/atuin/src/command/client/search/keybindings/conditions.rs
index ed414300..bc485713 100644
--- a/crates/atuin/src/command/client/search/keybindings/conditions.rs
+++ b/crates/atuin/src/command/client/search/keybindings/conditions.rs
@@ -8,6 +8,7 @@ pub enum ConditionAtom {
CursorAtStart,
CursorAtEnd,
InputEmpty,
+ OriginalInputEmpty,
ListAtEnd,
ListAtStart,
NoResults,
@@ -46,6 +47,8 @@ pub struct EvalContext {
pub selected_index: usize,
/// Total number of results.
pub results_len: usize,
+ /// Whether the original input (query passed to the TUI) was empty.
+ pub original_input_empty: bool,
}
// ---------------------------------------------------------------------------
@@ -59,6 +62,7 @@ impl ConditionAtom {
ConditionAtom::CursorAtStart => ctx.cursor_position == 0,
ConditionAtom::CursorAtEnd => ctx.cursor_position == ctx.input_width,
ConditionAtom::InputEmpty => ctx.input_byte_len == 0,
+ ConditionAtom::OriginalInputEmpty => ctx.original_input_empty,
ConditionAtom::ListAtEnd => {
ctx.results_len == 0 || ctx.selected_index >= ctx.results_len.saturating_sub(1)
}
@@ -74,6 +78,7 @@ impl ConditionAtom {
"cursor-at-start" => Ok(ConditionAtom::CursorAtStart),
"cursor-at-end" => Ok(ConditionAtom::CursorAtEnd),
"input-empty" => Ok(ConditionAtom::InputEmpty),
+ "original-input-empty" => Ok(ConditionAtom::OriginalInputEmpty),
"list-at-end" => Ok(ConditionAtom::ListAtEnd),
"list-at-start" => Ok(ConditionAtom::ListAtStart),
"no-results" => Ok(ConditionAtom::NoResults),
@@ -88,6 +93,7 @@ impl ConditionAtom {
ConditionAtom::CursorAtStart => "cursor-at-start",
ConditionAtom::CursorAtEnd => "cursor-at-end",
ConditionAtom::InputEmpty => "input-empty",
+ ConditionAtom::OriginalInputEmpty => "original-input-empty",
ConditionAtom::ListAtEnd => "list-at-end",
ConditionAtom::ListAtStart => "list-at-start",
ConditionAtom::NoResults => "no-results",
@@ -370,12 +376,24 @@ mod tests {
selected: usize,
len: usize,
) -> EvalContext {
+ ctx_with_original(cursor, width, byte_len, selected, len, false)
+ }
+
+ fn ctx_with_original(
+ cursor: usize,
+ width: usize,
+ byte_len: usize,
+ selected: usize,
+ len: usize,
+ original_input_empty: bool,
+ ) -> EvalContext {
EvalContext {
cursor_position: cursor,
input_width: width,
input_byte_len: byte_len,
selected_index: selected,
results_len: len,
+ original_input_empty,
}
}
@@ -401,6 +419,22 @@ mod tests {
}
#[test]
+ fn atom_original_input_empty() {
+ // original_input_empty = true
+ assert!(
+ ConditionAtom::OriginalInputEmpty.evaluate(&ctx_with_original(0, 0, 0, 0, 10, true))
+ );
+ // original_input_empty = false
+ assert!(
+ !ConditionAtom::OriginalInputEmpty.evaluate(&ctx_with_original(0, 0, 0, 0, 10, false))
+ );
+ // original_input_empty is independent of current input state
+ assert!(
+ ConditionAtom::OriginalInputEmpty.evaluate(&ctx_with_original(0, 5, 5, 0, 10, true))
+ );
+ }
+
+ #[test]
fn atom_list_at_end() {
assert!(ConditionAtom::ListAtEnd.evaluate(&ctx(0, 0, 0, 99, 100)));
assert!(!ConditionAtom::ListAtEnd.evaluate(&ctx(0, 0, 0, 50, 100)));
@@ -428,6 +462,7 @@ mod tests {
"cursor-at-start",
"cursor-at-end",
"input-empty",
+ "original-input-empty",
"list-at-end",
"list-at-start",
"no-results",
diff --git a/crates/atuin/src/command/client/search/keybindings/defaults.rs b/crates/atuin/src/command/client/search/keybindings/defaults.rs
index 121c59fe..64dca691 100644
--- a/crates/atuin/src/command/client/search/keybindings/defaults.rs
+++ b/crates/atuin/src/command/client/search/keybindings/defaults.rs
@@ -529,6 +529,7 @@ mod tests {
input_byte_len: width,
selected_index: selected,
results_len: len,
+ original_input_empty: false,
}
}
@@ -1217,4 +1218,58 @@ mod tests {
modified.prefix = "x".to_string();
assert!(modified.has_non_default_values());
}
+
+ #[test]
+ fn original_input_empty_condition_in_config() {
+ use atuin_client::settings::{KeyBindingConfig, KeyRuleConfig};
+ use std::collections::HashMap;
+
+ let mut settings = default_settings();
+ // Configure esc to: if original-input-empty -> return-query, else return-original
+ settings.keymap.emacs = HashMap::from([(
+ "esc".to_string(),
+ KeyBindingConfig::Rules(vec![
+ KeyRuleConfig {
+ when: Some("original-input-empty".to_string()),
+ action: "return-query".to_string(),
+ },
+ KeyRuleConfig {
+ when: None,
+ action: "return-original".to_string(),
+ },
+ ]),
+ )]);
+
+ let set = KeymapSet::from_settings(&settings);
+
+ // When original input was empty, should return-query
+ let ctx_original_empty = EvalContext {
+ cursor_position: 0,
+ input_width: 5,
+ input_byte_len: 5,
+ selected_index: 0,
+ results_len: 10,
+ original_input_empty: true,
+ };
+ assert_eq!(
+ set.emacs.resolve(&key("esc"), &ctx_original_empty),
+ Some(Action::ReturnQuery),
+ "esc with original_input_empty=true should return-query"
+ );
+
+ // When original input was not empty, should return-original
+ let ctx_original_not_empty = EvalContext {
+ cursor_position: 0,
+ input_width: 5,
+ input_byte_len: 5,
+ selected_index: 0,
+ results_len: 10,
+ original_input_empty: false,
+ };
+ assert_eq!(
+ set.emacs.resolve(&key("esc"), &ctx_original_not_empty),
+ Some(Action::ReturnOriginal),
+ "esc with original_input_empty=false should return-original"
+ );
+ }
}
diff --git a/crates/atuin/src/command/client/search/keybindings/keymap.rs b/crates/atuin/src/command/client/search/keybindings/keymap.rs
index 4d91e180..bbf034b2 100644
--- a/crates/atuin/src/command/client/search/keybindings/keymap.rs
+++ b/crates/atuin/src/command/client/search/keybindings/keymap.rs
@@ -126,6 +126,7 @@ mod tests {
input_byte_len: width,
selected_index: selected,
results_len: len,
+ original_input_empty: false,
}
}
diff --git a/docs/docs/configuration/advanced-key-binding.md b/docs/docs/configuration/advanced-key-binding.md
index 5027c5a2..1fe9c1e4 100644
--- a/docs/docs/configuration/advanced-key-binding.md
+++ b/docs/docs/configuration/advanced-key-binding.md
@@ -246,6 +246,7 @@ Conditions let a single key do different things depending on the current state.
| `cursor-at-start` | The cursor is at position 0 |
| `cursor-at-end` | The cursor is at the end of the input |
| `input-empty` | The input line is empty (no text entered) |
+| `original-input-empty` | The original query passed to the TUI was empty |
| `list-at-start` | The selection is at the first entry (index 0) |
| `list-at-end` | The selection is at the last entry |
| `no-results` | The search returned zero results |