From 1d31a1f76366d7e03065cd2ac96e3906a347adab Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Wed, 25 Dec 2024 07:00:30 -0600 Subject: [PATCH] Server,Session,Window,Pane: Add `.acmd` --- src/libtmux/pane.py | 49 ++++++++++++++++++++- src/libtmux/server.py | 97 ++++++++++++++++++++++++++++++++++++++++-- src/libtmux/session.py | 57 +++++++++++++++++++++++++ src/libtmux/window.py | 51 +++++++++++++++++++++- 4 files changed, 249 insertions(+), 5 deletions(-) diff --git a/src/libtmux/pane.py b/src/libtmux/pane.py index 9586e68e5..a15df2558 100644 --- a/src/libtmux/pane.py +++ b/src/libtmux/pane.py @@ -12,7 +12,7 @@ import warnings from typing import overload -from libtmux.common import has_gte_version, has_lt_version, tmux_cmd +from libtmux.common import AsyncTmuxCmd, has_gte_version, has_lt_version, tmux_cmd from libtmux.constants import ( PANE_DIRECTION_FLAG_MAP, RESIZE_ADJUSTMENT_DIRECTION_FLAG_MAP, @@ -153,6 +153,53 @@ def cmd( return self.server.cmd(cmd, *args, target=target) + async def acmd( + self, + cmd: str, + *args: t.Any, + target: t.Optional[t.Union[str, int]] = None, + ) -> AsyncTmuxCmd: + """Execute tmux subcommand within pane context. + + Automatically binds target by adding ``-t`` for object's pane ID to the + command. Pass ``target`` to keyword arguments to override. + + Examples + -------- + >>> import asyncio + >>> async def test_acmd(): + ... result = await pane.acmd('split-window', '-P') + ... print(result.stdout[0]) + >>> asyncio.run(test_acmd()) + libtmux...:... + + From raw output to an enriched `Pane` object: + + >>> async def test_from_pane(): + ... pane_id_result = await pane.acmd( + ... 'split-window', '-P', '-F#{pane_id}' + ... ) + ... return Pane.from_pane_id( + ... pane_id=pane_id_result.stdout[0], + ... server=session.server + ... ) + >>> asyncio.run(test_from_pane()) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + + Parameters + ---------- + target : str, optional + Optional custom target override. By default, the target is the pane ID. + + Returns + ------- + :meth:`server.cmd` + """ + if target is None: + target = self.pane_id + + return await self.server.acmd(cmd, *args, target=target) + """ Commands (tmux-like) """ diff --git a/src/libtmux/server.py b/src/libtmux/server.py index 89ea2b90b..9e58c6ddf 100644 --- a/src/libtmux/server.py +++ b/src/libtmux/server.py @@ -22,6 +22,7 @@ from . import exc, formats from .common import ( + AsyncTmuxCmd, EnvironmentMixin, PaneDict, SessionDict, @@ -197,8 +198,12 @@ def cmd( Output of `tmux -L ... new-window -P -F#{window_id}` to a `Window` object: - >>> Window.from_window_id(window_id=session.cmd( - ... 'new-window', '-P', '-F#{window_id}').stdout[0], server=session.server) + >>> 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: @@ -209,7 +214,9 @@ def cmd( Output of `tmux -L ... split-window -P -F#{pane_id}` to a `Pane` object: >>> Pane.from_pane_id(pane_id=window.cmd( - ... 'split-window', '-P', '-F#{pane_id}').stdout[0], server=window.server) + ... 'split-window', '-P', '-F#{pane_id}').stdout[0], + ... server=window.server + ... ) Pane(%... Window(@... ...:..., Session($1 libtmux_...))) Parameters @@ -247,6 +254,90 @@ def cmd( return tmux_cmd(*svr_args, *cmd_args) + async def acmd( + self, + cmd: str, + *args: t.Any, + target: t.Optional[t.Union[str, int]] = None, + ) -> AsyncTmuxCmd: + """Execute tmux command respective of socket name and file, return output. + + Examples + -------- + >>> import asyncio + >>> async def test_acmd(): + ... result = await server.acmd('display-message', 'hi') + ... print(result.stdout) + >>> asyncio.run(test_acmd()) + [] + + New session: + + >>> async def test_new_session(): + ... result = await server.acmd( + ... 'new-session', '-d', '-P', '-F#{session_id}' + ... ) + ... print(result.stdout[0]) + >>> asyncio.run(test_new_session()) + $... + + Output of `tmux -L ... new-window -P -F#{window_id}` to a `Window` object: + + >>> async def test_new_window(): + ... result = await session.acmd('new-window', '-P', '-F#{window_id}') + ... window_id = result.stdout[0] + ... window = Window.from_window_id(window_id=window_id, server=server) + ... print(window) + >>> asyncio.run(test_new_window()) + Window(@... ...:..., Session($... libtmux_...)) + + Create a pane from a window: + + >>> async def test_split_window(): + ... result = await server.acmd('split-window', '-P', '-F#{pane_id}') + ... print(result.stdout[0]) + >>> asyncio.run(test_split_window()) + %... + + Output of `tmux -L ... split-window -P -F#{pane_id}` to a `Pane` object: + + >>> async def test_pane(): + ... result = await window.acmd('split-window', '-P', '-F#{pane_id}') + ... pane_id = result.stdout[0] + ... pane = Pane.from_pane_id(pane_id=pane_id, server=server) + ... print(pane) + >>> asyncio.run(test_pane()) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + + Parameters + ---------- + target : str, optional + Optional custom target. + + Returns + ------- + :class:`common.AsyncTmuxCmd` + """ + svr_args: list[t.Union[str, int]] = [cmd] + cmd_args: list[t.Union[str, int]] = [] + if self.socket_name: + svr_args.insert(0, f"-L{self.socket_name}") + if self.socket_path: + svr_args.insert(0, f"-S{self.socket_path}") + if self.config_file: + svr_args.insert(0, f"-f{self.config_file}") + if self.colors: + if self.colors == 256: + svr_args.insert(0, "-2") + elif self.colors == 88: + svr_args.insert(0, "-8") + else: + raise exc.UnknownColorOption + + cmd_args = ["-t", str(target), *args] if target is not None else [*args] + + return await AsyncTmuxCmd.run(*svr_args, *cmd_args) + @property def attached_sessions(self) -> list[Session]: """Return active :class:`Session`s. diff --git a/src/libtmux/session.py b/src/libtmux/session.py index 785459fd1..d8d289c5b 100644 --- a/src/libtmux/session.py +++ b/src/libtmux/session.py @@ -21,6 +21,7 @@ from . import exc from .common import ( + AsyncTmuxCmd, EnvironmentMixin, WindowDict, handle_option_error, @@ -186,6 +187,62 @@ def cmd( target = self.session_id return self.server.cmd(cmd, *args, target=target) + async def acmd( + self, + cmd: str, + *args: t.Any, + target: t.Optional[t.Union[str, int]] = None, + ) -> AsyncTmuxCmd: + """Execute tmux subcommand within session context. + + Automatically binds target by adding ``-t`` for object's session ID to the + command. Pass ``target`` to keyword arguments to override. + + Examples + -------- + >>> import asyncio + >>> async def test_acmd(): + ... result = await session.acmd('new-window', '-P') + ... print(result.stdout[0]) + >>> asyncio.run(test_acmd()) + libtmux...:....0 + + From raw output to an enriched `Window` object: + + >>> async def test_from_window(): + ... window_id_result = await session.acmd( + ... 'new-window', '-P', '-F#{window_id}' + ... ) + ... return Window.from_window_id( + ... window_id=window_id_result.stdout[0], + ... server=session.server + ... ) + >>> asyncio.run(test_from_window()) + Window(@... ...:..., Session($1 libtmux_...)) + + Parameters + ---------- + target : str, optional + Optional custom target override. By default, the target is the session ID. + + Returns + ------- + :meth:`server.cmd` + + Notes + ----- + .. versionchanged:: 0.34 + + Passing target by ``-t`` is ignored. Use ``target`` keyword argument instead. + + .. versionchanged:: 0.8 + + Renamed from ``.tmux`` to ``.cmd``. + """ + if target is None: + target = self.session_id + return await self.server.acmd(cmd, *args, target=target) + """ Commands (tmux-like) """ diff --git a/src/libtmux/window.py b/src/libtmux/window.py index 82b501cdf..51b8a034d 100644 --- a/src/libtmux/window.py +++ b/src/libtmux/window.py @@ -23,7 +23,7 @@ from libtmux.pane import Pane from . import exc -from .common import PaneDict, WindowOptionDict, handle_option_error +from .common import AsyncTmuxCmd, PaneDict, WindowOptionDict, handle_option_error if t.TYPE_CHECKING: from .server import Server @@ -175,6 +175,55 @@ def cmd( return self.server.cmd(cmd, *args, target=target) + async def acmd( + self, + cmd: str, + *args: t.Any, + target: t.Optional[t.Union[str, int]] = None, + ) -> AsyncTmuxCmd: + """Execute tmux subcommand within window context. + + Automatically binds target by adding ``-t`` for object's window ID to the + command. Pass ``target`` to keyword arguments to override. + + Examples + -------- + Create a pane from a window: + + >>> import asyncio + >>> async def test_acmd(): + ... result = await window.acmd('split-window', '-P', '-F#{pane_id}') + ... print(result.stdout[0]) + >>> asyncio.run(test_acmd()) + %... + + Magic, directly to a `Pane`: + + >>> async def test_from_pane(): + ... pane_id_result = await session.acmd( + ... 'split-window', '-P', '-F#{pane_id}' + ... ) + ... return Pane.from_pane_id( + ... pane_id=pane_id_result.stdout[0], + ... server=session.server + ... ) + >>> asyncio.run(test_from_pane()) + Pane(%... Window(@... ...:..., Session($1 libtmux_...))) + + Parameters + ---------- + target : str, optional + Optional custom target override. By default, the target is the window ID. + + Returns + ------- + :meth:`server.cmd` + """ + if target is None: + target = self.window_id + + return await self.server.acmd(cmd, *args, target=target) + """ Commands (tmux-like) """