Add eat-style input modes (char, emacs, copy, line, semi-char)#78
Open
Add eat-style input modes (char, emacs, copy, line, semi-char)#78
Conversation
b1e771f to
6d81a44
Compare
95cacee to
1a31d37
Compare
8 tasks
Adds five input modes inspired by eat.el so the user can switch
between a terminal-focused default, a "send everything" char mode,
a read-only but still-live Emacs mode, a frozen copy mode for
precise selection, and a shell-like line mode that buffers input
locally and sends whole lines to the shell.
- `ghostel--input-mode' (semi-char, char, emacs, copy, line) is the
single source of truth. `ghostel--copy-mode-active' is removed.
- Keymap is split into a slim `ghostel-mode-map' base (C-c prefix,
drag-n-drop) and one keymap per mode
(`ghostel-{semi-char,char,emacs,copy,line}-mode-map'). Helpers
`ghostel--define-terminal-keys' and `ghostel--define-mouse-keys'
share the terminal-sending bindings between semi-char and char.
- Emacs mode is unfrozen: the terminal keeps running and redraws
keep streaming new output into the buffer while it is read-only,
so `isearch', `occur', `M-x', and the rest of Emacs's vocabulary
work over a live log. The delayed-redraw path preserves point
unconditionally in Emacs mode so navigation is stable.
- Copy mode is refactored to share read-only/cursor/hl-line
state-helpers (`ghostel--enter-readonly-state' /
`ghostel--leave-readonly-state') with Emacs mode. Copy mode
additionally freezes the redraw timer.
- Line mode (C-c C-l) never forwards keystrokes to the shell: the
user edits input locally with standard Emacs keys and sends the
whole line on RET via a single `process-send-string'. Redraws
are suppressed while composing so in-progress input is never
clobbered; output keeps feeding libghostty internally and
catches up when the user exits line mode. History ring with
M-p / M-n, C-c C-c interrupt, C-d EOF on empty input, requires
OSC 133 shell integration, refuses to enter on the alt screen.
- Prompt navigation (`C-c C-n' / `C-c C-p') now enters Emacs mode
instead of copy mode so the terminal keeps running while the
user jumps between prompts.
- `C-c C-l' is reserved for line mode; clear-scrollback moves to
`C-c M-l'.
- The five check sites that used `ghostel--copy-mode-active' migrate
to the new predicates `ghostel--buffer-editable-p',
`ghostel--terminal-live-p', `ghostel--terminal-frozen-p'.
- `ghostel-evil' now checks for semi-char mode explicitly so its
editing commands don't fire in any of the read-only modes.
- README gains a new "Input modes" section covering all five modes
with per-mode keymap tables.
- 27 new tests cover mode state and predicates, keymap definitions,
mode-switch transitions (including copy <-> emacs round-trips),
prompt navigation, line-mode prompt discovery, send/history/
interrupt/EOF behavior, and the redraw interaction with emacs
and line modes. 120 ghostel tests + 30 evil tests all pass.
Closes #40 (line mode landed alongside the other four modes).
…vation Addresses five issues found while using the new input modes: 1. `M-RET' did not exit char mode. Graphical Emacs sends M-RET as the `<M-return>' symbol, not the `\M-\r' character that the old binding used; terminal Emacs sends it as the character. Bind both forms (and keep `C-M-m' as a synonym) in `ghostel-char-mode-map' and document the choices. 2. `C-c' still reached Emacs from char mode when global minor mode keymaps bound `C-c' as a prefix. Route char mode through `emulation-mode-map-alists' so `ghostel-char-mode-map' overrides minor-mode keymaps rather than just the local map. A buffer- local flag `ghostel--char-mode-override-active' gates the override and is cleared whenever the mode is left. 3. Emacs mode exited on self-insert and `q'. Both bindings are removed so typing in the read-only buffer signals the normal "Buffer is read-only" error and the user must exit explicitly via a `C-c' prefix mode-switch command. Copy mode keeps its fast exit. 4. Copy mode always returned to semi-char on exit, dropping the user back from wherever they came (e.g. Emacs mode). Remember the previous mode in `ghostel--pre-copy-mode' on entry and dispatch to the right mode-entry function on exit. Copy's fast-exit via self-insert only forwards the typed key when the target mode accepts terminal input (semi-char or char). Line mode is not a safe restore target (it is stateful), so falling back to semi-char in that case. 5. Exiting line mode discarded the user's in-progress input. `ghostel--line-mode-teardown' now forwards any pending input to the PTY raw (no newline) so the user can keep editing it at the shell's own prompt after the mode switch instead of losing what they typed. `ghostel-line-mode-send' and `ghostel-line-mode-interrupt' delete the input from the buffer first so teardown does not double-send. Interrupt now also exits to semi-char so the new prompt becomes visible. Tests updated/added: - `ghostel-test-emacs-mode-no-self-insert-exit' replaces the old exit-and-send test, asserting that typing in emacs mode falls through to `self-insert-command'. - `ghostel-test-copy-mode-restores-previous-mode' exercises the pre-copy-mode restore for semi-char, char, and Emacs. - `ghostel-test-line-mode-exit-sends-pending' verifies that exiting line mode via a mode switch ships the in-progress input back to the shell. - `ghostel-test-line-mode-interrupt' updated to expect the exit-to-semi-char transition. All 122 ghostel tests, 30 evil tests, and lint pass.
Two UX fixes found after using the modes: 1. Emacs mode now forwards typed chars to the shell instead of falling through to `self-insert-command' (which errored because the buffer is read-only). The map remaps self-insert to `ghostel--self-insert' and binds RET/TAB/DEL to `ghostel--send-event'. `ghostel--self-insert' just sends the char to the PTY — it never touches the buffer — so the buffer stays read-only and the user's point stays wherever they navigated (the delayed-redraw preserves point in Emacs mode). Navigation keys (C-n/C-p/M-x/arrows/isearch/…) still fall through to the global map so Emacs mode is still a full-Emacs buffer on top of the live terminal. 2. Line mode now protects the scrollback / previous-output region with a `read-only' text property so typing in scrollback signals `text-read-only'. `rear-nonsticky' is set on ONLY the last char of the protected region (not the whole region), so insertion at the input marker — the first typed character — does not inherit read-only from the preceding prompt. If rear-nonsticky covers the whole region, Emacs allows insertion at any position inside it, which is exactly the bug the user hit. `ghostel--line-mode-teardown' removes the properties before the final redraw so the buffer is editable again on exit. Tests updated: - `ghostel-test-emacs-mode-self-insert-forwards' replaces the old "no self-insert exit" test and asserts the remap goes to `ghostel--self-insert', RET/TAB/DEL forward to `ghostel--send-event', and navigation keys still fall through to the global map. - `ghostel-test-line-mode-scrollback-read-only' covers both sides: editing mid-scrollback errors with `text-read-only', editing at the marker works normally, and exiting line mode clears the property. - `ghostel-test-line-mode-teardown-on-exit' adds a sanity check that the read-only property is gone after teardown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds five eat.el-inspired input modes for ghostel. Closes #40.
C-cprefix reservedC-c M-d): all keys to terminal,M-RETexits — for TUI apps that wantC-x,M-x,C-hC-c C-e): unfrozen — terminal keeps running, buffer is read-only, standard Emacs keys (isearch,occur,M-x,C-SPC+M-w) fall through to the global map. Lets you search across a live logC-c C-t): frozen + read-only + aggressive copy keymap (M-wcopies and exits)C-c C-l): buffers input locally in Emacs;RETsends the whole line to the shell in one write. Full Emacs editing (yank, transpose, kill-word…) works. Requires OSC 133 shell integrationMechanics
ghostel--input-mode;ghostel--copy-mode-activeremovedghostel-mode-map(C-c prefix + drag-n-drop) with per-mode child mapsghostel--buffer-editable-p,ghostel--terminal-live-p,ghostel--terminal-frozen-p— all five check sites that usedcopy-mode-activemigratedinhibit-read-only/inhibit-redisplaybindings aroundghostel--redraw, so nothing in the Zig layer changes; the delayed-redraw path now preserves point unconditionally in Emacs modeC-c C-n/C-c C-p) now auto-enters Emacs mode instead of copy mode so the terminal keeps running during navigationBase
This PR targets
scrollback-in-buffer(PR #73) since it depends on the always-materialized-scrollback model. Once #73 merges to main, this can be rebased onto main.Test plan
make build— native module compiles (no Zig changes, sanity check)make test-all— 120 ghostel tests pass (93 existing + 27 new mode tests)make test-evil— 30 evil-integration tests still passmake lint— package-lint + checkdoc cleanC-c M-dchar mode,C-c C-eEmacs mode + isearch across streaming output,C-c C-tcopy mode,C-c C-lline mode with bash +ls -la+ RET