From cbd72046162095f03d257d7d8b6a392d1ac0f2a9 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 10:04:32 -0600 Subject: [PATCH 01/14] feat(Pane.kill): Add Pane.kill() --- src/libtmux/pane.py | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index f757d7c2a..2b4923cb0 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -353,6 +353,62 @@ def display_message( self.cmd("display-message", cmd) return None + def kill( + self, + all_except: t.Optional[bool] = None, + ) -> None: + """Kill :class:`Pane`. + + ``$ tmux kill-pane``. + + Examples + -------- + Kill a pane: + >>> pane_1 = pane.split_window() + + >>> pane_1 in window.panes + True + + >>> pane_1.kill() + + >>> pane_1 not in window.panes + True + + Kill all panes except the current one: + >>> pane.window.resize(height=100, width=100) + Window(@1 1...) + + >>> one_pane_to_rule_them_all = pane.split_window() + + >>> other_panes = pane.split_window( + ... ), pane.split_window() + + >>> all([p in window.panes for p in other_panes]) + True + + >>> one_pane_to_rule_them_all.kill(all_except=True) + + >>> all([p not in window.panes for p in other_panes]) + True + + >>> one_pane_to_rule_them_all in window.panes + True + """ + flags: t.Tuple[str, ...] = () + + if all_except: + flags += ("-a",) + + proc = self.cmd( + "kill-pane", + *flags, + ) + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + + return None + """ Commands ("climber"-helpers) From 5230cc60f86652c41b22a0f926b4c071f34849d3 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 07:18:01 -0600 Subject: [PATCH 02/14] feat(Server.new_session): Support environment variables in tmux 3.2+ --- src/libtmux/server.py | 10 ++++++++++ tests/test_server.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 27fe20c08..6b7d31133 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -350,6 +350,7 @@ def new_session( window_command: t.Optional[str] = None, x: t.Optional[t.Union[int, "DashLiteral"]] = None, y: t.Optional[t.Union[int, "DashLiteral"]] = None, + environment: t.Optional[t.Dict[str, str]] = None, *args: t.Any, **kwargs: t.Any, ) -> Session: @@ -468,6 +469,15 @@ def new_session( if window_command: tmux_args += (window_command,) + if environment: + if has_gte_version("3.2"): + for k, v in environment.items(): + tmux_args += (f"-e{k}={v}",) + else: + logger.warning( + "Environment flag ignored, tmux 3.2 or newer required.", + ) + proc = self.cmd("new-session", *tmux_args) if proc.stderr: diff --git a/tests/test_server.py b/tests/test_server.py index 3a84bd590..a5d0583a9 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -146,6 +146,21 @@ def test_new_session_width_height(server: Server) -> None: assert pane.display_message("#{window_height}", get_text=True)[0] == "32" +def test_new_session_environmental_variables( + server: Server, + caplog: pytest.LogCaptureFixture, +) -> None: + """Server.new_session creates and returns valid session.""" + my_session = server.new_session("test_new_session", environment={"FOO": "HI"}) + + if has_gte_version("3.2"): + assert my_session.show_environment()["FOO"] == "HI" + else: + assert any( + "Environment flag ignored" in record.msg for record in caplog.records + ), "Warning missing" + + def test_no_server_sessions() -> None: """Verify ``Server.sessions`` returns empty list without tmux server.""" server = Server(socket_name="test_attached_session_no_server") From f80712e97fd5a1ec40f6a68b14c41a29a1b2a66b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 07:29:38 -0600 Subject: [PATCH 03/14] docs(Window.split_window): Note -l in tmux 3.1+ --- src/libtmux/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 68daf9277..dc3cea2ae 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -249,6 +249,7 @@ def split_window( tmux_args += ("-h",) if percent is not None: + # Deprecated in 3.1 in favor of -l tmux_args += ("-p %d" % percent,) tmux_args += ("-P", "-F%s" % "".join(tmux_formats)) # output From 98346f7a3404c424a450e52dce66a2abc3b58ae6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 07:43:59 -0600 Subject: [PATCH 04/14] feat(Window.split_window): Add size argument for tmux 3.2+ --- src/libtmux/window.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index dc3cea2ae..dedf2d038 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -21,7 +21,7 @@ from libtmux.pane import Pane from . import exc -from .common import PaneDict, WindowOptionDict, handle_option_error +from .common import PaneDict, WindowOptionDict, handle_option_error, has_lt_version from .formats import FORMAT_SEPARATOR if t.TYPE_CHECKING: @@ -184,7 +184,8 @@ def split_window( attach: bool = False, vertical: bool = True, shell: t.Optional[str] = None, - percent: t.Optional[int] = None, + size: t.Optional[t.Union[str, int]] = None, + percent: t.Optional[int] = None, # deprecated environment: t.Optional[t.Dict[str, str]] = None, ) -> "Pane": """Split window and return the created :class:`Pane`. @@ -209,8 +210,11 @@ def split_window( NOTE: When this command exits the pane will close. This feature is useful for long-running processes where the closing of the window upon completion is desired. + size: int, optional + Cell/row or percentage to occupy with respect to current window. percent: int, optional - percentage to occupy with respect to current window + Deprecated in favor of size. Percentage to occupy with respect to current + window. environment: dict, optional Environmental variables for new pane. tmux 3.0+ only. Passthrough to ``-e``. @@ -228,6 +232,10 @@ def split_window( .. versionchanged:: 0.28.0 ``attach`` default changed from ``True`` to ``False``. + + .. deprecated:: 0.28.0 + + ``percent=25`` deprecated in favor of ``size="25%"``. """ tmux_formats = ["#{pane_id}" + FORMAT_SEPARATOR] @@ -248,9 +256,28 @@ def split_window( else: tmux_args += ("-h",) + if size is not None: + if has_lt_version("3.1"): + if isinstance(size, str) and size.endswith("%"): + tmux_args += (f'-p{str(size).rstrip("%")}',) + else: + warnings.warn( + 'Ignored size. Use percent in tmux < 3.1, e.g. "size=50%"', + stacklevel=2, + ) + else: + tmux_args += (f"-l{size}",) + if percent is not None: # Deprecated in 3.1 in favor of -l - tmux_args += ("-p %d" % percent,) + warnings.warn( + f'Deprecated in favor of size="{str(percent).rstrip("%")}%" ' + + ' ("-l" flag) in tmux 3.1+.', + category=DeprecationWarning, + stacklevel=2, + ) + tmux_args += (f"-p{percent}",) + tmux_args += ("-P", "-F%s" % "".join(tmux_formats)) # output if start_directory is not None: From 03901505de74099a2b204e578779a90eb9c3de94 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 10:01:36 -0600 Subject: [PATCH 05/14] feat!(Pane.split_window): Accept same arguments as Window.split_window --- src/libtmux/pane.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 2b4923cb0..6895c8e05 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -432,9 +432,12 @@ def select_pane(self) -> "Pane": def split_window( self, attach: bool = False, - vertical: bool = True, start_directory: t.Optional[str] = None, - percent: t.Optional[int] = None, + vertical: bool = True, + shell: t.Optional[str] = None, + size: t.Optional[t.Union[str, int]] = None, + percent: t.Optional[int] = None, # deprecated + environment: t.Optional[t.Dict[str, str]] = None, ) -> "Pane": # New Pane, not self """Split window at pane and return newly created :class:`Pane`. @@ -448,13 +451,22 @@ def split_window( specifies the working directory in which the new pane is created. percent: int, optional percentage to occupy with respect to current pane + + Notes + ----- + .. deprecated:: 0.28.0 + + ``percent=25`` deprecated in favor of ``size="25%"``. """ return self.window.split_window( target=self.pane_id, - start_directory=start_directory, attach=attach, + start_directory=start_directory, vertical=vertical, + shell=shell, + size=size, percent=percent, + environment=environment, ) """ From d6e5c6970ddef3bd3482e28469e77da0b7f77453 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 07:17:07 -0600 Subject: [PATCH 06/14] chore(Window): Change copy in warning --- src/libtmux/session.py | 2 +- src/libtmux/window.py | 2 +- tests/legacy_api/test_session.py | 2 +- tests/legacy_api/test_window.py | 2 +- tests/test_session.py | 2 +- tests/test_window.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index e65c389b1..44caa1911 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -500,7 +500,7 @@ def new_window( window_args += (f"-e{k}={v}",) else: logger.warning( - "Cannot set up environment as tmux 3.0 or newer is required.", + "Environment flag ignored, requires tmux 3.0 or newer.", ) if window_shell: diff --git a/src/libtmux/window.py b/src/libtmux/window.py index dedf2d038..2f03e0853 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -294,7 +294,7 @@ def split_window( tmux_args += (f"-e{k}={v}",) else: logger.warning( - "Cannot set up environment as tmux 3.0 or newer is required.", + "Environment flag ignored, tmux 3.0 or newer required.", ) if shell: diff --git a/tests/legacy_api/test_session.py b/tests/legacy_api/test_session.py index 93626a38e..dab299032 100644 --- a/tests/legacy_api/test_session.py +++ b/tests/legacy_api/test_session.py @@ -313,5 +313,5 @@ def test_new_window_with_environment_logs_warning_for_old_tmux( ) assert any( - "Cannot set up environment" in record.msg for record in caplog.records + "Environment flag ignored" in record.msg for record in caplog.records ), "Warning missing" diff --git a/tests/legacy_api/test_window.py b/tests/legacy_api/test_window.py index 924049d0f..8d8063d79 100644 --- a/tests/legacy_api/test_window.py +++ b/tests/legacy_api/test_window.py @@ -383,5 +383,5 @@ 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 + "Environment flag ignored" in record.msg for record in caplog.records ), "Warning missing" diff --git a/tests/test_session.py b/tests/test_session.py index b091a5f5f..6be7ac10f 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -324,5 +324,5 @@ def test_new_window_with_environment_logs_warning_for_old_tmux( ) assert any( - "Cannot set up environment" in record.msg for record in caplog.records + "Environment flag ignored" in record.msg for record in caplog.records ), "Warning missing" diff --git a/tests/test_window.py b/tests/test_window.py index 4252f8469..b518f8bcc 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -396,7 +396,7 @@ 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 + "Environment flag ignored" in record.msg for record in caplog.records ), "Warning missing" From b149c03241361c22dba993628b9cf115d61ee43b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 07:44:15 -0600 Subject: [PATCH 07/14] tests(Window.split_window): Add support for size --- tests/test_window.py | 50 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/tests/test_window.py b/tests/test_window.py index b518f8bcc..4d1e71e78 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -8,7 +8,7 @@ from libtmux import exc from libtmux._internal.query_list import ObjectDoesNotExist -from libtmux.common import has_gte_version, has_lt_version +from libtmux.common import has_gte_version, has_lt_version, has_version from libtmux.constants import ResizeAdjustmentDirection from libtmux.pane import Pane from libtmux.server import Server @@ -171,6 +171,54 @@ def test_split_window_horizontal(session: Session) -> None: assert float(first_pane.pane_width) <= ((float(window.window_width) + 1) / 2) +def test_split_percentage(session: Session) -> None: + """Test deprecated percent param.""" + window = session.new_window(window_name="split window size") + window.resize(height=100, width=100) + window_height_before = ( + int(window.window_height) if isinstance(window.window_height, str) else 0 + ) + if has_version("3.4"): + pytest.skip( + "tmux 3.4 has a split-window bug." + + " See https://github.com/tmux/tmux/pull/3840." + ) + with pytest.warns(match="Deprecated in favor of size.*"): + pane = window.split_window(percent=10) + assert pane.pane_height == str(int(window_height_before * 0.1)) + + +def test_split_window_size(session: Session) -> None: + """Window.split_window() respects size.""" + window = session.new_window(window_name="split window size") + window.resize(height=100, width=100) + + if has_gte_version("3.1"): + pane = window.split_window(size=10) + assert pane.pane_height == "10" + + pane = window.split_window(vertical=False, size=10) + assert pane.pane_width == "10" + + pane = window.split_window(size="10%") + assert pane.pane_height == "8" + + pane = window.split_window(vertical=False, size="10%") + assert pane.pane_width == "8" + else: + window_height_before = ( + int(window.window_height) if isinstance(window.window_height, str) else 0 + ) + window_width_before = ( + int(window.window_width) if isinstance(window.window_width, str) else 0 + ) + pane = window.split_window(size="10%") + assert pane.pane_height == str(int(window_height_before * 0.1)) + + pane = window.split_window(vertical=False, size="10%") + assert pane.pane_width == str(int(window_width_before * 0.1)) + + @pytest.mark.parametrize( "window_name_before,window_name_after", [("test", "ha ha ha fjewlkjflwef"), ("test", "hello \\ wazzup 0")], From 9d93cf666a95d40d6a6fae326713aa5a055e8d5d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 12:13:44 -0600 Subject: [PATCH 08/14] feat(Server.kill): Add Server.kill(), deprecate kill_server() --- src/libtmux/server.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 6b7d31133..9e195c352 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -270,8 +270,24 @@ def has_session(self, target_session: str, exact: bool = True) -> bool: return False - def kill_server(self) -> None: - """Kill tmux server.""" + def kill(self) -> None: + """Kill tmux server. + + >>> svr = Server(socket_name="testing") + >>> svr + Server(socket_name=testing) + + >>> svr.new_session() + Session(...) + + >>> svr.is_alive() + True + + >>> svr.kill() + + >>> svr.is_alive() + False + """ self.cmd("kill-server") def kill_session(self, target_session: t.Union[str, int]) -> "Server": @@ -585,6 +601,23 @@ def __repr__(self) -> str: # # Legacy: Redundant stuff we want to remove # + def kill_server(self) -> None: + """Kill tmux server. + + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.kill()`. + + """ + warnings.warn( + "Server.kill_server() is deprecated in favor of Server.kill()", + category=DeprecationWarning, + stacklevel=2, + ) + self.cmd("kill-server") + def _list_panes(self) -> t.List[PaneDict]: """Return list of panes in :py:obj:`dict` form. From 7b9427567e00c90d3757cab9b9213402a6f38d90 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 12:20:57 -0600 Subject: [PATCH 09/14] feat(Window.kill): Add Window.kill(), deprecate kill_window() --- README.md | 2 +- docs/quickstart.md | 2 +- src/libtmux/test.py | 4 +-- src/libtmux/window.py | 71 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b45031e5d..d5693fdaf 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Close window: ```python >>> w = session.attached_window ->>> w.kill_window() +>>> w.kill() ``` Grab remaining tmux window: diff --git a/docs/quickstart.md b/docs/quickstart.md index c61e5223e..9d0f808a8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -260,7 +260,7 @@ Window(@2 2:check this out, Session($1 ...)) And kill: ```python ->>> window.kill_window() +>>> window.kill() ``` Use {meth}`Session.windows` and {meth}`Session.windows.filter()` to list and sort diff --git a/src/libtmux/test.py b/src/libtmux/test.py index c298af300..71cbe4433 100644 --- a/src/libtmux/test.py +++ b/src/libtmux/test.py @@ -242,7 +242,7 @@ def temp_window( Return a context manager with a temporary window. The window will destroy itself upon closing with :meth:`window. - kill_window()`. + kill()`. If no ``window_name`` is entered, :func:`get_test_window_name` will make an unused window name. @@ -290,7 +290,7 @@ def temp_window( yield window finally: if len(session.windows.filter(window_id=window_id)) > 0: - window.kill_window() + window.kill() return diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 2f03e0853..704914c53 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -597,16 +597,59 @@ def rename_window(self, new_name: str) -> "Window": return self - def kill_window(self) -> None: - """Kill the current :class:`Window` object. ``$ tmux kill-window``.""" + def kill( + self, + all_except: t.Optional[bool] = None, + ) -> None: + """Kill :class:`Window`. + + ``$ tmux kill-window``. + + Examples + -------- + Kill a window: + >>> window_1 = session.new_window() + + >>> window_1 in session.windows + True + + >>> window_1.kill() + + >>> window_1 not in session.windows + True + + Kill all windows except the current one: + >>> one_window_to_rule_them_all = session.new_window() + + >>> other_windows = session.new_window( + ... ), session.new_window() + + >>> all([w in session.windows for w in other_windows]) + True + + >>> one_window_to_rule_them_all.kill(all_except=True) + + >>> all([w not in session.windows for w in other_windows]) + True + + >>> one_window_to_rule_them_all in session.windows + True + """ + flags: t.Tuple[str, ...] = () + + if all_except: + flags += ("-a",) + proc = self.cmd( "kill-window", - f"-t{self.session_id}:{self.window_index}", + *flags, ) if proc.stderr: raise exc.LibTmuxException(proc.stderr) + return None + def move_window( self, destination: str = "", @@ -749,6 +792,28 @@ def width(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + def kill_window(self) -> None: + """Kill the current :class:`Window` object. ``$ tmux kill-window``. + + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.kill()`. + """ + warnings.warn( + "Window.kill_server() is deprecated in favor of Window.kill()", + category=DeprecationWarning, + stacklevel=2, + ) + proc = self.cmd( + "kill-window", + f"-t{self.session_id}:{self.window_index}", + ) + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: """Return key-based lookup. Deprecated by attributes. From 448881d1dde3589fbf8cc96c285335ed4115228c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 12:33:39 -0600 Subject: [PATCH 10/14] feat(Session.kill): Add Session.kill(), deprecate kill_session() --- src/libtmux/session.py | 82 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 3 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 44caa1911..8b684e60a 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -379,13 +379,70 @@ def attach_session(self) -> "Session": return self - def kill_session(self) -> None: - """Destroy session.""" - proc = self.cmd("kill-session", "-t%s" % self.session_id) + def kill( + self, + all_except: t.Optional[bool] = None, + clear: t.Optional[bool] = None, + ) -> None: + """Kill :class:`Session`, closes linked windows and detach all clients. + + ``$ tmux kill-session``. + + Parameters + ---------- + all_except : bool, optional + Kill all sessions in server except this one. + clear : bool, optional + Clear alerts (bell, activity, or silence) in all windows. + + Examples + -------- + Kill a session: + >>> session_1 = server.new_session() + + >>> session_1 in server.sessions + True + + >>> session_1.kill() + + >>> session_1 not in server.sessions + True + + Kill all sessions except the current one: + >>> one_session_to_rule_them_all = server.new_session() + + >>> other_sessions = server.new_session( + ... ), server.new_session() + + >>> all([w in server.sessions for w in other_sessions]) + True + + >>> one_session_to_rule_them_all.kill(all_except=True) + + >>> all([w not in server.sessions for w in other_sessions]) + True + + >>> one_session_to_rule_them_all in server.sessions + True + """ + flags: t.Tuple[str, ...] = () + + if all_except: + flags += ("-a",) + + if clear: # Clear alerts (bell, activity, or silence) in all windows + flags += ("-C",) + + proc = self.cmd( + "kill-session", + *flags, + ) if proc.stderr: raise exc.LibTmuxException(proc.stderr) + return None + def switch_client(self) -> "Session": """Switch client to session. @@ -595,6 +652,25 @@ def name(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + def kill_session(self) -> None: + """Destroy session. + + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.kill()`. + """ + warnings.warn( + "Session.kill_session() is deprecated in favor of Session.kill()", + category=DeprecationWarning, + stacklevel=2, + ) + proc = self.cmd("kill-session", "-t%s" % self.session_id) + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + def get(self, key: str, default: t.Optional[t.Any] = None) -> t.Any: """Return key-based lookup. Deprecated by attributes. From 9a5147aa834954d26fb66dfe0727162429b9e3bd Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 12:51:42 -0600 Subject: [PATCH 11/14] feat(Session.attach): Add Session.attach(), deprecate attach_session() --- src/libtmux/session.py | 52 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 8b684e60a..e7e7c732c 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -370,13 +370,38 @@ def attached_window(self) -> "Window": if len(self._windows) == 0: raise exc.NoWindowsExist() - def attach_session(self) -> "Session": - """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``.""" - proc = self.cmd("attach-session", "-t%s" % self.session_id) + def attach( + self, + _exit: t.Optional[bool] = None, + _flags: t.Optional[t.List[str]] = None, + ) -> "Session": + """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``. + + Examples + -------- + >>> session = server.new_session() + + >>> session not in server.attached_sessions + True + """ + flags: t.Tuple[str, ...] = () + + if _exit is not None and _exit: + flags += ("-x",) + + if _flags is not None and isinstance(_flags, list): + flags += tuple(f'{",".join(_flags)}') + + proc = self.cmd( + "attach-session", + *flags, + ) if proc.stderr: raise exc.LibTmuxException(proc.stderr) + self.refresh() + return self def kill( @@ -652,6 +677,27 @@ def name(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + def attach_session(self) -> "Session": + """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``. + + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.attach()`. + """ + warnings.warn( + "Session.attach_session() is deprecated in favor of Session.attach()", + category=DeprecationWarning, + stacklevel=2, + ) + proc = self.cmd("attach-session", "-t%s" % self.session_id) + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + + return self + def kill_session(self) -> None: """Destroy session. From 8ae52b2f1774458ecba1ae541a7e0596afe94359 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 13:54:17 -0600 Subject: [PATCH 12/14] feat(Window.select): Add Window.select(), deprecate select_window() --- src/libtmux/window.py | 47 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 704914c53..c37746553 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -686,15 +686,37 @@ def move_window( # # Climbers # - def select_window(self) -> "Window": + def select(self) -> "Window": """Select window. To select a window object asynchrously. If a ``window`` object exists - and is no longer longer the current window, ``w.select_window()`` + and is no longer the current window, ``w.select_window()`` will make ``w`` the current window. + + Examples + -------- + >>> window = session.attached_window + >>> new_window = session.new_window() + >>> session.refresh() + >>> active_windows = [w for w in session.windows if w.window_active == '1'] + + >>> new_window.window_active == '1' + False + + >>> new_window.select() + Window(...) + + >>> new_window.window_active == '1' + True """ - assert isinstance(self.window_index, str) - return self.session.select_window(self.window_index) + proc = self.cmd("select-window") + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + + self.refresh() + + return self # # Computed properties @@ -792,6 +814,23 @@ def width(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + def select_window(self) -> "Window": + """Select window. + + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.select()`. + """ + warnings.warn( + "Window.select_window() is deprecated in favor of Window.select()", + category=DeprecationWarning, + stacklevel=2, + ) + assert isinstance(self.window_index, str) + return self.session.select_window(self.window_index) + def kill_window(self) -> None: """Kill the current :class:`Window` object. ``$ tmux kill-window``. From 3b2f5be3f7b210d26907111804d7f62903996aec Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 13:59:43 -0600 Subject: [PATCH 13/14] feat(Pane.select): Add Pane.select(), deprecate select_pane() --- README.md | 2 +- docs/quickstart.md | 4 ++-- src/libtmux/pane.py | 46 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d5693fdaf..aec7e8ca5 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ Split window (create a new pane): ```python >>> pane = window.split_window() >>> pane = window.split_window(attach=False) ->>> pane.select_pane() +>>> pane.select() Pane(%3 Window(@1 1:..., Session($1 ...))) >>> window = session.new_window(attach=False, window_name="test") >>> window diff --git a/docs/quickstart.md b/docs/quickstart.md index 9d0f808a8..a4926fdf8 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -311,14 +311,14 @@ For one, arguments such as `attach=False` can be omittted. This gives you the {class}`Pane` along with moving the cursor to a new window. You can also use the `.select_*` available on the object, in this case the pane has -{meth}`Pane.select_pane()`. +{meth}`Pane.select()`. ```python >>> pane = window.split_window(attach=False) ``` ```python ->>> pane.select_pane() +>>> pane.select() Pane(%1 Window(@1 ...:..., Session($1 ...))) ``` diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 6895c8e05..e19cfbb9b 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -416,13 +416,53 @@ def kill( additional scoped window info. """ + def select(self) -> "Pane": + """Select pane. + + Examples + -------- + >>> pane = window.attached_pane + >>> new_pane = window.split_window() + >>> pane.refresh() + >>> active_panes = [p for p in window.panes if p.pane_active == '1'] + + >>> pane in active_panes + True + >>> new_pane in active_panes + False + + >>> new_pane.pane_active == '1' + False + + >>> new_pane.select() + Pane(...) + + >>> new_pane.pane_active == '1' + True + """ + proc = self.cmd("select-pane") + + if proc.stderr: + raise exc.LibTmuxException(proc.stderr) + + self.refresh() + + return self + def select_pane(self) -> "Pane": """Select pane. - To select a window object asynchrously. If a ``pane`` object exists - and is no longer longer the current window, ``w.select_pane()`` - will make ``p`` the current pane. + Notes + ----- + .. deprecated:: 0.30 + + Deprecated in favor of :meth:`.select()`. """ + warnings.warn( + "Pane.select_pane() is deprecated in favor of Pane.select()", + category=DeprecationWarning, + stacklevel=2, + ) assert isinstance(self.pane_id, str) pane = self.window.select_pane(self.pane_id) if pane is None: From 5fa852c796acf47d9e5b9b24a7d88dbc744bb5e1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 14:19:09 -0600 Subject: [PATCH 14/14] docs(CHANGES): Note command updates --- CHANGES | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGES b/CHANGES index 199549c08..122a2ccb8 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,32 @@ $ pip install --user --upgrade --pre libtmux +### New commands + +- {meth}`Pane.kill()` + +### Renamed commands + +- `Window.select_window()` renamed to {meth}`Window.select()` + - Deprecated `Window.select_window()` +- `Pane.select_pane()` renamed to {meth}`Pane.select()` + - Deprecated `Pane.pane_select()` +- `Session.attach_session()` renamed to {meth}`Session.attach()` + - Deprecated `Session.attach_session()` +- `Server.kill_server()` renamed to {meth}`Server.kill()` + - Deprecated `Server.kill_server()` +- `Session.kill_session()` renamed to {meth}`Session.kill()` + - Deprecated `Session.kill_session()` +- `Window.kill_window()` renamed to {meth}`Window.kill()` + - Deprecated `Window.kill_window()` + +### Improved commands + +- {meth}`Server.new_session()`: Support environment variables +- {meth}`Window.split_window()`: Support `size` via `-l` + + Supports columns/rows (`size=10`) and percentage (`size='10%'`) + ## libtmux 0.29.0 (2024-02-16) #### Bug fixes