aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblasty <blasty@local>2026-07-10 16:43:32 +0200
committerblasty <blasty@local>2026-07-10 16:43:32 +0200
commit7ae37ba3b432bbb2482df9c99864350e685b0241 (patch)
treefe96899d03e683792ba696dc1908cf8875ffa86b
parentdrive: add save + retype; point docs/skills at the ergonomic frontend (diff)
downloadida-tui-7ae37ba3b432bbb2482df9c99864350e685b0241.tar.gz
ida-tui-7ae37ba3b432bbb2482df9c99864350e685b0241.tar.xz
ida-tui-7ae37ba3b432bbb2482df9c99864350e685b0241.zip
app: multi-line comments via literal \\n (long notes were clipping)
The comment prompt is single-line, so a long note rendered as one giant '//' line that ran off the right edge and got clipped. Interpret a literal '\\n' (backslash-n) in the comment text as a real newline in _do_comment (the single choke point for the ';' prompt and the RPC 'comment' verb) — Hex-Rays renders each as its own '//' line. Verified live and locked in the comment_func scenario (multi-line render); suite 104 green.
-rw-r--r--docs/RPC.md2
-rw-r--r--idatui/app.py4
-rw-r--r--tests/test_scenarios.py16
3 files changed, 17 insertions, 5 deletions
diff --git a/docs/RPC.md b/docs/RPC.md
index 5cb73ee..4a68fbe 100644
--- a/docs/RPC.md
+++ b/docs/RPC.md
@@ -81,7 +81,7 @@ predicate so the returned state is final.
|--------|--------|--------|
| `goto` / `open` | `target`, `delay_ms?`, `timeout?` | `g` prompt → name or `0xADDR` → Enter. |
| `rename` | `name`, `word?`, `delay_ms?` | `n` on the token under the cursor → replace → Enter. `word` first places the cursor on that token (see `cursor_on`). |
-| `comment` | `text`, `delay_ms?` | `;` on the current line. |
+| `comment` | `text`, `delay_ms?` | `;` on the current line (a literal `\n` in `text` becomes a real newline → multi-line comment). |
| `retype` | `proto`, `word?`, `delay_ms?` | `y` on the token/function under the cursor (`word` places it first). |
| `follow` | `word?` | Enter: follow the reference under the cursor (`word` places it first); waits for the jump. |
| `back` | — | Escape: pop the nav stack. |
diff --git a/idatui/app.py b/idatui/app.py
index 8fc259e..d7bc390 100644
--- a/idatui/app.py
+++ b/idatui/app.py
@@ -2413,6 +2413,10 @@ class IdaTui(App):
@work(thread=True, exclusive=True, group="comment")
def _do_comment(self, ea: int, text: str) -> None:
assert self.program is not None
+ # The prompt is single-line, so a literal '\n' (backslash-n) means a real
+ # newline — Hex-Rays renders each as its own '//' line. Lets long notes
+ # wrap instead of running off the right edge and clipping.
+ text = text.replace("\\n", "\n")
try:
res = self.program.set_comment(ea, text)
except IDAToolError as e:
diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py
index 0cad008..b16cd98 100644
--- a/tests/test_scenarios.py
+++ b/tests/test_scenarios.py
@@ -1070,11 +1070,19 @@ async def s_comment_func(c: Ctx):
c.check("';' on the signature line opens a function-comment prompt",
ci.display and "function comment" in str(ci.placeholder).lower(),
f"display={ci.display} ph={ci.placeholder!r}")
- ci.value = note
+ # literal '\n' in the comment becomes a real newline -> multi-line render
+ a, b = f"{note}_A", f"{note}_B"
+ ci.value = f"{a}\\n{b}"
await c.press("enter")
- await c.wait(lambda: dec.loaded_ea == fn.addr and any(note in t for t in dec._texts), 25)
- c.check("function comment appears in the pseudocode",
- any(note in t for t in dec._texts), "not shown")
+ await c.wait(lambda: dec.loaded_ea == fn.addr
+ and any(a in t for t in dec._texts)
+ and any(b in t for t in dec._texts), 25)
+ la = next((i for i, t in enumerate(dec._texts) if a in t), None)
+ lb = next((i for i, t in enumerate(dec._texts) if b in t), None)
+ c.check("multi-line function comment renders on separate lines",
+ la is not None and lb is not None and lb > la
+ and a not in dec._texts[lb],
+ f"la={la} lb={lb}")
app.program.client.call("set_comments", items=[{"addr": hex(fn.addr), "comment": ""}])