Skip to content

Feature: allow setting up custom environment when creating windows and/or panes #453

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ $ pip install --user --upgrade --pre libtmux

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

### Features

- `Window.split_window()` and `Session.new_window()` now support an optional
dictionary of environmental variables, via (#453), credit @zappolowski.

## libtmux 0.15.10 (2022-11-05)

_There will be more improvements over the coming weeks and months to shore up
Expand Down
14 changes: 14 additions & 0 deletions src/libtmux/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
TmuxRelationalObject,
WindowDict,
handle_option_error,
has_gte_version,
has_version,
session_check_name,
)
Expand Down Expand Up @@ -202,6 +203,7 @@ def new_window(
attach: bool = True,
window_index: str = "",
window_shell: t.Optional[str] = None,
environment: t.Optional[t.Dict[str, str]] = None,
) -> Window:
"""
Return :class:`Window` from ``$ tmux new-window``.
Expand All @@ -227,6 +229,9 @@ def new_window(
When this command exits the window will close. This feature is
useful for long-running processes where the closing of the
window upon completion is desired.
environment: dict, optional
Environmental variables for new window. tmux 3.0+ only. Passthrough to
``-e``.

Returns
-------
Expand Down Expand Up @@ -259,6 +264,15 @@ def new_window(
% (self.id, window_index),
)

if environment:
if has_gte_version("3.0"):
for k, v in environment.items():
window_args += (f"-e{k}={v}",)
else:
logger.warning(
"Cannot set up environment as tmux 3.0 or newer is required."
)

if window_shell:
window_args += (window_shell,)

Expand Down
14 changes: 13 additions & 1 deletion src/libtmux/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import shlex
import typing as t

from libtmux.common import tmux_cmd
from libtmux.common import has_gte_version, tmux_cmd
from libtmux.pane import Pane

from . import exc, formats
Expand Down Expand Up @@ -442,6 +442,7 @@ def split_window(
vertical: bool = True,
shell: t.Optional[str] = None,
percent: t.Optional[int] = None,
environment: t.Optional[t.Dict[str, str]] = None,
) -> Pane:
"""
Split window and return the created :class:`Pane`.
Expand All @@ -468,6 +469,8 @@ def split_window(
window upon completion is desired.
percent: int, optional
percentage to occupy with respect to current window
environment: dict, optional
Environmental variables for new pane. tmux 3.0+ only. Passthrough to ``-e``.

Returns
-------
Expand Down Expand Up @@ -520,6 +523,15 @@ def split_window(
if not attach:
tmux_args += ("-d",)

if environment:
if has_gte_version("3.0"):
for k, v in environment.items():
tmux_args += (f"-e{k}={v}",)
else:
logger.warning(
"Cannot set up environment as tmux 3.0 or newer is required."
)

if shell:
tmux_args += (shell,)

Expand Down
57 changes: 56 additions & 1 deletion tests/test_session.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Test for libtmux Session object."""
import logging
import shutil
import typing as t

import pytest

from libtmux import exc
from libtmux.common import has_gte_version
from libtmux.common import has_gte_version, has_lt_version
from libtmux.pane import Pane
from libtmux.server import Server
from libtmux.session import Session
Expand Down Expand Up @@ -257,3 +258,57 @@ def test_cmd_inserts_sesion_id(session: Session) -> None:
assert "-t" in cmd.cmd
assert current_session_id in cmd.cmd
assert cmd.cmd[-1] == last_arg


@pytest.mark.skipif(
has_lt_version("3.0"),
reason="needs -e flag for new-window which was introduced in 3.0",
)
@pytest.mark.parametrize(
"environment",
[
{"ENV_VAR": "window"},
{"ENV_VAR_1": "window_1", "ENV_VAR_2": "window_2"},
],
)
def test_new_window_with_environment(
session: Session,
environment: t.Dict[str, str],
) -> None:
env = shutil.which("env")
assert env is not None, "Cannot find usable `env` in PATH."

window = session.new_window(
attach=True,
window_name="window_with_environment",
window_shell=f"{env} PS1='$ ' sh",
environment=environment,
)
pane = window.attached_pane
assert pane is not None
for k, v in environment.items():
pane.send_keys(f"echo ${k}")
assert pane.capture_pane()[-2] == v


@pytest.mark.skipif(
has_gte_version("3.0"),
reason="3.0 has the -e flag on new-window",
)
def test_new_window_with_environment_logs_warning_for_old_tmux(
session: Session,
caplog: pytest.LogCaptureFixture,
) -> None:
env = shutil.which("env")
assert env is not None, "Cannot find usable `env` in PATH."

session.new_window(
attach=True,
window_name="window_with_environment",
window_shell=f"{env} PS1='$ ' sh",
environment={"ENV_VAR": "window"},
)

assert any(
"Cannot set up environment" in record.msg for record in caplog.records
), "Warning missing"
55 changes: 55 additions & 0 deletions tests/test_window.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test for libtmux Window object."""
import logging
import shutil
import time
import typing as t

import pytest
Expand Down Expand Up @@ -310,3 +312,56 @@ def test_empty_window_name(session: Session) -> None:
"#{==:#{session_name}," + session.name + "}",
)
assert "''" in cmd.stdout


@pytest.mark.skipif(
has_lt_version("3.0"),
reason="needs -e flag for split-window which was introduced in 3.0",
)
@pytest.mark.parametrize(
"environment",
[
{"ENV_VAR": "pane"},
{"ENV_VAR_1": "pane_1", "ENV_VAR_2": "pane_2"},
],
)
def test_split_window_with_environment(
session: Session,
environment: t.Dict[str, str],
) -> None:
env = shutil.which("env")
assert env is not None, "Cannot find usable `env` in Path."

window = session.new_window(window_name="split_window_with_environment")
pane = window.split_window(
shell=f"{env} PS1='$ ' sh",
environment=environment,
)
assert pane is not None
# wait a bit for the prompt to be ready as the test gets flaky otherwise
time.sleep(0.05)
for k, v in environment.items():
pane.send_keys(f"echo ${k}")
assert pane.capture_pane()[-2] == v


@pytest.mark.skipif(
has_gte_version("3.0"),
reason="3.0 has the -e flag on split-window",
)
def test_split_window_with_environment_logs_warning_for_old_tmux(
session: Session,
caplog: pytest.LogCaptureFixture,
) -> None:
env = shutil.which("env")
assert env is not None, "Cannot find usable `env` in Path."

window = session.new_window(window_name="split_window_with_environment")
window.split_window(
shell=f"{env} PS1='$ ' sh",
environment={"ENV_VAR": "pane"},
)

assert any(
"Cannot set up environment" in record.msg for record in caplog.records
), "Warning missing"