Skip to content

Type hints for tmux 3.4+ format tokens#674

Draft
tony wants to merge 8 commits into
masterfrom
typed-format-fields-tmux-3-4-plus
Draft

Type hints for tmux 3.4+ format tokens#674
tony wants to merge 8 commits into
masterfrom
typed-format-fields-tmux-3-4-plus

Conversation

@tony

@tony tony commented May 17, 2026

Copy link
Copy Markdown
Member

Summary

Adds typed Python attributes for format tokens tmux added in releases
after 3.2a — covering 3.4 (`pane_unseen_changes`), 3.5
(`pane_key_mode`), 3.6 (`session_active`, `session_activity_flag`,
`session_alert`, `session_bell_flag`, `session_silence_flag`,
`client_theme`), and the forward-looking set expected to land with
tmux 3.7+ (`bracket_paste_flag`, `pane_flags`, `pane_floating_flag`,
`pane_pb_progress`, `pane_pb_state`, `pane_pipe_pid`,
`pane_zoomed_flag`, `synchronized_output_flag`).

Problem solved

IDE autocomplete and `mypy` awareness for newer tmux format tokens.
Today users can read these via raw `display-message -p`, but the
typed surface saves the field-name lookup and centralizes the
version gate.

Why this is its own shipment

The forward-looking tokens can't be validated in CI until tmux 3.7
reaches a tagged release. Holding the shipment lets the typed
declarations land alongside real version-checked tests instead of
declarations gated by a version number that doesn't yet exist.

Status

Draft, gated on tmux 3.7 reaching a tagged release. This branch is
parented on `parity-pt-2` (#672); once #672 merges, rebase onto
`master` and revisit. The 3.4 / 3.5 / 3.6 tokens are already
validatable today and could land sooner if needed — but the
forward-looking set drives the held-back timing.

Acceptance criteria for merge

  • tmux 3.7 has reached a tagged release and is reachable from CI
  • Test matrix asserts `get_output_format(list_cmd, version)`
    returns the correct field set for every combination of
    `list_cmd ∈ {list-sessions, list-windows, list-panes, list-clients}`
    \× `version ∈ {3.2a, 3.3, 3.4, 3.5, 3.6a, 3.7}`
  • Negative tests prove master-only tokens are excluded on 3.2a
  • Integration test against tmux 3.7 in CI confirms forward-looking
    fields hydrate as expected

Test plan

  • `uv run ruff check . && uv run mypy src tests` — clean
  • `uv run pytest --reruns 0 -q` — 1265 passed, 2 skipped (the
    pre-existing 3.2a control-mode skips)
  • CI matrix green on `parity-pt-2` rows (3.2a through master)
    after the rebase target merges

Refs

@codecov

codecov Bot commented May 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 51.79%. Comparing base (d8b7127) to head (9e927a4).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #674      +/-   ##
==========================================
- Coverage   52.44%   51.79%   -0.65%     
==========================================
  Files          26       26              
  Lines        3726     3726              
  Branches      747      747              
==========================================
- Hits         1954     1930      -24     
- Misses       1468     1482      +14     
- Partials      304      314      +10     

☔ 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 parity-pt-2 branch 6 times, most recently from d886138 to 61688af Compare May 18, 2026 02:31
Base automatically changed from parity-pt-2 to master May 18, 2026 02:36
tony added 8 commits July 18, 2026 05:38
why: The 0.57.x section claimed list-clients "emits the client_*
tokens but never pane_* ones" — backwards: tmux's format_defaults
cascades downward through c->session, the session's current window,
and that window's active pane, so list-clients (and list-sessions
and list-windows) all hydrate pane fields via cascade. Pinned by the
strengthened cascade tests on this branch and by upstream
cmd-list-clients.c calling format_defaults(ft, l[i], NULL, NULL,
NULL). Two adjacent overclaims compounded the issue: a fixed token
count ("twelve client_* tokens") that didn't match the typed Client
surface, and a "every wrapper uses raise_if_stderr" claim that didn't
match the supported surface in practice.
what:
- Rewrite the cascade paragraph in the typed format-token entry to
  describe the actual downward cascade (Client/Session/Window rows
  hydrate active-pane/window fields; client_* resolves only under
  list-clients because tmux has no reverse cascade).
- Drop number slop: "twelve client_* tokens", "~45 additional format
  tokens", the explicit list of eight forward-looking tokens, and
  the embedded token-name catalogs that belong in autodoc.
- Replace "every wrapper" with "shared helper used across the
  supported surface". Trim "matching the rest of the typed wrappers"
  to "matching the rest of the supported surface".
- Add the Client snapshot caveat (session_id/window_id/pane_id are
  attached-view, not identity) into the Client entry.
- Add the malformed-predicate caveat to the C-side filter entry.
- Add a Fixes bullet for Server.clients and Server.search_sessions
  propagating tmux errors instead of swallowing them.
- Add a Documentation bullet for the Obj cascade-semantics
  docstring.
- Tighten the lead paragraph: drop "closes wrapper gaps" and the
  token count.
Carry the wrapper-to-tmux-support pattern from the earlier 0.56-section copy-improvements commit into the 0.57.x
section.
why: In 0.57 the typed wrappers migrated to raise_if_stderr, which
attaches a LibTmuxException.subcommand attribute and prefixes
str(exc) with "<subcommand>: ". The release entry framed this purely
as additive — there was no breaking-change subheading for upgraders
who pattern-match on str(exc) exactly or anchor a regex with ^.

The wrapped stderr is still in exc.args[0]; the subcommand name is
exposed as a typed attribute. Substring containment and unanchored
regex matches keep working.

what:
- CHANGES: new ### Breaking changes subsection under 0.57 with three
  migration paths (exc.subcommand, exc.args[0], substring match).
- MIGRATION: new ## libtmux 0.57.0 section covering the same
  contract from the upgrader's perspective, with before/after code
  for each migration path.
why: client.session_id / window_id / pane_id are hydrated from
tmux's downward format cascade at the moment the Client dataclass
is built and go stale as soon as the client switches view. The
existing class-level docstring warning isn't enough on its own —
users iterating over server.clients still reach for the raw fields
and treat them as identity.

what:
- Add Client.attached_session / .attached_window / .attached_pane.
  Each property re-reads list-clients before resolving and returns
  the live typed Session / Window / Pane (or None), mirroring the
  Session.active_window fresh-lookup convention.
- Tighten the Client class-level warning to point at the new
  properties as the safe accessors.
- Tests: typed resolution, fresh window tracking (selects a new
  active window post-hydration and asserts the property reflects
  it — proves the property re-queries rather than returning the
  snapshot), pane resolution, None propagation when session_id is
  absent.
- CHANGES: extend the Client what's-new entry to mention the
  attached_* accessors.
- MIGRATION: 0.57 section gains a "snapshots, not identity"
  subheading covering the snapshot vs. live access pattern.
why: The Client documentation should distinguish attached_* convenience behavior from explicit refresh lookups.
what:
- Clarify None behavior for missing live client rows
- Preserve refresh/from_client_name missing-object semantics
…stead of raising

why: tmux's stderr from display-message conflates genuine argument-parser
errors (e.g. -F-with-positional rejection) with operational quirks like
3.2a's control-mode dispatch path silently failing without emitting stderr
at all. Raising LibTmuxException on every stderr forced an in-branch
workaround — pytest.skip patches gated on has_gte_version("3.3") — that
masked the underlying mismatch instead of solving it. Switch to
warnings.warn so callers see the stderr without losing the return value,
and the eventual raise/per-call-opt-in contract can land in a follow-up
shipment that exercises real tmux versions end-to-end.

what:
- src/libtmux/server.py, src/libtmux/window.py, src/libtmux/pane.py:
  replace raise_if_stderr(proc, "display-message") with
  warnings.warn("display-message: …", stacklevel=2). Wrapper return
  value unchanged on success and on warn paths.
- All three display_message docstrings gain a Notes block describing
  the warn-not-raise contract and showing the
  warnings.catch_warnings/filterwarnings("error") escalation pattern.
- tests/test_pane.py, tests/test_window.py, tests/test_server.py:
  rename test_*_display_message_raises_on_tmux_error to
  test_*_display_message_warns_on_tmux_error and switch to
  pytest.warns(UserWarning, match=…). Drop the 3.2a control-mode skip
  added by the prior commit on test_server_display_message_no_text_returns_none —
  with warn-not-raise the 3.2a control-mode stderr no longer fails the
  test (the test only asserts result is None on get_text=False).
- CHANGES: rewrite the display_message Fixes entry to describe the
  warn contract and how to escalate.
- MIGRATION: add a new section under 0.57.0 documenting the warn
  contract and the warnings.catch_warnings escalation pattern.
- MIGRATION: add a section noting that Pane.reset now dispatches via
  self.server.cmd; mocks targeting pane.cmd no longer intercept reset.
- docs/topics/pane_interaction.md: tighten the capture_pane(pending=True)
  wording to describe tmux's parser pending buffer rather than "slow
  consumer / paused program" (the latter framing implies a PTY/app
  buffering issue that pending= doesn't address).
- docs/topics/filtering.md: note that there is no search_clients();
  filter via Server.clients and Python-side QueryList.filter.
why: tmux master (post-3.6a) registers eight new format tokens that the next
tmux release will ship: pane_zoomed_flag, pane_floating_flag, pane_flags,
pane_pb_state, pane_pb_progress, pane_pipe_pid, synchronized_output_flag,
bracket_paste_flag. Declaring them now means libtmux is ready when the tag
lands; older tmux releases expand unknown tokens to empty strings, so the
fields stay None until the user upgrades tmux.

what:
- src/libtmux/neo.py: add the fields alphabetically within the existing
  Obj layout (pane_* tokens among the pane_* block, bracket_paste_flag
  near buffer_*, synchronized_output_flag near start_time).
- tests/test_pane.py: parametrized test asserts each field is declared
  on the dataclass and hydrates either as None or as a string after
  refresh(). No runtime-value assertions — those will activate when the
  shipping tmux release exposes the tokens.
why: with the scope+version gating in place, the format string sent to older
tmux versions automatically excludes tokens that those versions don't
recognize. The tokens below first registered in tmux 3.4-3.6 — tagging them
with FIELD_VERSION makes them appear on supported tmux releases that include
them, and absent on older tmux without sending unknown tokens that bloat the
format string or trigger crashes.

what:
- src/libtmux/neo.py:
  - Re-add fields to Obj alphabetically: pane_key_mode,
    pane_unseen_changes, session_active, session_activity_flag,
    session_alert, session_bell_flag, session_silence_flag, client_theme.
  - Populate FIELD_VERSION with each token's minimum tmux release
    (3.4 for pane_unseen_changes, 3.5 for pane_key_mode, 3.6 for the
    five session_* tokens and client_theme).
- tests/test_pane.py: restore pane_key_mode and pane_unseen_changes in
  PANE_FORMAT_FIELDS (the parametrized declaration+hydration test).
- tests/test_session.py: restore the new session_* entries in
  SESSION_FORMAT_FIELDS.

Verification:
- On tmux 3.6a (local), all tokens hydrate via refresh(); tests
  pass.
- On tmux 3.2a, FIELD_VERSION skips all 8 — the -F template stays at
  its pre--rollback shape for that version.

Version anchors verified via: rg '"<token>"'
https://github.com/tmux/tmux/blob/<TAG>/format.c across 3.2a, 3.3a, 3.4,
3.5, 3.5a, 3.6, 3.6a.
@tony
tony force-pushed the typed-format-fields-tmux-3-4-plus branch from 8db405d to 9e927a4 Compare July 18, 2026 11:02
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