aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKkoi <79646021+lmBored@users.noreply.github.com>2026-01-28 22:08:41 +0100
committerGitHub <noreply@github.com>2026-01-28 13:08:41 -0800
commit1fef7508186a755bc1e0c651a4815167848f19ec (patch)
tree5627908157f0b8079030f7ef3fa7d5890ca4b447
parentfix: new session on shlvl change (#3111) (diff)
downloadatuin-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.toml16
-rw-r--r--crates/atuin-client/src/settings.rs28
-rw-r--r--crates/atuin/src/command/client/init.rs33
-rw-r--r--crates/atuin/src/command/client/init/bash.rs16
-rw-r--r--crates/atuin/src/command/client/init/fish.rs16
-rw-r--r--crates/atuin/src/command/client/init/powershell.rs7
-rw-r--r--crates/atuin/src/command/client/init/xonsh.rs8
-rw-r--r--crates/atuin/src/command/client/init/zsh.rs16
-rw-r--r--crates/atuin/src/shell/atuin.bash63
-rw-r--r--crates/atuin/src/shell/atuin.fish84
-rw-r--r--crates/atuin/src/shell/atuin.zsh63
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