diff options
| author | Kkoi <79646021+lmBored@users.noreply.github.com> | 2026-01-28 22:08:41 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-28 13:08:41 -0800 |
| commit | 1fef7508186a755bc1e0c651a4815167848f19ec (patch) | |
| tree | 5627908157f0b8079030f7ef3fa7d5890ca4b447 | |
| parent | fix: new session on shlvl change (#3111) (diff) | |
| download | atuin-1fef7508186a755bc1e0c651a4815167848f19ec.zip | |
feat: add option to use tmux display-popup (#3058)
This is a "continuation" of #1177 - which was a draft that used FIFOs
(named pipes) to get output from the popup, however this causes popup
not being closed properly, so in this PR I use tmpfile to store the
result and read after popup closes. @ellie could you review this PR
please?
P.S. Thank you @immae for sharing your idea!
## Feature
+ Option to use tmux popup window in `config.toml`
+ Customize window width/height in `config.toml`
+ Tmux display-popup for `zsh, bash, fish`
## 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
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
| -rw-r--r-- | crates/atuin-client/config.toml | 16 | ||||
| -rw-r--r-- | crates/atuin-client/src/settings.rs | 28 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init.rs | 33 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init/bash.rs | 16 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init/fish.rs | 16 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init/powershell.rs | 7 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init/xonsh.rs | 8 | ||||
| -rw-r--r-- | crates/atuin/src/command/client/init/zsh.rs | 16 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.bash | 63 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.fish | 84 | ||||
| -rw-r--r-- | crates/atuin/src/shell/atuin.zsh | 63 |
11 files changed, 323 insertions, 27 deletions
diff --git a/crates/atuin-client/config.toml b/crates/atuin-client/config.toml index 94f4a180..69cfea84 100644 --- a/crates/atuin-client/config.toml +++ b/crates/atuin-client/config.toml @@ -296,6 +296,22 @@ records = true ## Default filter mode can be overridden with the filter_mode setting. # filters = [ "global", "host", "session", "session-preload", "workspace", "directory" ] +[tmux] +## Enable using atuin with tmux popup (requires tmux >= 3.2) +## When enabled and running inside tmux, Atuin will use a popup window for interactive search. +## Set to false to disable the popup. +## This can also be controlled with the ATUIN_TMUX_POPUP environment variable. +## Note: tmux popup is currently supported in zsh, bash, and fish shells. +# enabled = true + +## Width of the tmux popup window +## Can be a percentage, or integer (e.g. "100" means 100 characters wide) +# width = "80%" + +## Height of the tmux popup window +## Can be a percentage, or integer (e.g. "100" means 100 lines tall) +# height = "60%" + [ui] ## Columns to display in the interactive search, from left to right. ## The selection indicator (" > ") is always shown first implicitly. diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs index d0e0ae2f..d849e816 100644 --- a/crates/atuin-client/src/settings.rs +++ b/crates/atuin-client/src/settings.rs @@ -393,6 +393,18 @@ pub struct Search { pub filters: Vec<FilterMode>, } +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Tmux { + /// Enable using atuin with tmux popup (tmux >= 3.2) + pub enabled: bool, + + /// Width of the tmux popup (percentage) + pub width: String, + + /// Height of the tmux popup (percentage) + pub height: String, +} + impl Default for Preview { fn default() -> Self { Self { @@ -438,6 +450,16 @@ impl Default for Search { } } +impl Default for Tmux { + fn default() -> Self { + Self { + enabled: true, + width: "80%".to_string(), + height: "60%".to_string(), + } + } +} + // The preview height strategy also takes max_preview_height into account. #[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum, Serialize)] pub enum PreviewStrategy { @@ -720,6 +742,9 @@ pub struct Settings { #[serde(default)] pub kv: kv::Settings, + + #[serde(default)] + pub tmux: Tmux, } impl Settings { @@ -1040,6 +1065,9 @@ impl Settings { )? .set_default("theme.name", "default")? .set_default("theme.debug", None::<bool>)? + .set_default("tmux.enabled", true)? + .set_default("tmux.width", "80%")? + .set_default("tmux.height", "60%")? .set_default( "prefers_reduced_motion", std::env::var("NO_MOTION") diff --git a/crates/atuin/src/command/client/init.rs b/crates/atuin/src/command/client/init.rs index 410f9b6a..3b55f160 100644 --- a/crates/atuin/src/command/client/init.rs +++ b/crates/atuin/src/command/client/init.rs @@ -1,6 +1,10 @@ use std::path::PathBuf; -use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings}; +use atuin_client::{ + encryption, + record::sqlite_store::SqliteStore, + settings::{Settings, Tmux}, +}; use atuin_dotfiles::store::{AliasStore, var::VarStore}; use clap::{Parser, ValueEnum}; use eyre::{Result, WrapErr}; @@ -43,8 +47,10 @@ pub enum Shell { } impl Cmd { - fn init_nu(&self) { + fn init_nu(&self, _tmux: &Tmux) { let full = include_str!("../../shell/atuin.nu"); + + // TODO: tmux popup for Nu println!("{full}"); if std::env::var("ATUIN_NOBIND").is_err() { @@ -88,25 +94,25 @@ $env.config = ( } } - fn static_init(&self) { + fn static_init(&self, tmux: &Tmux) { match self.shell { Shell::Zsh => { - zsh::init_static(self.disable_up_arrow, self.disable_ctrl_r); + zsh::init_static(self.disable_up_arrow, self.disable_ctrl_r, tmux); } Shell::Bash => { - bash::init_static(self.disable_up_arrow, self.disable_ctrl_r); + bash::init_static(self.disable_up_arrow, self.disable_ctrl_r, tmux); } Shell::Fish => { - fish::init_static(self.disable_up_arrow, self.disable_ctrl_r); + fish::init_static(self.disable_up_arrow, self.disable_ctrl_r, tmux); } Shell::Nu => { - self.init_nu(); + self.init_nu(tmux); } Shell::Xonsh => { - xonsh::init_static(self.disable_up_arrow, self.disable_ctrl_r); + xonsh::init_static(self.disable_up_arrow, self.disable_ctrl_r, tmux); } Shell::PowerShell => { - powershell::init_static(self.disable_up_arrow, self.disable_ctrl_r); + powershell::init_static(self.disable_up_arrow, self.disable_ctrl_r, tmux); } } } @@ -130,6 +136,7 @@ $env.config = ( var_store, self.disable_up_arrow, self.disable_ctrl_r, + &settings.tmux, ) .await?; } @@ -139,6 +146,7 @@ $env.config = ( var_store, self.disable_up_arrow, self.disable_ctrl_r, + &settings.tmux, ) .await?; } @@ -148,16 +156,18 @@ $env.config = ( var_store, self.disable_up_arrow, self.disable_ctrl_r, + &settings.tmux, ) .await?; } - Shell::Nu => self.init_nu(), + Shell::Nu => self.init_nu(&settings.tmux), Shell::Xonsh => { xonsh::init( alias_store, var_store, self.disable_up_arrow, self.disable_ctrl_r, + &settings.tmux, ) .await?; } @@ -167,6 +177,7 @@ $env.config = ( var_store, self.disable_up_arrow, self.disable_ctrl_r, + &settings.tmux, ) .await?; } @@ -186,7 +197,7 @@ $env.config = ( if settings.dotfiles.enabled { self.dotfiles_init(settings).await?; } else { - self.static_init(); + self.static_init(&settings.tmux); } Ok(()) diff --git a/crates/atuin/src/command/client/init/bash.rs b/crates/atuin/src/command/client/init/bash.rs index 00bdad29..8e1349ed 100644 --- a/crates/atuin/src/command/client/init/bash.rs +++ b/crates/atuin/src/command/client/init/bash.rs @@ -1,7 +1,17 @@ +use atuin_client::settings::Tmux; use atuin_dotfiles::store::{AliasStore, var::VarStore}; use eyre::Result; -pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { +fn print_tmux_config(tmux: &Tmux) { + if tmux.enabled { + println!("export ATUIN_TMUX_POPUP_WIDTH='{}'", tmux.width); + println!("export ATUIN_TMUX_POPUP_HEIGHT='{}'", tmux.height); + } else { + println!("export ATUIN_TMUX_POPUP=false"); + } +} + +pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) { let base = include_str!("../../../shell/atuin.bash"); let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { @@ -10,6 +20,7 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { (!disable_ctrl_r, !disable_up_arrow) }; + print_tmux_config(tmux); println!("__atuin_bind_ctrl_r={bind_ctrl_r}"); println!("__atuin_bind_up_arrow={bind_up_arrow}"); println!("{base}"); @@ -20,8 +31,9 @@ pub async fn init( vars: VarStore, disable_up_arrow: bool, disable_ctrl_r: bool, + tmux: &Tmux, ) -> Result<()> { - init_static(disable_up_arrow, disable_ctrl_r); + init_static(disable_up_arrow, disable_ctrl_r, tmux); let aliases = atuin_dotfiles::shell::bash::alias_config(&aliases).await; let vars = atuin_dotfiles::shell::bash::var_config(&vars).await; diff --git a/crates/atuin/src/command/client/init/fish.rs b/crates/atuin/src/command/client/init/fish.rs index 47d41b4b..4a1cda6f 100644 --- a/crates/atuin/src/command/client/init/fish.rs +++ b/crates/atuin/src/command/client/init/fish.rs @@ -1,6 +1,16 @@ +use atuin_client::settings::Tmux; use atuin_dotfiles::store::{AliasStore, var::VarStore}; use eyre::Result; +fn print_tmux_config(tmux: &Tmux) { + if tmux.enabled { + println!("set -gx ATUIN_TMUX_POPUP_WIDTH '{}'", tmux.width); + println!("set -gx ATUIN_TMUX_POPUP_HEIGHT '{}'", tmux.height); + } else { + println!("set -gx ATUIN_TMUX_POPUP false"); + } +} + fn print_bindings( indent: &str, disable_up_arrow: bool, @@ -27,11 +37,12 @@ fn print_bindings( println!("{indent}end"); } -pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { +pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) { let indent = " ".repeat(4); let base = include_str!("../../../shell/atuin.fish"); + print_tmux_config(tmux); println!("{base}"); if std::env::var("ATUIN_NOBIND").is_err() { @@ -81,8 +92,9 @@ pub async fn init( vars: VarStore, disable_up_arrow: bool, disable_ctrl_r: bool, + tmux: &Tmux, ) -> Result<()> { - init_static(disable_up_arrow, disable_ctrl_r); + init_static(disable_up_arrow, disable_ctrl_r, tmux); let aliases = atuin_dotfiles::shell::fish::alias_config(&aliases).await; let vars = atuin_dotfiles::shell::fish::var_config(&vars).await; diff --git a/crates/atuin/src/command/client/init/powershell.rs b/crates/atuin/src/command/client/init/powershell.rs index 7d1b8876..d3399404 100644 --- a/crates/atuin/src/command/client/init/powershell.rs +++ b/crates/atuin/src/command/client/init/powershell.rs @@ -1,6 +1,7 @@ +use atuin_client::settings::Tmux; use atuin_dotfiles::store::{AliasStore, var::VarStore}; -pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { +pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, _tmux: &Tmux) { let base = include_str!("../../../shell/atuin.ps1"); let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { @@ -9,6 +10,7 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { (!disable_ctrl_r, !disable_up_arrow) }; + // TODO: tmux popup for Powershell println!("{base}"); println!( "Enable-AtuinSearchKeys -CtrlR {} -UpArrow {}", @@ -22,8 +24,9 @@ pub async fn init( vars: VarStore, disable_up_arrow: bool, disable_ctrl_r: bool, + tmux: &Tmux, ) -> eyre::Result<()> { - init_static(disable_up_arrow, disable_ctrl_r); + init_static(disable_up_arrow, disable_ctrl_r, tmux); let aliases = atuin_dotfiles::shell::powershell::alias_config(&aliases).await; let vars = atuin_dotfiles::shell::powershell::var_config(&vars).await; diff --git a/crates/atuin/src/command/client/init/xonsh.rs b/crates/atuin/src/command/client/init/xonsh.rs index f28b02f6..8b9f1595 100644 --- a/crates/atuin/src/command/client/init/xonsh.rs +++ b/crates/atuin/src/command/client/init/xonsh.rs @@ -1,7 +1,8 @@ +use atuin_client::settings::Tmux; use atuin_dotfiles::store::{AliasStore, var::VarStore}; use eyre::Result; -pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { +pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, _tmux: &Tmux) { let base = include_str!("../../../shell/atuin.xsh"); let (bind_ctrl_r, bind_up_arrow) = if std::env::var("ATUIN_NOBIND").is_ok() { @@ -9,6 +10,8 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { } else { (!disable_ctrl_r, !disable_up_arrow) }; + + // TODO: tmux popup for xonsh println!( "_ATUIN_BIND_CTRL_R={}", if bind_ctrl_r { "True" } else { "False" } @@ -25,8 +28,9 @@ pub async fn init( vars: VarStore, disable_up_arrow: bool, disable_ctrl_r: bool, + tmux: &Tmux, ) -> Result<()> { - init_static(disable_up_arrow, disable_ctrl_r); + init_static(disable_up_arrow, disable_ctrl_r, tmux); let aliases = atuin_dotfiles::shell::xonsh::alias_config(&aliases).await; let vars = atuin_dotfiles::shell::xonsh::var_config(&vars).await; diff --git a/crates/atuin/src/command/client/init/zsh.rs b/crates/atuin/src/command/client/init/zsh.rs index 2cfdae72..0ab832fe 100644 --- a/crates/atuin/src/command/client/init/zsh.rs +++ b/crates/atuin/src/command/client/init/zsh.rs @@ -1,9 +1,20 @@ +use atuin_client::settings::Tmux; use atuin_dotfiles::store::{AliasStore, var::VarStore}; use eyre::Result; -pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool) { +fn print_tmux_config(tmux: &Tmux) { + if tmux.enabled { + println!("export ATUIN_TMUX_POPUP_WIDTH='{}'", tmux.width); + println!("export ATUIN_TMUX_POPUP_HEIGHT='{}'", tmux.height); + } else { + println!("export ATUIN_TMUX_POPUP=false"); + } +} + +pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) { let base = include_str!("../../../shell/atuin.zsh"); + print_tmux_config(tmux); println!("{base}"); if std::env::var("ATUIN_NOBIND").is_err() { @@ -33,8 +44,9 @@ pub async fn init( vars: VarStore, disable_up_arrow: bool, disable_ctrl_r: bool, + tmux: &Tmux, ) -> Result<()> { - init_static(disable_up_arrow, disable_ctrl_r); + init_static(disable_up_arrow, disable_ctrl_r, tmux); let aliases = atuin_dotfiles::shell::zsh::alias_config(&aliases).await; let vars = atuin_dotfiles::shell::zsh::var_config(&vars).await; diff --git a/crates/atuin/src/shell/atuin.bash b/crates/atuin/src/shell/atuin.bash index a57b6801..fd692d24 100644 --- a/crates/atuin/src/shell/atuin.bash +++ b/crates/atuin/src/shell/atuin.bash @@ -240,6 +240,67 @@ __atuin_accept_line() { #------------------------------------------------------------------------------ +# Check if tmux popup is available (tmux >= 3.2) +__atuin_tmux_popup_check() { + [[ -n "${TMUX-}" ]] || return 1 + [[ "${ATUIN_TMUX_POPUP:-true}" != "false" ]] || return 1 + + # https://github.com/tmux/tmux/wiki/FAQ#how-often-is-tmux-released-what-is-the-version-number-scheme + local tmux_version + tmux_version=$(tmux -V 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p') # Could have used grep... + [[ -z "$tmux_version" ]] && return 1 + + local m1 m2 + m1=${tmux_version%%.*} + m2=${tmux_version#*.} + m2=${m2%%.*} + [[ "$m1" =~ ^[0-9]+$ ]] || return 1 + [[ "$m2" =~ ^[0-9]+$ ]] || m2=0 + (( m1 > 3 || (m1 == 3 && m2 >= 2) )) +} + +# Use global variable to fix scope issues with traps +__atuin_popup_tmpdir="" +__atuin_tmux_popup_cleanup() { + [[ -n "$__atuin_popup_tmpdir" && -d "$__atuin_popup_tmpdir" ]] && rm -rf "$__atuin_popup_tmpdir" + __atuin_popup_tmpdir="" +} + +__atuin_search_cmd() { + local -a search_args=("$@") + + if __atuin_tmux_popup_check; then + __atuin_popup_tmpdir=$(mktemp -d) || return 1 + local result_file="$__atuin_popup_tmpdir/result" + + trap '__atuin_tmux_popup_cleanup' EXIT HUP INT TERM + + local escaped_query escaped_args + escaped_query=$(printf '%s' "$READLINE_LINE" | sed "s/'/'\\\\''/g") + escaped_args="" + for arg in "${search_args[@]}"; do + escaped_args+=" '$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")'" + done + + # In the popup, atuin goes to terminal, stderr goes to file + local cdir popup_width popup_height + cdir=$(pwd) + popup_width="${ATUIN_TMUX_POPUP_WIDTH:-80%}" # Keep default value anyways + popup_height="${ATUIN_TMUX_POPUP_HEIGHT:-60%}" + tmux display-popup -d "$cdir" -w "$popup_width" -h "$popup_height" -E -E -- \ + sh -c "ATUIN_SHELL=bash ATUIN_LOG=error ATUIN_QUERY='$escaped_query' atuin search $escaped_args -i 2>'$result_file'" + + if [[ -f "$result_file" ]]; then + cat "$result_file" + fi + + __atuin_tmux_popup_cleanup + trap - EXIT HUP INT TERM + else + ATUIN_SHELL=bash ATUIN_LOG=error ATUIN_QUERY=$READLINE_LINE atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 + fi +} + __atuin_history() { # Default action of the up key: When this function is called with the first # argument `--shell-up-key-binding`, we perform Atuin's history search only @@ -268,7 +329,7 @@ __atuin_history() { READLINE_LINE="" READLINE_POINT=0 local __atuin_output - __atuin_output=$(ATUIN_SHELL=bash ATUIN_LOG=error ATUIN_QUERY="$READLINE_LINE" atuin search "$@" -i 3>&1 1>&2 2>&3) + __atuin_output=$(__atuin_search_cmd "$@") # We do nothing when the search is canceled. [[ $__atuin_output ]] || return 0 diff --git a/crates/atuin/src/shell/atuin.fish b/crates/atuin/src/shell/atuin.fish index 683b0fd7..3aab52d2 100644 --- a/crates/atuin/src/shell/atuin.fish +++ b/crates/atuin/src/shell/atuin.fish @@ -21,6 +21,47 @@ function _atuin_postexec --on-event fish_postexec set --erase ATUIN_HISTORY_ID end +# Check if tmux popup is available (tmux >= 3.2) +function _atuin_tmux_popup_check + if not test -n "$TMUX" + echo 0 + return + end + + if test "$ATUIN_TMUX_POPUP" = "false" + echo 0 + return + end + + set -l tmux_version (tmux -V 2>/dev/null | string match -r '\d+\.\d+') + if not test -n "$tmux_version" + echo 0 + return + end + + set -l parts (string split '.' $tmux_version) + set -l m1 $parts[1] + set -l m2 0 + if test (count $parts) -ge 2 + set m2 $parts[2] + end + + if not string match -rq '^[0-9]+$' -- "$m1" + echo 0 + return + end + + if not string match -rq '^[0-9]+$' -- "$m2" + set m2 0 + end + + if test "$m1" -gt 3 2>/dev/null; or begin; test "$m1" -eq 3 2>/dev/null; and test "$m2" -ge 2 2>/dev/null; end + echo 1 + else + echo 0 + end +end + function _atuin_search set -l keymap_mode switch $fish_key_bindings @@ -35,10 +76,45 @@ function _atuin_search set keymap_mode emacs end - # In fish 3.4 and above we can use `"$(some command)"` to keep multiple lines separate; - # but to support fish 3.3 we need to use `(some command | string collect)`. - # https://fishshell.com/docs/current/relnotes.html#id24 (fish 3.4 "Notable improvements and fixes") - set -l ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 | string collect) + set -l use_tmux_popup (_atuin_tmux_popup_check) + + set -l ATUIN_H + if test "$use_tmux_popup" -eq 1 + set -l tmpdir (mktemp -d) + if not test -d "$tmpdir" + # if mktemp got errors + set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 | string collect) + else + set -l result_file "$tmpdir/result" + + set -l query (commandline -b | string replace -a "'" "'\\''") + set -l escaped_args "" + for arg in $argv + set escaped_args "$escaped_args '"(string replace -a "'" "'\\''" -- $arg)"'" + end + + # In the popup, atuin goes to terminal, stderr goes to file + set -l cdir (pwd) + # Keep default value anyways + set -l popup_width (test -n "$ATUIN_TMUX_POPUP_WIDTH" && echo "$ATUIN_TMUX_POPUP_WIDTH" || echo "80%") + set -l popup_height (test -n "$ATUIN_TMUX_POPUP_HEIGHT" && echo "$ATUIN_TMUX_POPUP_HEIGHT" || echo "60%") + tmux display-popup -d "$cdir" -w "$popup_width" -h "$popup_height" -E -E -- \ + sh -c "ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY='$query' atuin search --keymap-mode=$keymap_mode$escaped_args -i 2>'$result_file'" + + if test -f "$result_file" + set ATUIN_H (cat "$result_file" | string collect) + end + + rm -rf "$tmpdir" + end + else + # In fish 3.4 and above we can use `"$(some command)"` to keep multiple lines separate; + # but to support fish 3.3 we need to use `(some command | string collect)`. + # https://fishshell.com/docs/current/relnotes.html#id24 (fish 3.4 "Notable improvements and fixes") + set ATUIN_H (ATUIN_SHELL=fish ATUIN_LOG=error ATUIN_QUERY=(commandline -b) atuin search --keymap-mode=$keymap_mode $argv -i 3>&1 1>&2 2>&3 | string collect) + end + + set ATUIN_H (string trim -- $ATUIN_H) # trim whitespace if test -n "$ATUIN_H" if string match --quiet '__atuin_accept__:*' "$ATUIN_H" diff --git a/crates/atuin/src/shell/atuin.zsh b/crates/atuin/src/shell/atuin.zsh index 5a817209..0f7bf8ad 100644 --- a/crates/atuin/src/shell/atuin.zsh +++ b/crates/atuin/src/shell/atuin.zsh @@ -52,6 +52,67 @@ _atuin_precmd() { export ATUIN_HISTORY_ID="" } +# Check if tmux popup is available (tmux >= 3.2) +__atuin_tmux_popup_check() { + [[ -n "${TMUX-}" ]] || return 1 + [[ "${ATUIN_TMUX_POPUP:-true}" != "false" ]] || return 1 + + # https://github.com/tmux/tmux/wiki/FAQ#how-often-is-tmux-released-what-is-the-version-number-scheme + local tmux_version + tmux_version=$(tmux -V 2>/dev/null | sed -n 's/^[^0-9]*\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p') # Could have used grep... + [[ -z "$tmux_version" ]] && return 1 + + local m1 m2 + m1=${tmux_version%%.*} + m2=${tmux_version#*.} + m2=${m2%%.*} + [[ "$m1" =~ ^[0-9]+$ ]] || return 1 + [[ "$m2" =~ ^[0-9]+$ ]] || m2=0 + (( m1 > 3 || (m1 == 3 && m2 >= 2) )) +} + +# Use global variable to fix scope issues with traps +__atuin_popup_tmpdir="" +__atuin_tmux_popup_cleanup() { + [[ -n "$__atuin_popup_tmpdir" && -d "$__atuin_popup_tmpdir" ]] && rm -rf "$__atuin_popup_tmpdir" + __atuin_popup_tmpdir="" +} + +__atuin_search_cmd() { + local -a search_args=("$@") + + if __atuin_tmux_popup_check; then + __atuin_popup_tmpdir=$(mktemp -d) || return 1 + local result_file="$__atuin_popup_tmpdir/result" + + trap '__atuin_tmux_popup_cleanup' EXIT HUP INT TERM + + local escaped_query escaped_args + escaped_query=$(printf '%s' "$BUFFER" | sed "s/'/'\\\\''/g") + escaped_args="" + for arg in "${search_args[@]}"; do + escaped_args+=" '$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")'" + done + + # In the popup, atuin goes to terminal, stderr goes to file + local cdir popup_width popup_height + cdir=$(pwd) + popup_width="${ATUIN_TMUX_POPUP_WIDTH:-80%}" # Keep default value anyways + popup_height="${ATUIN_TMUX_POPUP_HEIGHT:-60%}" + tmux display-popup -d "$cdir" -w "$popup_width" -h "$popup_height" -E -E -- \ + sh -c "ATUIN_SHELL=zsh ATUIN_LOG=error ATUIN_QUERY='$escaped_query' atuin search $escaped_args -i 2>'$result_file'" + + if [[ -f "$result_file" ]]; then + cat "$result_file" + fi + + __atuin_tmux_popup_cleanup + trap - EXIT HUP INT TERM + else + ATUIN_SHELL=zsh ATUIN_LOG=error ATUIN_QUERY=$BUFFER atuin search "${search_args[@]}" -i 3>&1 1>&2 2>&3 + fi +} + _atuin_search() { emulate -L zsh zle -I @@ -60,7 +121,7 @@ _atuin_search() { # TODO: not this local output # shellcheck disable=SC2048 - output=$(ATUIN_SHELL=zsh ATUIN_LOG=error ATUIN_QUERY=$BUFFER atuin search $* -i 3>&1 1>&2 2>&3) + output=$(__atuin_search_cmd $*) zle reset-prompt # re-enable bracketed paste |
