Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions dot_config/fish/conf.d/zz_04_abbr.fish
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,13 @@ if status is-interactive

# Broot
abbr --add br 'broot'

# --- Container management (distrobox) ---

abbr --add db 'distrobox'
abbr --add dbe 'distrobox enter'
abbr --add dbl 'distrobox list'
abbr --add dbs 'distrobox stop'
abbr --add dbrm 'distrobox rm'
abbr --add dbc 'distrobox create'
end
7 changes: 6 additions & 1 deletion dot_config/starship.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ add_newline = false
# $character
# """
format = """
$cmd_duration 󰜥 $directory $git_branch
$cmd_duration$container 󰜥 $directory $git_branch
$character"""

# Replace the "❯" symbol in the prompt with "➜"
Expand Down Expand Up @@ -95,6 +95,11 @@ format = '[](bold fg:#a6e3a1)[󰉋 $path]($style)[](bold fg:#a6e3a1)'
"Videos" = "  "
"GitHub" = " 󰊤 "

[container]
symbol = "󰏖"
style = "bold bg:#cba6f7 fg:#11111b"
format = ' [](bold fg:#cba6f7)[$symbol $name]($style)[](bold fg:#cba6f7)'

[cmd_duration]
min_time = 0
format = '[](bold fg:#f9e2af)[󰪢 $duration](bold bg:#f9e2af fg:#11111b)[](bold fg:#f9e2af)'
6 changes: 6 additions & 0 deletions dot_config/tmux/scripts/executable_container-pick-send.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# container-pick-send.sh — tmux popup wrapper for container-fzf
# Runs the multi-action container picker inside a tmux popup.
# The popup provides the TTY that fzf needs.

exec container-fzf --tmux
44 changes: 44 additions & 0 deletions dot_config/tmux/scripts/executable_container-split.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# container-split.sh — Container-aware tmux split
# If the current pane is inside a distrobox container, the new split
# automatically enters the same container. Otherwise, opens a normal shell.
#
# Usage: container-split.sh [-h|-v]
# -h horizontal split (side by side)
# -v vertical split (top/bottom)

set -euo pipefail

SPLIT_DIR="${1:--h}"

# Get the active pane's PID, then walk its child processes to find CONTAINER_ID
PANE_PID=$(tmux display-message -p '#{pane_pid}')
CONTAINER_ID=""

# Check the pane's environment for CONTAINER_ID
# Walk the process tree to find a shell that has CONTAINER_ID set
if [ -n "$PANE_PID" ]; then
# Try to read CONTAINER_ID from any child process of the pane
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null || true) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | sed -n 's/^CONTAINER_ID=//p' | head -1)
if [ -n "$cid" ]; then
CONTAINER_ID="$cid"
break
fi
fi
done
fi

if [ -n "$CONTAINER_ID" ]; then
# Sanitize: only allow alphanumeric, dash, underscore, dot
if ! [[ "$CONTAINER_ID" =~ ^[A-Za-z0-9._-]+$ ]]; then
echo "container-split.sh: invalid CONTAINER_ID: $CONTAINER_ID" >&2
exit 1
fi
# Inside a container — new split enters the same container
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" -- distrobox enter "$CONTAINER_ID"
else
# On host — normal split
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}"
fi
20 changes: 20 additions & 0 deletions dot_config/tmux/scripts/executable_container-status.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# container-status.sh — Show the container name for the active tmux pane
# Returns the CONTAINER_ID if the active pane is inside a distrobox/toolbox,
# or empty string if on the host. Used in tmux status-right.

set -euo pipefail

PANE_PID=$(tmux display-message -p '#{pane_pid}' 2>/dev/null)
[ -z "$PANE_PID" ] && exit 0

# Walk the pane's process tree looking for CONTAINER_ID
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null || true) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | sed -n 's/^CONTAINER_ID=//p' | head -1)
if [ -n "$cid" ]; then
echo "󰏖 $cid"
exit 0
fi
fi
done
30 changes: 25 additions & 5 deletions dot_config/tmux/tmux.conf.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ setw -g mode-keys vi
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel

# Pane Controls
bind h split-window -h -c "#{pane_current_path}"
bind v split-window -v -c "#{pane_current_path}"
# Pane Controls (prefix+h/v are container-aware splits, see Container Integration below)
bind -n C-M-PageUp split-window -h -c "#{pane_current_path}"
bind -n C-M-PageDown split-window -v -c "#{pane_current_path}"
bind -n C-M-Home split-window -h -c "#{pane_current_path}"
Expand Down Expand Up @@ -147,8 +145,18 @@ bind n new-session
# [omarchy override] Prefix+R is rename-session in omarchy; this rebinds to reload config
# bind R source-file ~/.config/tmux/tmux.conf

# Mouse: double-click status bar for new window
# ── Container Integration ─────────────────────────────────────────────
# prefix+e: fzf container picker (enter=new window, alt-s=split)
bind e display-popup -E -w 80% -h 60% "~/.config/tmux/scripts/container-pick-send.sh"

# Container-aware splits: if current pane is inside a container, new splits
# auto-enter the same container. Falls back to normal split on host.
bind h run-shell "~/.config/tmux/scripts/container-split.sh -h"
bind v run-shell "~/.config/tmux/scripts/container-split.sh -v"
Comment on lines +152 to +155
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This re-binds prefix+h/prefix+v to run the container-aware split scripts, but the config still contains earlier bindings for bind h split-window ... / bind v split-window ... (lines 22–23) which will now be overridden. Consider removing or updating the earlier bindings to avoid confusing duplicates and make it clear that these keys are intentionally repurposed.

Copilot uses AI. Check for mistakes.

# Mouse: double-click status bar for new window, middle-click tab to close
bind -T root DoubleClick1Status new-window -c "#{pane_current_path}"
bind -T root MouseDown2Status kill-window -t=

# ── Plugins ──────────────────────────────────────────────────────────
set-environment -g TMUX_PLUGIN_MANAGER_PATH '~/.config/tmux/plugins'
Expand Down Expand Up @@ -189,13 +197,24 @@ set -g @catppuccin_window_current_text " #T (#{window_panes})"
set -g status-right-length 200
set -g status-left-length 100
set -g status-left ""
bind -T root MouseDown1StatusLeft new-window -c "#{pane_current_path}"
bind -T root MouseDown1StatusRight choose-tree -Zw
set -g status 2
set -g status-format[1] "#[fg=#{@thm_surface_1},bg=default,fill=default]#(printf '·%.0s' $(seq 1 #{client_width}))"
set -g status-right "#{E:@catppuccin_status_application}"
set -g status-right "#[fg=#{@thm_mauve}]#(~/.config/tmux/scripts/container-status.sh) "
set -agF status-right "#{E:@catppuccin_status_application}"
set -agF status-right "#{E:@catppuccin_status_cpu}"
set -ag status-right "#{E:@catppuccin_status_host}"
set -ag status-right "#{E:@catppuccin_status_session}"

# ── Statusbar Auto-hide ──────────────────────────────────────────────
# Show statusbar only when >1 window; toggle manually with prefix+b
set-hook -g after-new-window 'if -F "#{==:#{session_windows},1}" "set status off" "set status 2"'
set-hook -g window-unlinked 'if -F "#{==:#{session_windows},1}" "set status off" "set status 2"'
set-hook -g client-session-changed 'if -F "#{==:#{session_windows},1}" "set status off" "set status 2"'
set-hook -g session-created 'set status off'
bind b if -F '#{==:#{status},off}' 'set status 2' 'set status off'

# ── Right-click Menu Fix ─────────────────────────────────────────────
# Patch right-click menus: add -O flag so menus stay open after release
run-shell 'tmux list-keys -T root | grep "MouseDown3.*display-menu" | sed "s/display-menu /display-menu -O /;s/-O -O /-O /" > /tmp/tmux-menu-fix.conf && tmux source-file /tmp/tmux-menu-fix.conf && rm -f /tmp/tmux-menu-fix.conf'
Expand All @@ -207,3 +226,4 @@ run '~/.config/tmux/plugins/tpm/tpm'
# otherwise the plugin's set -gF overwrites them). Solid bg breaks custom separators.
set -g window-status-activity-style "bold"
set -g window-status-bell-style "bold"
set -g status-left "#[fg=#313244,bg=default]#[fg=#cdd6f4,bg=#313244,bold]+#[fg=#313244,bg=default,nobold] "
8 changes: 8 additions & 0 deletions dot_config/zellij/config.kdl.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ keybinds clear-defaults=true {
bind "w" { SearchToggleOption "Wrap"; }
}
session {
// Container picker: enter=new tab, alt-s=split
bind "e" {
Run "container-fzf" "--zellij" {
floating true
close_on_exit true
}
SwitchToMode "normal"
}
bind "a" {
LaunchOrFocusPlugin "zellij:about" {
floating true
Expand Down
25 changes: 25 additions & 0 deletions dot_config/zellij/layouts/container.kdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Container development layout for zellij
// Usage: zellij --layout container
//
// Opens a host tab and a container picker tab.
// To create a named session using this layout, use:
// zellij --layout container --session my-project

layout {
default_tab_template {
pane size=1 borderless=true {
plugin location="compact-bar"
}
children
}

tab name="host" focus=true {
pane name="shell"
}

tab name="container" {
pane name="picker" command="fish" {
args "-c" "container-fzf"
}
}
}
171 changes: 171 additions & 0 deletions dot_local/bin/executable_container-fzf
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env bash
# container-fzf: Interactive container picker using fzf
# Lists distrobox containers and enters the selected one.
# Works from any shell. Called standalone or by tmux/zellij integrations.
#
# Flags:
# --tmux tmux mode: enter=new window, alt-s=split, alt-f=N/A
# --zellij zellij mode: enter=new tab, alt-s=split
# --print-only Print the enter command instead of executing it
# --running-only Only show running containers
# --header TEXT Override the fzf header text

set -euo pipefail

# ── Parse flags ──────────────────────────────────────────────────────
mode=""
print_only=0
running_only=0
custom_header=""

while [[ $# -gt 0 ]]; do
case "$1" in
--tmux) mode="tmux"; shift ;;
--zellij) mode="zellij"; shift ;;
--print-only) print_only=1; shift ;;
--running-only) running_only=1; shift ;;
--header) custom_header="$2"; shift 2 ;;
*) echo "container-fzf: unknown flag: $1" >&2; exit 1 ;;
esac
done

# ── Guards ───────────────────────────────────────────────────────────
if ! command -v distrobox >/dev/null 2>&1; then
echo "container-fzf: distrobox not found" >&2
exit 1
fi
if ! command -v fzf >/dev/null 2>&1; then
echo "container-fzf: fzf not found" >&2
exit 1
fi

# ── Header text ──────────────────────────────────────────────────────
if [[ -n "$custom_header" ]]; then
fzf_header="$custom_header"
elif [[ "$mode" == "tmux" ]]; then
fzf_header="enter: new window | alt-s: split"
elif [[ "$mode" == "zellij" ]]; then
fzf_header="enter: new tab | alt-s: split"
else
fzf_header="Select container"
fi

# ── Get container list ───────────────────────────────────────────────
db_output=$(distrobox list --no-color 2>/dev/null | tail -n +2)

if [[ -z "$db_output" ]]; then
echo "container-fzf: no distrobox containers found" >&2
echo "Create one with: distrobox create --name <name> --image <image>" >&2
exit 1
fi

# Filter to running only if requested
if [[ "$running_only" -eq 1 ]]; then
db_output=$(echo "$db_output" | grep 'Up' || true)
if [[ -z "$db_output" ]]; then
echo "container-fzf: no running containers" >&2
exit 1
fi
fi

# ── Build fzf command ────────────────────────────────────────────────
fzf_args=(
--header "$fzf_header"
--preview 'sh -c '\''name=$(echo "$1" | awk -F"|" "{print \$2}" | xargs); echo "$1"; echo "---"; podman inspect --format "Image: {{.Config.Image}}\nCreated: {{.Created}}\nState: {{.State.Status}}" "$name" 2>/dev/null || echo "Container not running - will start on enter"'\'' -- {}'
--prompt "container> "
--layout reverse
--border rounded
--ansi
)

# Multi-action modes use --expect to capture which key was pressed
if [[ "$mode" == "tmux" ]]; then
fzf_args+=(--expect "alt-s")
elif [[ "$mode" == "zellij" ]]; then
fzf_args+=(--expect "alt-s")
fi

# ── Run fzf ──────────────────────────────────────────────────────────
fzf_output=$(echo "$db_output" | fzf "${fzf_args[@]}") || exit 0

if [[ -z "$fzf_output" ]]; then
exit 0
fi

# With --expect, first line is the key pressed, second is the selection
# Without --expect, the only line is the selection
key=""
selected=""
if [[ -n "$mode" ]]; then
key=$(echo "$fzf_output" | head -1)
selected=$(echo "$fzf_output" | tail -1)
else
selected="$fzf_output"
fi

if [[ -z "$selected" ]]; then
exit 0
fi

# Extract container name (second column, pipe-delimited)
container_name=$(echo "$selected" | awk -F'|' '{print $2}' | xargs)

if [[ -z "$container_name" ]]; then
echo "container-fzf: could not parse container name" >&2
exit 1
fi

# ── --print-only ─────────────────────────────────────────────────────
if [[ "$print_only" -eq 1 ]]; then
echo "distrobox enter $container_name"
exit 0
fi

# ── tmux multi-action mode ───────────────────────────────────────────
# All actions run distrobox as the pane/window command directly.
# The popup closes automatically when this script exits.
if [[ "$mode" == "tmux" ]]; then
case "$key" in
alt-s) tmux split-window -v "distrobox enter $container_name" ;;
*) tmux new-window -n "$container_name" "distrobox enter $container_name" ;;
esac
exit 0
fi

# ── zellij multi-action mode ────────────────────────────────────────
# Picker pane has close_on_exit=true, so it auto-closes when script exits.
# All actions launch distrobox as the pane's command directly.
if [[ "$mode" == "zellij" ]]; then
case "$key" in
alt-s)
# Tiled split running distrobox directly
zellij action toggle-floating-panes
zellij action new-pane -d down --name "$container_name" -- distrobox enter "$container_name"
;;
*)
# New tab via temp layout with UI chrome (Enter key)
layout_file=$(mktemp /tmp/zellij-container-XXXXXX.kdl)
cat > "$layout_file" << LAYOUT
layout {
pane size=1 borderless=true {
plugin location="compact-bar"
}
pane command="distrobox" {
args "enter" "$container_name"
name "$container_name"
}
pane size=1 borderless=true {
plugin location="status-bar"
}
}
LAYOUT
zellij action new-tab --layout "$layout_file" --name "$container_name"
rm -f "$layout_file"
;;
esac
exit 0
fi

# ── Default (no multiplexer flag) ────────────────────────────────────
# Enter the container in the current shell
exec distrobox enter "$container_name"