From fee8d4c47a7ae6a430fa8a2fb13bdfd9cc6a7c67 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 20:19:47 -0600 Subject: [PATCH 01/28] conftest(doctest): Add libtmux objects --- conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/conftest.py b/conftest.py index 3f7bc577b..d23acccb3 100644 --- a/conftest.py +++ b/conftest.py @@ -14,7 +14,11 @@ import pytest from _pytest.doctest import DoctestItem +from libtmux.pane import Pane from libtmux.pytest_plugin import USING_ZSH +from libtmux.server import Server +from libtmux.session import Session +from libtmux.window import Window if t.TYPE_CHECKING: from libtmux.session import Session @@ -30,6 +34,10 @@ def add_doctest_fixtures( """Configure doctest fixtures for pytest-doctest.""" if isinstance(request._pyfuncitem, DoctestItem) and shutil.which("tmux"): request.getfixturevalue("set_home") + doctest_namespace["Server"] = Server + doctest_namespace["Session"] = Session + doctest_namespace["Window"] = Window + doctest_namespace["Pane"] = Pane doctest_namespace["server"] = request.getfixturevalue("server") session: "Session" = request.getfixturevalue("session") doctest_namespace["session"] = session From 88ee7cddd0d57915ef39b937ff2f34a85c5d532a Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 17:29:00 -0600 Subject: [PATCH 02/28] chore(tmux_cmd): Improve docstring, add doctest --- src/libtmux/common.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index b67621b87..a5b91a930 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -187,20 +187,21 @@ def getenv(self, name: str) -> Optional[t.Union[str, bool]]: class tmux_cmd: - """:term:`tmux(1)` command via :py:mod:`subprocess`. + """Run any :term:`tmux(1)` command through :py:mod:`subprocess`. Examples -------- - .. code-block:: python + Create a new session, check for error: - proc = tmux_cmd('new-session', '-s%' % 'my session') + >>> proc = tmux_cmd(f'-L{server.socket_name}', 'new-session', '-d', '-P', '-F#S') + >>> if proc.stderr: + ... raise exc.LibTmuxException( + ... 'Command: %s returned error: %s' % (proc.cmd, proc.stderr) + ... ) + ... - if proc.stderr: - raise exc.LibTmuxException( - 'Command: %s returned error: %s' % (proc.cmd, proc.stderr) - ) - - print('tmux command returned %s' % proc.stdout) + >>> print(f'tmux command returned {" ".join(proc.stdout)}') + tmux command returned 2 Equivalent to: From f9245be0468122689c87e6c240ddfef894f9156b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 19:00:44 -0600 Subject: [PATCH 03/28] docs(Server.cmd): Fix typo --- src/libtmux/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 9e195c352..d6b3d1ff5 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -174,7 +174,7 @@ def raise_if_dead(self) -> None: # Command # def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: - """Execute tmux command, rsepective of socket name and file, return output. + """Execute tmux command respective of socket name and file, return output. Examples -------- From 53ce1c56dafb1d9483bae10af502b1c9a5d0a943 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Fri, 16 Feb 2024 20:19:52 -0600 Subject: [PATCH 04/28] docs(README): Direct usage examples --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aec7e8ca5..cf86247b7 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ Connect to a live tmux session: ```python >>> import libtmux ->>> s = libtmux.Server() ->>> s +>>> svr = libtmux.Server() +>>> svr Server(socket_path=/tmp/tmux-.../default) ``` @@ -66,6 +66,49 @@ current tmux server / session / window pane. [ptpython]: https://github.com/prompt-toolkit/ptpython [ipython]: https://ipython.org/ +Run any tmux command, respective of context: + +Honors tmux socket name and path: + +```python +>>> server = Server(socket_name='libtmux_doctest') +>>> server.cmd('display-message', 'hello world') + +``` + +New session: + +```python +>>> server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0] +'$2' +``` + +```python +>>> session.cmd('new-window', '-P').stdout[0] +'libtmux...:2.0' +``` + +Time for some tech, direct to a rich, `Window` object: + +```python +>>> Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) +Window(@2 2:..., Session($1 libtmux_...)) +``` + +Create a pane from a window: + +```python +>>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] +'%2' +``` + +Magic, directly to a `Pane`: + +```python +>>> Pane.from_pane_id(pane_id=session.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server) +Pane(%... Window(@1 1:..., Session($1 libtmux_...))) +``` + List sessions: ```python From 7552e3baa96a044884c28d421603fa4f965d783c Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 06:45:59 -0600 Subject: [PATCH 05/28] docs(README): Overhaul doctests --- README.md | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cf86247b7..a9c0d7e5e 100644 --- a/README.md +++ b/README.md @@ -130,30 +130,44 @@ Direct lookup: Session($1 ...) ``` -Find session by dict lookup: +Filter sesions: ```python >>> server.sessions[0].rename_session('foo') Session($1 foo) ->>> server.sessions.filter(session_name="foo")[0] +>>> server.sessions.filter(session_name="foo") +[Session($1 foo)] +>>> server.sessions.get(session_name="foo") Session($1 foo) ``` Control your session: ```python ->>> session.rename_session('foo') -Session($1 foo) ->>> session.new_window(attach=False, window_name="ha in the bg") -Window(@2 2:ha in the bg, Session($1 foo)) ->>> session.kill_window("ha in") +>>> session +Session($1 ...) + +>>> session.rename_session('my-session') +Session($1 my-session) ``` Create new window in the background (don't switch to it): ```python ->>> session.new_window(attach=False, window_name="ha in the bg") -Window(@2 2:ha in the bg, Session($1 ...)) +>>> bg_window = session.new_window(attach=False, window_name="ha in the bg") +>>> bg_window +Window(@... 2:ha in the bg, Session($1 ...)) + +# Session can search the window +>>> session.windows.filter(window_name__startswith="ha") +[Window(@... 2:ha in the bg, Session($1 ...))] + +# Directly +>>> session.windows.get(window_name__startswith="ha") +Window(@... 2:ha in the bg, Session($1 ...)) + +# Clean up +>>> bg_window.kill() ``` Close window: From 5a86a9540b17b96bf8fe0f5330d6281a9b8e5eee Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 03:45:19 -0600 Subject: [PATCH 06/28] docs(quickstart): Add raw commands --- docs/quickstart.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index a4926fdf8..583037489 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -120,10 +120,44 @@ equivalent to `$ tmux -L mysocket`. `server` is now a living object bound to the tmux server's Sessions, Windows and Panes. +## Raw, contextual commands + +New session: + +```python +>>> server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0] +'$2' +``` + +```python +>>> session.cmd('new-window', '-P').stdout[0] +'libtmux...:2.0' +``` + +Time for some tech, direct to a rich, `Window` object: + +```python +>>> Window.from_window_id(window_id=session.cmd('new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) +Window(@2 2:..., Session($1 libtmux_...)) +``` + +Create a pane from a window: + +```python +>>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] +'%2' +``` + +Magic, directly to a `Pane`: + +```python +>>> Pane.from_pane_id(pane_id=session.cmd('split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server) +Pane(%... Window(@1 1:..., Session($1 libtmux_...))) +``` + ## Find your {class}`Session` -If you have multiple tmux sessions open, you can see that all of the -methods in {class}`Server` are available. +If you have multiple tmux sessions open, all methods in {class}`Server` are available. We can list sessions with {meth}`Server.sessions`: From 7547e9c70a1b8bc499619b730c59ad33f268af07 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 03:54:13 -0600 Subject: [PATCH 07/28] docs(Server.cmd): Add examples --- src/libtmux/server.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index d6b3d1ff5..3d9a9ace9 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -181,6 +181,31 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: >>> server.cmd('display-message', 'hi') + New session: + + >>> server.cmd('new-session', '-d', '-P', '-F#{session_id}').stdout[0] + '$2' + + >>> session.cmd('new-window', '-P').stdout[0] + 'libtmux...:2.0' + + Time for some tech, direct to a rich, `Window` object: + + >>> Window.from_window_id(window_id=session.cmd( + ... 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) + Window(@4 3:..., Session($1 libtmux_...)) + + Create a pane from a window: + + >>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] + '%5' + + Magic, directly to a `Pane`: + + >>> Pane.from_pane_id(pane_id=session.cmd( + ... 'split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + Returns ------- :class:`common.tmux_cmd` From e4a47c273169a1c685ad5871d6195e967c6030fd Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:03:05 -0600 Subject: [PATCH 08/28] chore(Server.cmd): Accept cmd as first positional argument --- src/libtmux/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 3d9a9ace9..51b84d0d6 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -173,7 +173,7 @@ def raise_if_dead(self) -> None: # # Command # - def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """Execute tmux command respective of socket name and file, return output. Examples @@ -216,7 +216,7 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: Renamed from ``.tmux`` to ``.cmd``. """ - cmd_args: t.List[t.Union[str, int]] = list(args) + cmd_args: t.List[t.Union[str, int]] = [cmd, *args] if self.socket_name: cmd_args.insert(0, f"-L{self.socket_name}") if self.socket_path: From 8c297fce90671b1e04b6106c5af9dd58e67f442f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:04:46 -0600 Subject: [PATCH 09/28] chore(Session.cmd): Accept cmd as first positional argument --- src/libtmux/session.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index f69a5cfba..ff0a8c1dc 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -139,7 +139,7 @@ def panes(self) -> QueryList["Pane"]: # # Command # - def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: """Execute tmux subcommand against target session. See :meth:`server.cmd`. Returns @@ -156,15 +156,14 @@ def cmd(self, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: if not any("-t" in str(x) for x in args): # insert -t immediately after 1st arg, as per tmux format new_args: t.Tuple[str, ...] = () - new_args += (args[0],) assert isinstance(self.session_id, str) new_args += ( "-t", self.session_id, ) - new_args += tuple(args[1:]) + new_args += args args = new_args - return self.server.cmd(*args, **kwargs) + return self.server.cmd(cmd, *args, **kwargs) """ Commands (tmux-like) From 2dd6331339df057d19650cb3f7d0b3b5eb22f8b5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:11:48 -0600 Subject: [PATCH 10/28] Session(cmd): Remove **kwargs --- src/libtmux/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index ff0a8c1dc..6b83d11b5 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -139,7 +139,7 @@ def panes(self) -> QueryList["Pane"]: # # Command # - def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd(self, cmd: str, *args: t.Any) -> tmux_cmd: """Execute tmux subcommand against target session. See :meth:`server.cmd`. Returns @@ -163,7 +163,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: ) new_args += args args = new_args - return self.server.cmd(cmd, *args, **kwargs) + return self.server.cmd(cmd, *args) """ Commands (tmux-like) From a801351d3a573f1487a148adb836924d7f6e55fa Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:12:18 -0600 Subject: [PATCH 11/28] Server(cmd): Remove **kwargs --- src/libtmux/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 51b84d0d6..c49578ae7 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -173,7 +173,7 @@ def raise_if_dead(self) -> None: # # Command # - def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd(self, cmd: str, *args: t.Any) -> tmux_cmd: """Execute tmux command respective of socket name and file, return output. Examples @@ -231,7 +231,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: else: raise exc.UnknownColorOption() - return tmux_cmd(*cmd_args, **kwargs) + return tmux_cmd(*cmd_args) @property def attached_sessions(self) -> t.List[Session]: From c44225af7ca6bccf137c1beb8ca9564ad6838b01 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:14:09 -0600 Subject: [PATCH 12/28] Window(cmd): Remove **kwargs --- src/libtmux/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 4615c208e..97dc804e9 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -136,7 +136,11 @@ def panes(self) -> QueryList["Pane"]: Commands (pane-scoped) """ - def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd( + self, + cmd: str, + *args: t.Any, + ) -> tmux_cmd: """Execute tmux subcommand against target window. See :meth:`Server.cmd`. Send command to tmux with :attr:`window_id` as ``target-window``. @@ -147,7 +151,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: if not any("-t" in str(x) for x in args): args = ("-t", self.window_id, *args) - return self.server.cmd(cmd, *args, **kwargs) + return self.server.cmd(cmd, *args) """ Commands (tmux-like) From 20c4cd0ec3979b12c9f0eb43b8a93e968370e2a1 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:14:50 -0600 Subject: [PATCH 13/28] Pane(cmd): Remove **kwargs --- src/libtmux/pane.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 6704c07a0..6fa70b28c 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -112,7 +112,7 @@ def session(self) -> "Session": Commands (pane-scoped) """ - def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: + def cmd(self, cmd: str, *args: t.Any) -> tmux_cmd: """Execute tmux subcommand against target pane. See also: :meth:`Server.cmd`. Send command to tmux with :attr:`pane_id` as ``target-pane``. @@ -123,7 +123,7 @@ def cmd(self, cmd: str, *args: t.Any, **kwargs: t.Any) -> tmux_cmd: if not any("-t" in str(x) for x in args): args = ("-t", self.pane_id, *args) - return self.server.cmd(cmd, *args, **kwargs) + return self.server.cmd(cmd, *args) """ Commands (tmux-like) From d512f90f8dbb7003887bd9cbfd1fc5f1f0d4a78f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:17:51 -0600 Subject: [PATCH 14/28] Server.attached_sessions: Simplify by using filter --- src/libtmux/server.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/libtmux/server.py b/src/libtmux/server.py index c49578ae7..00763a28f 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -246,22 +246,7 @@ def attached_sessions(self) -> t.List[Session]: ------- list of :class:`Session` """ - try: - sessions = self.sessions - attached_sessions = [] - - for session in sessions: - attached = session.session_attached - # for now session_active is a unicode - if attached != "0": - logger.debug(f"session {session.name} attached") - attached_sessions.append(session) - else: - continue - - except Exception: - return [] - return attached_sessions + return self.sessions.filter(session_attached="1") def has_session(self, target_session: str, exact: bool = True) -> bool: """Return True if session exists. From eb3bc27d53852517e827081357d3a050e6475cd5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:25:54 -0600 Subject: [PATCH 15/28] Session.attached_window: Use .filter --- src/libtmux/session.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 6b83d11b5..434bb99f7 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -355,9 +355,7 @@ def select_window(self, target_window: t.Union[str, int]) -> "Window": @property def attached_window(self) -> "Window": """Return active :class:`Window` object.""" - active_windows = [ - window for window in self.windows if window.window_active == "1" - ] + active_windows = self.windows.filter(window_active="1") if len(active_windows) == 1: return next(iter(active_windows)) From 9d95438664db15fb0de43ae8d46ac12c31b85309 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:28:00 -0600 Subject: [PATCH 16/28] feat: Rename Session.attached_window -> Session.active_window Deprecate Session.attached_window --- src/libtmux/session.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 434bb99f7..601a509cd 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -353,7 +353,7 @@ def select_window(self, target_window: t.Union[str, int]) -> "Window": # Computed properties # @property - def attached_window(self) -> "Window": + def active_window(self) -> "Window": """Return active :class:`Window` object.""" active_windows = self.windows.filter(window_active="1") @@ -676,6 +676,24 @@ def name(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + @property + def attached_window(self) -> "Window": + """Return active :class:`Window` object. + + Notes + ----- + .. deprecated:: 0.31 + + Deprecated in favor of :meth:`.active_window`. + """ + warnings.warn( + "Session.attached_window() is deprecated in favor of " + + "Session.active_window()", + category=DeprecationWarning, + stacklevel=2, + ) + return self.active_window + def attach_session(self) -> "Session": """Return ``$ tmux attach-session`` aka alias: ``$ tmux attach``. From 9c67ae66f356cb8680a25b8ae9d6eff1786950e4 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:32:05 -0600 Subject: [PATCH 17/28] refactor(Session.active_window): Use renamed method --- README.md | 4 +-- conftest.py | 2 +- docs/quickstart.md | 6 ++-- docs/reference/properties.md | 2 +- docs/topics/traversal.md | 2 +- src/libtmux/server.py | 2 +- src/libtmux/session.py | 6 ++-- src/libtmux/test.py | 2 +- src/libtmux/window.py | 8 +++--- tests/test_dataclasses.py | 2 +- tests/test_pane.py | 10 +++---- tests/test_session.py | 26 ++++++++--------- tests/test_tmuxobject.py | 4 +-- tests/test_window.py | 54 ++++++++++++++++++------------------ 14 files changed, 65 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index a9c0d7e5e..af17af0ff 100644 --- a/README.md +++ b/README.md @@ -173,14 +173,14 @@ Window(@... 2:ha in the bg, Session($1 ...)) Close window: ```python ->>> w = session.attached_window +>>> w = session.active_window >>> w.kill() ``` Grab remaining tmux window: ```python ->>> window = session.attached_window +>>> window = session.active_window >>> window.split_window(attach=False) Pane(%2 Window(@1 1:... Session($1 ...))) ``` diff --git a/conftest.py b/conftest.py index d23acccb3..fdab7fd6d 100644 --- a/conftest.py +++ b/conftest.py @@ -41,7 +41,7 @@ def add_doctest_fixtures( doctest_namespace["server"] = request.getfixturevalue("server") session: "Session" = request.getfixturevalue("session") doctest_namespace["session"] = session - doctest_namespace["window"] = session.attached_window + doctest_namespace["window"] = session.active_window doctest_namespace["pane"] = session.attached_pane doctest_namespace["request"] = request diff --git a/docs/quickstart.md b/docs/quickstart.md index 583037489..93585d766 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -302,11 +302,11 @@ through active {class}`Window`'s. ## Manipulating windows -Now that we know how to create windows, let's use one. Let's use {meth}`Session.attached_window()` +Now that we know how to create windows, let's use one. Let's use {meth}`Session.active_window()` to grab our current window. ```python ->>> window = session.attached_window +>>> window = session.active_window ``` `window` now has access to all of the objects inside of {class}`Window`. @@ -320,7 +320,7 @@ Pane(%2 Window(@1 ...:..., Session($1 ...))) Powered up. Let's have a break down: -1. `window = session.attached_window()` gave us the {class}`Window` of the current attached to window. +1. `window = session.active_window()` gave us the {class}`Window` of the current attached to window. 2. `attach=False` assures the cursor didn't switch to the newly created pane. 3. Returned the created {class}`Pane`. diff --git a/docs/reference/properties.md b/docs/reference/properties.md index 6fd90ebcd..4098cb731 100644 --- a/docs/reference/properties.md +++ b/docs/reference/properties.md @@ -75,7 +75,7 @@ from libtmux.neo import Obj The same concepts apply for {class}`~libtmux.Window`: ```python ->>> window = session.attached_window +>>> window = session.active_window >>> window Window(@1 ...:..., Session($1 ...)) diff --git a/docs/topics/traversal.md b/docs/topics/traversal.md index 77ace139a..915d9a48c 100644 --- a/docs/topics/traversal.md +++ b/docs/topics/traversal.md @@ -71,7 +71,7 @@ Window(@1 ...:..., Session($1 ...)) Grab the currently focused window from session: ```python ->>> session.attached_window +>>> session.active_window Window(@1 ...:..., Session($1 ...)) ``` diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 00763a28f..7d4cc1b54 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -68,7 +68,7 @@ class Server(EnvironmentMixin): >>> server.sessions[0].windows [Window(@1 1:..., Session($1 ...)] - >>> server.sessions[0].attached_window + >>> server.sessions[0].active_window Window(@1 1:..., Session($1 ...)) >>> server.sessions[0].attached_pane diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 601a509cd..8f2abb1fb 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -52,7 +52,7 @@ class Session(Obj, EnvironmentMixin): >>> session.windows [Window(@1 ...:..., Session($1 ...)] - >>> session.attached_window + >>> session.active_window Window(@1 ...:..., Session($1 ...) >>> session.attached_pane @@ -347,7 +347,7 @@ def select_window(self, target_window: t.Union[str, int]) -> "Window": if proc.stderr: raise exc.LibTmuxException(proc.stderr) - return self.attached_window + return self.active_window # # Computed properties @@ -631,7 +631,7 @@ def kill_window(self, target_window: t.Optional[str] = None) -> None: @property def attached_pane(self) -> t.Optional["Pane"]: """Return active :class:`Pane` object.""" - return self.attached_window.attached_pane + return self.active_window.attached_pane # # Dunder diff --git a/src/libtmux/test.py b/src/libtmux/test.py index 90b1c95ad..3cabb8f33 100644 --- a/src/libtmux/test.py +++ b/src/libtmux/test.py @@ -85,7 +85,7 @@ def retry_until( Examples -------- >>> def fn(): - ... p = session.attached_window.attached_pane + ... p = session.active_window.attached_pane ... return p.pane_current_path is not None >>> retry_until(fn) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 97dc804e9..ebbb264ab 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -61,10 +61,10 @@ class Window(Obj): >>> window.session Session(...) - >>> window.window_id == session.attached_window.window_id + >>> window.window_id == session.active_window.window_id True - >>> window == session.attached_window + >>> window == session.active_window True >>> window in session.windows @@ -577,7 +577,7 @@ def rename_window(self, new_name: str) -> "Window": Examples -------- - >>> window = session.attached_window + >>> window = session.active_window >>> window.rename_window('My project') Window(@1 1:My project, Session($1 ...)) @@ -701,7 +701,7 @@ def select(self) -> "Window": Examples -------- - >>> window = session.attached_window + >>> window = session.active_window >>> new_window = session.new_window() >>> session.refresh() >>> active_windows = [w for w in session.windows if w.window_active == '1'] diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py index 65f80faf4..a3e3c627f 100644 --- a/tests/test_dataclasses.py +++ b/tests/test_dataclasses.py @@ -39,7 +39,7 @@ def test_pane( assert __session is not None - __window = __session.attached_window + __window = __session.active_window __window.split_window() __pane = __window.split_window() __window.select_layout("main-vertical") diff --git a/tests/test_pane.py b/tests/test_pane.py index f05528a9e..b2d750d2b 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -13,7 +13,7 @@ def test_send_keys(session: Session) -> None: """Verify Pane.send_keys().""" - pane = session.attached_window.attached_pane + pane = session.active_window.attached_pane assert pane is not None pane.send_keys("c-c", literal=True) @@ -66,7 +66,7 @@ def test_capture_pane(session: Session) -> None: window_name="capture_pane", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.attached_window.attached_pane + pane = session.active_window.attached_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" @@ -91,7 +91,7 @@ def test_capture_pane_start(session: Session) -> None: window_name="capture_pane_start", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.attached_window.attached_pane + pane = session.active_window.attached_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" @@ -119,7 +119,7 @@ def test_capture_pane_end(session: Session) -> None: window_name="capture_pane_end", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.attached_window.attached_pane + pane = session.active_window.attached_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" @@ -142,7 +142,7 @@ def test_resize_pane( """Verify resizing window.""" session.cmd("detach-client", "-s") - window = session.attached_window + window = session.active_window pane = window.split_window(attach=False) window.split_window(vertical=True, attach=False) diff --git a/tests/test_session.py b/tests/test_session.py index 6be7ac10f..3349fee86 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -31,7 +31,7 @@ def test_select_window(session: Session) -> None: """Session.select_window moves window.""" # get the current window_base_index, since different user tmux config # may start at 0 or 1, or whatever they want. - window_idx = session.attached_window.window_index + window_idx = session.active_window.window_index assert window_idx is not None window_base_index = int(window_idx) @@ -42,22 +42,22 @@ def test_select_window(session: Session) -> None: assert len(session.windows) == window_count - # tmux selects a window, moves to it, shows it as attached_window + # tmux selects a window, moves to it, shows it as active_window selected_window1 = session.select_window(window_base_index) assert isinstance(selected_window1, Window) - attached_window1 = session.attached_window + active_window1 = session.active_window - assert selected_window1 == attached_window1 - assert selected_window1.__dict__ == attached_window1.__dict__ + assert selected_window1 == active_window1 + assert selected_window1.__dict__ == active_window1.__dict__ # again: tmux selects a window, moves to it, shows it as - # attached_window + # active_window selected_window2 = session.select_window(window_base_index + 1) assert isinstance(selected_window2, Window) - attached_window2 = session.attached_window + active_window2 = session.active_window - assert selected_window2 == attached_window2 - assert selected_window2.__dict__ == attached_window2.__dict__ + assert selected_window2 == active_window2 + assert selected_window2.__dict__ == active_window2.__dict__ # assure these windows were really different assert selected_window1 != selected_window2 @@ -69,16 +69,16 @@ def test_select_window_returns_Window(session: Session) -> None: window_count = len(session.windows) assert len(session.windows) == window_count - window_idx = session.attached_window.window_index + window_idx = session.active_window.window_index assert window_idx is not None window_base_index = int(window_idx) window = session.select_window(window_base_index) assert isinstance(window, Window) -def test_attached_window(session: Session) -> None: - """Session.attached_window returns Window.""" - assert isinstance(session.attached_window, Window) +def test_active_window(session: Session) -> None: + """Session.active_window returns Window.""" + assert isinstance(session.active_window, Window) def test_attached_pane(session: Session) -> None: diff --git a/tests/test_tmuxobject.py b/tests/test_tmuxobject.py index 8042aacb8..27c02619c 100644 --- a/tests/test_tmuxobject.py +++ b/tests/test_tmuxobject.py @@ -97,7 +97,7 @@ def test_find_where_multiple_infos(server: Server, session: Session) -> None: def test_where(server: Server, session: Session) -> None: """Test self.where() returns matching objects.""" - window = session.attached_window + window = session.active_window window.split_window() # create second pane for session in server.sessions: @@ -153,7 +153,7 @@ def test_where(server: Server, session: Session) -> None: def test_filter(server: Server) -> None: """Test self.filter() retrieves child object.""" sess = server.new_session("test") - window = sess.attached_window + window = sess.active_window window.split_window() # create second pane diff --git a/tests/test_window.py b/tests/test_window.py index 4d1e71e78..60fb3e692 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -25,63 +25,63 @@ def test_select_window(session: Session) -> None: # for now however, let's get the index from the first window. assert window_count == 1 - assert session.attached_window.window_index is not None - window_base_index = int(session.attached_window.window_index) + assert session.active_window.window_index is not None + window_base_index = int(session.active_window.window_index) window = session.new_window(window_name="testing 3") # self.assertEqual(2, - # int(session.attached_window.index)) + # int(session.active_window.index)) assert window.window_index is not None assert int(window_base_index) + 1 == int(window.window_index) session.select_window(str(window_base_index)) - assert window_base_index == int(session.attached_window.window_index) + assert window_base_index == int(session.active_window.window_index) session.select_window("testing 3") - assert session.attached_window.window_index is not None - assert int(window_base_index) + 1 == int(session.attached_window.window_index) + assert session.active_window.window_index is not None + assert int(window_base_index) + 1 == int(session.active_window.window_index) assert len(session.windows) == 2 def test_fresh_window_data(session: Session) -> None: """Verify window data is fresh.""" - attached_window = session.attached_window - assert attached_window is not None - pane_base_idx = attached_window.show_window_option("pane-base-index", g=True) + active_window = session.active_window + assert active_window is not None + pane_base_idx = active_window.show_window_option("pane-base-index", g=True) assert pane_base_idx is not None pane_base_index = int(pane_base_idx) assert len(session.windows) == 1 - assert len(session.attached_window.panes) == 1 + assert len(session.active_window.panes) == 1 current_windows = len(session.windows) assert session.session_id != "@0" assert current_windows == 1 - assert len(session.attached_window.panes) == 1 + assert len(session.active_window.panes) == 1 assert isinstance(session.server, Server) - # len(session.attached_window.panes)) + # len(session.active_window.panes)) assert len(session.windows), 1 - assert len(session.attached_window.panes) == 1 + assert len(session.active_window.panes) == 1 for w in session.windows: assert isinstance(w, Window) - window = session.attached_window + window = session.active_window assert isinstance(window, Window) - assert len(session.attached_window.panes) == 1 + assert len(session.active_window.panes) == 1 window.split_window() - attached_window = session.attached_window - assert attached_window is not None - attached_window.select_pane(pane_base_index) + active_window = session.active_window + assert active_window is not None + active_window.select_pane(pane_base_index) attached_pane = session.attached_pane assert attached_pane is not None attached_pane.send_keys("cd /srv/www/flaskr") - attached_window.select_pane(pane_base_index + 1) + active_window.select_pane(pane_base_index + 1) attached_pane = session.attached_pane assert attached_pane is not None attached_pane.send_keys("source .venv/bin/activate") @@ -113,8 +113,8 @@ def test_newest_pane_data(session: Session) -> None: def test_attached_pane(session: Session) -> None: - """Window.attached_window returns active Pane.""" - window = session.attached_window # current window + """Window.active_window returns active Pane.""" + window = session.active_window # current window assert isinstance(window.attached_pane, Pane) @@ -235,16 +235,16 @@ def test_window_rename( session.set_option("automatic-rename", "off") window = session.new_window(window_name=window_name_before, attach=True) - assert window == session.attached_window + assert window == session.active_window assert window.window_name == window_name_before window.rename_window(window_name_after) - window = session.attached_window + window = session.active_window assert window.window_name == window_name_after - window = session.attached_window + window = session.active_window assert window.window_name == window_name_after @@ -255,7 +255,7 @@ def test_kill_window(session: Session) -> None: # create a second window to not kick out the client. # there is another way to do this via options too. - w = session.attached_window + w = session.active_window assert w.window_id is not None @@ -379,7 +379,7 @@ def test_empty_window_name(session: Session) -> None: session.set_option("automatic-rename", "off") window = session.new_window(window_name="''", attach=True) - assert window == session.attached_window + assert window == session.active_window assert window.window_name == "''" assert session.session_name is not None @@ -458,7 +458,7 @@ def test_resize( """Verify resizing window.""" session.cmd("detach-client", "-s") - window = session.attached_window + window = session.active_window window_height_adjustment = 10 assert window.window_height is not None From 3b7aa9f5b11343834054399343afcd37b2d5333e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:35:01 -0600 Subject: [PATCH 18/28] Window.attached_pane: Use .filter --- src/libtmux/window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index ebbb264ab..b11ff58ea 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -730,9 +730,9 @@ def select(self) -> "Window": @property def attached_pane(self) -> t.Optional["Pane"]: """Return attached :class:`Pane`.""" - for pane in self.panes: - if pane.pane_active == "1": - return pane + panes = self.panes.filter(pane_active="1") + if len(panes) > 0: + return panes[0] return None # From e35e3d9ef879d65695f219c3188574ef0a34f528 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:37:05 -0600 Subject: [PATCH 19/28] feat: Rename Window.attached_pane -> Window.active_pane Deprecate Window.attached_pane --- src/libtmux/window.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index b11ff58ea..98253bebe 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -728,7 +728,7 @@ def select(self) -> "Window": # Computed properties # @property - def attached_pane(self) -> t.Optional["Pane"]: + def active_pane(self) -> t.Optional["Pane"]: """Return attached :class:`Pane`.""" panes = self.panes.filter(pane_active="1") if len(panes) > 0: @@ -820,6 +820,26 @@ def width(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + @property + def attached_pane(self) -> t.Optional["Pane"]: + """Return attached :class:`Pane`. + + Notes + ----- + .. deprecated:: 0.31 + + Deprecated in favor of :meth:`.active_pane`. + """ + warnings.warn( + "Window.attached_pane() is deprecated in favor of Window.active_pane()", + category=DeprecationWarning, + stacklevel=2, + ) + panes = self.panes.filter(pane_active="1") + if len(panes) > 0: + return panes[0] + return None + def select_window(self) -> "Window": """Select window. From bc4a971126e99298318de9224cc536e47d0dd625 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:38:15 -0600 Subject: [PATCH 20/28] Session: Consolidate duplicate computed property (attached_pane) --- src/libtmux/session.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 8f2abb1fb..eb538fa84 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -352,6 +352,11 @@ def select_window(self, target_window: t.Union[str, int]) -> "Window": # # Computed properties # + @property + def attached_pane(self) -> t.Optional["Pane"]: + """Return active :class:`Pane` object.""" + return self.active_window.attached_pane + @property def active_window(self) -> "Window": """Return active :class:`Window` object.""" @@ -625,14 +630,6 @@ def kill_window(self, target_window: t.Optional[str] = None) -> None: if proc.stderr: raise exc.LibTmuxException(proc.stderr) - # - # Computed properties - # - @property - def attached_pane(self) -> t.Optional["Pane"]: - """Return active :class:`Pane` object.""" - return self.active_window.attached_pane - # # Dunder # From 5cede5ae94238a906c048e526f812c617accfa3b Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:40:26 -0600 Subject: [PATCH 21/28] feat: Rename Session.attached_pane -> Session.active_pane Deprecate Session.attached_pane --- src/libtmux/session.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index eb538fa84..4436b710b 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -353,9 +353,9 @@ def select_window(self, target_window: t.Union[str, int]) -> "Window": # Computed properties # @property - def attached_pane(self) -> t.Optional["Pane"]: + def active_pane(self) -> t.Optional["Pane"]: """Return active :class:`Pane` object.""" - return self.active_window.attached_pane + return self.active_window.active_pane @property def active_window(self) -> "Window": @@ -673,6 +673,23 @@ def name(self) -> t.Optional[str]: # # Legacy: Redundant stuff we want to remove # + @property + def attached_pane(self) -> t.Optional["Pane"]: + """Return active :class:`Pane` object. + + Notes + ----- + .. deprecated:: 0.31 + + Deprecated in favor of :meth:`.active_pane`. + """ + warnings.warn( + "Session.attached_pane() is deprecated in favor of Session.active_pane()", + category=DeprecationWarning, + stacklevel=2, + ) + return self.active_window.active_pane + @property def attached_window(self) -> "Window": """Return active :class:`Window` object. From d75d5ff9a9b7a6bed268ec9bf3c985568aaf5c1d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:46:20 -0600 Subject: [PATCH 22/28] refactor({Window,Session}.active_pane): Use renamed method --- conftest.py | 2 +- docs/reference/properties.md | 2 +- docs/topics/traversal.md | 6 +++--- src/libtmux/pane.py | 2 +- src/libtmux/server.py | 2 +- src/libtmux/session.py | 2 +- src/libtmux/test.py | 2 +- src/libtmux/window.py | 4 ++-- tests/test_pane.py | 12 ++++++------ tests/test_session.py | 8 ++++---- tests/test_window.py | 16 ++++++++-------- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/conftest.py b/conftest.py index fdab7fd6d..187ee3e03 100644 --- a/conftest.py +++ b/conftest.py @@ -42,7 +42,7 @@ def add_doctest_fixtures( session: "Session" = request.getfixturevalue("session") doctest_namespace["session"] = session doctest_namespace["window"] = session.active_window - doctest_namespace["pane"] = session.attached_pane + doctest_namespace["pane"] = session.active_pane doctest_namespace["request"] = request diff --git a/docs/reference/properties.md b/docs/reference/properties.md index 4098cb731..d937f556c 100644 --- a/docs/reference/properties.md +++ b/docs/reference/properties.md @@ -109,7 +109,7 @@ Use attribute access for details not accessible via properties: Get the {class}`~libtmux.Pane`: ```python ->>> pane = window.attached_pane +>>> pane = window.active_pane >>> pane Pane(%1 Window(@1 ...:..., Session($1 libtmux_...))) diff --git a/docs/topics/traversal.md b/docs/topics/traversal.md index 915d9a48c..a6ab4d6f7 100644 --- a/docs/topics/traversal.md +++ b/docs/topics/traversal.md @@ -78,20 +78,20 @@ Window(@1 ...:..., Session($1 ...)) Grab the currently focused {class}`Pane` from session: ```python ->>> session.attached_pane +>>> session.active_pane Pane(%1 Window(@1 ...:..., Session($1 ...))) ``` Assign the attached {class}`~libtmux.Pane` to `p`: ```python ->>> p = session.attached_pane +>>> p = session.active_pane ``` Access the window/server of a pane: ```python ->>> p = session.attached_pane +>>> p = session.active_pane >>> p.window Window(@1 ...:..., Session($1 ...)) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 6fa70b28c..b2b545e1b 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -423,7 +423,7 @@ def select(self) -> "Pane": Examples -------- - >>> pane = window.attached_pane + >>> pane = window.active_pane >>> new_pane = window.split_window() >>> pane.refresh() >>> active_panes = [p for p in window.panes if p.pane_active == '1'] diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 7d4cc1b54..1bf77a44f 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -71,7 +71,7 @@ class Server(EnvironmentMixin): >>> server.sessions[0].active_window Window(@1 1:..., Session($1 ...)) - >>> server.sessions[0].attached_pane + >>> server.sessions[0].active_pane Pane(%1 Window(@1 1:..., Session($1 ...))) References diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 4436b710b..39c014186 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -55,7 +55,7 @@ class Session(Obj, EnvironmentMixin): >>> session.active_window Window(@1 ...:..., Session($1 ...) - >>> session.attached_pane + >>> session.active_pane Pane(%1 Window(@1 ...:..., Session($1 ...))) References diff --git a/src/libtmux/test.py b/src/libtmux/test.py index 3cabb8f33..b343db25c 100644 --- a/src/libtmux/test.py +++ b/src/libtmux/test.py @@ -85,7 +85,7 @@ def retry_until( Examples -------- >>> def fn(): - ... p = session.active_window.attached_pane + ... p = session.active_window.active_pane ... return p.pane_current_path is not None >>> retry_until(fn) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 98253bebe..d5f05e70a 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -53,7 +53,7 @@ class Window(Obj): >>> window.panes [Pane(...)] - >>> window.attached_pane + >>> window.active_pane Pane(...) Relations moving up: @@ -179,7 +179,7 @@ def select_pane(self, target_pane: t.Union[str, int]) -> t.Optional["Pane"]: if proc.stderr: raise exc.LibTmuxException(proc.stderr) - return self.attached_pane + return self.active_pane def split_window( self, diff --git a/tests/test_pane.py b/tests/test_pane.py index b2d750d2b..10bf63e0a 100644 --- a/tests/test_pane.py +++ b/tests/test_pane.py @@ -13,7 +13,7 @@ def test_send_keys(session: Session) -> None: """Verify Pane.send_keys().""" - pane = session.active_window.attached_pane + pane = session.active_window.active_pane assert pane is not None pane.send_keys("c-c", literal=True) @@ -28,7 +28,7 @@ def test_set_height(session: Session) -> None: """Verify Pane.set_height().""" window = session.new_window(window_name="test_set_height") window.split_window() - pane1 = window.attached_pane + pane1 = window.active_pane assert pane1 is not None pane1_height = pane1.pane_height @@ -44,7 +44,7 @@ def test_set_width(session: Session) -> None: window.split_window() window.select_layout("main-vertical") - pane1 = window.attached_pane + pane1 = window.active_pane assert pane1 is not None pane1_width = pane1.pane_width @@ -66,7 +66,7 @@ def test_capture_pane(session: Session) -> None: window_name="capture_pane", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.active_window.attached_pane + pane = session.active_window.active_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" @@ -91,7 +91,7 @@ def test_capture_pane_start(session: Session) -> None: window_name="capture_pane_start", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.active_window.attached_pane + pane = session.active_window.active_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" @@ -119,7 +119,7 @@ def test_capture_pane_end(session: Session) -> None: window_name="capture_pane_end", window_shell=f"{env} PS1='$ ' sh", ) - pane = session.active_window.attached_pane + pane = session.active_window.active_pane assert pane is not None pane_contents = "\n".join(pane.capture_pane()) assert pane_contents == "$" diff --git a/tests/test_session.py b/tests/test_session.py index 3349fee86..4b27cc248 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -81,9 +81,9 @@ def test_active_window(session: Session) -> None: assert isinstance(session.active_window, Window) -def test_attached_pane(session: Session) -> None: - """Session.attached_pane returns Pane.""" - assert isinstance(session.attached_pane, Pane) +def test_active_pane(session: Session) -> None: + """Session.active_pane returns Pane.""" + assert isinstance(session.active_pane, Pane) def test_session_rename(session: Session) -> None: @@ -297,7 +297,7 @@ def test_new_window_with_environment( window_shell=f"{env} PS1='$ ' sh", environment=environment, ) - pane = window.attached_pane + pane = window.active_pane assert pane is not None for k, v in environment.items(): pane.send_keys(f"echo ${k}") diff --git a/tests/test_window.py b/tests/test_window.py index 60fb3e692..0f137415c 100644 --- a/tests/test_window.py +++ b/tests/test_window.py @@ -77,14 +77,14 @@ def test_fresh_window_data(session: Session) -> None: assert active_window is not None active_window.select_pane(pane_base_index) - attached_pane = session.attached_pane - assert attached_pane is not None - attached_pane.send_keys("cd /srv/www/flaskr") + active_pane = session.active_pane + assert active_pane is not None + active_pane.send_keys("cd /srv/www/flaskr") active_window.select_pane(pane_base_index + 1) - attached_pane = session.attached_pane - assert attached_pane is not None - attached_pane.send_keys("source .venv/bin/activate") + active_pane = session.active_pane + assert active_pane is not None + active_pane.send_keys("source .venv/bin/activate") session.new_window(window_name="second") current_windows += 1 assert current_windows == len(session.windows) @@ -112,10 +112,10 @@ def test_newest_pane_data(session: Session) -> None: assert len(window.panes) == 3 -def test_attached_pane(session: Session) -> None: +def test_active_pane(session: Session) -> None: """Window.active_window returns active Pane.""" window = session.active_window # current window - assert isinstance(window.attached_pane, Pane) + assert isinstance(window.active_pane, Pane) def test_split_window(session: Session) -> None: From 01ab013770146cbbdf2a97065ac9d98a76970382 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:47:08 -0600 Subject: [PATCH 23/28] refactor!(tmux_cmd): Remove kwargs --- src/libtmux/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtmux/common.py b/src/libtmux/common.py index a5b91a930..d72a9fa30 100644 --- a/src/libtmux/common.py +++ b/src/libtmux/common.py @@ -215,7 +215,7 @@ class tmux_cmd: Renamed from ``tmux`` to ``tmux_cmd``. """ - def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + def __init__(self, *args: t.Any) -> None: tmux_bin = shutil.which("tmux") if not tmux_bin: raise exc.TmuxCommandNotFound() From 25e45fd3c997a2679008a0438c6af8426afd1c2d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:53:36 -0600 Subject: [PATCH 24/28] docs(Session.cmd): Add examples --- src/libtmux/session.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 39c014186..28a3ba4c1 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -140,11 +140,25 @@ def panes(self) -> QueryList["Pane"]: # Command # def cmd(self, cmd: str, *args: t.Any) -> tmux_cmd: - """Execute tmux subcommand against target session. See :meth:`server.cmd`. + """Execute tmux subcommand within session context. + + Automatically adds ``-t`` for object's session ID to the command. Pass ``-t`` + in args to override. + + Examples + -------- + >>> session.cmd('new-window', '-P').stdout[0] + 'libtmux...:....0' + + From raw output to an enriched `Window` object: + + >>> Window.from_window_id(window_id=session.cmd( + ... 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) + Window(@... ...:..., Session($1 libtmux_...)) Returns ------- - :class:`server.cmd` + :meth:`server.cmd` Notes ----- From 702d5309f4a245cb035a86c92c634b1f279ca69f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:55:13 -0600 Subject: [PATCH 25/28] docs(Window.cmd): Add examples --- src/libtmux/window.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libtmux/window.py b/src/libtmux/window.py index d5f05e70a..1e2ce9569 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -141,12 +141,27 @@ def cmd( cmd: str, *args: t.Any, ) -> tmux_cmd: - """Execute tmux subcommand against target window. See :meth:`Server.cmd`. + """Execute tmux subcommand within window context. - Send command to tmux with :attr:`window_id` as ``target-window``. + Automatically adds ``-t`` for object's indow ID to the command. Pass ``-t`` + in args to override. - Specifying ``('-t', 'custom-target')`` or ``('-tcustom_target')`` in - ``args`` will override using the object's ``window_id`` as target. + Examples + -------- + Create a pane from a window: + + >>> window.cmd('split-window', '-P', '-F#{pane_id}').stdout[0] + '%...' + + Magic, directly to a `Pane`: + + >>> Pane.from_pane_id(pane_id=session.cmd( + ... 'split-window', '-P', '-F#{pane_id}').stdout[0], server=session.server) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + + Returns + ------- + :meth:`server.cmd` """ if not any("-t" in str(x) for x in args): args = ("-t", self.window_id, *args) From bcae86af2761b6fce1c2c16ff5550dd53d1fc424 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 04:57:59 -0600 Subject: [PATCH 26/28] docs(Pane.cmd): Add examples --- src/libtmux/pane.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index b2b545e1b..fbe7cee9b 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -113,12 +113,25 @@ def session(self) -> "Session": """ def cmd(self, cmd: str, *args: t.Any) -> tmux_cmd: - """Execute tmux subcommand against target pane. See also: :meth:`Server.cmd`. + """Execute tmux subcommand within pane context. - Send command to tmux with :attr:`pane_id` as ``target-pane``. + Automatically adds ``-t`` for object's pane ID to the command. Pass ``-t`` + in args to override. - Specifying ``('-t', 'custom-target')`` or ``('-tcustom_target')`` in - ``args`` will override using the object's ``pane_id`` as target. + Examples + -------- + >>> pane.cmd('split-window', '-P').stdout[0] + 'libtmux...:...' + + From raw output to an enriched `Pane` object: + + >>> Pane.from_pane_id(pane_id=pane.cmd( + ... 'split-window', '-P', '-F#{pane_id}').stdout[0], server=pane.server) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + + Returns + ------- + :meth:`server.cmd` """ if not any("-t" in str(x) for x in args): args = ("-t", self.pane_id, *args) From 0ade87a0e37493c6fa98d6af3ff3725a36ad82b4 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 05:45:50 -0600 Subject: [PATCH 27/28] docs(CHANGES): Typo --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index cf1c3e8b0..0f425f2c7 100644 --- a/CHANGES +++ b/CHANGES @@ -164,7 +164,7 @@ Tip: If {meth}`Pane.resize()` was not taking affect <= 0.27.1, try to resize wit ## libtmux 0.26.0 (2024-02-06) -### Breaking change +### Breaking changes - `get_by_id()` (already deprecated) keyword argument renamed from `id` to `Server.get_by_id(session_id)`, `Session.get_by_id(window_id)`, and `Window.get_by_id(pane_id)` (#514) From e7eb5a503f6658cb8dd45c1275cd993288779996 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 17 Feb 2024 06:34:42 -0600 Subject: [PATCH 28/28] docs(CHANGES): Note command updates --- CHANGES | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 0f425f2c7..5cb4c75de 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,34 @@ $ pip install --user --upgrade --pre libtmux +### Cleanups (#527) + +- Streamline `{Server,Session,Window,Pane}.cmd()`, across all usages to: + - Use `cmd: str` as first positional + - Removed unused keyword arguments `**kwargs` + +### Renamings (#527) + +- `Session.attached_window` renamed to {meth}`Session.active_window` + - `Session.attached_window` deprecated +- `Session.attached_pane` renamed to {meth}`Session.active_pane` + - `Session.attached_pane` deprecated +- `Window.attached_pane` renamed to {meth}`Window.active_pane` + - `Window.attached_pane` deprecated + +### Improvements (#527) + +- `Server.attached_windows` now users `QueryList`'s `.filter()` + +### Documentation (#527) + +- Document `.cmd` in README and quickstart +- Add doctests and improve docstrings to `cmd()` methods across: + - {meth}`Server.cmd()` + - {meth}`Session.cmd()` + - {meth}`Window.cmd()` + - {meth}`Pane.cmd()` + ## libtmux 0.30.2 (2024-02-16) ### Development @@ -80,7 +108,7 @@ _Maintenance only, no bug fixes or new features_ ## libtmux 0.28.0 (2024-02-14) -### Breaking change +### Breaking changes #### Detached / unselected by default (#523)