From c14b4e431ad79dc0ecf06e7fa8f75d5bf72815fc Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 14 Nov 2026 16:32:05 -0600 Subject: [PATCH] Server,Session,Window,Pane(docs): Add task directories to the class docstrings why: libtmux.org draws a class docstring as an API task directory when it holds three or more bold-led paragraphs, each followed by a labelled list of Sphinx roles. The python port had none, so none of the four core types told a reader which call does which job or what it raises. what: - Server, Session, Window and Pane: add bold-led rows in the intro of the class docstring, each followed by a bullet list of `Label: :meth:` roles in the fully qualified `~libtmux.X.y` form - State, from the source, what each group raises (ObjectDoesNotExist, TmuxObjectDoesNotExist, BadSessionName, OptionError, ValueError), which listings answer empty instead of raising, and which flags warn and are ignored on an older tmux - CHANGES: record the documentation change --- CHANGES | 8 +++ src/libtmux/pane.py | 93 +++++++++++++++++++++++++++++++++ src/libtmux/server.py | 116 +++++++++++++++++++++++++++++++++++++++++ src/libtmux/session.py | 85 ++++++++++++++++++++++++++++++ src/libtmux/window.py | 80 ++++++++++++++++++++++++++++ 5 files changed, 382 insertions(+) diff --git a/CHANGES b/CHANGES index d48218d54..1e160d617 100644 --- a/CHANGES +++ b/CHANGES @@ -94,6 +94,14 @@ environment-setup plumbing, so each example leads with the constructor call it demonstrates instead of the socket-path and `$TMUX` boilerplate needed to run it. +#### Task directories on Server, Session, Window and Pane + +The class docstrings of {class}`~libtmux.Server`, {class}`~libtmux.Session`, +{class}`~libtmux.Window`, and {class}`~libtmux.Pane` now group their methods by +the job a reader arrives with, such as finding one thing, listing everything, +or sending input. Each group says what a call returns and which exception it +raises, then lists the members that do the job. + ### Development #### Ruff 0.17.3 for development (#825) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index cacd58a7c..8d14d32d3 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -52,6 +52,99 @@ class Pane( ``Pane`` instances can send commands directly to a pane, or traverse between linked tmux objects. + **Reading it.** Every field is a tmux format token read when the pane was + listed or last refreshed, and is ``None`` when tmux gave no value. + :meth:`~libtmux.Pane.refresh` rewrites the fields in place and raises + :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when the pane has gone. The edge + properties convert tmux's flags to bools. + + - Identity: :attr:`~libtmux.Pane.pane_id`, :attr:`~libtmux.Pane.id` + - Position: :attr:`~libtmux.Pane.pane_index`, :attr:`~libtmux.Pane.index` + - Size: :attr:`~libtmux.Pane.width`, :attr:`~libtmux.Pane.height` + - Edges: :attr:`~libtmux.Pane.at_top`, :attr:`~libtmux.Pane.at_bottom`, + :attr:`~libtmux.Pane.at_left`, :attr:`~libtmux.Pane.at_right` + - Title: :attr:`~libtmux.Pane.title` + - Re-read from tmux: :meth:`~libtmux.Pane.refresh` + + **Finding it.** :meth:`~libtmux.Pane.from_pane_id` asks tmux for one pane and + raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when it is not there. + :meth:`~libtmux.Pane.from_env` returns the pane this process runs inside, read + from ``$TMUX`` and ``$TMUX_PANE``, and raises :exc:`~libtmux.exc.NotInsideTmux` + outside one. + + - By ID: :meth:`~libtmux.Pane.from_pane_id` + - The pane you run inside: :meth:`~libtmux.Pane.from_env` + - From a listing: :attr:`~libtmux.Server.panes`, + :attr:`~libtmux.Window.panes` + + **Finding its parents.** Each access asks tmux. + + - Parents: :attr:`~libtmux.Pane.window`, :attr:`~libtmux.Pane.session` + + **Sending input.** :meth:`~libtmux.Pane.send_keys` presses Enter after the + keys unless ``enter=False``, and sends them literally with ``literal=True``. + Called without keys it needs ``reset``, ``repeat`` or ``copy_mode_cmd``, and + raises :exc:`ValueError` otherwise. + + - Send keys: :meth:`~libtmux.Pane.send_keys` + - Press Enter: :meth:`~libtmux.Pane.enter` + - Paste a buffer: :meth:`~libtmux.Pane.paste_buffer` + - Send the prefix key: :meth:`~libtmux.Pane.send_prefix` + + **Reading output.** :meth:`~libtmux.Pane.capture_pane` returns a list with one + string per row. ``start`` and ``end`` count from the first visible line, with + negative numbers reaching into the history, ``-`` for the start of the history + as ``start`` and for the end of the visible pane as ``end``. With ``to_buffer`` + it stores the capture in a tmux buffer and returns ``None``. A flag newer than + the running tmux warns and is ignored. + + - Capture rows: :meth:`~libtmux.Pane.capture_pane` + - Ask tmux for a format: :meth:`~libtmux.Pane.display_message` + + **Splitting and arranging.** :meth:`~libtmux.Pane.split` divides this exact + pane, beneath it by default, and returns the new pane. Passing both ``size`` + and ``percentage`` raises :exc:`ValueError`. A resize takes an adjustment, a + height and width, or zoom, and a height or width given as text must be digits + or a percentage. + + - Split: :meth:`~libtmux.Pane.split`, :meth:`~libtmux.Pane.new_pane` + - Resize: :meth:`~libtmux.Pane.resize`, :meth:`~libtmux.Pane.set_width`, + :meth:`~libtmux.Pane.set_height` + - Exchange two panes: :meth:`~libtmux.Pane.swap` + - Move it into another window: :meth:`~libtmux.Pane.join`, + :meth:`~libtmux.Pane.move` + - Move it into a window of its own: :meth:`~libtmux.Pane.break_pane` + - Make it active: :meth:`~libtmux.Pane.select` + + **Modes, pipes and history.** :meth:`~libtmux.Pane.pipe` copies the pane's + output to a shell command, and stops piping when given no command. + :meth:`~libtmux.Pane.clear_history` forgets the scrollback, and + :meth:`~libtmux.Pane.reset` also resets the terminal state. + + - Copy mode: :meth:`~libtmux.Pane.copy_mode` + - Pipe output: :meth:`~libtmux.Pane.pipe` + - Scrollback and screen: :meth:`~libtmux.Pane.clear_history`, + :meth:`~libtmux.Pane.clear`, :meth:`~libtmux.Pane.reset` + - Title: :meth:`~libtmux.Pane.set_title` + + **Changing it.** :meth:`~libtmux.Pane.respawn` documents ``kill=True`` as + required if the pane is still active. :meth:`~libtmux.Pane.kill` takes + ``all_except`` to kill every other pane in the window instead. + + - Restart what it runs: :meth:`~libtmux.Pane.respawn` + - Destroy it: :meth:`~libtmux.Pane.kill` + + **Options and hooks.** An option or hook call acts on the pane scope by + default. The setters return the pane, so calls chain, and an unknown or + invalid name raises :exc:`~libtmux.exc.OptionError` or one of its subclasses. + + - Options: :meth:`~libtmux.Pane.show_options`, + :meth:`~libtmux.Pane.show_option`, :meth:`~libtmux.Pane.set_option`, + :meth:`~libtmux.Pane.unset_option` + - Hooks: :meth:`~libtmux.Pane.show_hooks`, :meth:`~libtmux.Pane.show_hook`, + :meth:`~libtmux.Pane.set_hook`, :meth:`~libtmux.Pane.unset_hook`, + :meth:`~libtmux.Pane.run_hook` + Attributes ---------- default_option_scope : OptionScope | None diff --git a/src/libtmux/server.py b/src/libtmux/server.py index f8e49ead6..d79e0c757 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -97,6 +97,122 @@ class Server( When instantiated stores information on live, running tmux server. + **Connecting.** Constructing a :class:`~libtmux.Server` runs no tmux command + of its own; it records the socket and settings every later call uses. + :meth:`~libtmux.Server.from_env` reads ``$TMUX`` without a subprocess, raises + :exc:`~libtmux.exc.NotInsideTmux` when it is unset, and does not check that the + server is alive. :meth:`~libtmux.Server.is_alive` answers a bool and never + raises, where :meth:`~libtmux.Server.raise_if_dead` raises + :exc:`~libtmux.exc.TmuxCommandNotFound` or + :class:`subprocess.CalledProcessError`. + + - Name a server: :class:`~libtmux.Server`, :meth:`~libtmux.Server.from_env` + - Is anything listening: :meth:`~libtmux.Server.is_alive`, + :meth:`~libtmux.Server.raise_if_dead` + - Start the daemon: :meth:`~libtmux.Server.start_server` + + **Finding one thing.** Each ``from_*_id`` constructor asks tmux for one object + and raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when it is not there. + ``get`` on a listing returns exactly one match, and raises + :exc:`~libtmux.exc.ObjectDoesNotExist` for none unless a default is passed, + and :exc:`~libtmux.exc.MultipleObjectsReturned` for several. + :meth:`~libtmux.Server.has_session` answers a bool, matches the name exactly + unless ``exact=False``, and raises :exc:`~libtmux.exc.BadSessionName` for an + empty name or one holding a period or colon. + + - By ID: :meth:`~libtmux.Session.from_session_id`, + :meth:`~libtmux.Window.from_window_id`, :meth:`~libtmux.Pane.from_pane_id` + - One match from a listing: + :meth:`~libtmux._internal.query_list.QueryList.get` + - Exists, without returning it: :meth:`~libtmux.Server.has_session` + + **Listing everything.** Each access reads tmux again and returns a + :class:`~libtmux._internal.query_list.QueryList`, so a list you keep is a + snapshot. :attr:`~libtmux.Server.sessions` and :attr:`~libtmux.Server.clients` + answer an empty list when the tmux command fails for any reason, so tell "no + sessions" from "tmux unreachable" with :meth:`~libtmux.Server.is_alive`. + :attr:`~libtmux.Server.windows` and :attr:`~libtmux.Server.panes` answer empty + only while the daemon is not up and raise on other tmux errors. Windows and + panes span every session. + + - Listings: :attr:`~libtmux.Server.sessions`, :attr:`~libtmux.Server.windows`, + :attr:`~libtmux.Server.panes`, :attr:`~libtmux.Server.clients` + - With a client attached: :attr:`~libtmux.Server.attached_sessions` + + **Filtering.** ``filter`` on a listing runs in Python against the full list. + The ``search_*`` methods hand tmux a ``-f`` format expression instead, and tmux + drops rows before any object is built. A malformed expression expands to empty + and hides every row without an error. + + - In Python: :meth:`~libtmux._internal.query_list.QueryList.filter` + - In tmux: :meth:`~libtmux.Server.search_sessions`, + :meth:`~libtmux.Server.search_windows`, :meth:`~libtmux.Server.search_panes` + + **Creating and destroying.** :meth:`~libtmux.Server.new_session` creates a + detached session and returns it. A name already in use raises + :exc:`~libtmux.exc.TmuxSessionExists` unless ``kill_session=True``. + :meth:`~libtmux.Server.kill_session` passes its target to tmux, which matches + it with ``fnmatch(3)``, so ``asdf`` also kills ``asdfasd``. + :meth:`~libtmux.Server.kill` stops the server and returns quietly when no + daemon is running. + + - New session: :meth:`~libtmux.Server.new_session` + - Kill one session: :meth:`~libtmux.Server.kill_session` + - Stop the server: :meth:`~libtmux.Server.kill` + + **Clients.** :attr:`~libtmux.Server.clients` returns typed + :class:`~libtmux.Client` objects and :meth:`~libtmux.Server.list_clients` + returns tmux's raw lines. Switching and attaching take a session name, which + raises :exc:`~libtmux.exc.BadSessionName` when it is empty or holds a period + or colon. + + - Listing: :attr:`~libtmux.Server.clients`, + :meth:`~libtmux.Server.list_clients` + - Move one: :meth:`~libtmux.Server.switch_client`, + :meth:`~libtmux.Server.attach_session` + - Detach: :meth:`~libtmux.Server.detach_client`, + :meth:`~libtmux.Server.detach_all_clients` + + **Options, hooks and environment.** These calls come from mixins and act on + the server scope by default. The option and hook setters return the object, so + calls chain, and an unknown or invalid option or hook name raises + :exc:`~libtmux.exc.OptionError` or one of its subclasses. The environment + calls act on the server's global environment, and the setters raise + :exc:`ValueError` when tmux reports an error. + + - Options: :meth:`~libtmux.Server.show_options`, + :meth:`~libtmux.Server.show_option`, :meth:`~libtmux.Server.set_option`, + :meth:`~libtmux.Server.unset_option` + - Hooks: :meth:`~libtmux.Server.show_hooks`, + :meth:`~libtmux.Server.show_hook`, :meth:`~libtmux.Server.set_hook`, + :meth:`~libtmux.Server.set_hooks`, :meth:`~libtmux.Server.unset_hook`, + :meth:`~libtmux.Server.run_hook` + - Environment: :meth:`~libtmux.Server.show_environment`, + :meth:`~libtmux.Server.getenv`, :meth:`~libtmux.Server.set_environment`, + :meth:`~libtmux.Server.unset_environment`, + :meth:`~libtmux.Server.remove_environment` + + **Buffers and key bindings.** Each wraps the tmux command of the same name and + raises :exc:`~libtmux.exc.LibTmuxException` when tmux writes to stderr. + + - Buffers: :meth:`~libtmux.Server.list_buffers`, + :meth:`~libtmux.Server.show_buffer`, :meth:`~libtmux.Server.set_buffer`, + :meth:`~libtmux.Server.delete_buffer`, :meth:`~libtmux.Server.save_buffer`, + :meth:`~libtmux.Server.load_buffer` + - Key bindings: :meth:`~libtmux.Server.list_keys`, + :meth:`~libtmux.Server.bind_key`, :meth:`~libtmux.Server.unbind_key` + + **Running a command.** Anything tmux can do that has no method here goes + through :meth:`~libtmux.Server.cmd`, which adds the socket, config file and + color flags and returns a :class:`~libtmux.common.tmux_cmd` holding the + return code, stdout and stderr. It does not check them. The shell and sourcing + helpers do, and raise :exc:`~libtmux.exc.LibTmuxException`. + + - One command: :meth:`~libtmux.Server.cmd` + - Shell and conditionals: :meth:`~libtmux.Server.run_shell`, + :meth:`~libtmux.Server.if_shell`, :meth:`~libtmux.Server.wait_for` + - Configuration: :meth:`~libtmux.Server.source_file` + Parameters ---------- socket_name : str, optional diff --git a/src/libtmux/session.py b/src/libtmux/session.py index c6b27867c..6b4b9a7cb 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -54,6 +54,91 @@ class Session( Holds :class:`Window` objects. + **Reading it.** Every field is a tmux format token read when the session was + listed or last refreshed, and is ``None`` when tmux gave no value. + :meth:`~libtmux.Session.refresh` rewrites the fields in place and returns + ``None``. + + - Identity: :attr:`~libtmux.Session.session_id`, + :attr:`~libtmux.Session.id` + - Name: :attr:`~libtmux.Session.session_name`, :attr:`~libtmux.Session.name` + - Re-read from tmux: :meth:`~libtmux.Session.refresh` + + **Finding it.** :meth:`~libtmux.Session.from_session_id` asks tmux for one + session and raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when it is gone. + :meth:`~libtmux.Session.from_env` resolves the session of the pane this + process runs inside, through ``$TMUX_PANE``, and raises + :exc:`~libtmux.exc.NotInsideTmux` outside one. + + - By ID: :meth:`~libtmux.Session.from_session_id` + - The session you run inside: :meth:`~libtmux.Session.from_env` + - From a listing: :attr:`~libtmux.Server.sessions` + - By name, without returning it: :meth:`~libtmux.Server.has_session` + + **Finding what is inside.** Each access reads tmux again and returns a + :class:`~libtmux._internal.query_list.QueryList`. Unlike + :attr:`~libtmux.Server.sessions`, these raise + :exc:`~libtmux.exc.LibTmuxException` when tmux writes to stderr. + :attr:`~libtmux.Session.active_window` raises + :exc:`~libtmux.exc.NoActiveWindow` or + :exc:`~libtmux.exc.MultipleActiveWindows` unless exactly one window is active. + :attr:`~libtmux.Session.active_pane` is the active window's active pane, or + ``None``. + + - Windows and panes: :attr:`~libtmux.Session.windows`, + :attr:`~libtmux.Session.panes` + - The active ones: :attr:`~libtmux.Session.active_window`, + :attr:`~libtmux.Session.active_pane` + - Filtered by tmux: :meth:`~libtmux.Session.search_windows`, + :meth:`~libtmux.Session.search_panes` + + **Moving the selection.** Each returns the window that is now active and + raises :exc:`~libtmux.exc.LibTmuxException` when tmux refuses. + + - Relative: :meth:`~libtmux.Session.next_window`, + :meth:`~libtmux.Session.previous_window`, + :meth:`~libtmux.Session.last_window` + - A named window: :meth:`~libtmux.Session.select_window` + + **Adding windows.** :meth:`~libtmux.Session.new_window` returns the new + :class:`~libtmux.Window`. It is created detached unless ``attach=True``, and + it takes the next free index unless ``window_index`` names one. + + - New window: :meth:`~libtmux.Session.new_window` + - Next to a window you hold: :meth:`~libtmux.Window.new_window` + + **Changing it.** :meth:`~libtmux.Session.rename_session` raises + :exc:`~libtmux.exc.BadSessionName` for an empty name or one holding a period + or colon, refreshes the session and returns it. + :meth:`~libtmux.Session.kill` closes the linked windows and detaches every + client, and ``group=True`` needs tmux 3.7 or later, below which it warns and is + ignored. + + - Rename: :meth:`~libtmux.Session.rename_session` + - Destroy it: :meth:`~libtmux.Session.kill` + - Close one window: :meth:`~libtmux.Session.kill_window` + - Detach its clients: :meth:`~libtmux.Session.detach_client` + - Attach a terminal or switch to it: :meth:`~libtmux.Session.attach`, + :meth:`~libtmux.Session.switch_client` + + **Options, hooks and environment.** An option or hook call leaves the scope + flag off by default, which tmux resolves as session scope. The setters return + the session, so calls chain, and an unknown or invalid name raises + :exc:`~libtmux.exc.OptionError` or one of its subclasses. The environment + calls act on this session and raise :exc:`ValueError` when tmux reports an + error. + + - Options: :meth:`~libtmux.Session.show_options`, + :meth:`~libtmux.Session.show_option`, :meth:`~libtmux.Session.set_option`, + :meth:`~libtmux.Session.unset_option` + - Hooks: :meth:`~libtmux.Session.show_hooks`, + :meth:`~libtmux.Session.show_hook`, :meth:`~libtmux.Session.set_hook`, + :meth:`~libtmux.Session.unset_hook`, :meth:`~libtmux.Session.run_hook` + - Environment: :meth:`~libtmux.Session.show_environment`, + :meth:`~libtmux.Session.getenv`, :meth:`~libtmux.Session.set_environment`, + :meth:`~libtmux.Session.unset_environment`, + :meth:`~libtmux.Session.remove_environment` + Parameters ---------- server : :class:`Server` diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 3960ca4ee..511e8f5e0 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -55,6 +55,86 @@ class Window( Holds :class:`Pane` objects. + **Reading it.** Every field is a tmux format token read when the window was + listed or last refreshed, and is ``None`` when tmux gave no value. + :meth:`~libtmux.Window.refresh` rewrites the fields in place and raises + :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when the window has gone. + + - Identity: :attr:`~libtmux.Window.window_id`, :attr:`~libtmux.Window.id` + - Name and position: :attr:`~libtmux.Window.window_name`, + :attr:`~libtmux.Window.name`, :attr:`~libtmux.Window.window_index`, + :attr:`~libtmux.Window.index` + - Size: :attr:`~libtmux.Window.width`, :attr:`~libtmux.Window.height` + - Re-read from tmux: :meth:`~libtmux.Window.refresh` + + **Finding it.** :meth:`~libtmux.Window.from_window_id` asks tmux for one window + and raises :exc:`~libtmux.exc.TmuxObjectDoesNotExist` when it is not there. + :meth:`~libtmux.Window.from_env` resolves the window of the pane this process + runs inside, and raises :exc:`~libtmux.exc.NotInsideTmux` outside one. + + - By ID: :meth:`~libtmux.Window.from_window_id` + - The window you run inside: :meth:`~libtmux.Window.from_env` + - From a listing: :attr:`~libtmux.Server.windows`, + :attr:`~libtmux.Session.windows` + + **Finding its neighbours.** A window can sit in several sessions. + :attr:`~libtmux.Window.session` is the session recorded on this instance, and + :attr:`~libtmux.Window.linked_sessions` lists every one that holds the window, + each once, and is empty when either listing it needs fails. + :attr:`~libtmux.Window.active_pane` is ``None`` when no pane is active. + + - Parent: :attr:`~libtmux.Window.session` + - Every session holding it: :attr:`~libtmux.Window.linked_sessions` + - Panes: :attr:`~libtmux.Window.panes`, :attr:`~libtmux.Window.active_pane` + - Panes filtered by tmux: :meth:`~libtmux.Window.search_panes` + + **Splitting and arranging.** :meth:`~libtmux.Window.split` divides the active + pane, beneath it by default, and returns the new :class:`~libtmux.Pane`. + Passing both ``size`` and ``percentage`` raises :exc:`ValueError`, and flags + newer than the running tmux warn and are ignored. A layout name and a layout + flag together raise :exc:`ValueError` in + :meth:`~libtmux.Window.select_layout`. Floating panes need tmux 3.7 or later. + + - Split: :meth:`~libtmux.Window.split`, :meth:`~libtmux.Window.new_pane` + - Apply a layout: :meth:`~libtmux.Window.select_layout` + - Cycle through the presets: :meth:`~libtmux.Window.next_layout`, + :meth:`~libtmux.Window.previous_layout` + - Rotate the panes: :meth:`~libtmux.Window.rotate` + - Resize the window: :meth:`~libtmux.Window.resize` + - Choose the active pane: :meth:`~libtmux.Window.select_pane`, + :meth:`~libtmux.Window.last_pane` + + **Linking and moving.** :meth:`~libtmux.Window.link` shows the window in + another session as well, and :meth:`~libtmux.Window.unlink` removes it from the + current one. :meth:`~libtmux.Window.move_window` with ``renumber=True`` only + renumbers the windows and moves nothing. + + - Show it in another session: :meth:`~libtmux.Window.link` + - Remove it from this session: :meth:`~libtmux.Window.unlink` + - Move it: :meth:`~libtmux.Window.move_window` + - Exchange two windows: :meth:`~libtmux.Window.swap` + - Make it current: :meth:`~libtmux.Window.select` + - New window next to it: :meth:`~libtmux.Window.new_window` + + **Changing it.** :meth:`~libtmux.Window.rename_window` refreshes the window and + returns it. :meth:`~libtmux.Window.respawn` documents ``kill=True`` as required + if the window is still active. + + - Rename: :meth:`~libtmux.Window.rename_window` + - Restart what it runs: :meth:`~libtmux.Window.respawn` + - Destroy it: :meth:`~libtmux.Window.kill` + + **Options and hooks.** An option or hook call acts on the window scope by + default. The setters return the window, so calls chain, and an unknown or + invalid name raises :exc:`~libtmux.exc.OptionError` or one of its subclasses. + + - Options: :meth:`~libtmux.Window.show_options`, + :meth:`~libtmux.Window.show_option`, :meth:`~libtmux.Window.set_option`, + :meth:`~libtmux.Window.unset_option` + - Hooks: :meth:`~libtmux.Window.show_hooks`, + :meth:`~libtmux.Window.show_hook`, :meth:`~libtmux.Window.set_hook`, + :meth:`~libtmux.Window.unset_hook`, :meth:`~libtmux.Window.run_hook` + Parameters ---------- session : :class:`Session`