diff --git a/atuin-client/config.toml b/atuin-client/config.toml index d1c1081..19e8e30 100644 --- a/atuin-client/config.toml +++ b/atuin-client/config.toml @@ -123,3 +123,7 @@ ## 4. Slack webhooks ## 5. Stripe live/test keys # secrets_filter = true + +## Defaults to true. If enabled, upon hitting enter Atuin will immediately execute the command. Press tab to return to the shell and edit. +# This applies for new installs. Old installs will keep the old behaviour unless configured otherwise. +enter_accept = true diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs index 15aa9e0..fca7600 100644 --- a/atuin-client/src/settings.rs +++ b/atuin-client/src/settings.rs @@ -179,6 +179,7 @@ pub struct Settings { pub network_connect_timeout: u64, pub network_timeout: u64, + pub enter_accept: bool, // This is automatically loaded when settings is created. Do not set in // config! Keep secrets and settings apart. @@ -378,6 +379,12 @@ impl Settings { .set_default("secrets_filter", true)? .set_default("network_connect_timeout", 5)? .set_default("network_timeout", 30)? + // enter_accept defaults to false here, but true in the default config file. The dissonance is + // intentional! + // Existing users will get the default "False", so we don't mess with any potential + // muscle memory. + // New users will get the new default, that is more similar to what they are used to. + .set_default("enter_accept", false)? .add_source( Environment::with_prefix("atuin") .prefix_separator("_") @@ -391,6 +398,7 @@ impl Settings { create_dir_all(&config_dir) .wrap_err_with(|| format!("could not create dir {config_dir:?}"))?; + create_dir_all(&data_dir).wrap_err_with(|| format!("could not create dir {data_dir:?}"))?; let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") { diff --git a/atuin-common/src/utils.rs b/atuin-common/src/utils.rs index 7cf4e9d..cd2cd4d 100644 --- a/atuin-common/src/utils.rs +++ b/atuin-common/src/utils.rs @@ -113,6 +113,11 @@ pub fn get_current_dir() -> String { } } +pub fn is_zsh() -> bool { + // only set on zsh + env::var("ZSH_VERSION").is_ok() +} + #[cfg(test)] mod tests { use time::Month; diff --git a/atuin/src/command/client/search/interactive.rs b/atuin/src/command/client/search/interactive.rs index bc93302..bfee81d 100644 --- a/atuin/src/command/client/search/interactive.rs +++ b/atuin/src/command/client/search/interactive.rs @@ -3,6 +3,7 @@ use std::{ time::Duration, }; +use atuin_common::utils; use crossterm::{ event::{ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, @@ -47,6 +48,7 @@ struct State { switched_search_mode: bool, search_mode: SearchMode, results_len: usize, + accept: bool, search: SearchState, engine: Box, @@ -130,7 +132,14 @@ impl State { ExitMode::ReturnQuery => RETURN_QUERY, }) } + KeyCode::Tab => { + return Some(self.results_state.selected()); + } KeyCode::Enter => { + if settings.enter_accept { + self.accept = true; + } + return Some(self.results_state.selected()); } KeyCode::Char('y') if ctrl => { @@ -588,7 +597,7 @@ impl Write for Stdout { // this is a big blob of horrible! clean it up! // for now, it works. But it'd be great if it were more easily readable, and // modular. I'd like to add some more stats and stuff at some point -#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_possible_truncation, clippy::too_many_lines)] pub async fn history( query: &[String], settings: &Settings, @@ -646,10 +655,12 @@ pub async fn history( }, engine: engines::engine(search_mode), results_len: 0, + accept: false, }; let mut results = app.query_results(&mut db).await?; + let accept; let index = 'render: loop { terminal.draw(|f| app.draw(f, &results, settings))?; @@ -664,6 +675,7 @@ pub async fn history( if event_ready?? { loop { if let Some(i) = app.handle_input(settings, &event::read()?, &mut std::io::stdout())? { + accept = app.accept; break 'render i; } if !event::poll(Duration::ZERO)? { @@ -690,8 +702,12 @@ pub async fn history( } if index < results.len() { + let mut command = results.swap_remove(index).command; + if accept && utils::is_zsh() { + command = String::from("__atuin_accept__:") + &command; + } // index is in bounds so we return that entry - Ok(results.swap_remove(index).command) + Ok(command) } else if index == RETURN_ORIGINAL { Ok(String::new()) } else if index == COPY_QUERY { diff --git a/atuin/src/shell/atuin.zsh b/atuin/src/shell/atuin.zsh index edea0c8..19941be 100644 --- a/atuin/src/shell/atuin.zsh +++ b/atuin/src/shell/atuin.zsh @@ -36,12 +36,18 @@ _atuin_search() { # shellcheck disable=SC2048 output=$(ATUIN_LOG=error atuin search $* -i -- $BUFFER 3>&1 1>&2 2>&3) + zle reset-prompt + if [[ -n $output ]]; then RBUFFER="" LBUFFER=$output fi - zle reset-prompt + if [[ $LBUFFER == __atuin_accept__:* ]] + then + LBUFFER=${LBUFFER#__atuin_accept__:} + zle accept-line + fi } _atuin_up_search() { diff --git a/docs/docs/config/config.md b/docs/docs/config/config.md index e0403d2..5cdbe92 100644 --- a/docs/docs/config/config.md +++ b/docs/docs/config/config.md @@ -305,3 +305,16 @@ Default: 5 The max time (in seconds) we wait for a connection to become established with a remote sync server. Any longer than this and the request will fail. + +## enter_accept +Default: false + +Only supported on Zsh. + +When set to true, Atuin will default to immediately executing a command rather +than the user having to press enter twice. Pressing tab will return to the +shell and give the user a chance to edit. + +This technically defaults to true for new users, but false for existing. We +have set `enter_accept = true` in the default config file. This is likely to +change to be the default for everyone in a later release.