Skip to content

Commit

Permalink
feat,fix!: Resize, refresh improvements (#523)
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
tony authored Feb 15, 2024
2 parents e67c5a9 + a5980cf commit 48ea20b
Show file tree
Hide file tree
Showing 16 changed files with 525 additions and 53 deletions.
47 changes: 47 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,57 @@ $ pip install --user --upgrade --pre libtmux

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

### Breaking changes

#### `Session.new_window()` + `Window.split_window()`: No longer attaches by default

- 0.28 +: Now _defaults_ to `attach=False`.
- 0.27.1 and before: _defaults_ to `attach=True`.

Pass `attach=True` for the old behavior.

#### `Pane.resize_pane()` renamed to `Pane.resize()`: (#523)

This convention will be more consistent with `Window.resize()`.

#### `Pane.resize_pane()`: Params changed (#523)

- No longer accepts `-U`, `-D`, `-L`, `-R` directly, instead accepts
`ResizeAdjustmentDirection`.

### New features

#### `Pane.resize()`: Improved param coverage (#523)

- Learned to accept adjustments via `adjustment_direction` w/
`ResizeAdjustmentDirection` + `adjustment`.

- Learned to accept manual `height` and / or `width` (columns/rows or percentage)

- Zoom (and unzoom)

#### `Window.resize_window()`: New Method (#523)

If `Pane.resize_pane()` (now `Pane.resize()`) didn't work before, try resizing the window.

### Bug fixes

#### `Window.refresh()` and `Pane.refresh()`: Refresh more underlying state (#523)

#### `Obj._refresh`: Allow passing args (#523)

e.g. `-a` (all) to `list-panes` and `list-windows`

#### `Server.panes`: Fix listing of panes (#523)

Would list only panes in attached session, rather than all in a server.

### Improvement

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

### Tests

Expand Down
6 changes: 6 additions & 0 deletions docs/reference/constants.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Constants

```{eval-rst}
.. automodule:: libtmux.constants
:members:
```
1 change: 1 addition & 0 deletions docs/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ servers
sessions
windows
panes
constants
common
exceptions
```
2 changes: 1 addition & 1 deletion src/libtmux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# flake8: NOQA
"""libtmux, a typed, pythonic API wrapper for the tmux terminal multiplexer."""
from .__about__ import (
__author__,
__copyright__,
Expand Down
1 change: 0 additions & 1 deletion src/libtmux/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# flake8: NOQA: W605
"""Helper methods and mixins for libtmux.
libtmux.common
Expand Down
20 changes: 20 additions & 0 deletions src/libtmux/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Constant variables for libtmux."""
import enum
import typing as t


class ResizeAdjustmentDirection(enum.Enum):
"""Used for *adjustment* in ``resize_window``, ``resize_pane``."""

Up = "UP"
Down = "DOWN"
Left = "LEFT"
Right = "RIGHT"


RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP: t.Dict[ResizeAdjustmentDirection, str] = {
ResizeAdjustmentDirection.Up: "-U",
ResizeAdjustmentDirection.Down: "-D",
ResizeAdjustmentDirection.Left: "-L",
ResizeAdjustmentDirection.Right: "-R",
}
30 changes: 30 additions & 0 deletions src/libtmux/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,33 @@ class NoWindowsExist(WindowError):

def __init__(self, *args: object):
return super().__init__("No windows exist for object")


class AdjustmentDirectionRequiresAdjustment(LibTmuxException, ValueError):
"""If *adjustment_direction* is set, *adjustment* must be set."""

def __init__(self) -> None:
super().__init__("adjustment_direction requires adjustment")


class WindowAdjustmentDirectionRequiresAdjustment(
WindowError, AdjustmentDirectionRequiresAdjustment
):
"""ValueError for :meth:`libtmux.Window.resize_window`."""

pass


class PaneAdjustmentDirectionRequiresAdjustment(
WindowError, AdjustmentDirectionRequiresAdjustment
):
"""ValueError for :meth:`libtmux.Pane.resize_pane`."""

pass


class RequiresDigitOrPercentage(LibTmuxException, ValueError):
"""Requires digit (int or str digit) or a percentage."""

def __init__(self) -> None:
super().__init__("Requires digit (int or str digit) or a percentage.")
2 changes: 2 additions & 0 deletions src/libtmux/neo.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,14 @@ def _refresh(
obj_key: str,
obj_id: str,
list_cmd: "ListCmd" = "list-panes",
list_extra_args: "t.Optional[ListExtraArgs]" = None,
) -> None:
assert isinstance(obj_id, str)
obj = fetch_obj(
obj_key=obj_key,
obj_id=obj_id,
list_cmd=list_cmd,
list_extra_args=list_extra_args,
server=self.server,
)
assert obj is not None
Expand Down
146 changes: 128 additions & 18 deletions src/libtmux/pane.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# flake8: NOQA: W605
"""Pythonization of the :ref:`tmux(1)` pane.
libtmux.pane
Expand All @@ -11,7 +10,11 @@
import warnings
from typing import overload

from libtmux.common import tmux_cmd
from libtmux.common import has_gte_version, tmux_cmd
from libtmux.constants import (
RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP,
ResizeAdjustmentDirection,
)
from libtmux.neo import Obj, fetch_obj

from . import exc
Expand Down Expand Up @@ -71,7 +74,11 @@ class Pane(Obj):
def refresh(self) -> None:
"""Refresh pane attributes from tmux."""
assert isinstance(self.pane_id, str)
return super()._refresh(obj_key="pane_id", obj_id=self.pane_id)
return super()._refresh(
obj_key="pane_id",
obj_id=self.pane_id,
list_extra_args=("-a",),
)

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

def resize_pane(self, *args: t.Any, **kwargs: t.Any) -> "Pane":
def resize(
self,
# Adjustments
adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None,
adjustment: t.Optional[int] = None,
# Manual
height: t.Optional[t.Union[str, int]] = None,
width: t.Optional[t.Union[str, int]] = None,
# Zoom
zoom: t.Optional[bool] = None,
# Mouse
mouse: t.Optional[bool] = None,
# Optional flags
trim_below: t.Optional[bool] = None,
) -> "Pane":
"""Resize tmux pane.
Parameters
----------
target_pane : str
``target_pane``, or ``-U``,``-D``, ``-L``, ``-R``.
adjustment_direction : ResizeAdjustmentDirection, optional
direction to adjust, ``Up``, ``Down``, ``Left``, ``Right``.
adjustment : ResizeAdjustmentDirection, optional
Other Parameters
----------------
height : int
height : int, optional
``resize-pane -y`` dimensions
width : int
width : int, optional
``resize-pane -x`` dimensions
zoom : bool
expand pane
mouse : bool
resize via mouse
trim_below : bool
trim below cursor
Raises
------
exc.LibTmuxException
:exc:`exc.LibTmuxException`,
:exc:`exc.PaneAdjustmentDirectionRequiresAdjustment`,
:exc:`exc.RequiresDigitOrPercentage`
Returns
-------
:class:`Pane`
Notes
-----
Three types of resizing are available:
1. Adjustments: ``adjustment_direction`` and ``adjustment``.
2. Manual resizing: ``height`` and / or ``width``.
3. Zoom / Unzoom: ``zoom``.
"""
if "height" in kwargs:
proc = self.cmd("resize-pane", "-y%s" % int(kwargs["height"]))
elif "width" in kwargs:
proc = self.cmd("resize-pane", "-x%s" % int(kwargs["width"]))
else:
proc = self.cmd("resize-pane", args[0])
tmux_args: t.Tuple[str, ...] = ()

## Adjustments
if adjustment_direction:
if adjustment is None:
raise exc.PaneAdjustmentDirectionRequiresAdjustment()
tmux_args += (
f"{RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP[adjustment_direction]}",
str(adjustment),
)
elif height or width:
## Manual resizing
if height:
if isinstance(height, str):
if height.endswith("%") and not has_gte_version("3.1"):
raise exc.VersionTooLow
if not height.isdigit() and not height.endswith("%"):
raise exc.RequiresDigitOrPercentage
tmux_args += (f"-y{height}",)

if width:
if isinstance(width, str):
if width.endswith("%") and not has_gte_version("3.1"):
raise exc.VersionTooLow
if not width.isdigit() and not width.endswith("%"):
raise exc.RequiresDigitOrPercentage

tmux_args += (f"-x{width}",)
elif zoom:
## Zoom / Unzoom
tmux_args += ("-Z",)
elif mouse:
tmux_args += ("-M",)

if trim_below:
tmux_args += ("-T",)

proc = self.cmd("resize-pane", *tmux_args)

if proc.stderr:
raise exc.LibTmuxException(proc.stderr)
Expand Down Expand Up @@ -442,7 +517,7 @@ def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any:
.. deprecated:: 0.16
Deprecated by attribute lookup.e.g. ``pane['window_name']`` is now
Deprecated by attribute lookup, e.g. ``pane['window_name']`` is now
accessed via ``pane.window_name``.
"""
Expand All @@ -460,3 +535,38 @@ def __getitem__(self, key: str) -> t.Any:
"""
warnings.warn(f"Item lookups, e.g. pane['{key}'] is deprecated", stacklevel=2)
return getattr(self, key)

def resize_pane(
self,
# Adjustments
adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None,
adjustment: t.Optional[int] = None,
# Manual
height: t.Optional[t.Union[str, int]] = None,
width: t.Optional[t.Union[str, int]] = None,
# Zoom
zoom: t.Optional[bool] = None,
# Mouse
mouse: t.Optional[bool] = None,
# Optional flags
trim_below: t.Optional[bool] = None,
) -> "Pane":
"""Resize pane, deprecated by :meth:`Pane.resize`.
.. deprecated:: 0.28
Deprecated by :meth:`Pane.resize`.
"""
warnings.warn(
"Deprecated: Use Pane.resize() instead of Pane.resize_pane()",
stacklevel=2,
)
return self.resize(
adjustment_direction=adjustment_direction,
adjustment=adjustment,
height=height,
width=width,
zoom=zoom,
mouse=mouse,
trim_below=trim_below,
)
9 changes: 7 additions & 2 deletions src/libtmux/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,9 @@ def session(
session_name = "tmuxp"

if not server.has_session(session_name):
server.cmd("new-session", "-d", "-s", session_name)
server.new_session(
session_name=session_name,
)

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

TEST_SESSION_NAME = get_test_session_name(server=server)

session = server.new_session(session_name=TEST_SESSION_NAME, **session_params)
session = server.new_session(
session_name=TEST_SESSION_NAME,
**session_params,
)

"""
Make sure that tmuxp can :ref:`test_builder_visually` and switches to
Expand Down
2 changes: 1 addition & 1 deletion src/libtmux/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ def panes(self) -> QueryList[Pane]:
Pane(server=self, **obj)
for obj in fetch_objs(
list_cmd="list-panes",
list_extra_args=["-s"],
list_extra_args=("-a",),
server=self,
)
]
Expand Down
6 changes: 5 additions & 1 deletion src/libtmux/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ def new_window(
self,
window_name: t.Optional[str] = None,
start_directory: None = None,
attach: bool = True,
attach: bool = False,
window_index: str = "",
window_shell: t.Optional[str] = None,
environment: t.Optional[t.Dict[str, str]] = None,
Expand Down Expand Up @@ -465,6 +465,10 @@ def new_window(
useful for long-running processes where the closing of the
window upon completion is desired.
.. versionchanged:: 0.28.0
``attach`` default changed from ``True`` to ``False``.
Returns
-------
:class:`Window`
Expand Down
Loading

0 comments on commit 48ea20b

Please sign in to comment.