Skip to content

feat(tui): grouped prompt undo that survives submits and keeps pasted text - #54460

Open
Nowaker wants to merge 2 commits into
anomalyco:v1from
Nowaker:prompt-undo-history
Open

Nowaker wants to merge 2 commits into
anomalyco:v1from
Nowaker:prompt-undo-history

Conversation

@Nowaker

@Nowaker Nowaker commented Oct 11, 2026

Copy link
Copy Markdown

Note: I'm aware v1 is now the maintenance branch and new features go to v2 only. This PR is here for others to use, and I'll submit the v2 version once I've migrated my whole plugin system to it.

Issue for this PR

Related: #32721.

Stacked on #54457 (atomic placeholder deletes), whose commit is included; the change in this PR is the last commit, feat(tui): grouped prompt undo that survives submits and keeps pasted text.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

input_undo runs the textarea's own history, which steps back one character at a time, so taking back a paragraph takes dozens of presses. It knows nothing of the prompt's parts: undoing the deletion of a [Pasted ~N lines] placeholder brings its label back as plain text, and the prompt then sends the label instead of the pasted text. A submit clears it, so text lost to a stray submit or ctrl+u can only be found again through history.

The prompt now keeps its own history (src/prompt/undo.ts) and binds input.undo / input.redo to it while focused:

  • Typing is grouped by word: a group ends where a new word starts, after a pause over a second, when the caret moves, or between typing and deleting. Keys that arrive in one read still count as typing.
  • A paste, a word or line delete, a newline, clearing, recalling history and a submit are each one step.
  • Every entry records the text, the parts with their pasted content, the mode and the caret, and undo restores them together.
  • The history lives outside the prompt component, so after a submit undo brings back what was sent, even once the route has moved to the new session, without touching the running turn. Redo empties the prompt again.

prompt.undo_depth sets how many steps are kept (default 500). The default keys are unchanged; keybinds.mdx shows how to move suspend off ctrl+z and use ctrl+z / ctrl+shift+z:

{
  "keybinds": {
    "terminal_suspend": "none",
    "input_undo": "ctrl+z,ctrl+-,super+z",
    "input_redo": "ctrl+shift+z,ctrl+.,super+shift+z"
  }
}

How did you verify your code works?

  • test/prompt/undo.test.ts: word grouping, the pause, newlines, line deletes, pastes, typing vs deleting, caret moves, redo, the depth limit, a submit with its placeholder parts, and keys arriving together.
  • test/component/prompt-undo.test.tsx drives the real TUI through stdin: ctrl+u then undo word by word; undo after deleting a placeholder sends the pasted text, not the label; undo after a submit brings the text back without an abort request. All three fail on the textarea's own undo.
  • bun typecheck and bun test in packages/tui.
  • In a real terminal through tmux with ctrl+z / ctrl+shift+z configured as above: hello brave world, ctrl+u, then undo gave back hello brave world, hello brave, hello; after submitting while a turn streamed, undo restored the prompt and the turn kept streaming.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This PR is AI generated, but under direct supervision and on request of @Nowaker.

ctrl+w (or alt+backspace) right after a "[Pasted ~5 lines]" placeholder
deleted only the last word of its label, leaving "[Pasted ~5 " behind
with the pasted text detached from it; ctrl+u and ctrl+k did the same
when the line edge cut into a placeholder. Backspace and delete already
removed it whole.

The cause is in @opentui/core: its extmark controller's deleteRange,
which every word and line delete goes through, trimmed a virtual
extmark instead of treating it as one unit. The fix is upstream as
anomalyco/opentui#1628; patches/@OpenTui%2Fcore@0.4.5.patch backports
it to the pinned 0.4.5 until a release with it is adopted. deleteRange
now widens its range over every virtual extmark it cuts into.
… text

input_undo ran the textarea's own history, which steps back one
character at a time, so taking back a paragraph took dozens of
presses. It knew nothing of the prompt's parts: undoing the deletion
of a "[Pasted ~N lines]" placeholder brought its label back as plain
text with the pasted content gone. A submit cleared it, so text lost to
a stray submit or ctrl+u could only be found again through the
history.

The prompt now keeps its own history (src/prompt/undo.ts) and binds
input.undo and input.redo to it while focused. Typing is grouped by
word: a group ends where a new word starts, after a pause over a
second, when the caret moves, or between typing and deleting. A paste,
a word or line delete, a newline, clearing, recalling history and a
submit are each one step. Every entry records the text, the parts with
their pasted content, the mode and the caret, and undo restores them
together. The history lives outside the prompt component, so after a
submit undo brings back what was sent, even once the route has moved
to the new session, without touching the running turn.

prompt.undo_depth sets how many steps are kept (default 500). The
default keys are unchanged; keybinds.mdx shows how to move suspend off
ctrl+z and use ctrl+z / ctrl+shift+z.
@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Nowaker added a commit to Nowaker/opencode that referenced this pull request Oct 11, 2026
Session record and five feature records for item 28: atomic
placeholder deletes (opentui#1628 backport), configurable paste
summary, raw paste on <leader>v, grouped prompt undo with fork ctrl+z
defaults, and the click-to-edit paste dialog, with their upstream v1
PRs anomalyco#54457-anomalyco#54460 and anomalyco#54463, key-byte measurements through Vibeterm's
tmux, and the vt-206 installs on desktop and m4max.

AI-Tool: opencode 1.18.34-vt-194-907b3bc518
AI-Model: anthropic/claude-opus-5-5
AI-Variant: high
AI-Platform: linux
AI-Harness: Vibeterm 9343cff
AI-Session-ID: ses_ed7634c31ffeH84yR0S99Ty8Nd

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant