Skip to content

Commit 48ea20b

Browse files
authored
feat,fix!: Resize, refresh improvements (#523)
Resolves #468. # Changes ## Breaking changes - `Session.new_window()` + `Window.split_window()`: No longer `attach` by default. Pass `attach=True` for the old behavior. - `Pane.resize_pane` renamed to `Pane.resize()` ## `Window.resize_window()`: Added If `Pane.resize_pane()` didn't work before, try resizing the window. ## `Pane.resize()`: Arguments added ## `Server.panes`: Fix listing of panes Would list only panes in attached session, rather than all in a server. ## `Window.refresh()` and `Pane.refresh()`: Refresh more underlying state ## `Obj._refresh`: Allow passing args e.g. `-a` (all) to `list-panes` and `list-windows`
2 parents e67c5a9 + a5980cf commit 48ea20b

16 files changed

+525
-53
lines changed

CHANGES

+47
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,57 @@ $ pip install --user --upgrade --pre libtmux
1414

1515
<!-- Maintainers and contributors: Insert change notes for the next release above -->
1616

17+
### Breaking changes
18+
19+
#### `Session.new_window()` + `Window.split_window()`: No longer attaches by default
20+
21+
- 0.28 +: Now _defaults_ to `attach=False`.
22+
- 0.27.1 and before: _defaults_ to `attach=True`.
23+
24+
Pass `attach=True` for the old behavior.
25+
26+
#### `Pane.resize_pane()` renamed to `Pane.resize()`: (#523)
27+
28+
This convention will be more consistent with `Window.resize()`.
29+
30+
#### `Pane.resize_pane()`: Params changed (#523)
31+
32+
- No longer accepts `-U`, `-D`, `-L`, `-R` directly, instead accepts
33+
`ResizeAdjustmentDirection`.
34+
35+
### New features
36+
37+
#### `Pane.resize()`: Improved param coverage (#523)
38+
39+
- Learned to accept adjustments via `adjustment_direction` w/
40+
`ResizeAdjustmentDirection` + `adjustment`.
41+
42+
- Learned to accept manual `height` and / or `width` (columns/rows or percentage)
43+
44+
- Zoom (and unzoom)
45+
46+
#### `Window.resize_window()`: New Method (#523)
47+
48+
If `Pane.resize_pane()` (now `Pane.resize()`) didn't work before, try resizing the window.
49+
50+
### Bug fixes
51+
52+
#### `Window.refresh()` and `Pane.refresh()`: Refresh more underlying state (#523)
53+
54+
#### `Obj._refresh`: Allow passing args (#523)
55+
56+
e.g. `-a` (all) to `list-panes` and `list-windows`
57+
58+
#### `Server.panes`: Fix listing of panes (#523)
59+
60+
Would list only panes in attached session, rather than all in a server.
61+
1762
### Improvement
1863

1964
- Pane, Window: Improve parsing of option values that return numbers
2065
(#520)
66+
- `Obj._refresh`: Allow passing `list_extra_args` to ensure `list-windows` and
67+
`list-panes` can return more than the target (#523)
2168

2269
### Tests
2370

docs/reference/constants.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Constants
2+
3+
```{eval-rst}
4+
.. automodule:: libtmux.constants
5+
:members:
6+
```

docs/reference/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ servers
1111
sessions
1212
windows
1313
panes
14+
constants
1415
common
1516
exceptions
1617
```

src/libtmux/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# flake8: NOQA
1+
"""libtmux, a typed, pythonic API wrapper for the tmux terminal multiplexer."""
22
from .__about__ import (
33
__author__,
44
__copyright__,

src/libtmux/common.py

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# flake8: NOQA: W605
21
"""Helper methods and mixins for libtmux.
32
43
libtmux.common

src/libtmux/constants.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Constant variables for libtmux."""
2+
import enum
3+
import typing as t
4+
5+
6+
class ResizeAdjustmentDirection(enum.Enum):
7+
"""Used for *adjustment* in ``resize_window``, ``resize_pane``."""
8+
9+
Up = "UP"
10+
Down = "DOWN"
11+
Left = "LEFT"
12+
Right = "RIGHT"
13+
14+
15+
RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP: t.Dict[ResizeAdjustmentDirection, str] = {
16+
ResizeAdjustmentDirection.Up: "-U",
17+
ResizeAdjustmentDirection.Down: "-D",
18+
ResizeAdjustmentDirection.Left: "-L",
19+
ResizeAdjustmentDirection.Right: "-R",
20+
}

src/libtmux/exc.py

+30
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,33 @@ class NoWindowsExist(WindowError):
132132

133133
def __init__(self, *args: object):
134134
return super().__init__("No windows exist for object")
135+
136+
137+
class AdjustmentDirectionRequiresAdjustment(LibTmuxException, ValueError):
138+
"""If *adjustment_direction* is set, *adjustment* must be set."""
139+
140+
def __init__(self) -> None:
141+
super().__init__("adjustment_direction requires adjustment")
142+
143+
144+
class WindowAdjustmentDirectionRequiresAdjustment(
145+
WindowError, AdjustmentDirectionRequiresAdjustment
146+
):
147+
"""ValueError for :meth:`libtmux.Window.resize_window`."""
148+
149+
pass
150+
151+
152+
class PaneAdjustmentDirectionRequiresAdjustment(
153+
WindowError, AdjustmentDirectionRequiresAdjustment
154+
):
155+
"""ValueError for :meth:`libtmux.Pane.resize_pane`."""
156+
157+
pass
158+
159+
160+
class RequiresDigitOrPercentage(LibTmuxException, ValueError):
161+
"""Requires digit (int or str digit) or a percentage."""
162+
163+
def __init__(self) -> None:
164+
super().__init__("Requires digit (int or str digit) or a percentage.")

src/libtmux/neo.py

+2
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,14 @@ def _refresh(
167167
obj_key: str,
168168
obj_id: str,
169169
list_cmd: "ListCmd" = "list-panes",
170+
list_extra_args: "t.Optional[ListExtraArgs]" = None,
170171
) -> None:
171172
assert isinstance(obj_id, str)
172173
obj = fetch_obj(
173174
obj_key=obj_key,
174175
obj_id=obj_id,
175176
list_cmd=list_cmd,
177+
list_extra_args=list_extra_args,
176178
server=self.server,
177179
)
178180
assert obj is not None

src/libtmux/pane.py

+128-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# flake8: NOQA: W605
21
"""Pythonization of the :ref:`tmux(1)` pane.
32
43
libtmux.pane
@@ -11,7 +10,11 @@
1110
import warnings
1211
from typing import overload
1312

14-
from libtmux.common import tmux_cmd
13+
from libtmux.common import has_gte_version, tmux_cmd
14+
from libtmux.constants import (
15+
RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP,
16+
ResizeAdjustmentDirection,
17+
)
1518
from libtmux.neo import Obj, fetch_obj
1619

1720
from . import exc
@@ -71,7 +74,11 @@ class Pane(Obj):
7174
def refresh(self) -> None:
7275
"""Refresh pane attributes from tmux."""
7376
assert isinstance(self.pane_id, str)
74-
return super()._refresh(obj_key="pane_id", obj_id=self.pane_id)
77+
return super()._refresh(
78+
obj_key="pane_id",
79+
obj_id=self.pane_id,
80+
list_extra_args=("-a",),
81+
)
7582

7683
@classmethod
7784
def from_pane_id(cls, server: "Server", pane_id: str) -> "Pane":
@@ -122,31 +129,99 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd:
122129
Commands (tmux-like)
123130
"""
124131

125-
def resize_pane(self, *args: t.Any, **kwargs: t.Any) -> "Pane":
132+
def resize(
133+
self,
134+
# Adjustments
135+
adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None,
136+
adjustment: t.Optional[int] = None,
137+
# Manual
138+
height: t.Optional[t.Union[str, int]] = None,
139+
width: t.Optional[t.Union[str, int]] = None,
140+
# Zoom
141+
zoom: t.Optional[bool] = None,
142+
# Mouse
143+
mouse: t.Optional[bool] = None,
144+
# Optional flags
145+
trim_below: t.Optional[bool] = None,
146+
) -> "Pane":
126147
"""Resize tmux pane.
127148
128149
Parameters
129150
----------
130-
target_pane : str
131-
``target_pane``, or ``-U``,``-D``, ``-L``, ``-R``.
151+
adjustment_direction : ResizeAdjustmentDirection, optional
152+
direction to adjust, ``Up``, ``Down``, ``Left``, ``Right``.
153+
adjustment : ResizeAdjustmentDirection, optional
132154
133-
Other Parameters
134-
----------------
135-
height : int
155+
height : int, optional
136156
``resize-pane -y`` dimensions
137-
width : int
157+
width : int, optional
138158
``resize-pane -x`` dimensions
139159
160+
zoom : bool
161+
expand pane
162+
163+
mouse : bool
164+
resize via mouse
165+
166+
trim_below : bool
167+
trim below cursor
168+
140169
Raises
141170
------
142-
exc.LibTmuxException
171+
:exc:`exc.LibTmuxException`,
172+
:exc:`exc.PaneAdjustmentDirectionRequiresAdjustment`,
173+
:exc:`exc.RequiresDigitOrPercentage`
174+
175+
Returns
176+
-------
177+
:class:`Pane`
178+
179+
Notes
180+
-----
181+
Three types of resizing are available:
182+
183+
1. Adjustments: ``adjustment_direction`` and ``adjustment``.
184+
2. Manual resizing: ``height`` and / or ``width``.
185+
3. Zoom / Unzoom: ``zoom``.
143186
"""
144-
if "height" in kwargs:
145-
proc = self.cmd("resize-pane", "-y%s" % int(kwargs["height"]))
146-
elif "width" in kwargs:
147-
proc = self.cmd("resize-pane", "-x%s" % int(kwargs["width"]))
148-
else:
149-
proc = self.cmd("resize-pane", args[0])
187+
tmux_args: t.Tuple[str, ...] = ()
188+
189+
## Adjustments
190+
if adjustment_direction:
191+
if adjustment is None:
192+
raise exc.PaneAdjustmentDirectionRequiresAdjustment()
193+
tmux_args += (
194+
f"{RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP[adjustment_direction]}",
195+
str(adjustment),
196+
)
197+
elif height or width:
198+
## Manual resizing
199+
if height:
200+
if isinstance(height, str):
201+
if height.endswith("%") and not has_gte_version("3.1"):
202+
raise exc.VersionTooLow
203+
if not height.isdigit() and not height.endswith("%"):
204+
raise exc.RequiresDigitOrPercentage
205+
tmux_args += (f"-y{height}",)
206+
207+
if width:
208+
if isinstance(width, str):
209+
if width.endswith("%") and not has_gte_version("3.1"):
210+
raise exc.VersionTooLow
211+
if not width.isdigit() and not width.endswith("%"):
212+
raise exc.RequiresDigitOrPercentage
213+
214+
tmux_args += (f"-x{width}",)
215+
elif zoom:
216+
## Zoom / Unzoom
217+
tmux_args += ("-Z",)
218+
elif mouse:
219+
tmux_args += ("-M",)
220+
221+
if trim_below:
222+
tmux_args += ("-T",)
223+
224+
proc = self.cmd("resize-pane", *tmux_args)
150225

151226
if proc.stderr:
152227
raise exc.LibTmuxException(proc.stderr)
@@ -442,7 +517,7 @@ def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any:
442517
443518
.. deprecated:: 0.16
444519
445-
Deprecated by attribute lookup.e.g. ``pane['window_name']`` is now
520+
Deprecated by attribute lookup, e.g. ``pane['window_name']`` is now
446521
accessed via ``pane.window_name``.
447522
448523
"""
@@ -460,3 +535,38 @@ def __getitem__(self, key: str) -> t.Any:
460535
"""
461536
warnings.warn(f"Item lookups, e.g. pane['{key}'] is deprecated", stacklevel=2)
462537
return getattr(self, key)
538+
539+
def resize_pane(
540+
self,
541+
# Adjustments
542+
adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None,
543+
adjustment: t.Optional[int] = None,
544+
# Manual
545+
height: t.Optional[t.Union[str, int]] = None,
546+
width: t.Optional[t.Union[str, int]] = None,
547+
# Zoom
548+
zoom: t.Optional[bool] = None,
549+
# Mouse
550+
mouse: t.Optional[bool] = None,
551+
# Optional flags
552+
trim_below: t.Optional[bool] = None,
553+
) -> "Pane":
554+
"""Resize pane, deprecated by :meth:`Pane.resize`.
555+
556+
.. deprecated:: 0.28
557+
558+
Deprecated by :meth:`Pane.resize`.
559+
"""
560+
warnings.warn(
561+
"Deprecated: Use Pane.resize() instead of Pane.resize_pane()",
562+
stacklevel=2,
563+
)
564+
return self.resize(
565+
adjustment_direction=adjustment_direction,
566+
adjustment=adjustment,
567+
height=height,
568+
width=width,
569+
zoom=zoom,
570+
mouse=mouse,
571+
trim_below=trim_below,
572+
)

src/libtmux/pytest_plugin.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ def session(
217217
session_name = "tmuxp"
218218

219219
if not server.has_session(session_name):
220-
server.cmd("new-session", "-d", "-s", session_name)
220+
server.new_session(
221+
session_name=session_name,
222+
)
221223

222224
# find current sessions prefixed with tmuxp
223225
old_test_sessions = []
@@ -228,7 +230,10 @@ def session(
228230

229231
TEST_SESSION_NAME = get_test_session_name(server=server)
230232

231-
session = server.new_session(session_name=TEST_SESSION_NAME, **session_params)
233+
session = server.new_session(
234+
session_name=TEST_SESSION_NAME,
235+
**session_params,
236+
)
232237

233238
"""
234239
Make sure that tmuxp can :ref:`test_builder_visually` and switches to

src/libtmux/server.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ def panes(self) -> QueryList[Pane]:
542542
Pane(server=self, **obj)
543543
for obj in fetch_objs(
544544
list_cmd="list-panes",
545-
list_extra_args=["-s"],
545+
list_extra_args=("-a",),
546546
server=self,
547547
)
548548
]

src/libtmux/session.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def new_window(
436436
self,
437437
window_name: t.Optional[str] = None,
438438
start_directory: None = None,
439-
attach: bool = True,
439+
attach: bool = False,
440440
window_index: str = "",
441441
window_shell: t.Optional[str] = None,
442442
environment: t.Optional[t.Dict[str, str]] = None,
@@ -465,6 +465,10 @@ def new_window(
465465
useful for long-running processes where the closing of the
466466
window upon completion is desired.
467467
468+
.. versionchanged:: 0.28.0
469+
470+
``attach`` default changed from ``True`` to ``False``.
471+
468472
Returns
469473
-------
470474
:class:`Window`

0 commit comments

Comments
 (0)