tmuxp progress - tmuxp.cli._progress¶
Progress indicators for tmuxp CLI.
This module provides a threaded spinner for long-running operations, using only standard library and ANSI escape sequences.
- tmuxp.cli._progress._visible_len(s)[source]¶
Return visible length of s, ignoring ANSI escapes.
- Return type:
Examples
>>> _visible_len("hello") 5 >>> _visible_len("\033[32mgreen\033[0m") 5 >>> _visible_len("") 0
- tmuxp.cli._progress._truncate_visible(text, max_visible, suffix='...')[source]¶
Truncate text to max_visible visible characters, preserving ANSI sequences.
If the visible length of text is already within max_visible, it is returned unchanged. Otherwise the text is cut so that exactly max_visible visible characters remain, a
\x1b[0mreset is appended (to prevent color bleed), followed by suffix.- Return type:
- Parameters:
- Returns:
Truncated string with ANSI sequences intact.
- Return type:
Examples
Plain text truncation:
>>> _truncate_visible("hello world", 5) 'hello\x1b[0m...'
ANSI sequences are preserved whole:
>>> _truncate_visible("\033[32mgreen\033[0m", 3) '\x1b[32mgre\x1b[0m...'
No truncation needed:
>>> _truncate_visible("short", 10) 'short'
Empty string:
>>> _truncate_visible("", 5) ''
- tmuxp.cli._progress.render_bar(done, total, width=10)[source]¶
Render a plain-text ASCII progress bar without color.
- Return type:
- Parameters:
- Returns:
A bar like
"█████░░░░░". Returns""when total <= 0 or width <= 0.- Return type:
Examples
>>> render_bar(0, 10) '░░░░░░░░░░' >>> render_bar(5, 10) '█████░░░░░' >>> render_bar(10, 10) '██████████' >>> render_bar(0, 0) '' >>> render_bar(3, 10, width=5) '█░░░░'
- class tmuxp.cli._progress._SafeFormatMap[source]¶
Bases:
dictdict subclass that returns
{key}for missing keys in format_map.
- tmuxp.cli._progress.resolve_progress_format(fmt)[source]¶
Return the format string for fmt, resolving preset names.
If fmt is a key in
PROGRESS_PRESETSthe corresponding format string is returned; otherwise fmt is returned as-is.- Return type:
Examples
>>> resolve_progress_format("minimal") == PROGRESS_PRESETS["minimal"] True >>> resolve_progress_format("{session} w{window_progress}") '{session} w{window_progress}' >>> resolve_progress_format("unknown-preset") 'unknown-preset'
- class tmuxp.cli._progress._WindowStatus(name, done=False, pane_num=None, pane_total=None, pane_done=0)[source]¶
Bases:
objectState for a single window in the build tree.
- class tmuxp.cli._progress.BuildTree(workspace_path='')[source]¶
Bases:
objectTracks session/window/pane build state; renders a structural progress tree.
Template Token Lifecycle
Each token is first available at the event listed in its column.
—means the value does not change at that phase.Token
Pre-
session_createdAfter
session_createdAfter
window_startedAfter
pane_creatingAfter
window_done{session}""session name
—
—
—
{window}""""window name
—
last window name
{window_index}00N (1-based started count)
—
—
{window_total}0total
—
—
—
{window_progress}"""""N/M"when > 0—
—
{windows_done}0000increments
{windows_remaining}0total
total
total
decrements
{window_progress_rel}"""0/M""0/M"—
"N/M"{pane_index}000pane_num
0{pane_total}00window’s pane total
—
window’s pane total
{pane_progress}"""""""N/M"""{pane_done}000pane_num
pane_total
{pane_remaining}00pane_total
decrements
0{pane_progress_rel}"""""0/M""N/M""M/M"{progress}"""""N/M win""N/M win · P/Q pane"—
{session_pane_total}0total
—
—
—
{session_panes_done}0000accumulated
{session_panes_remaining}0total
total
total
decrements
{session_pane_progress}"""0/T"—
—
"N/T"{overall_percent}0000updates
{summary}"""""""""[N win, M panes]"{bar}(spinner)[░░…][░░…]starts filling
fractional
jumps
{pane_bar}(spinner)""[░░…]—
—
updates
{window_bar}(spinner)""[░░…]—
—
updates
{status_icon}(spinner)""""""""""During
before_script:{bar},{pane_bar},{window_bar}show a marching animation;{status_icon}=⏸.Examples
Empty tree renders nothing:
>>> from tmuxp.cli._colors import ColorMode, Colors >>> colors = Colors(ColorMode.NEVER) >>> tree = BuildTree() >>> tree.render(colors, 80) []
After session_created event the header appears:
>>> tree.on_event({"event": "session_created", "name": "my-session"}) >>> tree.render(colors, 80) ['Session']
After window_started and pane_creating:
>>> tree.on_event({"event": "window_started", "name": "editor", "pane_total": 2}) >>> tree.on_event({"event": "pane_creating", "pane_num": 1, "pane_total": 2}) >>> lines = tree.render(colors, 80) >>> lines[1] '- editor, pane (1 of 2)'
After window_done the window gets a checkmark:
>>> tree.on_event({"event": "window_done"}) >>> lines = tree.render(colors, 80) >>> lines[1] '- ✓ editor'
Inline status format:
>>> tree2 = BuildTree() >>> tree2.format_inline("Building projects...") 'Building projects...' >>> tree2.on_event({"event": "session_created", "name": "cihai", "window_total": 3}) >>> tree2.format_inline("Building projects...") 'Building projects... cihai' >>> tree2.on_event({"event": "window_started", "name": "gp-libs", "pane_total": 2}) >>> tree2.on_event({"event": "pane_creating", "pane_num": 1, "pane_total": 2}) >>> tree2.format_inline("Building projects...") 'Building projects... cihai [1 of 3 windows, 1 of 2 panes] gp-libs'
- on_event(event)[source]¶
Update tree state from a build event dict.
- Return type:
Examples
>>> tree = BuildTree() >>> tree.on_event({ ... "event": "session_created", "name": "dev", "window_total": 2, ... }) >>> tree.session_name 'dev' >>> tree.window_total 2 >>> tree.on_event({ ... "event": "window_started", "name": "editor", "pane_total": 3, ... }) >>> len(tree.windows) 1 >>> tree.windows[0].name 'editor'
- _context()[source]¶
Return the current build-state token dict for template rendering.
Examples
Zero-state before any events:
>>> tree = BuildTree(workspace_path="~/.tmuxp/myapp.yaml") >>> ev = { ... "event": "session_created", ... "name": "myapp", ... "window_total": 5, ... "session_pane_total": 10, ... } >>> tree.on_event(ev) >>> ctx = tree._context() >>> ctx["workspace_path"] '~/.tmuxp/myapp.yaml' >>> ctx["session"] 'myapp' >>> ctx["window_total"] 5 >>> ctx["window_index"] 0 >>> ctx["progress"] '' >>> ctx["windows_done"] 0 >>> ctx["windows_remaining"] 5 >>> ctx["window_progress_rel"] '0/5' >>> ctx["session_pane_total"] 10 >>> ctx["session_panes_remaining"] 10 >>> ctx["session_pane_progress"] '0/10' >>> ctx["summary"] ''
After windows complete, summary shows counts:
>>> tree.on_event({"event": "window_started", "name": "w1", "pane_total": 3}) >>> tree.on_event({"event": "window_done"}) >>> tree.on_event({"event": "window_started", "name": "w2", "pane_total": 5}) >>> tree.on_event({"event": "window_done"}) >>> tree._context()["summary"] '[2 win, 8 panes]'
- format_template(fmt, extra=None)[source]¶
Render fmt with the current build state.
Returns
""beforesession_createdfires so callers can fall back to a pre-build message. Unknown{tokens}are left as-is (not dropped silently).The optional extra dict is merged on top of
_context()so callers (e.g.Spinner) can inject ANSI-colored tokens like{bar}without adding color concerns toBuildTree.- Return type:
Examples
>>> tree = BuildTree() >>> tree.format_template("{session} [{progress}] {window}") '' >>> ev = {"event": "session_created", "name": "cihai", "window_total": 3} >>> tree.on_event(ev) >>> tree.format_template("{session} [{progress}] {window}") 'cihai [] ' >>> ev = {"event": "window_started", "name": "editor", "pane_total": 4} >>> tree.on_event(ev) >>> tree.format_template("{session} [{progress}] {window}") 'cihai [1/3 win] editor' >>> tree.on_event({"event": "pane_creating", "pane_num": 2, "pane_total": 4}) >>> tree.format_template("{session} [{progress}] {window}") 'cihai [1/3 win · 2/4 pane] editor' >>> tree.format_template("minimal: {session} [{window_progress}]") 'minimal: cihai [1/3]' >>> tree.format_template("{session} {unknown_token}") 'cihai {unknown_token}' >>> tree.format_template("{session}", extra={"custom": "value"}) 'cihai'
- format_inline(base)[source]¶
Return base message with current build state appended inline.
- Return type:
- Parameters:
base (str) – The original spinner message to start from.
- Returns:
basealone if no session has been created yet; otherwise"base session_name [W of N windows, P of M panes] window_name", omitting the bracket section when there is no current window, and omitting individual parts when their totals are not known.- Return type:
- class tmuxp.cli._progress.Spinner(message='Loading...', color_mode=ColorMode.AUTO, stream=<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, interval=0.1, output_lines=3, progress_format=None, workspace_path='')[source]¶
Bases:
objectA threaded spinner for CLI progress.
Examples
>>> import io >>> stream = io.StringIO() >>> with Spinner("Build...", color_mode=ColorMode.NEVER, stream=stream) as spinner: ... spinner.add_output_line("Session created: test") ... spinner.update_message("Creating window: editor")
- _restore_cursor()[source]¶
Unconditionally restore cursor — called by atexit on abnormal exit.
- Return type:
- add_output_line(line)[source]¶
Append a line to the live output panel (thread-safe via GIL).
When the spinner is disabled (non-TTY), writes directly to the stream so output is not silently swallowed.
- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("test", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.add_output_line("hello world") >>> stream.getvalue() 'hello world\n'
- update_message(message)[source]¶
Update the message displayed next to the spinner.
- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("initial", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.message 'initial' >>> spinner.update_message("updated") >>> spinner.message 'updated'
- _build_extra()[source]¶
Return spinner-owned template tokens (colored bar, status_icon).
These are separated from
BuildTree._context()to keep ANSI/color concerns out ofBuildTree, which is also used in tests without colors.Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("x", color_mode=ColorMode.NEVER, stream=stream) >>> spinner._build_tree.on_event( ... { ... "event": "session_created", ... "name": "s", ... "window_total": 4, ... "session_pane_total": 8, ... } ... ) >>> extra = spinner._build_extra() >>> extra["bar"] '░░░░░░░░░░' >>> extra["status_icon"] ''
- on_build_event(event)[source]¶
Forward build event to BuildTree and update spinner message inline.
- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("Loading", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.on_build_event({ ... "event": "session_created", "name": "myapp", ... "window_total": 2, "session_pane_total": 3, ... }) >>> spinner._build_tree.session_name 'myapp'
- start()[source]¶
Start the spinner thread.
- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("test", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.start() >>> spinner.stop()
- stop()[source]¶
Stop the spinner thread.
- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("test", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.start() >>> spinner.stop() >>> spinner._thread is None True
- format_success()[source]¶
Render the success template with current build state.
Uses
SUCCESS_TEMPLATEwith colored{session}(highlight()),{workspace_path}(info()), and{summary}(muted()) fromBuildTree._context().- Return type:
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("x", color_mode=ColorMode.NEVER, stream=stream, ... workspace_path="~/.tmuxp/myapp.yaml") >>> spinner._build_tree.on_event({ ... "event": "session_created", "name": "myapp", ... "window_total": 2, "session_pane_total": 4, ... }) >>> spinner._build_tree.on_event( ... {"event": "window_started", "name": "w1", "pane_total": 2}) >>> spinner._build_tree.on_event({"event": "window_done"}) >>> spinner._build_tree.on_event( ... {"event": "window_started", "name": "w2", "pane_total": 2}) >>> spinner._build_tree.on_event({"event": "window_done"}) >>> spinner.format_success() 'Loaded workspace: myapp (~/.tmuxp/myapp.yaml) [2 win, 4 panes]'
- success(text=None)[source]¶
Stop the spinner and print a success line.
- Return type:
- Parameters:
text (str | None) – The success message to display after the checkmark. When
None, usesformat_success()if a progress format is configured, otherwise falls back to_base_message.
Examples
>>> import io >>> stream = io.StringIO() >>> spinner = Spinner("x", color_mode=ColorMode.NEVER, stream=stream) >>> spinner.success("done") >>> "✓ done" in stream.getvalue() True
With no args and no progress format, falls back to base message:
>>> stream2 = io.StringIO() >>> spinner2 = Spinner("Loading...", color_mode=ColorMode.NEVER, stream=stream2) >>> spinner2.success() >>> "✓ Loading..." in stream2.getvalue() True