diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-15 09:33:28 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-15 09:33:28 +0200 |
| commit | 5044bdfdfffceceecc0d92a776983ad361079839 (patch) | |
| tree | 4a80ab66ef53f9ba76cdf8db4276dfc07d0fc1f7 /packages/meshbay-cli/tests/test_interactive.py | |
| parent | 1e8eae1f3de765fcd8163716de6e5fe5aab5a034 (diff) | |
| download | meshbay-5044bdfdfffceceecc0d92a776983ad361079839.tar.gz | |
feat(cli): help, and right-click copy and paste in the interactive chat
help prints what --help prints; help ACTION, what ACTION --help prints.
In the interactive chat a right-click on the conversation copies the text
selected with the mouse, else the line under the pointer: to the terminal
(OSC 52) and to the platform's clipboard tool. A right-click on the line
written in pastes the system clipboard's text at the cursor, its lines side
by side, read with wl-paste, xclip or xsel, pbpaste or PowerShell, else what
was copied here last. The conversation is selectable with the mouse, which
RichLog alone is not. The deb and rpm packages recommend wl-clipboard or
xclip for the paste.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/tests/test_interactive.py')
| -rw-r--r-- | packages/meshbay-cli/tests/test_interactive.py | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/packages/meshbay-cli/tests/test_interactive.py b/packages/meshbay-cli/tests/test_interactive.py index fa7e96c..5b9d9b0 100644 --- a/packages/meshbay-cli/tests/test_interactive.py +++ b/packages/meshbay-cli/tests/test_interactive.py @@ -208,6 +208,17 @@ async def test_delete_removes_what_is_yours_and_asks_first(world): assert any("select them on the right" in n.message for n in app._notifications) +async def _right_click(pilot, widget, offset=(0, 0)) -> None: + """Through App.on_event, as the terminal driver sends it; the pilot's own + clicks skip it.""" + from textual import events + from textual.pilot import _get_mouse_message_arguments + args = _get_mouse_message_arguments(widget, offset, button=3) + for cls in (events.MouseDown, events.MouseUp): + pilot.app.post_message(cls(**args)) + await pilot.pause() + + def _log_text(screen) -> str: from textual.widgets import RichLog return "\n".join("".join(seg.text for seg in strip) @@ -285,3 +296,71 @@ async def test_the_interactive_chat_joins_talks_leaves_and_comes_back(world, mon await _until(pilot, lambda: isinstance(app.screen, ChatGroupsScreen)) await pilot.press("q") assert app.return_code in (0, None) + + +async def test_a_right_click_copies_from_the_chat_and_pastes_into_the_line(world, monkeypatch): + """The selection, else the line under the pointer; pasted as one line.""" + from meshbay_cli.tui import clipboard + from meshbay_cli.tui.chat import ChatGroupsScreen, ChatInput, ChatLog, ChatRoomScreen + hub, node, _ = world + written, held = [], {"text": None} + + async def write_text(text): + written.append(text) + return True + + async def read_text(): + return held["text"] + monkeypatch.setattr(clipboard, "write_text", write_text) + monkeypatch.setattr(clipboard, "read_text", read_text) + node.say("earlier, from bob") + assert await asyncio.to_thread(cli.main, ["--hub", "https://hub.test", "-u", USERNAME, + "--password", PASSPHRASE, "login"]) == 0 + + app = MeshBayApp(context.Context(cli.build_parser().parse_args( + ["--hub", "https://hub.test"])), ChatGroupsScreen) + async with app.run_test(size=(120, 30)) as pilot: + await _until(pilot, lambda: isinstance(app.screen, ChatGroupsScreen)) + await pilot.pause(0.3) + await pilot.press("enter") + await _until(pilot, lambda: isinstance(app.screen, ChatRoomScreen)) + room = app.screen + log = room.query_one(ChatLog) + await _until(pilot, lambda: "<bob-test> earlier, from bob" in _log_text(room)) + y = next(i for i, line in enumerate(log.lines) if "earlier, from bob" in line.text) + line = log.lines[y].text + + # Nothing selected: the line under the pointer. + await _right_click(pilot, log, (4, y)) + await _until(pilot, lambda: written) + assert written[-1] == line.rstrip() and app.clipboard == written[-1] + assert "<bob-test> earlier, from bob" in written[-1] + + # Selected with the mouse: that text, and only it. + x = line.index("bob-test") + await pilot.mouse_down(log, offset=(1 + x, y)) + await pilot.hover(log, offset=(x + len("bob-test"), y)) + await pilot.mouse_up(log, offset=(x + len("bob-test"), y)) + await pilot.pause(0.1) + assert log.text_selection is not None + await _right_click(pilot, log, (1, y + 3)) + await _until(pilot, lambda: len(written) == 2) + assert written[-1] == "bob-test" + + # Pasted into the line written in, at the cursor, lines side by side. + field = room.query_one(ChatInput) + field.value = "say: " + field.cursor_position = len(field.value) + held["text"] = "first line\nsecond line\n" + await _right_click(pilot, field, (2, 0)) + await _until(pilot, lambda: field.value != "say: ") + assert field.value == "say: first line second line" + + # No tool to read the system clipboard: what was copied here. + held["text"] = None + field.value = "" + await _right_click(pilot, field, (2, 0)) + await _until(pilot, lambda: field.value) + assert field.value == "bob-test" + assert app.screen is room # a right-click left nothing behind + await pilot.press("escape") |