From 3f1e933fdd761a01170d5c0c92d3472a4fa7ba45 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 09:49:37 -0500 Subject: [PATCH 01/10] .tmuxp.{json,yaml}: Use percent for main-pane-height --- .tmuxp.json | 6 +++--- .tmuxp.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.tmuxp.json b/.tmuxp.json index cfe18c968b..a14ac2133f 100644 --- a/.tmuxp.json +++ b/.tmuxp.json @@ -10,7 +10,7 @@ "focus": true, "layout": "main-horizontal", "options": { - "main-pane-height": 35 + "main-pane-height": "67%" }, "panes": [ { @@ -25,7 +25,7 @@ "window_name": "docs", "layout": "main-horizontal", "options": { - "main-pane-height": 35 + "main-pane-height": "67%" }, "start_directory": "docs/", "panes": [ @@ -38,4 +38,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/.tmuxp.yaml b/.tmuxp.yaml index 5e4e1364f3..d071d0691a 100644 --- a/.tmuxp.yaml +++ b/.tmuxp.yaml @@ -7,7 +7,7 @@ windows: focus: True layout: main-horizontal options: - main-pane-height: 35 + main-pane-height: 67% panes: - focus: true - pane @@ -16,7 +16,7 @@ windows: - window_name: docs layout: main-horizontal options: - main-pane-height: 35 + main-pane-height: 67% start_directory: docs/ panes: - focus: true From 0777c3bbef652c4d4a595606f6d8987f686a08a2 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:01:51 -0500 Subject: [PATCH 02/10] builder: Use `shutil.get_terminal_size()` See also: - https://docs.python.org/3/library/shutil.html#shutil.get_terminal_size - https://docs.python.org/3/library/os.html#os.get_terminal_size --- src/tmuxp/workspace/builder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 75008d0b77..4c8283139d 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -1,6 +1,7 @@ """Create a tmux workspace from a workspace :py:obj:`dict`.""" import logging +import shutil import time import typing as t @@ -229,9 +230,12 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No new_session_kwargs["start_directory"] = self.session_config[ "start_directory" ] + if has_gte_version("2.6"): - new_session_kwargs["x"] = 800 - new_session_kwargs["y"] = 600 + terminal_size = shutil.get_terminal_size(fallback=(80, 24)) + new_session_kwargs["x"] = terminal_size.columns + new_session_kwargs["y"] = terminal_size.lines + session = self.server.new_session( session_name=self.session_config["session_name"], **new_session_kwargs, From 63ac676d29d3be19ab7ae79e7866849ef764bde9 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:27:33 -0500 Subject: [PATCH 03/10] builder: Column and row size defaults --- src/tmuxp/workspace/builder.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 4c8283139d..5b8bb6726f 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -1,6 +1,7 @@ """Create a tmux workspace from a workspace :py:obj:`dict`.""" import logging +import os import shutil import time import typing as t @@ -17,9 +18,25 @@ logger = logging.getLogger(__name__) -DEFAULT_WIDTH = "800" -DEFAULT_HEIGHT = "600" -DEFAULT_SIZE = f"{DEFAULT_WIDTH}x{DEFAULT_HEIGHT}" +COLUMNS_FALLBACK = 80 + + +def get_default_columns() -> int: + """Return default session column size use when building new tmux sessions.""" + return int( + os.getenv("TMUXP_DEFAULT_COLUMNS", os.getenv("COLUMNS", COLUMNS_FALLBACK)) + ) + + +ROWS_FALLBACK = int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", 24))) + + +def get_default_rows() -> int: + """Return default session row size use when building new tmux sessions.""" + return int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", ROWS_FALLBACK))) + + +DEFAULT_SIZE = f"{COLUMNS_FALLBACK}x{ROWS_FALLBACK}" class WorkspaceBuilder: @@ -232,7 +249,9 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No ] if has_gte_version("2.6"): - terminal_size = shutil.get_terminal_size(fallback=(80, 24)) + terminal_size = shutil.get_terminal_size( + fallback=(get_default_columns(), get_default_rows()) + ) new_session_kwargs["x"] = terminal_size.columns new_session_kwargs["y"] = terminal_size.lines From 8df1621951fb009ea5be8a7067f507236e3e30e1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 15:43:32 -0500 Subject: [PATCH 04/10] py(builder): Remove DEFAULT_SIZE (unused variable) --- src/tmuxp/workspace/builder.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 5b8bb6726f..6f58edaa5c 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -36,9 +36,6 @@ def get_default_rows() -> int: return int(os.getenv("TMUXP_DEFAULT_ROWS", os.getenv("ROWS", ROWS_FALLBACK))) -DEFAULT_SIZE = f"{COLUMNS_FALLBACK}x{ROWS_FALLBACK}" - - class WorkspaceBuilder: """Load workspace from workspace :py:obj:`dict` object. From 8e80c6edae926897bb7995c40ee415b01158c263 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 15:47:24 -0500 Subject: [PATCH 05/10] builder: TMUXP_DETECT_TERMINAL_SIZE=0 to disable new behavior --- src/tmuxp/workspace/builder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index 6f58edaa5c..78e99b3353 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -245,7 +245,10 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No "start_directory" ] - if has_gte_version("2.6"): + if ( + has_gte_version("2.6") + and os.getenv("TMUXP_DETECT_TERMINAL_SIZE", "1") == "1" + ): terminal_size = shutil.get_terminal_size( fallback=(get_default_columns(), get_default_rows()) ) From 498e6fab26adbdc184ae35e87f94d7a9de39931a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:27:47 -0500 Subject: [PATCH 06/10] test(builder): Fixes for size limits --- tests/workspace/test_builder.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 155c4d733f..1d02983657 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -486,6 +486,8 @@ def test_automatic_rename_option( ) -> None: """Test workspace builder with automatic renaming enabled.""" monkeypatch.setenv("DISABLE_AUTO_TITLE", "true") + monkeypatch.setenv("ROWS", "36") + workspace = ConfigReader._from_file( test_utils.get_workspace_file("workspace/builder/window_automatic_rename.yaml"), ) @@ -1506,7 +1508,12 @@ def test_issue_800_default_size_many_windows( a lot of panes. See also: https://github.com/tmux-python/tmuxp/issues/800 + + 2024-04-07: This test isn't being used as of this date, as default-size is totally + unused in builder.py. """ + monkeypatch.setenv("ROWS", "36") + yaml_workspace = test_utils.get_workspace_file( "regressions/issue_800_default_size_many_windows.yaml", ) From fdda3d7b08585745ae3f4f67f7b85177c4a23593 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:51:37 -0500 Subject: [PATCH 07/10] cli(load): Remove `set_layout_hook()` --- src/tmuxp/cli/load.py | 77 ------------------------------------------- 1 file changed, 77 deletions(-) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 708c8e4a43..f03447bf1a 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -9,7 +9,6 @@ import sys import typing as t -from libtmux.common import has_gte_version from libtmux.server import Server from libtmux.session import Session @@ -48,65 +47,6 @@ class CLILoadNamespace(argparse.Namespace): log_file: t.Optional[str] -def set_layout_hook(session: Session, hook_name: str) -> None: - """Set layout hooks to normalize layout. - - References - ---------- - - tmuxp issue: https://github.com/tmux-python/tmuxp/issues/309 - - tmux issue: https://github.com/tmux/tmux/issues/1106 - - tmux 2.6+ requires that the window be viewed with the client before - select-layout adjustments can take effect. - - To handle this, this function creates temporary hook for this session to - iterate through all windows and select the layout. - - In order for layout changes to take effect, a client must at the very - least be attached to the window (not just the session). - - hook_name is provided to allow this to set to multiple scenarios, such - as 'client-attached' (which the user attaches the session). You may - also want 'after-switch-client' for cases where the user loads tmuxp - sessions inside tmux since tmuxp offers to switch for them. - - Also, the hooks are set immediately unbind after they're invoked via -u. - - Parameters - ---------- - session : :class:`libtmux.session.Session` - session to bind hook to - hook_name : str - hook name to bind to, e.g. 'client-attached' - """ - assert session.id is not None - cmd: t.List[str] = ["set-hook", hook_name] - hook_cmd = [] - active_window = session.active_window - for window in session.windows: - # unfortunately, select-layout won't work unless - # we've literally selected the window at least once - # with the client - hook_cmd.append(f"selectw -t {window.id}") - # edit: removed -t, or else it won't respect main-pane-w/h - hook_cmd.append("selectl") - hook_cmd.append("selectw -p") - - # unset the hook immediately after executing - hook_cmd.extend( - (f"set-hook -u -t {session.id} {hook_name}", f"selectw -t {active_window.id}") - ) - - # join the hook's commands with semicolons - _hook_cmd = "{}".format("; ".join(hook_cmd)) - - # append the hook command - cmd.append(_hook_cmd) - - # create the hook - session.cmd(*cmd, target=session.id) - - def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]: """Load and return plugins in workspace.""" plugins = [] @@ -200,20 +140,10 @@ def _load_attached(builder: WorkspaceBuilder, detached: bool) -> None: # unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0' tmux_env = os.environ.pop("TMUX") - if has_gte_version("2.6"): - set_layout_hook(builder.session, "client-session-changed") - builder.session.switch_client() # switch client to new session os.environ["TMUX"] = tmux_env # set TMUX back again else: - if has_gte_version("2.6"): - # if attaching for first time - set_layout_hook(builder.session, "client-attached") - - # for cases where user switches client for first time - set_layout_hook(builder.session, "client-session-changed") - if not detached: builder.session.attach_session() @@ -230,10 +160,6 @@ def _load_detached(builder: WorkspaceBuilder) -> None: assert builder.session is not None - if has_gte_version("2.6"): # prepare for both cases - set_layout_hook(builder.session, "client-attached") - set_layout_hook(builder.session, "client-session-changed") - print("Session created in detached state.") @@ -248,9 +174,6 @@ def _load_append_windows_to_current_session(builder: WorkspaceBuilder) -> None: current_attached_session = builder.find_current_attached_session() builder.build(current_attached_session, append=True) assert builder.session is not None - if has_gte_version("2.6"): # prepare for both cases - set_layout_hook(builder.session, "client-attached") - set_layout_hook(builder.session, "client-session-changed") def _setup_plugins(builder: WorkspaceBuilder) -> Session: From 93b368bfb8cd16bf8f02b3cfe45087856c5d8096 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 15:44:31 -0500 Subject: [PATCH 08/10] tests(builder): Remove unused setenv --- tests/workspace/test_builder.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 1d02983657..801b462d5a 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -1526,9 +1526,6 @@ def test_issue_800_default_size_many_windows( for k, v in confoverrides.items(): workspace[k] = v - if TMUXP_DEFAULT_SIZE is not None: - monkeypatch.setenv("TMUXP_DEFAULT_SIZE", TMUXP_DEFAULT_SIZE) - builder = WorkspaceBuilder(session_config=workspace, server=server) if raises: From 5f91934e09a36fecc7e00d34fbef259d88f0413a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:34:54 -0500 Subject: [PATCH 09/10] docs(CHANGES): Note builder terminal size detection --- CHANGES | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGES b/CHANGES index 04f3a8c1ea..2c514ad7f3 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,31 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +### Breaking change + +#### Workspace builder now detects terminal size (#926) + +Dimensions used by workspace builder now use {py:func}`shutil.get_terminal_size()`. + +In conjunction with `main-pane-height: 67%`, for instance, this will render a +proportional layout: + +```yaml +session_name: my session +windows: +- window_name: example with percentage + focus: True + layout: main-horizontal + options: + main-pane-height: 67% + panes: + - focus: true + - pane +``` + +To use old behavior, set `TMUXP_DETECT_TERMINAL_SIZE=0` in your terminal +environment and file an issue on the tracker. + ### Documentation - Automatically linkify links that were previously only text. From cd5d114d1816684e06aeabe42ed7c262f55a4594 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sun, 7 Apr 2024 11:39:01 -0500 Subject: [PATCH 10/10] docs(examples): `main-pane-height` percentage --- docs/configuration/examples.md | 24 ++++++++++++++++++ examples/main-pane-height-percentage.json | 31 +++++++++++++++++++++++ examples/main-pane-height-percentage.yaml | 15 +++++++++++ 3 files changed, 70 insertions(+) create mode 100644 examples/main-pane-height-percentage.json create mode 100644 examples/main-pane-height-percentage.yaml diff --git a/docs/configuration/examples.md b/docs/configuration/examples.md index 16ba0f0f69..010b039c17 100644 --- a/docs/configuration/examples.md +++ b/docs/configuration/examples.md @@ -554,6 +554,30 @@ pane during creation. ## Main pane height +### Percentage + +:::{versionadded} 1.46.0 + +Before this, tmuxp layouts would not detect the terminal's size. + +::: + +````{tab} YAML +```{literalinclude} ../../examples/main-pane-height-percentage.yaml +:language: yaml + +``` +```` + +````{tab} JSON +```{literalinclude} ../../examples/main-pane-height-percentage.json +:language: json + +``` +```` + +### Rows + ````{tab} YAML ```{literalinclude} ../../examples/main-pane-height.yaml :language: yaml diff --git a/examples/main-pane-height-percentage.json b/examples/main-pane-height-percentage.json new file mode 100644 index 0000000000..32b593d0ef --- /dev/null +++ b/examples/main-pane-height-percentage.json @@ -0,0 +1,31 @@ +{ + "windows": [ + { + "panes": [ + { + "shell_command": [ + "top" + ], + "start_directory": "~" + }, + { + "shell_command": [ + "echo \"hey\"" + ] + }, + { + "shell_command": [ + "echo \"moo\"" + ] + } + ], + "layout": "main-horizontal", + "options": { + "main-pane-height": "67%" + }, + "window_name": "editor" + } + ], + "session_name": "main pane height", + "start_directory": "~" +} diff --git a/examples/main-pane-height-percentage.yaml b/examples/main-pane-height-percentage.yaml new file mode 100644 index 0000000000..061aad4ab6 --- /dev/null +++ b/examples/main-pane-height-percentage.yaml @@ -0,0 +1,15 @@ +session_name: main-pane-height +start_directory: "~" +windows: + - layout: main-horizontal + options: + main-pane-height: 67% + panes: + - shell_command: + - top + start_directory: "~" + - shell_command: + - echo "hey" + - shell_command: + - echo "moo" + window_name: my window name