Skip to content

Commit a4e5b92

Browse files
committed
Server(feat[buffers]): add set_buffer, show_buffer, delete_buffer wrapping tmux buffer commands
why: Paste buffer management is needed for programmatic clipboard-like operations between panes and for exporting/importing text data. what: - Add Server.set_buffer() wrapping set-buffer with append (-a) and buffer_name (-b) parameters - Add Server.show_buffer() wrapping show-buffer with buffer_name (-b) - Add Server.delete_buffer() wrapping delete-buffer with buffer_name (-b) - Add BufferCase NamedTuple parametrized tests for set/show cycle, named buffers, append, and delete
1 parent 0b7f25c commit a4e5b92

2 files changed

Lines changed: 173 additions & 0 deletions

File tree

src/libtmux/server.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,100 @@ def wait_for(
528528
if proc.stderr:
529529
raise exc.LibTmuxException(proc.stderr)
530530

531+
def set_buffer(
532+
self,
533+
data: str,
534+
*,
535+
buffer_name: str | None = None,
536+
append: bool | None = None,
537+
) -> None:
538+
"""Set a paste buffer via ``$ tmux set-buffer``.
539+
540+
Parameters
541+
----------
542+
data : str
543+
Data to store in the buffer.
544+
buffer_name : str, optional
545+
Name of the buffer (``-b`` flag).
546+
append : bool, optional
547+
Append to the buffer instead of replacing (``-a`` flag).
548+
549+
Examples
550+
--------
551+
>>> server.set_buffer('hello')
552+
>>> server.show_buffer()
553+
'hello'
554+
"""
555+
tmux_args: tuple[str, ...] = ()
556+
557+
if append:
558+
tmux_args += ("-a",)
559+
560+
if buffer_name is not None:
561+
tmux_args += ("-b", buffer_name)
562+
563+
tmux_args += (data,)
564+
565+
proc = self.cmd("set-buffer", *tmux_args)
566+
567+
if proc.stderr:
568+
raise exc.LibTmuxException(proc.stderr)
569+
570+
def show_buffer(self, *, buffer_name: str | None = None) -> str:
571+
"""Show content of a paste buffer via ``$ tmux show-buffer``.
572+
573+
Parameters
574+
----------
575+
buffer_name : str, optional
576+
Name of the buffer (``-b`` flag). Defaults to the most recent.
577+
578+
Returns
579+
-------
580+
str
581+
Buffer content.
582+
583+
Examples
584+
--------
585+
>>> server.set_buffer('test_data')
586+
>>> server.show_buffer()
587+
'test_data'
588+
"""
589+
tmux_args: tuple[str, ...] = ()
590+
591+
if buffer_name is not None:
592+
tmux_args += ("-b", buffer_name)
593+
594+
proc = self.cmd("show-buffer", *tmux_args)
595+
596+
if proc.stderr:
597+
raise exc.LibTmuxException(proc.stderr)
598+
599+
return "\n".join(proc.stdout)
600+
601+
def delete_buffer(self, *, buffer_name: str | None = None) -> None:
602+
"""Delete a paste buffer via ``$ tmux delete-buffer``.
603+
604+
Parameters
605+
----------
606+
buffer_name : str, optional
607+
Name of the buffer to delete (``-b`` flag). Defaults to the most
608+
recent.
609+
610+
Examples
611+
--------
612+
>>> server.set_buffer('to_delete', buffer_name='del_buf')
613+
>>> server.delete_buffer(buffer_name='del_buf')
614+
"""
615+
tmux_args: tuple[str, ...] = ()
616+
617+
if buffer_name is not None:
618+
tmux_args += ("-b", buffer_name)
619+
620+
proc = self.cmd("delete-buffer", *tmux_args)
621+
622+
if proc.stderr:
623+
raise exc.LibTmuxException(proc.stderr)
624+
531625
def switch_client(self, target_session: str) -> None:
532626
"""Switch tmux client.
533627

tests/test_server.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import pytest
1414

15+
from libtmux import exc
1516
from libtmux.server import Server
1617

1718
if t.TYPE_CHECKING:
@@ -482,6 +483,84 @@ def test_run_shell_background(server: Server) -> None:
482483
assert result is None
483484

484485

486+
class BufferCase(t.NamedTuple):
487+
"""Test case for buffer operations."""
488+
489+
test_id: str
490+
data: str
491+
buffer_name: str | None
492+
append: bool | None
493+
expected_content: str
494+
495+
496+
BUFFER_CASES: list[BufferCase] = [
497+
BufferCase(
498+
test_id="set_show_default",
499+
data="hello_buf",
500+
buffer_name=None,
501+
append=None,
502+
expected_content="hello_buf",
503+
),
504+
BufferCase(
505+
test_id="set_show_named",
506+
data="named_data",
507+
buffer_name="mybuf",
508+
append=None,
509+
expected_content="named_data",
510+
),
511+
]
512+
513+
514+
@pytest.mark.parametrize(
515+
list(BufferCase._fields),
516+
BUFFER_CASES,
517+
ids=[c.test_id for c in BUFFER_CASES],
518+
)
519+
def test_buffer_set_show(
520+
test_id: str,
521+
data: str,
522+
buffer_name: str | None,
523+
append: bool | None,
524+
expected_content: str,
525+
server: Server,
526+
) -> None:
527+
"""Test Server.set_buffer() and show_buffer() cycle."""
528+
server.new_session(session_name=f"buf_{test_id}")
529+
kwargs: dict[str, t.Any] = {}
530+
if buffer_name is not None:
531+
kwargs["buffer_name"] = buffer_name
532+
if append is not None:
533+
kwargs["append"] = append
534+
535+
server.set_buffer(data, **kwargs)
536+
result = server.show_buffer(buffer_name=buffer_name)
537+
assert result == expected_content
538+
539+
540+
def test_buffer_append(server: Server) -> None:
541+
"""Test Server.set_buffer() with append flag."""
542+
server.new_session(session_name="buf_append")
543+
server.set_buffer("first", buffer_name="append_test")
544+
server.set_buffer("_second", buffer_name="append_test", append=True)
545+
result = server.show_buffer(buffer_name="append_test")
546+
assert result == "first_second"
547+
548+
549+
def test_buffer_delete(server: Server) -> None:
550+
"""Test Server.delete_buffer()."""
551+
server.new_session(session_name="buf_delete")
552+
server.set_buffer("to_delete", buffer_name="del_buf")
553+
# Verify it exists
554+
assert server.show_buffer(buffer_name="del_buf") == "to_delete"
555+
556+
# Delete it
557+
server.delete_buffer(buffer_name="del_buf")
558+
559+
# Verify it's gone — show-buffer should raise
560+
with pytest.raises(exc.LibTmuxException):
561+
server.show_buffer(buffer_name="del_buf")
562+
563+
485564
def test_new_session_config_file(
486565
server: Server,
487566
tmp_path: pathlib.Path,

0 commit comments

Comments
 (0)