aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/app.py
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-10 12:23:52 +0200
committerblasty <blasty@local>2026-07-10 12:23:52 +0200
commit4fd71ed6202c4c9925963c9e9156d3cb0e549b35 (patch)
tree5b3ee0dd36224cb82fcb0201aee75e2e57fc61fd /idatui/app.py
parentstruct editor: discoverable, confirmed delete (d/Del + ConfirmScreen) (diff)
downloadida-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.py45
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: