Skip to content

Commit 5d50f02

Browse files
committed
Pane(feat[pipe]): add pipe() wrapping tmux pipe-pane
why: pipe-pane is needed for monitoring, logging, and capturing pane output to external commands or files programmatically. what: - Add pipe() method with command, output_only (-O), input_only (-I), toggle (-o) parameters wrapping pipe-pane - Calling with no command stops piping - Add test piping to file via cat, verifying output captured
1 parent 4d4b4ce commit 5d50f02

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

src/libtmux/pane.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,54 @@ def enter(self) -> Pane:
11391139
self.cmd("send-keys", "Enter")
11401140
return self
11411141

1142+
def pipe(
1143+
self,
1144+
command: str | None = None,
1145+
*,
1146+
output_only: bool | None = None,
1147+
input_only: bool | None = None,
1148+
toggle: bool | None = None,
1149+
) -> None:
1150+
"""Pipe pane output to a shell command via ``$ tmux pipe-pane``.
1151+
1152+
Parameters
1153+
----------
1154+
command : str, optional
1155+
Shell command to pipe to. If None, stops piping.
1156+
output_only : bool, optional
1157+
Only pipe output from the pane (``-O`` flag).
1158+
input_only : bool, optional
1159+
Only pipe input to the pane (``-I`` flag).
1160+
toggle : bool, optional
1161+
Toggle piping on/off (``-o`` flag).
1162+
1163+
Examples
1164+
--------
1165+
>>> pane.pipe('cat >> /tmp/output.txt')
1166+
1167+
Stop piping:
1168+
1169+
>>> pane.pipe()
1170+
"""
1171+
tmux_args: tuple[str, ...] = ()
1172+
1173+
if output_only:
1174+
tmux_args += ("-O",)
1175+
1176+
if input_only:
1177+
tmux_args += ("-I",)
1178+
1179+
if toggle:
1180+
tmux_args += ("-o",)
1181+
1182+
if command is not None:
1183+
tmux_args += (command,)
1184+
1185+
proc = self.cmd("pipe-pane", *tmux_args)
1186+
1187+
if proc.stderr:
1188+
raise exc.LibTmuxException(proc.stderr)
1189+
11421190
def respawn(
11431191
self,
11441192
*,

tests/test_pane.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,42 @@ def test_split_percentage_size_mutual_exclusion(session: Session) -> None:
740740
pane.split(size=10, percentage=50)
741741

742742

743+
def test_pipe_pane(session: Session, tmp_path: pathlib.Path) -> None:
744+
"""Test Pane.pipe() pipes output to a file."""
745+
env = shutil.which("env")
746+
assert env is not None
747+
748+
window = session.new_window(
749+
window_name="test_pipe",
750+
window_shell=f"{env} PS1='$ ' sh",
751+
)
752+
pane = window.active_pane
753+
assert pane is not None
754+
755+
retry_until(lambda: "$" in "\n".join(pane.capture_pane()), 2, raises=True)
756+
757+
pipe_file = tmp_path / "pipe_output.txt"
758+
759+
# Start piping
760+
pane.pipe(f"cat >> {pipe_file}")
761+
762+
# Send some text
763+
pane.send_keys("echo pipe_test_ok", enter=True)
764+
retry_until(
765+
lambda: "pipe_test_ok" in "\n".join(pane.capture_pane()), 3, raises=True
766+
)
767+
768+
# Stop piping
769+
pane.pipe()
770+
771+
# Verify file has content
772+
retry_until(
773+
lambda: pipe_file.exists() and pipe_file.stat().st_size > 0, 3, raises=True
774+
)
775+
content = pipe_file.read_text()
776+
assert "pipe_test_ok" in content
777+
778+
743779
def test_respawn_pane_kill(session: Session) -> None:
744780
"""Test Pane.respawn() with kill flag on active pane."""
745781
window = session.new_window(window_name="test_respawn")

0 commit comments

Comments
 (0)