Skip to content

here mode level 2: eliminate send_keys entirely — split-window/swap-pane/kill-pane #1032

@tony

Description

@tony

Parent: #1030

Approach

Eliminate all send_keys usage in --here mode by replacing the "mutate existing pane" pattern with "create fresh pane, swap into position." This is the cleanest possible approach — zero shell assumptions.

Core idea

Instead of sending cd, export, and shell commands to the existing pane (which assumes a POSIX shell is running), create a brand-new pane with the correct directory, environment, and shell, then swap it into the position of the original pane and kill the original.

Implementation

Replace builder.py:690-727:

# Before: send_keys cd, export, shell into existing pane
active_pane.send_keys(f"cd {shlex.quote(start_directory)}", enter=True)
for _ekey, _eval in environment.items():
    _here_pane.send_keys(f"export {_ekey}={shlex.quote(str(_eval))}", enter=True)
_here_pane.send_keys(window_shell, enter=True)

With:

# After: create fresh pane with correct state, swap into position
_active_pane = window.active_pane
assert _active_pane is not None

# Build split-window args
split_kwargs: dict[str, t.Any] = {}
if start_directory:
    split_kwargs["start_directory"] = start_directory
if environment:
    split_kwargs["environment"] = environment
if window_shell:
    split_kwargs["shell"] = window_shell

# Create new pane with correct dir/env/shell via tmux primitives
new_pane = window.split(**split_kwargs)

# Swap new pane into the position of the original
new_pane.cmd("swap-pane", "-t", _active_pane.pane_id)

# Kill the original pane (now in the split position)
_active_pane.kill()

How this maps to tmux commands

tmux split-window -c /path/to/dir -e KEY=VALUE [shell_command]
tmux swap-pane -t %original_pane_id
tmux kill-pane -t %original_pane_id

tmux version requirements

  • split-window -c: Available since tmux 1.9
  • split-window -e: Available since tmux 3.2 (tmuxp's minimum)
  • swap-pane: Available since tmux 0.8
  • All within tmuxp's tmux 3.2+ minimum requirement

Comparison with teamocil

teamocil uses send_keys cd for directory and split-window -c for new panes. This approach goes further than teamocil by eliminating even the cd send_keys call.

Trade-offs

Pros:

  • Zero send_keys for infrastructure — no shell assumption at all
  • Works regardless of what's running in the active pane (vim, python, fish)
  • Environment set at pane creation time via -e (tmux 3.2+)
  • Directory set via -c (tmux primitive)
  • Clean pane with fresh shell — no history pollution

Cons:

  • Destroys the user's existing shell state (history, local variables, aliases loaded via .bashrc)
  • Brief visual flash as the swap occurs
  • More complex than Level 1 — uses swap-pane + kill-pane which is unusual in libtmux
  • The existing pane is killed — if the user had unsaved work in a background process, it's gone
  • May need libtmux API additions if split() doesn't support -e kwargs yet

Behavioral change

This changes --here semantics from "modify the existing pane in-place" to "replace the existing pane with a fresh one." Users who rely on existing shell state in the first pane would lose it. This may be acceptable since --here is conceptually "rebuild this window" — but it should be documented.

Tests to add/update

  • Rewrite test_here_mode_provisions_environment to verify env via pane capture (new pane inherits env)
  • Rewrite test_here_mode_start_directory_special_chars to verify cwd without send_keys
  • Add test verifying --here works when active pane runs a non-shell program
  • Add test verifying original pane is replaced (pane count stays at 1)

libtmux prerequisites

Check whether window.split() supports:

  • environment kwarg (maps to -e KEY=VALUE)
  • shell kwarg (maps to the shell/command argument)
  • start_directory kwarg (maps to -c)

If not, these would need to be added to libtmux first.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions