Skip to content

Add tmux 3.7 feature parity: floating panes, options, formats#694

Merged
tony merged 41 commits into
masterfrom
tmux-3.7-feature
Jun 28, 2026
Merged

Add tmux 3.7 feature parity: floating panes, options, formats#694
tony merged 41 commits into
masterfrom
tmux-3.7-feature

Conversation

@tony

@tony tony commented Jun 27, 2026

Copy link
Copy Markdown
Member

Summary

Builds on #693 (tmux 3.7 compatibility, merged and shipped in v0.59.0) to expose tmux 3.7's new surface as typed, discoverable libtmux API. Targets master.

  • Add floating panes via Window.new_pane() / Pane.new_pane() — tmux 3.7's flagship new-pane.
  • Add the 11 options new in tmux 3.7 as typed fields on the option dataclasses.
  • Add the 11 pane format variables new in tmux 3.7 on Pane (version-gated).
  • Add new flags on existing wrappers: capture_pane -H/-L/-F, split -E, kill -g, paste_buffer -S.

Changes by area

Floating panes — src/libtmux/pane.py, src/libtmux/window.py

Pane.new_pane() / Window.new_pane() issue new-pane to create a floating pane that overlays the tiled layout like a popup but behaves like a real pane. Geometry maps width/height-x/-y (size) and x/y-X/-Y (position); also supports shell, start_directory, environment, zoom, empty, and style/active_border_style/inactive_border_style. Returns the floating Pane (pane_floating_flag == "1"); raises LibTmuxException on tmux < 3.7.

Typed options — src/libtmux/_internal/constants.py

Scope Options added
Server get-clipboard
Session focus-follows-mouse, message-format, prompt-command-cursor-style
Window copy-mode-line-numbers (+ -style, -current-line-number-style), tree-mode-preview-format/-style, window-pane-status-format/-current-status-format
Pane copy-mode line-number, tree-mode preview and window-pane status options (shared with Window in 3.7); remain-on-exit gains the key value

Format variables — src/libtmux/neo.py, src/libtmux/formats.py

Declare the 11 new tmux 3.7 pane format tokens on neo.Obj (pane_floating_flag, pane_x/y/z, pane_flags, pane_zoomed_flag, pane_pb_progress/pane_pb_state, pane_pipe_pid, bracket_paste_flag, synchronized_output_flag), each gated to tmux 3.7 in FIELD_VERSION so older tmux keeps a clean -F template. The two tokens without a pane_ prefix are routed via _SCOPE_OVERRIDES.

Command flags — src/libtmux/pane.py, src/libtmux/window.py, src/libtmux/session.py

Method Parameter Flag
Pane.capture_pane hyperlinks / line_numbers / line_flags -H / -L / -F
Pane.split / Window.split empty -E
Session.kill group -g
Pane.paste_buffer no_vis -S

Each is gated with has_gte_version("3.7") and warns-and-ignores on older tmux.

Design decisions

  • Dedicated new_pane(), not split(floating=True): tmux deliberately made floating panes a separate command (new-pane) because they overlay the layout rather than subdivide a pane. Mirroring that keeps split's semantics clean and avoids position parameters that are meaningless for a non-floating split.
  • Two version-gating idioms: flags on methods that work everywhere warn and ignore on old tmux (graceful degradation); a whole method that only exists on 3.7+ (new_pane) raises — there is no graceful fallback for a command that doesn't exist. Version-gated method doctests use the project's if has_gte_version(...) / else pattern so they run on every CI tmux version.
  • Verified against the man-page diff: "what's new in 3.7" was determined from the tmux 3.6 → 3.7 man-page diff, not the C source — tokens move between registration sites across releases, which makes the raw source diff falsely flag old symbols as new.

Test plan

  • uv run pytest — full suite green on tmux 3.7 (new options/formats/flags and floating-pane tests; new_pane doctests)
  • New format tokens absent from a tmux 3.6 -F template and present on 3.7 (tests/test_neo.py)
  • uv run ruff check . and uv run ruff format .
  • uv run mypy
  • just build-docs

Refs #691

@codecov

codecov Bot commented Jun 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.63636% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.89%. Comparing base (1495f9a) to head (22dec6c).

Files with missing lines Patch % Lines
src/libtmux/pane.py 80.48% 8 Missing and 8 partials ⚠️
src/libtmux/_internal/constants.py 0.00% 15 Missing ⚠️
src/libtmux/neo.py 0.00% 11 Missing ⚠️
src/libtmux/server.py 70.58% 3 Missing and 2 partials ⚠️
src/libtmux/window.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #694      +/-   ##
==========================================
+ Coverage   51.38%   51.89%   +0.50%     
==========================================
  Files          25       25              
  Lines        3493     3623     +130     
  Branches      688      733      +45     
==========================================
+ Hits         1795     1880      +85     
- Misses       1403     1439      +36     
- Partials      295      304       +9     

☔ View full report in Codecov by Harness.
📢 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 tony force-pushed the tmux-3.7-feature branch 4 times, most recently from 5b1bf74 to 47f5b7b Compare June 27, 2026 17:07
@tony tony force-pushed the tmux-3.7-feature branch from 47f5b7b to 21ada1a Compare June 27, 2026 17:09
Base automatically changed from tmux-3.7 to master June 27, 2026 17:15
@tony tony force-pushed the tmux-3.7-feature branch from 21ada1a to 063f847 Compare June 27, 2026 17:33
tony added 17 commits June 27, 2026 21:25
why: Expose tmux 3.7's new options as typed dataclass fields so users
get autocomplete and type-checking, matching the existing typed
options surface.

what:
- ServerOptions: get_clipboard
- SessionOptions: focus_follows_mouse, message_format,
  prompt_command_cursor_style
- WindowOptions: copy_mode_line_numbers (+ _style,
  _current_line_number_style), tree_mode_preview_format/_style,
  window_pane_status_format, window_pane_current_status_format
- PaneOptions: remain_on_exit gains the "key" value
why: Verify libtmux can set and show tmux 3.7's new options end to end.

what:
- Add OptionTestCase entries (min_version="3.7") feeding the existing
  parametrized set/show cycle, so they auto-skip on tmux < 3.7
- Cover get-clipboard, focus-follows-mouse, message-format,
  prompt-command-cursor-style, copy-mode line-number options,
  tree-mode preview options, window-pane status formats, and the
  new remain-on-exit "key" value
why: Record the new typed-options surface for the forthcoming release.

what:
- Add ### What's new entry under the unreleased section
why: Expose tmux 3.7's new pane format variables (floating-pane
geometry, OSC 9;4 progress, screen-mode flags) as typed Obj fields so
they hydrate on Pane objects.

what:
- Declare 11 new fields on neo.Obj (pane_floating_flag, pane_x/y/z,
  pane_flags, pane_zoomed_flag, pane_pb_progress, pane_pb_state,
  pane_pipe_pid, bracket_paste_flag, synchronized_output_flag)
- Gate each in FIELD_VERSION="3.7" so older tmux keeps a clean -F
- Route the two non-pane_-prefixed tokens via _SCOPE_OVERRIDES
- List them in formats.py PANE_FORMATS reference
why: Ensure the new 3.7 pane format tokens are version-gated and the
non-prefixed ones classify to pane scope.

what:
- Parametrized test that each 3.7 token is absent from a 3.6 -F
  template and present on 3.7 (FIELD_VERSION="3.7")
- Test that bracket_paste_flag and synchronized_output_flag resolve
  to pane scope and appear in list-panes
why: Record the new pane format-variable surface for the forthcoming
release.

what:
- Add ### What's new deliverable under the unreleased section
why: Expose new tmux 3.7 flags on existing wrappers so users can reach
them through the typed API.

what:
- Pane.capture_pane: hyperlinks (-H), line_numbers (-L), line_flags (-F)
- Pane.split / Window.split: empty (-E), an empty pane
- Pane.paste_buffer: no_vis (-S), paste raw bytes (no vis(3) escaping)
- Session.kill: group (-g), kill the whole session group
- Each gated on tmux 3.7+, warning and ignoring on older tmux
why: Verify the new flags work on tmux 3.7 and warn-and-ignore below.

what:
- capture_pane -H/-L/-F, split empty (-E), paste_buffer no_vis (-S)
- kill group (-g)
- Each test is version-aware: functional path on 3.7+, UserWarning
  assertion on older tmux, so both CI legs are covered
why: Record the new command-flag surface for the forthcoming release.

what:
- Add ### What's new deliverable under the unreleased section
why: Expose tmux 3.7's flagship feature, floating panes, through the
typed API. A floating pane overlays the tiled layout like a popup but
behaves like a real pane.

what:
- Pane.new_pane(): issue `new-pane` with geometry (width/height -> -x/-y,
  x/y -> -X/-Y), shell/start_directory/environment, zoom, empty, and
  style/active_border_style/inactive_border_style; returns the floating
  Pane (pane_floating_flag == "1")
- Window.new_pane(): delegate to the active pane, mirroring
  Window.split -> Pane.split
- Raise LibTmuxException on tmux < 3.7 (new-pane is unavailable)
why: Verify new_pane creates a floating pane on tmux 3.7 and raises on
older tmux.

what:
- Pane.new_pane / Window.new_pane create a floating pane (geometry
  honored, pane_floating_flag == "1") on tmux 3.7+
- Assert LibTmuxException on tmux < 3.7, so both CI legs are covered
why: Record the flagship floating-pane API for the forthcoming release.

what:
- Add ### What's new deliverable under the unreleased section
why: Give users a task-oriented walkthrough of tmux 3.7 floating panes
alongside the API reference.

what:
- Add docs/topics/floating_panes.md (create, size/position, style,
  and identify floating panes; version-gated doctests)
- Link it from the topics index grid and toctree
- Render Window.new_pane()/Pane.new_pane() with distinct link text
why: The tmux 3.7 feature PR now exists, and a dedicated guide ships
with it; the changelog should reference both.

what:
- Add the PR reference to each tmux 3.7 What's new deliverable
- Cross-link the floating-panes topic guide from its entry
- Render Window.new_pane()/Pane.new_pane() with distinct link text
why: new_pane() raised a bare LibTmuxException, so new-pane failures
lacked the .subcommand tag and "new-pane:" message prefix that the
sibling break_pane and the other migrated raise sites provide for
downstream consumers.

what:
- Replace the bare raise in Pane.new_pane with
  raise_if_stderr(pane_cmd, "new-pane")
- Add test_new_pane_error_tags_subcommand asserting .subcommand and
  the tagged str() on a provoked new-pane stderr (gated to tmux 3.7+)
why: Window.new_pane only cross-referenced Pane.new_pane, while sibling
wrappers such as Window.split restate every parameter in full (the
house NumPy-docstring pattern).

what:
- Add a Parameters section to Window.new_pane with a concise one-line
  entry per parameter, keeping the delegation note and doctest
why: Window.split's empty docstring stopped at "Requires tmux 3.7+",
omitting the graceful-degradation contract the delegate Pane.split
documents.

what:
- Append the warn-and-ignore-on-tmux<3.7 clause to Window.split's
  empty parameter, mirroring Pane.split
@tony tony force-pushed the tmux-3.7-feature branch from 063f847 to 3e79298 Compare June 28, 2026 12:02
why: tmux 3.7 capture-pane -H returns hyperlink targets only, not
normal pane text with OSC 8 escape sequences preserved.

what:
- Describe hyperlinks=True as hyperlink-target capture
- Point users to escape_sequences=True for preserved escapes
- Align the changelog command-flag summary
tony added 23 commits June 28, 2026 07:11
why: tmux 3.7 accepts several preview, copy-mode, and pane-status
options at pane scope, but PaneOptions omitted the typed fields.

what:
- Add pane-scope dataclass fields for shared tmux 3.7 options
- Cover the fields and pane set/show behavior in tests
- Align the changelog option summary
why: tmux new-pane -m keeps the pane open after command exit and sets
remain-on-exit-format, so describing it as a display-only message is
misleading.

what:
- Document message as keep-open remain-on-exit text
why: The format-token topic linked from the floating panes guide still
said only tmux 3.3 tokens are version-gated.

what:
- Add the tmux 3.7 FIELD_VERSION tokens to the version table
why: Window.new_pane's message docstring said only "Message line
shown", understating tmux new-pane -m, which keeps the pane open after
the command exits (remain-on-exit-format). Pane.new_pane, which it
delegates to, already documents this.

what:
- Match Window.new_pane's message docstring to Pane.new_pane's
  accurate description
why: The hyperlinks docstring's second sentence advised preserving
escape sequences "in normal pane text", which reads as a contradiction
under a flag that captures only hyperlink targets.

what:
- Reframe the escape_sequences pointer as an accurate alternative
  ("for full pane text use escape_sequences instead")
why: test_capture_pane_3_7_flags asserted the tmux <3.7 warn-and-ignore
path for hyperlinks only; line_numbers and line_flags warn paths were
uncovered.

what:
- Parametrize the test with a NamedTuple + test_id over all three flags,
  covering both the 3.7+ success path and the <3.7 warn path per flag
why: The hyperlinks docstring claimed "for full pane text use
escape_sequences instead", which is inaccurate: full pane text is the
default capture, and escape_sequences (-e) only adds text attributes.
The two flags are independent, not a binary choice.

what:
- Remove the second sentence; the first already fully describes -H
why: The message docstrings described -m as keeping the pane open but
omitted that tmux then waits for a keypress before closing it (man
page: -m is "equivalent to -k").

what:
- Add the keypress-to-close detail to the message param on both
  Pane.new_pane and Window.new_pane
why: Pane.new_pane's target param docstring lacked a type annotation
("target : optional") that every other param and Window.new_pane carry,
despite the signature being target: int | str | None.

what:
- Type target as "int or str, optional" and drop the redundant leading
  "Optional," from the description
why: Empty string values are valid for tmux 3.7 new-pane style and
message flags, but combined flag construction turns them into bare flags
and lets tmux consume -P as the option value.

what:
- Pass affected new-pane flags and values as separate argv entries
- Cover empty values for style, border style, and message options
why: PaneOptions typed six tmux 3.7 options that tmux's
options-table.c scopes OPTIONS_TABLE_WINDOW-only. set -p on such an
option silently routes to the window and show -p (list) omits it, so the
pane field misrepresents tmux's scoping. Only tree-mode-preview-format is
window+pane.

what:
- Remove copy-mode-line-numbers (+ both styles), tree-mode-preview-style,
  and window-pane-(current-)status-format from PaneOptions; keep
  tree_mode_preview_format (remain_on_exit stays, also window+pane)
- Drop their OptionScope.Pane set/show cases; add
  test_window_only_3_7_options_absent_from_pane as a regression guard
- Correct the CHANGES typed-options scope wording
why: tmux 3.7 gave split-window new-pane's -s/-S/-R/-m/-k via the shared
cmd_split_window_exec, but Pane/Window.split() exposed only empty (-E).

what:
- Add style (-s), active_border_style (-S), inactive_border_style (-R),
  message (-m) and keep (-k) to Pane.split and Window.split, under one
  has_gte_version("3.7") gate that warns-and-ignores on older tmux
- Add a NamedTuple-parametrized test_split_3_7_flags over the five flags
- Note the new flags in the CHANGES command-flags entry
why: new-pane's bare -k keeps the pane open with the default
remain-on-exit-format, distinct from -m (custom message). new_pane
exposed message (-m) but had no way to request plain -k.

what:
- Add keep (-k) to Pane.new_pane and Window.new_pane
- Add test_new_pane_keep asserting the float's remain-on-exit == "key"
- Note keep in the CHANGES floating-panes entry
why: split emitted its tmux 3.7 value flags as combined args
(f"-m{value}"), so an empty string collapsed to a bare flag. On tmux
3.7 pane.split(message="") raised "-m expects an argument", and
split(message="", shell=...) consumed the shell as the -m value. This
is the same bug already fixed for new_pane.

what:
- Emit style/active_border_style/inactive_border_style/message as
  separate argv entries (flag, value), matching Pane.new_pane
- Add test_split_preserves_empty_option_values regression test
why: tmux 3.7 widened pane-active-border-style and pane-border-style
from window-only to window+pane scope (options-table.c; set -p now
round-trips). PaneOptions typed them only on WindowOptions, so the pane
scope was unmodeled — the last tmux 3.7 option-scope gap.

what:
- Add pane_active_border_style / pane_border_style to PaneOptions
  (retained on WindowOptions; mirrors window_active_style/window_style)
- Cover both with pane-scope set/show cases and the shared-fields guard
- Note the scope widening in the CHANGES typed-options entry
why: tmux 3.7 added command-prompt -C (PROMPT_NOFREEZE) to keep panes
unfrozen while the prompt is open, matching display-message -C.
command_prompt already wrapped the sibling -e but not -C.

what:
- Add no_freeze (-C) to Server.command_prompt, gated has_gte_version
  ("3.7") with warn-and-ignore below, mirroring bspace_exit
- Add the no_freeze case to the parametrized argv test
- Note it in the CHANGES command-flags entry
why: tmux 3.7 added a custom output format to list-keys (-F); the
wrapper only supported -T (key_table). Note: the 3.7 -O/-r sort/reverse
flags are intentionally not wrapped — list_keys returns a list and
callers sort in Python.

what:
- Add format_ (-F) to Server.list_keys, gated has_gte_version("3.7")
  with warn-and-ignore below (param is format_ to avoid shadowing the
  builtin)
- Add test_list_keys_format asserting the per-line format expansion
- Note it in the CHANGES command-flags entry
why: tmux 3.7 lets run-shell take positional arguments after the shell
command, expanded as #{1}, #{2}, … inside it. run_shell accepted only a
single command string.

what:
- Add args (trailing positional arguments) to Server.run_shell, appended
  after command, gated has_gte_version("3.7") with warn-and-ignore below
- Add test_run_shell_args asserting #{1}/#{2} expansion
- Note it in the CHANGES command-flags entry
why: The floating-panes guide toured create/size/style/identify but
omitted keep and message, the remain-on-exit params new_pane gained,
leaving a notable user-facing affordance only in the autodoc reference.

what:
- Add a "Keeping a pane open" section showing keep=True / message set
  the pane's remain-on-exit to "key", with a runnable doctest
why: The bare name new_pane() reads as a generic pane creator, but it is
floating-only (tmux's new-pane defaults to floating; the tiled case is
split-window). The floating/version contract lived only further down the
docstring.

what:
- Lead the new_pane docstrings with the floating contract and cross-
  reference :meth:`split` for tiled panes
why: tmux 3.7 repurposed refresh-client -l from the legacy optional-arg
forward form into a no-arg clipboard query (tty_clipboard_query) — the
runtime companion to the get-clipboard option this branch already types.
refresh_client wrapped only target_client.

what:
- Add request_clipboard (-l) to Server.refresh_client, gated
  has_gte_version("3.7") with warn-and-ignore below (a bare -l on older
  tmux is the legacy form with different meaning)
- Add an argv-verification test for the emitted -l
- Note it in the CHANGES command-flags entry
why: split's `message` (-m) docstring omitted that the pane waits for a
keypress before closing. tmux's -m is equivalent to -k, which keeps the
pane open and waits for a key to be pressed — new_pane and the sibling
`keep` (-k) param already document this, so split read inconsistently.

what:
- Add "(until a key is pressed)" to the message param docstring in
  Pane.split and Window.split, matching new_pane's wording
why: CLAUDE.md asks tests to document why standard fixtures were skipped.
test_refresh_client_request_clipboard stubs Server.cmd to inspect argv
but didn't say why, unlike its siblings in this file. refresh-client -l
calls tty_clipboard_query (an xterm clipboard escape) with no observable
effect under the headless test server, so argv is the only thing to
assert on.

what:
- Expand the test docstring with a concise why-note for the cmd stub
@tony tony force-pushed the tmux-3.7-feature branch from 3125dc4 to 22dec6c Compare June 28, 2026 21:02
@tony tony merged commit b56b228 into master Jun 28, 2026
14 checks passed
@tony tony deleted the tmux-3.7-feature branch June 28, 2026 21:10
tony added a commit that referenced this pull request Jun 28, 2026
Completes tmux 3.7 feature parity on top of the 0.59.0 compatibility
release: floating panes, typed 3.7 options, new pane format variables,
and new command flags (#694). All 3.7-only surfaces are version-gated;
tmux 3.2a-3.6 remain supported with no breaking changes.

- CHANGES: date the 0.60.0 release section and open a fresh 0.61.x
  unreleased placeholder
- Bump version 0.59.0 -> 0.60.0 in pyproject.toml and __about__.py
- Refresh uv.lock for the new version
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