Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Builder: Use shutil.get_terminal_size() #926

Merged
merged 10 commits into from
Apr 7, 2024
6 changes: 3 additions & 3 deletions .tmuxp.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"focus": true,
"layout": "main-horizontal",
"options": {
"main-pane-height": 35
"main-pane-height": "67%"
},
"panes": [
{
Expand All @@ -25,7 +25,7 @@
"window_name": "docs",
"layout": "main-horizontal",
"options": {
"main-pane-height": 35
"main-pane-height": "67%"
},
"start_directory": "docs/",
"panes": [
Expand All @@ -38,4 +38,4 @@
]
}
]
}
}
4 changes: 2 additions & 2 deletions .tmuxp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ windows:
focus: True
layout: main-horizontal
options:
main-pane-height: 35
main-pane-height: 67%
panes:
- focus: true
- pane
Expand All @@ -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
Expand Down
25 changes: 25 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force

<!-- Maintainers, insert changes / features for the next release here -->

### 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.
Expand Down
24 changes: 24 additions & 0 deletions docs/configuration/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions examples/main-pane-height-percentage.json
Original file line number Diff line number Diff line change
@@ -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": "~"
}
15 changes: 15 additions & 0 deletions examples/main-pane-height-percentage.yaml
Original file line number Diff line number Diff line change
@@ -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
77 changes: 0 additions & 77 deletions src/tmuxp/cli/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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()

Expand All @@ -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.")


Expand All @@ -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:
Expand Down
35 changes: 29 additions & 6 deletions src/tmuxp/workspace/builder.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Create a tmux workspace from a workspace :py:obj:`dict`."""

import logging
import os
import shutil
import time
import typing as t

Expand All @@ -16,9 +18,22 @@

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)))


class WorkspaceBuilder:
Expand Down Expand Up @@ -229,9 +244,17 @@ 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

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())
)
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,
Expand Down
10 changes: 7 additions & 3 deletions tests/workspace/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)
Expand Down Expand Up @@ -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",
)
Expand All @@ -1519,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:
Expand Down