opencode icon indicating copy to clipboard operation
opencode copied to clipboard

feat(tui): add configurable readline-style text transformations

Open aspiers opened this issue 2 weeks ago • 8 comments

Summary

Adds missing Emacs/Readline-style text editing shortcuts to the TUI (terminal) prompt input, improving text editing efficiency in the terminal.

Closes #9234.

Changes

New shortcuts

Shortcut Action
alt+u Uppercase word from cursor
alt+l Lowercase word from cursor
alt+c Capitalize word from cursor
ctrl+y Yank (paste) last killed text
ctrl+t Transpose characters (requires rebind)

User-facing behavior

Case transformations (alt+u, alt+l, alt+c)

  • When cursor is on a word character: transforms the word starting from cursor position
  • When cursor is on whitespace: transforms the next word
  • When no next word exists: transforms the previous word
  • With text selection: transforms the selected text
  • After transformation: cursor moves to end of the transformed word

Kill ring support (ctrl+y)

  • The ctrl+k, ctrl+u, ctrl+w, and alt+d delete commands now save deleted text to a kill buffer
  • ctrl+y inserts the contents of the kill buffer at the current cursor position
  • Simple single-entry buffer (full kill ring can be added later)

Transpose characters (ctrl+t)

  • Swaps the character under the cursor with the character immediately preceding it
  • At beginning of line: swaps first two characters
  • At end of line: swaps last two characters
  • Cursor advances one position to the right (unless already at end of line)

Configuration

All new shortcuts are configurable via opencode.json:

{
  "keybinds": {
    "input_lowercase_word": "alt+l",
    "input_uppercase_word": "alt+u",
    "input_capitalize_word": "alt+c",
    "input_yank": "ctrl+y",
    "input_transpose_characters": "ctrl+t"
  }
}

Note on ctrl+t conflict: By default, ctrl+t is bound to variant_cycle (cycle model variants). To use ctrl+t for transpose characters, you can rebind, e.g.:

{
  "keybinds": {
    "variant_cycle": "<leader>v",
    "input_transpose_characters": "ctrl+t"
  }
}

Technical details

  • TUI uses OpenTUI's TextareaRenderable which has limited built-in actions
  • Implementation adds helper functions for word boundary detection matching Readline behavior
  • Tests added for word boundary detection and transformation functions

Files

  • packages/opencode/src/config/config.ts - Added keybind schema entries
  • packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx - Implemented shortcuts
  • packages/web/src/content/docs/keybinds.mdx - Updated documentation
  • packages/sdk/js/src/v2/gen/types.gen.ts - Regenerated from schema
  • packages/opencode/test/tui/text-transform.test.ts - Added 17 tests

aspiers avatar Jan 03 '26 21:01 aspiers

I'm guessing the test failures are due to #6674, rather than anything to do with this PR.

aspiers avatar Jan 03 '26 21:01 aspiers

Tests passing after rebase.

aspiers avatar Jan 05 '26 10:01 aspiers

Adding these key commands could indeed be helpful, I would be in favour of that. However, the impression that I've gotten from the maintainers over the course of some of my own recent PRs is that they'd generally prefer not to make changes to the default keybindings in the TUI and would generally prefer that new key commands instead be bound to "none" by default, leaving it to the user to bind them to keys in their own opencode.jsonc file should they so choose.

Sometimes I'll accidentally type words in a prompt in in the wrong order, so having equivalents to emacs' kill-word and transpose-words available could also be nice.

ariane-emory avatar Jan 18 '26 06:01 ariane-emory

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

github-actions[bot] avatar Jan 18 '26 06:01 github-actions[bot]

@ariane-emory Thanks a lot for the feedback. If the maintainers request removal of the default bindings then of course I'll do it. However in the absence of a request I'd prefer to keep them, since I think removal violates the Principle of Least Surprise - at this point there are multiple decades of precedent for these key bindings in all modern shells plus anything which uses readline(3).

I definitely agree with your suggestion regarding kill-word and transpose-words!

aspiers avatar Jan 18 '26 10:01 aspiers

I've submitted #9234 as the feature request for this PR.

aspiers avatar Jan 18 '26 10:01 aspiers

@aspiers You'll get no complaints from me: is's your PR, do it your way, and I would certainly love to see these key commands. FWIW, my interpretation of the behaviour I've seen on some of my own past PRs (which is of course merely an interpretation, and could be incorrect) was that it was also based on the application of the Principle of Least Surprise, just seen from the other side: users would be surprised if keys that did nothing in Opencode yesterday suddenly did something today.

Speaking as someone whose emacs keybindings elisp file Is closer to 5 hundred lines long than 4, I'm certainly not too concerned with what the defaults are (if any) and am certainly more than happy to configure the bindings to my own tastes. In my book, the fact that the can be rebound to suit my own weird preferences is the the more important aspect.

Setting the topic of defaults aside, this looks like great work so far. I haven't yet tested the branch out (I may find time to do so tomorrow), but upon a cursory read-through, it all looks pretty good to me! Best of luck guiding the PR in to a safe landing!

ariane-emory avatar Jan 18 '26 10:01 ariane-emory

Does this behave for CJK? The opentui native core already calculates word boundaries and could provide such actions.

kommander avatar Jan 18 '26 11:01 kommander