Skip to content

Commit 7ec766b

Browse files
authored
feat(options,hooks)!: Add OptionsMixin, HooksMixin, and SparseArray (#516)
why: tmux option and hook management was inconsistent across Server, Session, Window, and Pane objects. Each had different method signatures and return types, making the API difficult to use and maintain. what: - Add OptionsMixin providing unified show_options(), show_option(), set_option(), and unset_option() methods across all tmux objects - Add HooksMixin providing set_hook(), show_hook(), show_hooks(), unset_hook(), run_hook(), and set_hooks() bulk operations - Add SparseArray data structure for tmux indexed arrays that preserves sparse indices (e.g., command-alias[0], command-alias[99]) - Add OptionScope enum (Server, Session, Window, Pane) with flag maps - Add comprehensive dataclasses for typed option/hook access Breaking changes: - Deprecate Window.set_window_option() -> Window.set_option() - Deprecate Window.show_window_option() -> Window.show_option() - Deprecate Window.show_window_options() -> Window.show_options() - Deprecate `g` parameter in favor of `global_` (emits DeprecationWarning) New set_option() parameters: - _format (-F): Expand format strings - unset (-u): Unset the option - global_ (-g): Set as global option - unset_panes (-U): Also unset in child panes - prevent_overwrite (-o): Don't overwrite if exists - suppress_warnings (-q): Suppress warnings - append (-a): Append to existing value tmux version compatibility: - All options/hooks features: 3.2+ - Window/Pane hook scopes (-w, -p): 3.2+ - client-active, window-resized hooks: 3.3+ - pane-title-changed hook: 3.5+ Files added: - src/libtmux/options.py (OptionsMixin) - src/libtmux/hooks.py (HooksMixin) - src/libtmux/_internal/sparse_array.py (SparseArray) - src/libtmux/_internal/constants.py (Options/Hooks dataclasses) - tests/test_options.py (comprehensive option test grid) - tests/test_hooks.py (comprehensive hook test grid)
2 parents 309c27f + d24226e commit 7ec766b

28 files changed

+6125
-337
lines changed

CHANGES

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,149 @@ $ uvx --from 'libtmux' --prerelease allow python
3434

3535
_Future release notes will be placed here_
3636

37+
### Overview
38+
39+
libtmux 0.50 brings a major enhancement to option and hook management. The new
40+
{class}`~options.OptionsMixin` and {class}`~hooks.HooksMixin` classes provide a
41+
unified, typed API for managing tmux options and hooks across all object types.
42+
43+
**Highlights:**
44+
45+
- **Unified Options API**: New `show_option()`, `show_options()`, `set_option()`,
46+
and `unset_option()` methods available on Server, Session, Window, and Pane.
47+
- **Hook Management**: Full programmatic control over tmux hooks with support for
48+
indexed hook arrays and bulk operations.
49+
- **SparseArray**: New internal data structure for handling tmux's sparse indexed
50+
arrays (e.g., `command-alias[0]`, `command-alias[99]`).
51+
- **tmux 3.2+ baseline**: Removed support for tmux versions below 3.2a, enabling
52+
cleaner code and full hook/option feature support.
53+
54+
### What's New
55+
56+
#### Unified Options API (#516)
57+
58+
All tmux objects now share a consistent options interface through
59+
{class}`~options.OptionsMixin`:
60+
61+
```python
62+
import libtmux
63+
64+
server = libtmux.Server()
65+
session = server.sessions[0]
66+
window = session.windows[0]
67+
pane = window.panes[0]
68+
69+
# Get all options as a structured dict
70+
session.show_options()
71+
# {'activity-action': 'other', 'base-index': 0, ...}
72+
73+
# Get a single option value
74+
session.show_option('base-index')
75+
# 0
76+
77+
# Set an option
78+
window.set_option('automatic-rename', True)
79+
80+
# Unset an option (revert to default)
81+
window.unset_option('automatic-rename')
82+
```
83+
84+
**New methods on Server, Session, Window, and Pane:**
85+
86+
| Method | Description |
87+
|--------|-------------|
88+
| `show_options()` | Get all options as a structured dict |
89+
| `show_option(name)` | Get a single option value |
90+
| `set_option(name, value)` | Set an option |
91+
| `unset_option(name)` | Unset/remove an option |
92+
93+
**New parameters for `set_option()`:**
94+
95+
| Parameter | tmux flag | Description |
96+
|-----------|-----------|-------------|
97+
| `_format` | `-F` | Expand format strings in value |
98+
| `unset` | `-u` | Unset the option |
99+
| `global_` | `-g` | Set as global option |
100+
| `unset_panes` | `-U` | Also unset in child panes |
101+
| `prevent_overwrite` | `-o` | Don't overwrite if exists |
102+
| `suppress_warnings` | `-q` | Suppress warnings |
103+
| `append` | `-a` | Append to existing value |
104+
105+
#### Hook Management (#516)
106+
107+
New {class}`~hooks.HooksMixin` provides programmatic control over tmux hooks:
108+
109+
```python
110+
session = server.sessions[0]
111+
112+
# Set a hook
113+
session.set_hook('session-renamed', 'display-message "Renamed!"')
114+
115+
# Get hook value
116+
session.show_hook('session-renamed')
117+
# 'display-message "Renamed!"'
118+
119+
# Get all hooks
120+
session.show_hooks()
121+
# {'session-renamed': 'display-message "Renamed!"'}
122+
123+
# Remove a hook
124+
session.unset_hook('session-renamed')
125+
```
126+
127+
**Indexed hooks and bulk operations:**
128+
129+
tmux hooks support multiple values via indices (e.g., `session-renamed[0]`,
130+
`session-renamed[1]`). The bulk operations API makes this easy:
131+
132+
```python
133+
# Set multiple hooks at once
134+
session.set_hooks('session-renamed', {
135+
0: 'display-message "Hook 0"',
136+
1: 'display-message "Hook 1"',
137+
5: 'run-shell "echo hook 5"',
138+
})
139+
```
140+
141+
**Hook methods available on Server, Session, Window, and Pane:**
142+
143+
| Method | Description |
144+
|--------|-------------|
145+
| `set_hook(hook, value)` | Set a hook |
146+
| `show_hook(hook)` | Get hook value (returns SparseArray for indexed hooks) |
147+
| `show_hooks()` | Get all hooks |
148+
| `unset_hook(hook)` | Remove a hook |
149+
| `run_hook(hook)` | Run a hook immediately |
150+
| `set_hooks(hook, values)` | Set multiple indexed hooks at once |
151+
152+
#### SparseArray for Indexed Options (#516)
153+
154+
tmux uses sparse indexed arrays for options like `command-alias[0]`,
155+
`command-alias[99]`, `terminal-features[0]`. Python lists can't represent
156+
gaps in indices, so libtmux introduces {class}`~_internal.sparse_array.SparseArray`:
157+
158+
```python
159+
>>> from libtmux._internal.sparse_array import SparseArray
160+
161+
>>> arr: SparseArray[str] = SparseArray()
162+
>>> arr.add(0, "first")
163+
>>> arr.add(99, "ninety-ninth") # Gap in indices preserved!
164+
>>> arr[0]
165+
'first'
166+
>>> arr[99]
167+
'ninety-ninth'
168+
>>> list(arr.keys())
169+
[0, 99]
170+
>>> list(arr.iter_values()) # Values in index order
171+
['first', 'ninety-ninth']
172+
```
173+
174+
#### New Constants (#516)
175+
176+
- {class}`~constants.OptionScope` enum: `Server`, `Session`, `Window`, `Pane`
177+
- `OPTION_SCOPE_FLAG_MAP`: Maps scope to tmux flags (`-s`, `-w`, `-p`)
178+
- `HOOK_SCOPE_FLAG_MAP`: Maps scope to hook flags
179+
37180
## libtmux 0.49.0 (2025-11-29)
38181

39182
### Breaking Changes
@@ -48,6 +191,35 @@ deprecation announced in v0.48.0.
48191
- Removed version guards throughout the codebase
49192
- For users on older tmux, use libtmux v0.48.x
50193

194+
#### Deprecated Window methods (#516)
195+
196+
The following methods are deprecated and will be removed in a future release:
197+
198+
| Deprecated | Replacement |
199+
|------------|-------------|
200+
| `Window.set_window_option()` | `Window.set_option()` |
201+
| `Window.show_window_option()` | `Window.show_option()` |
202+
| `Window.show_window_options()` | `Window.show_options()` |
203+
204+
The old methods will emit a {class}`DeprecationWarning` when called:
205+
206+
```python
207+
window.set_window_option('automatic-rename', 'on')
208+
# DeprecationWarning: Window.set_window_option() is deprecated
209+
210+
# Use the new method instead:
211+
window.set_option('automatic-rename', True)
212+
```
213+
214+
### tmux Version Compatibility
215+
216+
| Feature | Minimum tmux |
217+
|---------|-------------|
218+
| All options/hooks features | 3.2+ |
219+
| Window/Pane hook scopes (`-w`, `-p`) | 3.2+ |
220+
| `client-active`, `window-resized` hooks | 3.3+ |
221+
| `pane-title-changed` hook | 3.5+ |
222+
51223
## libtmux 0.48.0 (2025-11-28)
52224

53225
### Breaking Changes

MIGRATION

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,93 @@ _Detailed migration steps for the next version will be posted here._
2525

2626
<!-- To the maintainers and contributors: please add migration details for the upcoming release here -->
2727

28+
## libtmux 0.50.0: Unified Options and Hooks API (#516)
29+
30+
### New unified options API
31+
32+
All tmux objects (Server, Session, Window, Pane) now share a consistent options
33+
interface through {class}`~libtmux.options.OptionsMixin`:
34+
35+
```python
36+
# Get all options
37+
session.show_options()
38+
39+
# Get a single option
40+
session.show_option('base-index')
41+
42+
# Set an option
43+
window.set_option('automatic-rename', True)
44+
45+
# Unset an option
46+
window.unset_option('automatic-rename')
47+
```
48+
49+
### New hooks API
50+
51+
All tmux objects now support hook management through
52+
{class}`~libtmux.hooks.HooksMixin`:
53+
54+
```python
55+
# Set a hook
56+
session.set_hook('session-renamed', 'display-message "Renamed!"')
57+
58+
# Get hook value
59+
session.show_hook('session-renamed')
60+
61+
# Get all hooks
62+
session.show_hooks()
63+
64+
# Remove a hook
65+
session.unset_hook('session-renamed')
66+
```
67+
68+
### Deprecated Window methods
69+
70+
The following `Window` methods are deprecated and will be removed in a future
71+
release:
72+
73+
| Deprecated | Replacement |
74+
|------------|-------------|
75+
| `Window.set_window_option()` | {meth}`Window.set_option() <libtmux.Window.set_option>` |
76+
| `Window.show_window_option()` | {meth}`Window.show_option() <libtmux.Window.show_option>` |
77+
| `Window.show_window_options()` | {meth}`Window.show_options() <libtmux.Window.show_options>` |
78+
79+
**Before (deprecated):**
80+
81+
```python
82+
window.set_window_option('automatic-rename', 'on')
83+
window.show_window_option('automatic-rename')
84+
window.show_window_options()
85+
```
86+
87+
**After (0.50.0+):**
88+
89+
```python
90+
window.set_option('automatic-rename', True)
91+
window.show_option('automatic-rename')
92+
window.show_options()
93+
```
94+
95+
### Deprecated `g` parameter
96+
97+
The `g` parameter for global options is deprecated in favor of `global_`:
98+
99+
**Before (deprecated):**
100+
101+
```python
102+
session.show_option('status', g=True)
103+
session.set_option('status', 'off', g=True)
104+
```
105+
106+
**After (0.50.0+):**
107+
108+
```python
109+
session.show_option('status', global_=True)
110+
session.set_option('status', 'off', global_=True)
111+
```
112+
113+
Using the old `g` parameter will emit a {class}`DeprecationWarning`.
114+
28115
## libtmux 0.46.0 (2025-02-25)
29116

30117
#### Imports removed from libtmux.test (#580)

docs/api/hooks.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Hooks
2+
3+
```{eval-rst}
4+
.. automodule:: libtmux.hooks
5+
:members:
6+
```

docs/api/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ servers
1111
sessions
1212
windows
1313
panes
14+
options
15+
hooks
1416
constants
1517
common
1618
exceptions

docs/api/options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Options
2+
3+
```{eval-rst}
4+
.. automodule:: libtmux.options
5+
:members:
6+
```

docs/internals/constants.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Internal Constants - `libtmux._internal.constants`
2+
3+
:::{warning}
4+
Be careful with these! These constants are private, internal as they're **not** covered by version policies. They can break or be removed between minor versions!
5+
6+
If you need a data structure here made public or stabilized please [file an issue](https://github.com/tmux-python/libtmux/issues).
7+
:::
8+
9+
```{eval-rst}
10+
.. automodule:: libtmux._internal.constants
11+
:members:
12+
:undoc-members:
13+
:inherited-members:
14+
:show-inheritance:
15+
```

docs/internals/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ If you need an internal API stabilized please [file an issue](https://github.com
1111
```{toctree}
1212
dataclasses
1313
query_list
14+
constants
15+
sparse_array
1416
```
1517

1618
## Environmental variables

docs/internals/sparse_array.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Internal Sparse Array - `libtmux._internal.sparse_array`
2+
3+
:::{warning}
4+
Be careful with these! Internal APIs are **not** covered by version policies. They can break or be removed between minor versions!
5+
6+
If you need an internal API stabilized please [file an issue](https://github.com/tmux-python/libtmux/issues).
7+
:::
8+
9+
```{eval-rst}
10+
.. automodule:: libtmux._internal.sparse_array
11+
:members:
12+
:undoc-members:
13+
:show-inheritance:
14+
```

docs/quickstart.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,38 @@ automatically sent, the leading space character prevents adding it to the user's
441441
shell history. Omitting `enter=false` means the default behavior (sending the
442442
command) is done, without needing to use `pane.enter()` after.
443443

444+
## Working with options
445+
446+
libtmux provides a unified API for managing tmux options across Server, Session,
447+
Window, and Pane objects.
448+
449+
### Getting options
450+
451+
```python
452+
>>> server.show_option('buffer-limit')
453+
50
454+
455+
>>> window.show_options() # doctest: +ELLIPSIS
456+
{...}
457+
```
458+
459+
### Setting options
460+
461+
```python
462+
>>> window.set_option('automatic-rename', False) # doctest: +ELLIPSIS
463+
Window(@... ...)
464+
465+
>>> window.show_option('automatic-rename')
466+
False
467+
468+
>>> window.unset_option('automatic-rename') # doctest: +ELLIPSIS
469+
Window(@... ...)
470+
```
471+
472+
:::{seealso}
473+
See {ref}`options-and-hooks` for more details on options and hooks.
474+
:::
475+
444476
## Final notes
445477

446478
These objects created use tmux's internal usage of ID's to make servers,

docs/topics/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ Explore libtmux’s core functionalities and underlying principles at a high lev
1010
1111
context_managers
1212
traversal
13+
options_and_hooks
1314
```

0 commit comments

Comments
 (0)