Skip to content

Example style guide and 80-column examples - #761

Draft
tony wants to merge 23 commits into
masterfrom
example-style
Draft

tony wants to merge 23 commits into
masterfrom
example-style

Conversation

@tony

@tony tony commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Doctests and README fences in this repository now follow the shared libtmux example rules, which hold examples to 80 columns. ruff cannot hold that limit, so a stdlib Python check holds it in CI; the commits before it fix every line it reported. Merge after libtmux/docs#101 deploys: it makes 80 columns fit a libtmux.org code block from 1024 px up. Run against origin/master, the checker reports 68 lines over 80 in 19 files. The README capture example also loses its # doctest: +SKIP, which WRITING.md bans, and becomes a real test.

What changed

Policy

  • .github/WRITING.md gains the shared example rules as subsections inside ## Documented examples that run (CONTRIBUTING links its anchor), plus an "In this repository" block: ruff format and E501 at ruff's default 88, reading .py only, docstring code and docs/*.md doctests unformatted, +HIDE lines skipped by the width check, and expected doctest output never wrapped (ELLIPSIS is global, so a long repr is trimmed with ...).
  • AGENTS.md routes code examples to that section.

Tooling

  • scripts/check_example_width.py and .github/example-width.toml read every **/*.md page plus MIGRATION and CHANGES, and the doc comments under src/. No excludes. One [[allow]] entry: the deliberately wide Bad line in WRITING.md's port block.
  • CI runs the self-test and the check in tests.yml beside the ruff steps; a check-example-width just recipe and a CONTRIBUTING entry document it.
  • scripts/ is excluded from mypy, which targets 3.10 while the script needs tomllib (3.11+).

Examples

  • Test the capture example replaces the skipped README capture with retry_until on capture_pane() and keeps the sample capture beside it as a text fence.
  • The filtering and traversal topics list the names of filtered windows instead of eliding their reprs, which asserts the exact windows.

Evidence

  • ruff does not format docstring code unless docstring-code-format is on (formatter docs). A probe rejected turning it on: it parsed a :: literal block in src/libtmux/options.py as Python and rewrote the tmux option text visual-activity* on to visual - activity * on, and it lengthened doctest lines past 80. Markdown fences are outside ruff's Python formatting entirely.
  • Rejected hand review alone (the limit drifted before this branch) and ruff E501 as the gate (88 columns, .py only).
  • README capture test: a planted absent string fails with WaitTimeout.

How to verify

$ python3 scripts/check_example_width.py --self-test
$ python3 scripts/check_example_width.py
$ uv run ruff check .
$ uv run pytest README.md docs src/libtmux

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.37%. Comparing base (500f99b) to head (3e605a4).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #761   +/-   ##
=======================================
  Coverage   52.37%   52.37%           
=======================================
  Files          26       26           
  Lines        3729     3729           
  Branches      747      747           
=======================================
  Hits         1953     1953           
  Misses       1472     1472           
  Partials      304      304           

☔ 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 example-style branch 7 times, most recently from 252dc6e to b00f03f Compare October 4, 2026 20:07
tony added 18 commits October 4, 2026 16:00
why: The capture-pane example carried `# doctest: +SKIP`, which
WRITING.md bans: it rendered a prompt and output that nothing ever
checked. Waiting for the echoed line and asserting it makes the
quickstart's capture step a real test. A planted absent string fails
with WaitTimeout.

what:
- Replace the skipped capture with retry_until on capture_pane()
- Keep the sample capture beside it as a text fence, so a reader still
  sees what capture_pane() returns
why: The install commands and the two from_*_id examples ran past 80
columns.

what:
- Split the pip and uv trunk-install commands with `\`, one option per
  continuation line, and quote the pip URL
- Name the new window and pane ids before passing them to
  Window.from_window_id and Pane.from_pane_id
why: Two new_window calls and the retry_until_success signature ran
past 80 columns in the rendered block.

what:
- Pass the window name to new_window by position in the timeout and
  cleanup demos
- Shorten the retry_until_success parameter `success_marker` to
  `marker` so its signature fits on a line
why: The comment under the innermost `with` ran past 80 columns. It
said "Everything is cleaned up automatically when exiting contexts",
which the prose below the block already states.

what:
- Reword to "Exiting the contexts cleans everything up"
why: Four link-window setups repeated a 78-column call split across
three lines, and several expected reprs listed the full Session(...)
tail. The repeated target and the long reprs hid the lookup being
taught.

what:
- Bind `target = f"{guest.session_id}:"` once per example and call
  link-window on one line
- Bind `name` for the exact-lookup equivalence example and drop the
  doctest directive the global flags already cover
- Bind `same` before comparing window ids in the from_window_id example
- Bind the istartswith and `in` results and list their window names
  instead of printing full reprs, which asserts the exact windows and
  fits on a line
why: The set_hook calls carried a trailing `# doctest: +ELLIPSIS` that
pushed them past 80 columns, though Session(...) already matches under
the global ELLIPSIS flag.

what:
- Name hook_command before the session-renamed set_hook call
- Drop the per-line ELLIPSIS directives, which lets the two
  after-split-window set_hook calls fit on one line
why: The two Pane.resize calls were 102 and 105 columns wide with a
trailing directive the global ELLIPSIS flag makes redundant.

what:
- Bind the Up and Left directions before the Pane.resize calls
- Drop the per-line ELLIPSIS directives
why: The holders set comprehension ran past 80 columns.

what:
- Bind worker_panes, then build holders from it
why: The window-name results printed every Session($... ...) tail,
which ran the lines to 100 columns and buried the window names the
example shows. One comment trailed the new_session call.

what:
- Bind the startswith, contains, istartswith and regex results and list
  their window names instead of printing full reprs, which asserts the
  exact windows and fits on a line
- Drop the trailing comment on new_session; the lead-in sentence
  "First, create a test session" already says it
- Drop per-line ELLIPSIS directives where the global flag applies
why: The v-split new_window call, the pane-grid split and the
create_pane_grid call each passed 80 columns.

what:
- Pass the window name to new_window and the arguments to
  create_pane_grid by position
- Bind `source` for the grid split so the split call fits on a line
why: parse_lookup, the QueryList example data and QueryList.get had
doctest lines over 80 columns.

what:
- Bind `obj` for the parse_lookup call and break the dict argument of
  the QueryList example
- Expand the two nested `foods` dicts in the QueryList data, one key per
  line
- Bind `rows` before building the QueryList in the get example
why: The tmux_cmd new-session example was 85 columns wide.

what:
- Bind the socket flag as `socket_flag` before the tmux_cmd call
why: Two doctests in _is_target_not_found_error and _best_winlink ran
past 80 columns.

what:
- Bind `stderr` (and `missing` for the connection error) before each
  _is_target_not_found_error call
- Bind `rows` before calling _best_winlink
why: The Pane.split full-window example was 88 columns wide, and the
83-column capture_pane example carried a `+NORMALIZE_WHITESPACE`
directive the global doctest flags already provide.

what:
- Bind `above` for PaneDirection.Above so the top_pane split fits on a
  line
- Drop the redundant directive from the print(capture_pane()) example
why: The fixture docstrings re-read the doctest examples with a
88-column line in three places, and the server example shadowed
`session` inside a comprehension.

what:
- Bind `examples` from the pytest item before slicing it into source
  in the server, session_params and session fixtures
- Rename the comprehension variable to `s` in the server fixture's
  sample test so it no longer shadows `session`
why: The from_window_id and from_pane_id examples, the control-mode
send-keys examples and the search_sessions example ran past 80
columns, two of them by splitting a call mid-argument.

what:
- Bind `new_id` and pass it to Window.from_window_id and
  Pane.from_pane_id
- In the control-mode examples bind `client`, then `show`, so the
  send-keys and show-options calls fit
- Bind `pattern` for the search_sessions filter
why: The search_windows filter and the from_window_id example ran past
80 columns.

what:
- Bind `pattern` for the search_windows filter
- Bind `new_id` and pass it to Window.from_window_id
why: Five doctests in Window ran past 80 columns.

what:
- Pass the server and window id to Window.from_window_id by position
- Bind `source` for the link-window call
- Bind `names` for the linked_sessions comparison
- Bind `new_id` and pass it to Pane.from_pane_id
- Bind `windows` before filtering for the active window
@tony
tony force-pushed the example-style branch 4 times, most recently from 4caecb3 to 7bedca6 Compare October 4, 2026 22:58
@tony
tony force-pushed the example-style branch 11 times, most recently from 0fcfa2c to 192f1e2 Compare October 11, 2026 16:26
tony added 4 commits October 11, 2026 11:49
why: The explode_complex doctests carried a status-format literal of
over 1,500 characters, written out twice (input and expected value),
plus terminal-features expectations broken mid-dict. A reader could not
see what the example demonstrates: an array option parsed into indexed
entries.

what:
- Rebuild the status-format example from two short named fragments,
  `left` and `centre`, joined into option text with an f-string and
  compared against the same fragments, so the expected value cannot
  drift from the input. It keeps indexes 0 and 1 and the #[...] and
  #{...} syntax, but no longer round-trips tmux's full default
  status-format string
- Break the terminal-features expected dict one key per line, and bind
  `features` and `sparse` for the two array inputs; the sparse result is
  still compared with a SparseArray literal
- Bind `raw` once for the two _show_option_raw assertions
why: Three fenced Python examples ran 81 to 90 columns. A trailing
comment, a three-name import, and a call with a long keyword argument
caused it.

what:
- Shorten the "Live" comment in the client snapshot example
- Parenthesize the libtmux.test.random import, one name per line
- Break the resize_pane call after its opening parenthesis
why: Example width, placement of comments and what may not be wrapped
were unwritten, so each page drifted its own way.

what:
- Add the shared example rules to "Documented examples that run"
- Add "In this repository" in the shared four-bullet shape: the hard
  limit and the width check command, the surfaces no formatter touches,
  the markers that decide what runs or is exempt, and the output and
  copied blocks that never break
- Add a Bad and Good pair from a doctest line the example commits fitted
why: An agent editing a doctest or Markdown code block was not sent to
the example rules.

what:
- Add code examples to the WRITING.md routing line
- Link the "Documented examples that run" section
@tony
tony force-pushed the example-style branch 14 times, most recently from 694b241 to 11609c0 Compare October 11, 2026 23:54
why: Examples render in fixed-width boxes that scroll sideways, and
nothing enforced a limit. Doctest lines and fenced blocks accumulated
past 80 columns. A libtmux.org code block holds 78 columns at 1024 px
(Firefox, WebKit, CI's Chrome) and 99 at 1440 px (Firefox, WebKit);
libtmux/docs#101 sizes the code font so 80 columns fit from 1024 px up.

ruff cannot enforce this. ruff 0.16.9 formats docstring code only when
`docstring-code-format` is on, and the option was rejected after a
probe: it parsed a `::` literal block in src/libtmux/options.py as
Python and rewrote the tmux option text `visual-activity* on` to
`visual - activity * on`. The formatter cannot tell a literal block
from code, so it corrupts documented output. Markdown fences are
outside ruff's Python formatting entirely.
https://github.com/astral-sh/ruff/blob/0.16.9/docs/formatter.md#docstring-formatting

Alternatives considered:
- ruff E501: already on (`extend-select` includes `"E"`), but it
  measures 88 columns and reads only .py files, never Markdown fences
- hand review: the limit drifted before this commit, so it needs a gate

what:
- Add scripts/check_example_width.py and .github/example-width.toml,
  which reads every `**/*.md` page plus MIGRATION and CHANGES, and the
  doc comments under src/
- No `examples/` directory and no exclusions; the one [[allow]] entry
  is the deliberately wide Bad line in the WRITING.md port block
- Record checker_digest from `--digest`, and shared_digest, the --digest
  of WRITING.md's shared section, so a local edit to it fails the check
- Run its self-test and the check in tests.yml beside the ruff steps,
  add a check-example-width just recipe, and list it in CONTRIBUTING
- Exclude scripts/ from mypy, which targets 3.10 while the script
  needs tomllib (3.11+)

This branch has not been deployed

No deployments
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