diff --git a/CHANGES b/CHANGES index 0176be6f7ca..3325409ecc9 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,34 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force - Add [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear) (#807) - Add [flake8-comprehensions](https://github.com/adamchainz/flake8-comprehensions) (#808) +## tmuxp 1.13.x (2022-09-10) + +### Bug fixes + +- Layout size has been bumped for those experiencing layout spacing issues + (#809, fixes #800) + + If you encounter issues with pane spacing, consider passing an `option` like + so: + + ```yaml + session_name: main-pane-height + start_directory: "~" + options: + default-size: 999x999 + windows: + - window_name: my window name + layout: main-horizontal + options: + main-pane-height: 30 + panes: + - shell_command: top + - shell_command: top + - shell_command: top + - shell_command: echo "hey" + - shell_command: echo "moo" + ``` + ## tmuxp 1.13.1 (2022-08-21) ### Bug fixes diff --git a/tests/fixtures/regressions/issue_800_default_size_many_windows.yaml b/tests/fixtures/regressions/issue_800_default_size_many_windows.yaml new file mode 100644 index 00000000000..e18607b3806 --- /dev/null +++ b/tests/fixtures/regressions/issue_800_default_size_many_windows.yaml @@ -0,0 +1,12 @@ +session_name: many-windows-issue +windows: +- window_name: moo + layout: main-horizontal + panes: + - echo hello + - echo hello + - echo hello + - echo hello + - echo hello + - echo hello + - echo hello diff --git a/tests/test_workspacebuilder.py b/tests/test_workspacebuilder.py index 048dd7e5c7b..d6c6c20dfbd 100644 --- a/tests/test_workspacebuilder.py +++ b/tests/test_workspacebuilder.py @@ -3,15 +3,16 @@ import pathlib import textwrap import time +import typing as t import pytest import kaptan import libtmux -from libtmux import Window from libtmux.common import has_gte_version, has_lt_version from libtmux.test import retry_until, temp_session +from libtmux.window import Window from tmuxp import config, exc from tmuxp.cli.load import load_plugins from tmuxp.workspacebuilder import WorkspaceBuilder @@ -19,6 +20,9 @@ from .constants import EXAMPLE_PATH, FIXTURE_PATH from .fixtures import utils as test_utils +if t.TYPE_CHECKING: + from libtmux.server import Server + def test_split_windows(session): yaml_config = test_utils.read_config_file("workspacebuilder/two_pane.yaml") @@ -1209,7 +1213,15 @@ def height(p): def width(p): return int(p._info["pane_width"]) - assert height(main_horizontal_pane) > height(panes[0]) + main_horizontal_pane_height = height(main_horizontal_pane) + pane_heights = [height(pane) for pane in panes] + # TODO: When libtmux has new pane formatters added, use that to detect top / bottom + assert all( + main_horizontal_pane_height != pane_height for pane_height in pane_heights + ), "The top row should not be the same size as the bottom row (even though it can)" + assert all( + pane_heights[0] == pane_height for pane_height in pane_heights + ), "The bottom row should be uniform height" assert width(main_horizontal_pane) > width(panes[0]) def is_almost_equal(x, y): @@ -1217,3 +1229,84 @@ def is_almost_equal(x, y): assert is_almost_equal(height(panes[0]), height(panes[1])) assert is_almost_equal(width(panes[0]), width(panes[1])) + + +class DefaultSizeNamespaceFixture(t.NamedTuple): + test_id: str + TMUXP_DEFAULT_SIZE: t.Optional[str] + raises: bool + confoverrides: t.Dict[str, t.Any] + + +DEFAULT_SIZE_FIXTURES = [ + DefaultSizeNamespaceFixture( + test_id="default-behavior", + TMUXP_DEFAULT_SIZE=None, + raises=False, + confoverrides={}, + ), + DefaultSizeNamespaceFixture( + test_id="v1.13.1 default-size-breaks", + TMUXP_DEFAULT_SIZE=None, + raises=True, + confoverrides={"options": {"default-size": "80x24"}}, + ), + DefaultSizeNamespaceFixture( + test_id="v1.13.1-option-workaround", + TMUXP_DEFAULT_SIZE=None, + raises=False, + confoverrides={"options": {"default-size": "800x600"}}, + ), +] + + +@pytest.mark.parametrize( + DefaultSizeNamespaceFixture._fields, + DEFAULT_SIZE_FIXTURES, + ids=[f.test_id for f in DEFAULT_SIZE_FIXTURES], +) +@pytest.mark.skipif(has_lt_version("2.9"), reason="default-size only applies there") +def test_issue_800_default_size_many_windows( + server: "Server", + monkeypatch: pytest.MonkeyPatch, + test_id: str, + TMUXP_DEFAULT_SIZE: t.Optional[str], + raises: bool, + confoverrides: t.Dict[str, t.Any], +) -> None: + """Recreate default-size issue. + + v1.13.1 added a default-size, but this can break building workspaces with + a lot of panes. + + See also: https://github.com/tmux-python/tmuxp/issues/800 + """ + yaml_config = test_utils.read_config_file( + "regressions/issue_800_default_size_many_windows.yaml" + ) + sconfig = kaptan.Kaptan(handler="yaml") + sconfig = sconfig.import_config(yaml_config).get() + sconfig = config.expand(sconfig) + sconfig = config.trickle(sconfig) + + if isinstance(confoverrides, dict): + for k, v in confoverrides.items(): + sconfig[k] = v + + if TMUXP_DEFAULT_SIZE is not None: + monkeypatch.setenv("TMUXP_DEFAULT_SIZE", TMUXP_DEFAULT_SIZE) + + builder = WorkspaceBuilder(sconf=sconfig, server=server) + + if raises: + with pytest.raises(Exception): + builder.build() + + builder.session.kill_session() + + with pytest.raises(libtmux.exc.LibTmuxException, match="no space for new pane"): + builder.build() + return + + builder.build() + assert len(server.list_sessions()) == 1 diff --git a/tmuxp/workspacebuilder.py b/tmuxp/workspacebuilder.py index 6b5cd23d051..e7465ccfe65 100644 --- a/tmuxp/workspacebuilder.py +++ b/tmuxp/workspacebuilder.py @@ -19,6 +19,10 @@ logger = logging.getLogger(__name__) +DEFAULT_WIDTH = "800" +DEFAULT_HEIGHT = "600" +DEFAULT_SIZE = f"{DEFAULT_WIDTH}x{DEFAULT_HEIGHT}" + class WorkspaceBuilder: @@ -205,22 +209,21 @@ def build(self, session=None, append=False): "Session name %s is already running." % self.sconf["session_name"] ) else: + new_session_kwargs = {} if "start_directory" in self.sconf: - session = self.server.new_session( - session_name=self.sconf["session_name"], - start_directory=self.sconf["start_directory"], - ) - else: - session = self.server.new_session( - session_name=self.sconf["session_name"] - ) + new_session_kwargs["start_directory"] = self.sconf[ + "start_directory" + ] + session = self.server.new_session( + session_name=self.sconf["session_name"] + ) assert self.sconf["session_name"] == session.name assert len(self.sconf["session_name"]) > 0 if has_gte_version("2.9"): # Use tmux default session size, overwrite Server::new_session - session.set_option("default-size", "80x24") + session.set_option("default-size", DEFAULT_SIZE) self.session = session self.server = session.server