Skip to content

Add distrobox container integration for tmux, zellij, and fish#10

Merged
tyvsmith merged 10 commits intomainfrom
feature/container-integration
Mar 1, 2026
Merged

Add distrobox container integration for tmux, zellij, and fish#10
tyvsmith merged 10 commits intomainfrom
feature/container-integration

Conversation

@tyvsmith
Copy link
Copy Markdown
Owner

@tyvsmith tyvsmith commented Mar 1, 2026

Summary

  • Add unified container picker (container-fzf) with multi-action fzf interface for both tmux and zellij
  • Fish abbreviations for distrobox commands (db, dbe, dbl, dbs, dbrm, dbc)
  • Starship prompt shows container name when inside a distrobox (Catppuccin mauve)
  • tmux: container-aware splits auto-enter same container, status bar shows container name

tmux (prefix+e)

Key Action
Enter New window running distrobox directly
Alt+h Horizontal split
Alt+v Vertical split

Container-aware splits (prefix+h/v) detect CONTAINER_ID in the current pane and auto-enter the same container in new splits.

zellij (Ctrl+o, e)

Key Action
Enter Floating pane
Alt+s Tiled split
Alt+t New tab (via temp KDL layout with UI chrome)

All actions launch distrobox enter as the pane's command directly — no shell wrapper or text injection.

Files changed

File Description
fish/conf.d/zz_04_abbr.fish Distrobox abbreviations
fish/functions/container-fzf.fish Core fzf picker with --tmux and --zellij modes
starship.toml Container module in prompt
tmux/scripts/container-pick-send.fish Thin tmux popup wrapper
tmux/scripts/container-split.sh Container-aware split detection
tmux/scripts/container-status.sh Status bar container indicator
tmux/tmux.conf.tmpl Keybinds and status bar integration
zellij/config.kdl.tmpl Session mode container picker keybind
zellij/layouts/container.kdl Container development layout

Auto-hide statusbar when only 1 window is open, show when 2+.
Add prefix+b toggle, middle-click to close tab, click interactions
for status-left/right areas, and styled session indicator.
- Fish abbreviations: db, dbe, dbl, dbs, dbrm, dbc for distrobox commands
- Fish function: container-fzf with fzf picker, supports tmux/zellij flags
- tmux: prefix+e enters container in current pane, prefix+E in new window
- tmux: container-aware splits (prefix+h/v inherit CONTAINER_ID)
- tmux: status bar shows container name for active pane
- zellij: session mode 'e' opens container picker in floating pane
- zellij: container.kdl layout with host + picker tabs
- Starship: container module shows name in prompt when inside distrobox
- Single keybind (Ctrl+o, e) opens floating picker with three actions:
  enter=current pane, alt-s=tiled split, alt-t=new tab
- Toggle floating off before creating panes to fix focus issues
- Use alt-s/alt-t instead of ctrl-s/ctrl-t to avoid zellij interception
- Add close_on_exit to picker pane for clean auto-dismiss
- enter: writes command to current pane
- alt-s: new tiled split with distrobox as pane command (direct)
- alt-t: new tab via temp KDL layout with compact-bar/status-bar chrome
- Remove broken --in-place and break-pane approaches
All three zellij picker actions now launch distrobox as a direct pane command:
enter=floating, alt-s=tiled split, alt-t=new tab via temp layout
- Add --tmux flag: enter=new window, alt-h=hsplit, alt-v=vsplit
- Remove old single-action flags (--new-window, --split-h, --split-v)
- Simplify tmux popup script to one-line wrapper calling container-fzf --tmux
- Single prefix+e keybind replaces prefix+e and prefix+E
- Both multiplexers now use the same fzf --expect pattern
Copilot AI review requested due to automatic review settings March 1, 2026 05:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds distrobox container awareness and a unified interactive picker across shell (fish), tmux, and zellij, with prompt/status indicators to surface the active container.

Changes:

  • Introduces container-fzf (fish) as a multi-action fzf-based distrobox container picker with tmux and zellij modes.
  • Integrates container picker + container-aware splits + status indicator into tmux configuration via new helper scripts.
  • Adds zellij keybind + optional layout for container workflows; adds distrobox fish abbreviations and a Starship container module.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
dot_config/zellij/layouts/container.kdl New zellij layout that opens a host tab and a container picker tab.
dot_config/zellij/config.kdl.tmpl Adds a session-mode keybind to launch the container picker in a floating pane.
dot_config/tmux/tmux.conf.tmpl Adds tmux keybinds for the picker, container-aware splits, status-right container indicator, and status auto-hide hooks.
dot_config/tmux/scripts/executable_container-pick-send.fish Thin tmux popup wrapper that runs container-fzf --tmux.
dot_config/tmux/scripts/executable_container-split.sh New script to detect CONTAINER_ID and split into the same container when applicable.
dot_config/tmux/scripts/executable_container-status.sh New status script to display container name/icon in tmux status-right.
dot_config/starship.toml Adds Starship [container] module and inserts it into the prompt format.
dot_config/fish/functions/container-fzf.fish New core picker function with tmux/zellij actions and preview.
dot_config/fish/conf.d/zz_04_abbr.fish Adds distrobox-related fish abbreviations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to +24
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-)
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.

With set -euo pipefail, the cid=$(... | grep '^CONTAINER_ID=' | ...) pipeline will terminate the script whenever CONTAINER_ID isn’t present (grep exit 1), which is expected on the host and for many child processes. That prevents the intended host fallback split path from running. Make the lookup tolerant of “not found” (e.g., grep ... || true / sed -n extraction) and guard pgrep returning 1 when there are no children.

Suggested change
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-)
children_pids=$(pgrep -P "$PANE_PID" 2>/dev/null || true)
for pid in $children_pids $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)

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +86
if set -q _flag_tmux; or set -q _flag_zellij
set key $fzf_output[1]
set selected $fzf_output[2]
else
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.

In tmux/zellij mode, fzf --expect outputs an empty first line when Enter is pressed. Fish command substitution drops empty fields, so $fzf_output[1] can become the selection (not the key) and $fzf_output[2] becomes empty, causing the Enter action to be skipped. Capture fzf output without whitespace-splitting (e.g., read the first two lines explicitly, or collect raw output and split while preserving empties, or use fzf --print0 + string split0).

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +118
tmux split-window -h -c "#{pane_current_path}" "distrobox enter $container_name"
case "alt-v"
tmux split-window -v -c "#{pane_current_path}" "distrobox enter $container_name"
case ""
tmux new-window -n "$container_name" "distrobox enter $container_name"
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.

These tmux commands interpolate $container_name into a single command string. If the container name contains spaces or shell-special characters, it can break the command or be interpreted by the shell. Prefer passing the command and args as separate arguments (and use distrobox enter -- "$container_name") instead of building one shell string.

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +132
zellij action new-pane -d down --name "$container_name" -- distrobox enter $container_name
case "alt-t"
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.

Same quoting/argument-separation issue here: $container_name is passed as unquoted args to zellij action ... -- distrobox enter $container_name. Use -- and quote the name (or otherwise ensure it’s passed as a single argument) to avoid breakage with non-trivial container names.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +157
# Default (no multiplexer flag): enter the container in the current shell
distrobox enter $container_name
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.

distrobox enter $container_name should pass the container name as a single argument (e.g., distrobox enter -- "$container_name") so names with spaces/special characters don’t get split or glob-expanded by fish.

Copilot uses AI. Check for mistakes.
Comment on lines +154 to +157
# 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"
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.
// Usage: zellij --layout container
//
// Opens a host tab and a container picker tab.
// To create a layout with a specific container, use:
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.

The comment claims this creates a layout "with a specific container", but the example command shown doesn’t pass any container selection/name into the layout. Either adjust the wording (it currently just names a session) or document the actual mechanism for preselecting a container if that’s intended.

Suggested change
// To create a layout with a specific container, use:
// To create a named session using this layout, use:

Copilot uses AI. Check for mistakes.
bind "w" { SearchToggleOption "Wrap"; }
}
session {
// Container picker: enter=current pane, alt-s=split, alt-t=new tab
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.

The comment says enter=current pane, but container-fzf --zellij uses Enter to open a floating pane (and then closes the picker). Please update the comment so it matches the actual behavior.

Suggested change
// Container picker: enter=current pane, alt-s=split, alt-t=new tab
// Container picker: enter=floating pane, alt-s=split, alt-t=new tab

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-)
if [ -n "$cid" ]; then
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.

With set -euo pipefail, the cid=$(... | grep '^CONTAINER_ID=' | ...) pipeline will exit the script when grep finds no match (exit 1), which is a normal case on the host and for many processes. That will make tmux status rendering fail instead of just producing an empty string. Make the lookup non-fatal (e.g., grep ... || true / use sed -n extraction) and also guard pgrep returning 1 when there are no children.

Suggested change
for pid in $(pgrep -P "$PANE_PID" 2>/dev/null) $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2-)
if [ -n "$cid" ]; then
children_pids=$(pgrep -P "$PANE_PID" 2>/dev/null || true)
for pid in $children_pids $PANE_PID; do
if [ -r "/proc/$pid/environ" ]; then
cid=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep '^CONTAINER_ID=' | head -1 | cut -d= -f2- || true)
if [ -n "${cid:-}" ]; then

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +35
# Inside a container — new split enters the same container
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter $CONTAINER_ID"
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.

The tmux split-window command builds a shell command string using the untrusted CONTAINER_ID environment value ("distrobox enter $CONTAINER_ID"), which enables command injection from inside the container onto the host. A process in the container can set CONTAINER_ID to contain shell metacharacters (for example foo; rm -rf /), so when you trigger a container-aware split tmux will run the injected commands in a new host shell. To fix this, treat CONTAINER_ID as untrusted: validate it against the expected container name format and/or invoke distrobox enter with CONTAINER_ID passed as a separate, safely-quoted argument so shell metacharacters cannot change the command structure.

Suggested change
# Inside a container — new split enters the same container
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter $CONTAINER_ID"
# Validate CONTAINER_ID to avoid command injection
if [[ "$CONTAINER_ID" =~ ^[A-Za-z0-9._-]+$ ]]; then
# Inside a container — new split enters the same container
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}" "distrobox enter \"$CONTAINER_ID\""
else
# Invalid CONTAINER_ID detected — fall back to a normal host split
tmux split-window "$SPLIT_DIR" -c "#{pane_current_path}"
fi

Copilot uses AI. Check for mistakes.
- Move container-fzf to ~/.local/bin/ as a bash script (works from any shell)
- Remove fish-only container-fzf.fish function
- Convert container-pick-send from .fish to .sh
- Update tmux/zellij configs to call container-fzf directly (no fish wrapper)
- Fix fzf preview to use explicit sh -c to avoid fish /bin/fish interference
Unify hotkeys so both multiplexers use the same mental model:
- Enter = primary action (new window in tmux, new tab in zellij)
- Alt-s = split pane
- Alt-f = floating pane (zellij only, N/A for tmux)

Replaces the old alt-h/alt-v (tmux) and alt-t (zellij) keybinds.
Alt+f is already bound to ToggleFloatingPanes in zellij's
shared_except keybinds, so it never reaches fzf. Users can
toggle floating with Alt+f after opening a container pane.
- container-split.sh / container-status.sh: replace grep with sed -n
  to avoid exit code 1 under set -euo pipefail when no match found;
  add || true to pgrep calls
- container-split.sh: sanitize CONTAINER_ID against [A-Za-z0-9._-]+
  before interpolation to prevent command injection from inside containers;
  pass distrobox as separate args instead of single shell string
- tmux.conf.tmpl: remove duplicate prefix+h/v plain split bindings
  (overridden by container-aware splits below)
- Fix stale comments in tmux.conf.tmpl and container.kdl
@tyvsmith tyvsmith merged commit 43a8fd4 into main Mar 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants