Skip to content

Rewrite evil-ghostel from advice to command-remap architecture#264

Open
dakra wants to merge 1 commit into
mainfrom
evil-ghostel-rewrite
Open

Rewrite evil-ghostel from advice to command-remap architecture#264
dakra wants to merge 1 commit into
mainfrom
evil-ghostel-rewrite

Conversation

@dakra
Copy link
Copy Markdown
Owner

@dakra dakra commented May 11, 2026

Summary

  • Replaces ~13 advice-add hooks on evil-* commands with evil-define-operator / evil-define-motion definitions bound via evil-ghostel-mode-map for normal and visual states (vterm-collection-style).
  • Adds a public input-region API in ghostel.el (ghostel-input-start-point, ghostel-cursor-point, ghostel-cursor-row-end-point, ghostel-point-on-cursor-row-p, ghostel-point-in-input-p, ghostel-clamp-to-input, ghostel-goto-input-position, ghostel-delete-input-region, ghostel-replace-input-region) that integrations can build on. Lifts what was internal evil-ghostel--cursor-to-point / --delete-region / --meaningful-length into ghostel core.
  • Drops the shadow-cursor model (and its two tests); the new operators read ghostel--cursor-pos directly each call and the existing sync-inhibit flag already covers the double-call scenarios the shadow was defending against.
  • Operator-level clamp on forward overshoot — dw on the last input word no longer over-deletes into the blank renderer rows below the prompt. End-of-typed-input is detected by stripping trailing whitespace on the cursor row; works for shells without OSC 133.
  • Forward word motions (w, W, e, E) clamped in normal state only so w from the last input word stays on the cursor row. Operator-pending state uses vanilla motions so dw / cw ranges can overshoot and get trimmed by the operator clamp.
  • ghostel-goto-input-position includes vterm-style recovery for literal ^[[C echo (inner program doesn't interpret arrow keys) and for bash-autosuggest accept-on-right-arrow.

Test plan

  • make -j4 all test-evil clean: 370 elisp + 173 native + 76 evil-ghostel tests, 0 unexpected, 2 unrelated skips, lint clean.
  • New regression tests cover: dw-overshoot clamping (evil-ghostel-test-delete-word-on-last-word-clamps-overshoot), w-stays-on-input-row (evil-ghostel-test-forward-word-stops-at-input-end), w-falls-through-in-scrollback (evil-ghostel-test-forward-word-falls-through-off-cursor-row).
  • Live-verified in a clean Emacs -Q against zsh -fi (no OSC 133): word word word<esc>bbcw → correct, w stays on cursor row, dw on last word deletes it, 0/^/$ work.
  • @noctuid scenarios from Issues with evil single line movement/editing #246: please retest bbcw 3× consecutively in zsh and pi to confirm no regressions.

Notes for reviewers

  • evil-ghostel.el ends up at 852 LOC vs evil-collection-vterm's 308. The delta is dominated by evil-ghostel--around-redraw (79 LOC — ghostel's renderer wipes the viewport region, vterm doesn't need this), the cursor↔point sync helpers (~50 LOC — viewport-row math), the insert-state-entry hook (45 LOC — vterm's cursor is the buffer cursor), and the three-way ESC routing (auto / terminal / evil).
  • API stability: once exposed, the ghostel-* input-region functions become public contract.

@dakra dakra mentioned this pull request May 11, 2026
@noctuid
Copy link
Copy Markdown

noctuid commented May 12, 2026

@dakra Thanks a lot. I didn't have time to test thoroughly. I'll test tomorrow but couldn't find any major problems with basic use. Here are two things:

  • it should use remaps only, not bind any specific keys
  • minor: word<esc>a (evil-ghostel-append) will add a visual space that isn't there (point will be after a space but backspace in insert would delete the "d"); test with evil-move-cursor-back nil and zsh (not sure if this is specific to my config, couldn't reproduce in bash)

@dakra dakra force-pushed the evil-ghostel-rewrite branch from 872b853 to fc7f17f Compare May 12, 2026 11:15
@dakra
Copy link
Copy Markdown
Owner Author

dakra commented May 12, 2026

@noctuid Thanks. I fixed the 2 bugs you mentioned.
I'll still have to have a closer look at it and test it more. but if you want to give it another test ride that's obviously helpful as well.

@dakra dakra force-pushed the evil-ghostel-rewrite branch from fc7f17f to 4228dc0 Compare May 12, 2026 15:38
@noctuid
Copy link
Copy Markdown

noctuid commented May 13, 2026

I still haven't seen any more issues with editing/operators, though I'm seeing similar issues to the appending one. Sometimes the cursor will jump to the right side of the window when appending.

evil-forward-char can sometimes go many characters beyond the end of the text in the prompt line in normal state (e.g. in pi always and in zsh I can go right as far as I had previously typed text even if it was deleted). Same applies to e.g. 0/$.

I saw weird point warping a couple of times but can't reliably reproduce.

@dakra dakra force-pushed the evil-ghostel-rewrite branch 4 times, most recently from 4ee9cd4 to 310826b Compare May 13, 2026 21:24
@AndiLavera
Copy link
Copy Markdown

Tested on aarch64-darwin, emacs 30, doom/evil latest with fish. Basic nav seems to be working.

I have the git branch justified right like:

~/.c/doom ❯❯❯ nslookup -type=txt on.quad9.net 2620:fe::9                                      main ✱
  • I will place the insert cursor perfectly before the nslookup. A will place the insert cursor after the main ✱ until first keystroke.
  • $ places my cursor on the in main ✱
  • y$ will copy the main ✱

When I move out of a git repo, the main * in my terminal is gone:

~ ❯❯❯ nslookup -type=txt on.quad9.net 2620:fe::9

These bugs do not persist. A snaps directly after the 9, $ places the cursor on the 9, y$ copies only the written text and does not contain whitespace characters after the 9

@iftheshoefritz
Copy link
Copy Markdown

iftheshoefritz commented May 19, 2026

Is this intended to fix insert at cursor problems as well? Currently:

A 1234 ESC h h i 1 inserts the 1 at the end of the line after 4 instead of just after the first 1.

Also given ☁ ~ 12341, ^ takes me to the cloud in my prompt, not to the first character I have control over (1).

@dakra dakra force-pushed the evil-ghostel-rewrite branch from 310826b to e446072 Compare May 19, 2026 07:04
dakra added a commit that referenced this pull request May 19, 2026
Replaces ~13 advice-add hooks on evil-* commands with proper
evil-define-operator / evil-define-motion definitions bound via
evil-ghostel-mode-map for normal and visual states.

New evil-ghostel commands: -delete (and -line/-char/-backward-char),
-change (and -line), -substitute (and -line), -replace,
-paste-after, -paste-before, -insert (and -line),
-append (and -line), -beginning-of-line, -first-non-blank,
-forward-word-begin/-WORD-begin/-word-end/-WORD-end, -undo, -redo.
Bound in evil-ghostel-mode-map; forward-word motions are
normal-only so operator-pending (dw, cw) uses vanilla motion +
operator clamp.

Drops the shadow-cursor model and all advice on evil-* commands.
Keeps advice on `ghostel--redraw' / `ghostel--set-cursor-style'
and the insert-state-entry hook (essential plumbing).  Those two
advice-add calls install on first mode-enable and are removed
only when the LAST `evil-ghostel-mode' buffer disables —
otherwise toggling off in one buffer would silently strip the
wrapper from every other ghostel buffer.

PTY-driven input editing helpers (new, live here because they all
depend on a cooperative line editor — readline / zle /
prompt_toolkit accepting arrow keys, backspace, bracketed paste):

- evil-ghostel-goto-input-position — moves the terminal cursor via
  arrow keys, with vterm-style recovery for literal `^[[C' echo
  and bash-autosuggest accept-on-right-arrow.
- evil-ghostel-delete-input-region, -replace-input-region.
- evil-ghostel-point-in-input-p — predicate for the editable
  input region.
- evil-ghostel--clamp-to-input — trims an operator's range to the
  live input region.  Clamps END to row-end on forward overshoot
  so `dw' on the last input word no longer over-deletes into blank
  renderer rows below the prompt.
- evil-ghostel--cursor-row-end-point — end of typed input on the
  cursor row.  Returns the end of the FIRST contiguous
  `ghostel-input' region (so fish's right-aligned prompt — which
  libghostty's per-cell heuristic also tags SEMANTIC_INPUT
  despite no OSC 133;B emission — is ignored, issue #264), with
  a whitespace-gap fallback (`evil-ghostel-right-prompt-gap',
  default 6) when no `ghostel-input' cells are on the row.
  `i' / `a' / `I' / `A' / `$' / `y$' clamp to this, so RPROMPT
  and zsh-autosuggest hint cells cannot capture the cursor.
- evil-ghostel--input-start-from-prop, --meaningful-input-length,
  --sync-render — internal helpers.  `--sync-render' loops the
  PTY drain (capped via `evil-ghostel-sync-render-max-iterations')
  and force-runs `ghostel--delayed-redraw' when the filter
  deferred a bulk-output redraw, so callers reading
  `ghostel--cursor-pos' after large echoes (100+ backspaces in
  `cc' / `cw') see post-drain state instead of stale values.

Tests: 113 evil-ghostel tests covering operators, motions, paste,
undo/redo, escape handling, plus unit tests for the new
input-region helpers, the advice lifecycle, and the sync-render
forced-redraw path.
@dakra dakra force-pushed the evil-ghostel-rewrite branch from e446072 to b5378fb Compare May 19, 2026 13:58
@dakra
Copy link
Copy Markdown
Owner Author

dakra commented May 19, 2026

I experimented a bit with different versions today.
It's very fragile, I feel like every time I fixed one bug, another edge-case triggered and didn't work :(
Especially fish with a right_prompt was tricky.

But in my short testing it seems to work now.
Can you test again @AndiLavera @iftheshoefritz @noctuid ?

@dakra dakra closed this May 22, 2026
@dakra dakra deleted the evil-ghostel-rewrite branch May 22, 2026 08:53
@noctuid
Copy link
Copy Markdown

noctuid commented May 22, 2026

@dakra Is this PR being canceled or replaced?

@dakra dakra restored the evil-ghostel-rewrite branch May 22, 2026 12:49
@dakra
Copy link
Copy Markdown
Owner Author

dakra commented May 22, 2026

@noctuid oh. I was doing some housekeeping in magit of git-refs and deleted a bunch of old branches.
This one was not supposed to be in there, sorry 🙈

@dakra dakra reopened this May 22, 2026
@iftheshoefritz
Copy link
Copy Markdown

None of these are fixed:

A 1234 ESC h h i 1 inserts the 1 at the end of the line after 4 instead of just after the first 1.

Also given ☁  ~  12341, ^ takes me to the cloud in my prompt, not to the first character I have control over (1).

@iftheshoefritz
Copy link
Copy Markdown

Also a weird one: typing f flickers the cursor briefly to the position just before as the letter appears. This doesn't happen for any other character in the (English) alphabet.

Replaces ~13 advice-add hooks on evil-* commands with proper
evil-define-operator / evil-define-motion definitions bound via
evil-ghostel-mode-map for normal and visual states.

New evil-ghostel commands: -delete (and -line/-char/-backward-char),
-change (and -line), -substitute (and -line), -replace,
-paste-after, -paste-before, -insert (and -line),
-append (and -line), -beginning-of-line, -first-non-blank,
-forward-word-begin/-WORD-begin/-word-end/-WORD-end, -undo, -redo.
Bound in evil-ghostel-mode-map; forward-word motions are
normal-only so operator-pending (dw, cw) uses vanilla motion +
operator clamp.

Drops the shadow-cursor model and all advice on evil-* commands.
Keeps advice on `ghostel--redraw' / `ghostel--set-cursor-style'
and the insert-state-entry hook (essential plumbing).  Those two
advice-add calls install on first mode-enable and are removed
only when the LAST `evil-ghostel-mode' buffer disables —
otherwise toggling off in one buffer would silently strip the
wrapper from every other ghostel buffer.

PTY-driven input editing helpers (new, live here because they all
depend on a cooperative line editor — readline / zle /
prompt_toolkit accepting arrow keys, backspace, bracketed paste):

- evil-ghostel-goto-input-position — moves the terminal cursor via
  arrow keys, with vterm-style recovery for literal `^[[C' echo
  and bash-autosuggest accept-on-right-arrow.
- evil-ghostel-delete-input-region, -replace-input-region.
- evil-ghostel-point-in-input-p — predicate for the editable
  input region.
- evil-ghostel--clamp-to-input — trims an operator's range to the
  live input region.  Clamps END to row-end on forward overshoot
  so `dw' on the last input word no longer over-deletes into blank
  renderer rows below the prompt.
- evil-ghostel--cursor-row-end-point — end of typed input on the
  cursor row.  Returns the end of the FIRST contiguous
  `ghostel-input' region (so fish's right-aligned prompt — which
  libghostty's per-cell heuristic also tags SEMANTIC_INPUT
  despite no OSC 133;B emission — is ignored, issue #264), with
  a whitespace-gap fallback (`evil-ghostel-right-prompt-gap',
  default 6) when no `ghostel-input' cells are on the row.
  `i' / `a' / `I' / `A' / `$' / `y$' clamp to this, so RPROMPT
  and zsh-autosuggest hint cells cannot capture the cursor.
- evil-ghostel--input-start-from-prop, --meaningful-input-length,
  --sync-render — internal helpers.  `--sync-render' loops the
  PTY drain (capped via `evil-ghostel-sync-render-max-iterations')
  and force-runs `ghostel--delayed-redraw' when the filter
  deferred a bulk-output redraw, so callers reading
  `ghostel--cursor-pos' after large echoes (100+ backspaces in
  `cc' / `cw') see post-drain state instead of stale values.

Tests: 113 evil-ghostel tests covering operators, motions, paste,
undo/redo, escape handling, plus unit tests for the new
input-region helpers, the advice lifecycle, and the sync-render
forced-redraw path.
@dakra dakra force-pushed the evil-ghostel-rewrite branch from b5378fb to 3994f6b Compare May 22, 2026 15:45
@dakra
Copy link
Copy Markdown
Owner Author

dakra commented May 22, 2026

A 1234 ESC h h i 1 inserts the 1 at the end of the line after 4 instead of just after the first 1.

that works for me.
Do you have the latest version? I rebased, so you have to do a git hard reset.

Also given ☁ ~ 12341, ^ takes me to the cloud in my prompt, not to the first character I have control over (1).

Do you have shell integration on?
What shell are you using?

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.

4 participants