Skip to content

tmux parity: add interactive command coverage and align flag semantics with tmux#653

Draft
tony wants to merge 79 commits intomasterfrom
tmux-parity
Draft

tmux parity: add interactive command coverage and align flag semantics with tmux#653
tony wants to merge 79 commits intomasterfrom
tmux-parity

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented Mar 29, 2026

Summary

This PR expands libtmux's tmux command parity across Server, Session, Window, and Pane, with a focus on interactive and client-dependent commands. It also tightens several flag mappings and state-refresh behaviors so the high-level API matches tmux semantics more closely.

In addition to the new command coverage, this includes the three follow-up fixes from review:

  • Session.detach_client() no longer forces -s, so targeted detach behaves like tmux.
  • Window.move_window() now refreshes after successful moves, so returned objects are not stale after relative or cross-session moves.
  • ControlMode now binds client_name to the spawned client by matching client_pid, which fixes multi-client races.

Main Changes

New or expanded tmux command coverage

  • Add Server support for commands such as bind_key, unbind_key, list_keys, list_commands, start_server, lock_server, lock_client, refresh_client, suspend_client, server_access, run_shell, if_shell, source_file, buffer commands, confirm_before, command_prompt, and display_menu.
  • Add Session.detach_client() and window navigation helpers.
  • Add Window support for move_window flags, select_layout flags, last_pane, next_layout, previous_layout, rotate, swap, respawn, link, and unlink.
  • Expand Pane coverage for display_popup, capture_pane, send_keys, select, copy_mode, clock_mode, choose_*, customize_mode, display_panes, find_window, paste_buffer, clear_history, pipe, join, break_pane, move, respawn, and related flags.

tmux semantics and compatibility fixes

  • Correct several flag mappings and version gates to match tmux behavior.
  • Treat move-window -r as standalone renumbering.
  • Fix popup, prompt, paste-buffer, rotate, choose-tree, clear-history, capture-pane, and run-shell semantics where prior mappings diverged from tmux.
  • Add version annotations and parity docs where new parameters were introduced on existing APIs.

Test and tooling support

  • Add ControlMode and pytest fixture support for commands that require a real attached client.
  • Add extensive functional coverage for new command paths and regression cases.
  • Add parity-analysis support files under .claude/ and skills/tmux-parity/ to help maintain command coverage against tmux.

Testing

Ran:

  • uv run ruff check . --fix --show-fixes
  • uv run ruff format .
  • uv run mypy
  • uv run py.test --reruns 0 -vvv

Latest full run:

  • 1067 passed, 1 skipped

Notes

  • Diff vs origin/master is broad because this branch includes both the parity feature work and the review-driven correctness fixes on top.
  • The attached-client test infrastructure is intentionally part of this PR because several new tmux commands cannot be exercised correctly without a real client.

tony added 30 commits March 28, 2026 05:39
why: libtmux wraps ~28 of tmux's ~88 commands (~32% coverage). Need
tooling to systematically audit gaps, compare across tmux versions,
and guide implementation of new command wrappers.
what:
- Add .claude-plugin/ with manifest, commands, agent, skill, and scripts
- /parity-audit: generate full feature parity report (commands, flags,
  format variables, options)
- /version-diff: compare tmux features across 41 version worktrees
- /implement-command: guided workflow for wrapping new tmux commands
- parity-analyzer agent: auto-triggers on natural language parity queries
- tmux-parity skill: shared domain knowledge with reference files
  (command mapping, implementation patterns, C source navigation)
- Extraction scripts: parse tmux cmd-*.c and libtmux .cmd() invocations
…uto-discovery

why: Claude Code auto-discovers plugin components at the project root,
not inside .claude-plugin/. Agent wasn't showing up because it was
nested under .claude-plugin/agents/.
what:
- Move agents/, commands/, skills/ to project root
- Keep scripts/ in .claude-plugin/ (not auto-discovered)
- Remove custom path overrides from plugin.json
- Update cross-references between components
…d discovery

why: Claude Code discovers project commands from .claude/commands/ and agents
from .claude/agents/, not top-level directories.
what:
- Move 3 commands to .claude/commands/
- Move parity-analyzer agent to .claude/agents/
- Remove now-empty top-level commands/ and agents/ dirs
…ent, key-name flags

why: send-keys has many useful flags (reset terminal, hex input, repeat count,
format expansion, copy-mode commands) that were not exposed in the Python API.
what:
- Add reset (-R), copy_mode_cmd (-X), repeat (-N), expand_formats (-F),
  hex_keys (-H), target_client (-c, 3.4+), key_name (-K, 3.4+) parameters
- Version-gate target_client and key_name with has_gte_version("3.4")
- Add SendKeysCase NamedTuple parametrized tests for all new flags
… flags

why: select-pane has rich flag support for directional navigation, pane marking,
and input control that was not exposed in the Python API.
what:
- Add direction (-D/-U/-L/-R), last (-l), keep_zoom (-Z), mark (-m),
  clear_mark (-M), disable_input (-d), enable_input (-e) parameters
- Reuse existing ResizeAdjustmentDirection enum for direction flags
- Skip deprecated -P (style) and -g (show style) flags
- Add tests for direction, last pane, mark/clear, and input toggle
…, and style flags

why: display-message supports many useful flags for format queries and output
control that were not exposed in the Python API.
what:
- Add format_string (-F), all_formats (-a), verbose (-v), no_expand (-I),
  target_client (-c), delay (-d), notify (-N), list_formats (-l, 3.4+),
  no_style (-C, 3.6+) parameters
- Version-gate list_formats and no_style with has_gte_version
- Fix cmd argument handling: only pass when non-empty
- Add DisplayMessageCase NamedTuple parametrized tests
why: select-layout supports flags for spreading panes evenly and cycling
through layouts that were not exposed in the Python API.
what:
- Add spread (-E), next_layout (-n), previous_layout (-o) parameters
- Validate mutual exclusion between layout string and flag parameters
- Add tests for spread, next/previous cycling, and mutual exclusion
…number flags

why: move-window supports flags for positioning, conflict resolution, and
renumbering that were not exposed in the Python API.
what:
- Add after (-a), before (-b), no_select (-d), kill_target (-k),
  renumber (-r) parameters to move_window()
- Add tests for kill_target, renumber, and no_select behaviors
why: new-session supports flags for detaching other clients, suppressing
initial sizing, and specifying config files that were not exposed.
what:
- Add detach_others (-D), no_size (-X), config_file (-f) parameters
- Skip -A (attach-or-create) as it requires a terminal and does not work
  in libtmux's programmatic non-terminal context
- Add test for config_file parameter
why: new-window supports flags for replacing existing windows at a target
index and selecting existing windows by name that were not exposed.
what:
- Add kill_existing (-k) and select_existing (-S) parameters to new_window()
- -k destroys existing window at target index before creating new one
- -S selects existing window with matching name instead of creating new
- Add tests for both flags
why: split-window supports -p for percentage-based sizing which is more
intuitive than the absolute cell count provided by the existing size parameter.
what:
- Add percentage (-p) parameter to Pane.split(), mutually exclusive with size
- Validate that size and percentage are not both specified
- Add tests for percentage split and mutual exclusion
…kup flags

why: capture-pane supports flags for alternate screen capture, silent error
handling, and markup escaping that were not exposed.
what:
- Add alternate_screen (-a), quiet (-q), escape_markup (-M, 3.6+) parameters
- Version-gate escape_markup with has_gte_version("3.6")
- Add tests for quiet, alternate_screen, and escape_markup flags
…en to set_environment

why: show-options supports -q (quiet) and -v (values only) flags, and
set-environment supports -F (format expansion) and -h (hidden) flags that
were not exposed in the Python API.
what:
- Add quiet (-q) and values_only (-v) to _show_options_raw()
- Add expand_format (-F) and hidden (-h) to set_environment()
- Add tests for quiet show_options, hidden env vars, and format expansion
…story

why: clear-history is useful for clearing pane scrollback buffers,
especially important for test isolation and monitoring workflows.
what:
- Add clear_history() method with clear_pane (-H, 3.4+) parameter
- Version-gate -H flag with has_gte_version("3.4")
- Add test verifying history is cleared after sending commands
…window

why: swap-pane and swap-window are core layout manipulation commands needed
for programmatic pane and window reordering.
what:
- Add Pane.swap() with target, detach (-d), move_up (-U), move_down (-D),
  keep_zoom (-Z) parameters wrapping swap-pane
- Add Window.swap() with target, detach (-d) parameters wrapping swap-window
- Add tests verifying pane indices and window indices swap correctly
why: break-pane is essential for layout management, allowing a pane to be
moved into its own window programmatically.
what:
- Add break_pane() method with detach (-d) and window_name (-n) parameters
- Use -P -F#{window_id} to capture new window ID from output
- Return Window object via Window.from_window_id()
- Use server.cmd with explicit -s flag to avoid auto-target conflicts
- Add tests for basic break and named window
why: join-pane is the inverse of break-pane, needed for programmatically
merging panes between windows.
what:
- Add join() method with vertical (-v/-h), detach (-d), full_window (-f),
  size (-l), before (-b) parameters
- Accept Pane, Window, or string target ID
- Use server.cmd with explicit -s/-t to avoid auto-target conflicts
- Add roundtrip test (break + join) and horizontal join test
…respawn-window

why: respawn-pane and respawn-window are needed for restarting processes
in panes/windows without destroying and recreating them.
what:
- Add Pane.respawn() with shell, start_directory (-c), environment (-e),
  kill (-k) parameters wrapping respawn-pane
- Add Window.respawn() with same parameters wrapping respawn-window
- Add tests verifying respawn with kill on active panes and windows
why: pipe-pane is needed for monitoring, logging, and capturing pane output
to external commands or files programmatically.
what:
- Add pipe() method with command, output_only (-O), input_only (-I),
  toggle (-o) parameters wrapping pipe-pane
- Calling with no command stops piping
- Add test piping to file via cat, verifying output captured
why: run-shell executes shell commands in the tmux server context, useful
for background tasks and capturing command output programmatically.
what:
- Add Server.run_shell() with background (-b), delay (-d), capture (-C),
  target_pane (-t) parameters
- Returns stdout when not in background mode, None otherwise
- Add tests for basic command execution and background mode
…rotate

why: Window navigation commands (last/next/previous) and pane rotation are
commonly needed for programmatic window management workflows.
what:
- Add Session.last_window() wrapping last-window
- Add Session.next_window() wrapping next-window
- Add Session.previous_window() wrapping previous-window
- Add Window.rotate() wrapping rotate-window with direction_up (-U),
  keep_zoom (-Z) parameters
- Add tests for all navigation commands and rotation
why: link-window, unlink-window, and wait-for are needed for sharing windows
across sessions and for synchronization between tmux commands.
what:
- Add Window.link() wrapping link-window with target_session, target_index,
  kill_existing (-k), after (-a), before (-b), detach (-d) parameters
- Add Window.unlink() wrapping unlink-window with kill_if_last (-k) parameter
- Add Server.wait_for() wrapping wait-for with lock (-L), unlock (-U),
  set_flag (-S) parameters
- Add tests for link/unlink roundtrip and wait-for set_flag
…pping tmux buffer commands

why: Paste buffer management is needed for programmatic clipboard-like
operations between panes and for exporting/importing text data.
what:
- Add Server.set_buffer() wrapping set-buffer with append (-a) and
  buffer_name (-b) parameters
- Add Server.show_buffer() wrapping show-buffer with buffer_name (-b)
- Add Server.delete_buffer() wrapping delete-buffer with buffer_name (-b)
- Add BufferCase NamedTuple parametrized tests for set/show cycle,
  named buffers, append, and delete
… buffer I/O

why: Buffer file I/O and listing are needed for exporting pane content,
importing data, and querying available buffers programmatically.
what:
- Add Server.save_buffer() wrapping save-buffer with append (-a),
  buffer_name (-b), and file path
- Add Server.load_buffer() wrapping load-buffer with buffer_name (-b)
  and file path
- Add Server.list_buffers() wrapping list-buffers returning raw output
- Add tests for save/load cycle, append mode, and buffer listing
why: paste-buffer is needed for programmatically inserting buffer content
into panes, completing the buffer management API.
what:
- Add Pane.paste_buffer() with buffer_name (-b), delete_after (-d),
  no_trailing_newline (-r), bracket (-p), separator (-s) parameters
- Add test verifying buffer content appears in pane after paste
…popup

why: display-popup (3.2+) creates overlay popups for running commands,
useful in interactive tmux sessions.
what:
- Add display_popup() with command, close_on_exit (-E), close_on_success (-C),
  width (-w), height (-h), start_directory (-d) core parameters
- Version-gate 3.3+ flags: title (-T), border_lines (-b), border_style (-s),
  environment (-e)
- Note: requires attached client, cannot be tested in headless context
…ents

why: list-clients is needed for monitoring connected clients and
multi-client session management.
what:
- Add Server.list_clients() returning raw stdout lines
- Returns empty list when no clients are attached
- Add test verifying return type
why: source-file is needed for loading configuration files programmatically,
useful for applying settings or initializing environments.
what:
- Add Server.source_file() with path and quiet (-q) parameters
- Quiet mode suppresses errors for missing files
- Add tests for sourcing a config and verifying option applied, and quiet mode
why: if-shell enables conditional tmux command execution based on shell
command exit status, useful for scripted environment setup.
what:
- Add Server.if_shell() with shell_command, tmux_command, else_command,
  background (-b), target_pane (-t) parameters
- Add tests for true branch and false-with-else branch
tony added 19 commits March 28, 2026 08:05
why: The pane/window split() and new_window() methods use the fused
form (f"-c{path}",) for the start-directory flag. The respawn methods
used the separated form ("-c", str(path)), diverging from the dominant
pattern in pane/window code.
what:
- Change Pane.respawn -c from ("-c", str(start_path)) to (f"-c{start_path}",)
- Change Window.respawn -c from ("-c", str(start_path)) to (f"-c{start_path}",)
why: last-pane -d disables input and -e enables input per tmux source
(cmd-select-pane.c). The parameters were misnamed: detach mapped to -d
(actually disables input, not detach) and disable_input mapped to -e
(actually enables input). There is no "detach" flag for last-pane.
what:
- Replace detach/disable_input params with disable_input(-d)/enable_input(-e)
- Match the pattern used by Pane.select() which already maps these correctly
- Update docstrings and versionadded annotations
why: Pane.split() gained a percentage parameter for the -p flag but
Window.split() which delegates to it neither accepts nor forwards it.
what:
- Add percentage parameter to Window.split() signature
- Forward percentage to active_pane.split()
- Fix size docstring to say "Cell/row count" (not "or percentage")
why: select-layout -o restores the old/saved layout (undo), while -p
cycles to the previous layout preset. The previous_layout parameter
was sending -o instead of -p. Verified in cmd-select-layout.c:89.
what:
- Change -o to -p for previous_layout flag
- Update docstring to reference correct flag
why: new-session -f calls server_client_set_flags(), setting client
flags like no-output and read-only. It does not load a config file
(that is the top-level tmux -f flag). Verified in cmd-new-session.c:326.
what:
- Rename config_file param to client_flags
- Change type from StrPath to str (flags are strings, not paths)
- Remove pathlib.Path wrapping in implementation
- Update test to use actual client flag value
why: run-shell -C parses the argument as a tmux command instead of a
shell command (ARGS_PARSE_COMMANDS_OR_STRING). It does not capture
output. Verified in cmd-run-shell.c:50.
what:
- Rename capture param to as_tmux_command
- Update docstring to reflect actual semantics
why: command-prompt -N sets PROMPT_NUMERIC (accept only numeric input).
It does not suppress command execution. Verified in
cmd-command-prompt.c:161.
what:
- Rename no_execute param to numeric
- Update docstring to reflect actual semantics
why: capture-pane -M captures from the mode screen (e.g. copy mode),
not escape markup. Verified in cmd-capture-pane.c:132 and tmux
CHANGES: "Add -M flag to capture-pane to use the copy mode screen."
what:
- Rename escape_markup param to mode_screen
- Update docstring and warning message
- Update test name and parameter usage
why: clear-history -H calls screen_reset_hyperlinks(), removing OSC 8
hyperlinks. It does not clear visible pane content. Verified in
cmd-capture-pane.c:226.
what:
- Rename clear_pane param to reset_hyperlinks
- Update docstring and warning message
why: copy-mode -q cancels all modes (window_pane_reset_mode_all), not
"quiet". And -e exits copy mode when scrolling reaches the bottom of
history, not "exit on copy". Verified in cmd-copy-mode.c and tmux
manpage. Internal tmux name for -e is scroll_exit.
what:
- Rename quiet param to cancel
- Rename exit_on_copy param to exit_on_bottom
- Update docstrings to reflect actual semantics
…ling

why: paste-buffer -r changes the inter-line separator from carriage
return to newline. It does not suppress a trailing newline. Verified
in cmd-paste-buffer.c: default sepstr is "\r", -r changes to "\n".
what:
- Rename no_trailing_newline param to linefeed_separator
- Update docstring to reflect actual semantics
why: choose-tree -s starts with sessions collapsed and -w with
windows collapsed. They do not filter to show only sessions/windows.
Verified in tmux manpage and cmd-choose.c source.
what:
- Rename sessions_only to sessions_collapsed
- Rename windows_only to windows_collapsed
- Update docstrings to reflect actual semantics
why: rotate-window with no flags defaults to upward rotation in tmux
(cmd-rotate-window.c:82). The code always injected -D in the else
branch, making rotate() behave as downward instead of tmux's default.
what:
- Replace direction_up with separate upward/downward params
- Only send -U/-D when explicitly requested
- No flags sent by default (tmux default = upward)
…t 3.4+

why: The -b flag for confirm-before and command-prompt was added in
tmux 3.3, not 3.4. Verified across version worktrees: tmux-3.2a has
args "p:t:" while tmux-3.3 has "bp:t:".
what:
- Change "Requires tmux 3.4+" to "Requires tmux 3.3+" in both docstrings
why: The -C flag in tmux display-popup means "close any existing popup on the
client" (server_client_clear_overlay), not "close on success exit code." The
close-on-success behavior is achieved by passing -E twice (-EE), which sets
POPUP_CLOSEEXITZERO in tmux's popup.c.
what:
- Fix close_on_success to emit -E -E instead of -C
- Add close_existing parameter for the actual -C flag behavior
- Update docstrings to document correct flag semantics
…er-move

why: In tmux's cmd-move-window.c, the -r flag triggers session_renumber_windows()
and returns CMD_RETURN_NORMAL immediately — the move logic on subsequent lines
is never reached. The docstring incorrectly said "Renumber all windows after
moving" implying both a renumber and a move occur.
what:
- Fix docstring to document -r as a standalone operation
- Note that other parameters are ignored when renumber is used
why: tmux detach-client treats -s before -t, so always forcing a session target
detached every client on the session instead of the requested client.
what:
- remove the unconditional session target from Session.detach_client
- clarify all_clients plus target_client behavior in the docstring
- add regressions for default and explicit target detach behavior
why: move-window can land on an index different from the requested target and can
also change the owning session, leaving the returned Window stale.
what:
- refresh Window state after every successful move-window command
- add regressions for relative move freshness
- add a cross-session move regression with an explicit destination
why: recording the first client returned by list-clients can capture an existing
attached client instead of the control-mode client spawned for the test.
what:
- wait for the spawned client pid to appear in list-clients
- record client_name from the matching pid row
- add a nested control-mode regression that verifies pid-to-name binding
tony added 4 commits March 29, 2026 10:59
…nup, socket_path

why: tempfile.mktemp() has a TOCTOU race; failure-path left open fds and live
     subprocess; socket_path was silently ignored.
what:
- Replace tempfile.mktemp() + os.mkfifo() FIFO with os.pipe() pair (no
  filesystem, no race condition)
- Add failure-path cleanup in __enter__: if retry_until fails, close _write_fd
  and terminate subprocess before re-raising
- Handle socket_path: build socket_args checking socket_name first (-L),
  then socket_path (-S), then empty
- Remove unused tempfile and pathlib imports
…_on_success combined

why: tmux counts -E flags via args_count(); 3x -E evaluates as != 2, falls
     through to the args_has() branch and silently behaves like 1x -E
     (close-on-any-exit), discarding the close_on_success intent entirely.
what:
- Add mutual-exclusion guard: raise ValueError when both close_on_exit and
  close_on_success are True
- Assign message to variable first to satisfy EM101/TRY003 linting rules
…rsion("3.3")

why: Both methods unconditionally emit -b despite their docstrings noting it
     requires tmux 3.3+. On older tmux this causes a hard command error instead
     of the project-convention warn-and-skip behaviour.
what:
- confirm_before: add has_gte_version("3.3") guard; warn and skip -b on older tmux
- command_prompt: same
- Add lazy imports for warnings and has_gte_version inside each method,
  matching the pattern already used in pane.py
… -b; scope detach-client to session

why: Warn-and-skip for -b would silently change semantics to blocking, hanging
     the caller indefinitely. Session.detach_client() without -s was not actually
     session-scoped, making it wrong on multi-session servers.
what:
- confirm_before / command_prompt: replace warnings.warn with LibTmuxException
  when tmux < 3.3; -b is not optional, dropping it hangs the command queue
- Session.detach_client(): restructure arg building so no-target uses
  -s self.session_id, target_client+all_clients uses -a -t, target_client
  alone uses -t
- Update docstring: no-target now says "detaches all clients in this session"
- Update tests: test_detach_client and renamed test now assert 0 clients
  remain (all session clients detached) rather than before-1
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 29, 2026

Codecov Report

❌ Patch coverage is 41.16223% with 486 lines in your changes missing coverage. Please review.
✅ Project coverage is 48.77%. Comparing base (6d5cc6e) to head (857384a).

Files with missing lines Patch % Lines
src/libtmux/pane.py 37.91% 121 Missing and 87 partials ⚠️
src/libtmux/server.py 39.08% 108 Missing and 65 partials ⚠️
src/libtmux/window.py 48.11% 32 Missing and 23 partials ⚠️
src/libtmux/_internal/control_mode.py 50.00% 25 Missing and 1 partial ⚠️
src/libtmux/session.py 45.71% 12 Missing and 7 partials ⚠️
src/libtmux/options.py 50.00% 1 Missing and 1 partial ⚠️
src/libtmux/pytest_plugin.py 33.33% 2 Missing ⚠️
src/libtmux/common.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #653      +/-   ##
==========================================
- Coverage   51.19%   48.77%   -2.42%     
==========================================
  Files          25       26       +1     
  Lines        2590     3401     +811     
  Branches      402      669     +267     
==========================================
+ Hits         1326     1659     +333     
- Misses       1094     1389     +295     
- Partials      170      353     +183     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tony
Copy link
Copy Markdown
Member Author

tony commented Mar 29, 2026

Code review

Found 4 issues:

  1. Pane.display_message(): no_expand maps to the wrong tmux flag. -I in display-message opens the pane for stdin input forwarding (window_pane_start_input()), not format suppression. The correct flag is -l (literal/no-expand, tmux 3.4+). Additionally, list_formats is also mapped to -l with the doc claiming it "lists format variables", but -l means suppress expansion; listing variables is done by -a (already mapped to all_formats). Both parameters are misassigned.

libtmux/src/libtmux/pane.py

Lines 700 to 714 in 857384a

tmux_args += ("-v",)
if no_expand:
tmux_args += ("-I",)
if notify:
tmux_args += ("-N",)
if list_formats:
if has_gte_version("3.4", tmux_bin=self.server.tmux_bin):
tmux_args += ("-l",)
else:
warnings.warn(
"list_formats requires tmux 3.4+, ignoring",
stacklevel=2,

  1. Window.swap() does not call self.refresh() after the operation, leaving window_index stale. The docstring example works around this by manually calling w1.refresh()/w2.refresh(), but the method itself never refreshes. Commit 3654a36e fixed the identical issue in move_window() for the same reason ("move-window can land on an index different from the requested target… leaving the returned Window stale"); swap-window has the same behaviour.

target_id = target.window_id if isinstance(target, Window) else target
tmux_args += ("-s", str(target_id))
proc = self.cmd("swap-window", *tmux_args)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)

  1. Server.show_prompt_history() and Server.clear_prompt_history() have no version guard. Both commands were added in tmux 3.3; libtmux's declared minimum is 3.2a. On tmux 3.2a the calls will raise LibTmuxException with a raw "unknown command" tmux error instead of a clean version message. The pattern established in the same PR for confirm_before and command_prompt (has_gte_version("3.3") + raise) should be applied here too.

libtmux/src/libtmux/server.py

Lines 1119 to 1158 in 857384a

def show_prompt_history(
self,
*,
prompt_type: str | None = None,
) -> list[str]:
"""Show prompt history via ``$ tmux show-prompt-history``.
Parameters
----------
prompt_type : str, optional
Prompt type to show (``-T`` flag). One of: ``command``,
``search``, ``target``, ``window-target``.
Returns
-------
list[str]
Prompt history lines.
Examples
--------
>>> result = server.show_prompt_history()
>>> isinstance(result, list)
True
"""
tmux_args: tuple[str, ...] = ()
if prompt_type is not None:
tmux_args += ("-T", prompt_type)
proc = self.cmd("show-prompt-history", *tmux_args)
if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
return proc.stdout
def clear_prompt_history(
self,
*,

  1. display_popup docstring cross-reference uses the wrong module path: ~libtmux.test.control_mode.ControlMode — that module does not exist. The class lives at libtmux._internal.control_mode.ControlMode, which is already used correctly in server.py line 1013. This will produce a broken Sphinx link in generated docs.

libtmux/src/libtmux/pane.py

Lines 1202 to 1206 in 857384a

Requires tmux 3.2+ and an attached client. Use
:class:`~libtmux.test.control_mode.ControlMode` in tests to provide
a client.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 4 commits March 29, 2026 12:59
why: libtmux.test.control_mode does not exist; Sphinx cross-reference
     would produce a broken link. Correct path is _internal.control_mode.
what:
- Replace ~libtmux.test.control_mode.ControlMode with
  ~libtmux._internal.control_mode.ControlMode in display_popup docstring
why: Both commands were added in tmux 3.3 but libtmux supports 3.2a.
     Without a guard, callers on 3.2a get a raw "unknown command" tmux
     error instead of a clean LibTmuxException with version context.
what:
- Add has_gte_version("3.3") guard to show_prompt_history
- Add has_gte_version("3.3") guard to clear_prompt_history
- Pattern matches confirm_before / command_prompt guards in same file
why: swap-window swaps window object pointers at tmux indices, changing
     window_index on both objects. Not refreshing leaves stale state,
     identical to the issue fixed in move_window by commit 3654a36.
what:
- Add self.refresh() after successful swap-window cmd
- Refresh target Window if target is a Window instance
- Remove manual refresh() calls from docstring examples (now automatic)
…_formats

why: -I opens pane stdin input mode (window_pane_start_input), not suppress
     expansion. -l (tmux 3.4, commit 3be36952) is the correct literal flag.
     list_formats mapped to -l with doc "List format variables" — but -l
     suppresses expansion, not lists variables; -a (all_formats) already
     covers listing, making list_formats a wrong duplicate.
what:
- Fix no_expand to emit -l with has_gte_version("3.4") guard + warn on older
- Remove list_formats param from both overloads, implementation, and docstring
- Update no_expand docstring: "-l flag. Requires tmux 3.4+"
- Remove list_formats test case; add no_expand test verifying literal output
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.

1 participant