diff options
| author | blasty <blasty@local> | 2026-07-10 12:23:52 +0200 |
|---|---|---|
| committer | blasty <blasty@local> | 2026-07-10 12:23:52 +0200 |
| commit | 4fd71ed6202c4c9925963c9e9156d3cb0e549b35 (patch) | |
| tree | 5b3ee0dd36224cb82fcb0201aee75e2e57fc61fd /idatui/app.py | |
| parent | struct editor: discoverable, confirmed delete (d/Del + ConfirmScreen) (diff) | |
| download | ida-tui-4fd71ed6202c4c9925963c9e9156d3cb0e549b35.tar.gz ida-tui-4fd71ed6202c4c9925963c9e9156d3cb0e549b35.tar.xz ida-tui-4fd71ed6202c4c9925963c9e9156d3cb0e549b35.zip | |
clipboard: keyboard copy (OSC 52 + tmux) since mouse-select is captured
The app captures the mouse (SGR tracking), so terminal/tmux drag-select can't
grab text. Add explicit copy actions:
- code views: 'y' copies the current line
- struct editor: Ctrl+Y copies the selection, else the whole C definition
App._copy() emits OSC 52 (Textual copy_to_clipboard) AND, inside tmux, pipes
through 'tmux load-buffer -w -' — the combination is what actually reaches the
system clipboard over tmux/ssh. Status shows the char count.
full suite 87/87.
Diffstat (limited to 'idatui/app.py')
| -rw-r--r-- | idatui/app.py | 45 |
1 files changed, 44 insertions, 1 deletions
diff --git a/idatui/app.py b/idatui/app.py index 6d2f38d..ab28d72 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -16,7 +16,9 @@ Design notes: from __future__ import annotations +import os import re +import subprocess from dataclasses import dataclass from rich.segment import Segment @@ -128,6 +130,7 @@ class NavMixin: Binding("x", "xrefs", "Xrefs"), Binding("n", "rename", "Rename"), Binding("semicolon", "comment", "Comment"), + Binding("y", "copy_line", "Copy line"), ] def action_follow(self) -> None: @@ -142,6 +145,13 @@ class NavMixin: def action_comment(self) -> None: self.post_message(CommentRequested(self)) + def action_copy_line(self) -> None: + plain = self._line_plain(self.cursor) + if not plain: + return + n = self.app._copy(plain) + self.app._status(f"copied line ({n} chars) to clipboard") + def _overlay_ranges(strip: Strip, ranges: list[tuple[int, int]], style: Style) -> Strip: """Return a copy of ``strip`` with ``style`` merged over the given cell @@ -1207,6 +1217,7 @@ class StructEditor(ModalScreen): BINDINGS = [ Binding("ctrl+s", "save", "Save", priority=True), Binding("ctrl+n", "new", "New", priority=True), + Binding("ctrl+y", "copy", "Copy", priority=True), Binding("delete,d", "delete", "Delete", show=False), Binding("escape", "close", "Close"), ] @@ -1229,7 +1240,7 @@ class StructEditor(ModalScreen): yield Static(" C definition", id="se-hint") yield TextArea("", id="se-edit") yield Static( - "Enter edit · Ctrl+S save · Ctrl+N new · d/Del delete · Esc back/close", + "Enter edit · Ctrl+S save · Ctrl+Y copy · Ctrl+N new · d/Del delete · Esc close", id="se-status") def on_mount(self) -> None: @@ -1351,6 +1362,17 @@ class StructEditor(ModalScreen): self._refresh() self._set_status(f"deleted {name}") + def action_copy(self) -> None: + """Ctrl+Y: copy the selection, or the whole C definition, to the clipboard.""" + ta = self.query_one("#se-edit", TextArea) + text = ta.selected_text or ta.text + if not text: + self._set_status("nothing to copy") + return + n = self.app._copy(text) + what = "selection" if ta.selected_text else "definition" + self._set_status(f"copied {what} ({n} chars) to clipboard") + def action_close(self) -> None: # A stray Esc while editing returns to the list instead of discarding. if self.focused is self.query_one("#se-edit", TextArea): @@ -1494,6 +1516,27 @@ class IdaTui(App): def _status(self, text: str) -> None: self.query_one("#status", Static).update(text) + # -- clipboard --------------------------------------------------------- # + def _copy(self, text: str) -> int: + """Copy ``text`` to the clipboard and return its length. Uses the OSC 52 + escape (Textual) and, inside tmux, also ``tmux load-buffer -w`` — the + combination is what actually reaches the system clipboard over + tmux/ssh when the app has the mouse captured (so drag-select can't). + """ + try: + self.copy_to_clipboard(text) # OSC 52 + except Exception: # noqa: BLE001 + pass + if os.environ.get("TMUX"): + try: + subprocess.run( + ["tmux", "load-buffer", "-w", "-"], + input=text.encode("utf-8", "replace"), + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=2.0) + except Exception: # noqa: BLE001 + pass + return len(text) + # -- connection + initial load ---------------------------------------- # @work(thread=True, exclusive=True, group="connect") def _connect(self) -> None: |
