diff --git a/CHANGES b/CHANGES index da82a88aa..c9a99b0c3 100644 --- a/CHANGES +++ b/CHANGES @@ -14,10 +14,57 @@ $ pip install --user --upgrade --pre libtmux +### 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 diff --git a/docs/reference/constants.md b/docs/reference/constants.md new file mode 100644 index 000000000..67d56ac91 --- /dev/null +++ b/docs/reference/constants.md @@ -0,0 +1,6 @@ +# Constants + +```{eval-rst} +.. automodule:: libtmux.constants + :members: +``` diff --git a/docs/reference/index.md b/docs/reference/index.md index 2a6c9ff63..99d614fee 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -11,6 +11,7 @@ servers sessions windows panes +constants common exceptions ``` diff --git a/src/libtmux/__init__.py b/src/libtmux/__init__.py index 4c2847c93..7dd7284f6 100644 --- a/src/libtmux/__init__.py +++ b/src/libtmux/__init__.py @@ -1,4 +1,4 @@ -# flake8: NOQA +"""libtmux, a typed, pythonic API wrapper for the tmux terminal multiplexer.""" from .__about__ import ( __author__, __copyright__, diff --git a/src/libtmux/common.py b/src/libtmux/common.py index c8bb6ccc4..46ad57d4a 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -1,4 +1,3 @@ -# flake8: NOQA: W605 """Helper methods and mixins for libtmux. libtmux.common diff --git a/src/libtmux/constants.py b/src/libtmux/constants.py new file mode 100644 index 000000000..6411ebb6b --- /dev/null +++ b/src/libtmux/constants.py @@ -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", +} diff --git a/src/libtmux/exc.py b/src/libtmux/exc.py index c77604599..cf31575f7 100644 --- a/src/libtmux/exc.py +++ b/src/libtmux/exc.py @@ -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.") diff --git a/src/libtmux/neo.py b/src/libtmux/neo.py index 926da8254..af8a0062b 100644 --- a/src/libtmux/neo.py +++ b/src/libtmux/neo.py @@ -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 diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index c9b5b7cc2..a2eeea33b 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -1,4 +1,3 @@ -# flake8: NOQA: W605 """Pythonization of the :ref:`tmux(1)` pane. libtmux.pane @@ -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 @@ -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": @@ -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) @@ -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``. """ @@ -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, + ) diff --git a/src/libtmux/pytest_plugin.py b/src/libtmux/pytest_plugin.py index 7cd44c91c..2819b33e5 100644 --- a/src/libtmux/pytest_plugin.py +++ b/src/libtmux/pytest_plugin.py @@ -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 = [] @@ -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 diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 8e12041a4..ec9ef384d 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -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, ) ] diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 9ea495779..6e2aa059b 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -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, @@ -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` diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 98401f0bd..b5b6bbcbe 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -13,6 +13,10 @@ from libtmux._internal.query_list import QueryList 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, fetch_objs from libtmux.pane import Pane @@ -39,7 +43,7 @@ class Window(Obj): Examples -------- - >>> window = session.new_window('My project') + >>> window = session.new_window('My project', attach=True) >>> window Window(@2 2:My project, Session($... ...)) @@ -85,6 +89,7 @@ def refresh(self) -> None: obj_key="window_id", obj_id=self.window_id, list_cmd="list-windows", + list_extra_args=("-a",), ) @classmethod @@ -158,8 +163,8 @@ def select_pane(self, target_pane: t.Union[str, int]) -> t.Optional["Pane"]: target_pane : str 'target_pane', '-U' ,'-D', '-L', '-R', or '-l'. - Return - ------ + Returns + ------- :class:`Pane` """ if target_pane in ["-l", "-U", "-D", "-L", "-R"]: @@ -176,7 +181,7 @@ def split_window( self, target: t.Optional[t.Union[int, str]] = None, start_directory: t.Optional[str] = None, - attach: bool = True, + attach: bool = False, vertical: bool = True, shell: t.Optional[str] = None, percent: t.Optional[int] = None, @@ -219,6 +224,10 @@ def split_window( By default, this will make the window the pane is created in active. To remain on the same window and split the pane in another target window, pass in ``attach=False``. + + .. versionchanged:: 0.28.0 + + ``attach`` default changed from ``True`` to ``False``. """ tmux_formats = ["#{pane_id}" + FORMAT_SEPARATOR] @@ -279,6 +288,87 @@ def split_window( return Pane.from_pane_id(server=self.server, pane_id=pane_formatters["pane_id"]) + def resize( + self, + # Adjustments + adjustment_direction: t.Optional[ResizeAdjustmentDirection] = None, + adjustment: t.Optional[int] = None, + # Manual + height: t.Optional[int] = None, + width: t.Optional[int] = None, + # Expand / Shrink + expand: t.Optional[bool] = None, + shrink: t.Optional[bool] = None, + ) -> "Window": + """Resize tmux window. + + Parameters + ---------- + adjustment_direction : ResizeAdjustmentDirection, optional + direction to adjust, ``Up``, ``Down``, ``Left``, ``Right``. + adjustment : ResizeAdjustmentDirection, optional + + height : int, optional + ``resize-window -y`` dimensions + width : int, optional + ``resize-window -x`` dimensions + + expand : bool + expand window + shrink : bool + shrink window + + Raises + ------ + :exc:`exc.LibTmuxException`, + :exc:`exc.PaneAdjustmentDirectionRequiresAdjustment` + + Returns + ------- + :class:`Window` + + Notes + ----- + Three types of resizing are available: + + 1. Adjustments: ``adjustment_direction`` and ``adjustment``. + 2. Manual resizing: ``height`` and / or ``width``. + 3. Expand or shrink: ``expand`` or ``shrink``. + """ + if not has_gte_version("2.9"): + warnings.warn("resize() requires tmux 2.9 or newer", stacklevel=2) + return self + + tmux_args: t.Tuple[str, ...] = () + + ## Adjustments + if adjustment_direction: + if adjustment is None: + raise exc.WindowAdjustmentDirectionRequiresAdjustment() + tmux_args += ( + f"{RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP[adjustment_direction]}", + str(adjustment), + ) + elif height or width: + ## Manual resizing + if height: + tmux_args += (f"-y{int(height)}",) + if width: + tmux_args += (f"-x{int(width)}",) + elif expand or shrink: + if expand: + tmux_args += ("-A",) + elif shrink: + tmux_args += ("-a",) + + proc = self.cmd("resize-window", *tmux_args) + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + + self.refresh() + return self + def last_pane(self) -> t.Optional["Pane"]: """Return last pane.""" return self.select_pane("-l") diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index a8dcdf517..65f80faf4 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -9,6 +9,7 @@ ObjectDoesNotExist, QueryList, ) +from libtmux.constants import ResizeAdjustmentDirection from libtmux.pane import Pane from libtmux.server import Server from libtmux.session import Session @@ -40,11 +41,9 @@ def test_pane( __window = __session.attached_window __window.split_window() - __window.split_window() + __pane = __window.split_window() __window.select_layout("main-vertical") - __pane = __window.attached_pane - assert __pane is not None assert __pane.pane_id is not None @@ -61,8 +60,10 @@ def test_pane( old_pane_size = pane.pane_height - pane.resize_pane("-D", 25) - pane.resize_pane("-R", 25) + pane.resize_pane(adjustment_direction=ResizeAdjustmentDirection.Down, adjustment=25) + pane.resize_pane( + adjustment_direction=ResizeAdjustmentDirection.Right, adjustment=25 + ) assert old_pane_size != pane.pane_height assert pane.pane_current_command is not None diff --git a/tests/test_pane.py b/tests/test_pane.py index 3ca421c69..f05528a9e 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -2,31 +2,15 @@ import logging import shutil +import pytest + +from libtmux.common import has_gte_version, has_lt_version +from libtmux.constants import ResizeAdjustmentDirection from libtmux.session import Session logger = logging.getLogger(__name__) -def test_resize_pane(session: Session) -> None: - """Test Pane.resize_pane().""" - window = session.attached_window - window.rename_window("test_resize_pane") - - pane1 = window.attached_pane - assert pane1 is not None - pane1_height = pane1.pane_height - window.split_window() - - pane1.resize_pane(height=4) - assert pane1.pane_height != pane1_height - assert pane1.pane_height is not None - assert int(pane1.pane_height) == 4 - - pane1.resize_pane(height=3) - assert pane1.pane_height is not None - assert int(pane1.pane_height) == 3 - - def test_send_keys(session: Session) -> None: """Verify Pane.send_keys().""" pane = session.attached_window.attached_pane @@ -146,3 +130,95 @@ def test_capture_pane_end(session: Session) -> None: assert pane_contents == '$ printf "%s"' pane_contents = "\n".join(pane.capture_pane(end="-")) assert pane_contents == '$ printf "%s"\n$' + + +@pytest.mark.skipif( + has_lt_version("2.9"), + reason="resize-window only exists in tmux 2.9+", +) +def test_resize_pane( + session: Session, +) -> None: + """Verify resizing window.""" + session.cmd("detach-client", "-s") + + window = session.attached_window + pane = window.split_window(attach=False) + window.split_window(vertical=True, attach=False) + + assert pane is not None + + window.resize(height=500, width=500) + + pane_height_adjustment = 10 + + assert pane.pane_height is not None + assert pane.pane_width is not None + + # + # Manual resizing + # + + # Manual: Height + pane_height_before = int(pane.pane_height) + pane.resize_pane( + height="50", + ) + assert int(pane.pane_height) == 50 + + # Manual: Width + window.select_layout("main-horizontal") + pane.resize_pane( + width="75", + ) + assert int(pane.pane_width) == 75 + + if has_gte_version("3.1"): + # Manual: Height percentage + window.select_layout("main-vertical") + pane_height_before = int(pane.pane_height) + pane.resize_pane( + height="15%", + ) + assert int(pane.pane_height) == 75 + + # Manual: Width percentage + window.select_layout("main-horizontal") + pane.resize_pane( + width="15%", + ) + assert int(pane.pane_width) == 75 + + # + # Adjustments + # + + # Adjustment: Down + pane_height_before = int(pane.pane_height) + pane.resize_pane( + adjustment_direction=ResizeAdjustmentDirection.Down, + adjustment=pane_height_adjustment * 2, + ) + assert pane_height_before - (pane_height_adjustment * 2) == int(pane.pane_height) + + # Adjustment: Up + pane_height_before = int(pane.pane_height) + pane.resize_pane( + adjustment_direction=ResizeAdjustmentDirection.Up, + adjustment=pane_height_adjustment, + ) + assert pane_height_before + pane_height_adjustment == int(pane.pane_height) + + # + # Zoom + # + pane.resize_pane(height=50) + + # Zoom + pane.resize_pane(height=2) + pane_height_before = int(pane.pane_height) + pane.resize_pane( + zoom=True, + ) + pane_height_expanded = int(pane.pane_height) + assert pane_height_before < pane_height_expanded diff --git a/tests/test_window.py b/tests/test_window.py index 47ec07205..4252f8469 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -9,6 +9,7 @@ from libtmux import exc from libtmux._internal.query_list import ObjectDoesNotExist from libtmux.common import has_gte_version, has_lt_version +from libtmux.constants import ResizeAdjustmentDirection from libtmux.pane import Pane from libtmux.server import Server from libtmux.session import Session @@ -397,3 +398,83 @@ def test_split_window_with_environment_logs_warning_for_old_tmux( assert any( "Cannot set up environment" in record.msg for record in caplog.records ), "Warning missing" + + +@pytest.mark.skipif( + has_lt_version("2.9"), + reason="resize-window only exists in tmux 2.9+", +) +def test_resize( + session: Session, +) -> None: + """Verify resizing window.""" + session.cmd("detach-client", "-s") + + window = session.attached_window + window_height_adjustment = 10 + + assert window.window_height is not None + assert window.window_width is not None + + # + # Manual resizing + # + + # Manual: Height + window_height_before = int(window.window_height) + window.resize( + height=10, + ) + assert int(window.window_height) == 10 + + # Manual: Width + window.resize( + width=10, + ) + assert int(window.window_width) == 10 + + # + # Adjustments + # + + # Adjustment: Down + window_height_before = int(window.window_height) + window.resize( + adjustment_direction=ResizeAdjustmentDirection.Down, + adjustment=window_height_adjustment * 2, + ) + assert window_height_before + (window_height_adjustment * 2) == int( + window.window_height + ) + + # Adjustment: Up + window_height_before = int(window.window_height) + window.resize( + adjustment_direction=ResizeAdjustmentDirection.Up, + adjustment=window_height_adjustment, + ) + assert window_height_before - window_height_adjustment == int(window.window_height) + + # + # Shrink and expand + # + window.resize(height=50) + + # Shrink + window_height_before = int(window.window_height) + window.resize( + shrink=True, + ) + window_height_shrunk = int(window.window_height) + assert window_height_before > window_height_shrunk + + assert window + + # Expand + window.resize(height=2) + window_height_before = int(window.window_height) + window.resize( + expand=True, + ) + window_height_expanded = int(window.window_height) + assert window_height_before < window_height_expanded