Skip to content

LLE: inline shadow ghost for the open completion menu's top candidate#357

Merged
berrym merged 2 commits into
masterfrom
lle/menu-shadow-ghost
Jun 25, 2026
Merged

LLE: inline shadow ghost for the open completion menu's top candidate#357
berrym merged 2 commits into
masterfrom
lle/menu-shadow-ghost

Conversation

@berrym

@berrym berrym commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the ghost-over-menu work begun in #356. When a completion menu opens in pre-navigation, its highlighted candidate is now shown inline after the command as a faint shadow ghost while the menu box lists the alternatives below -- the menu's companion to the history autosuggestion. A single Right (menu commit) takes it.

Built on #356's unified geometry: the ghost wrap rows live in screen_buffer (num_rows), so a shadow drawn over a menu composes with the menu rows in one cursor-math total. The earlier shadow attempt collapsed the cursor precisely because that accounting path did not exist yet; it does now.

Two commits:

  1. Suppress autosuggestion acceptance while a completion menu is open. A pre-existing defect the shadow made visible: the move-or-accept-suggestion keys (RIGHT, END, Ctrl-E, Ctrl-F, Ctrl-Right/Meta-f partial-accept) gated on has_autosuggestion(), which reported the (display-suppressed) history suggestion even under a menu, so they spliced it into the buffer underneath the menu. Fixed at the single shared predicate rather than per-handler: has_autosuggestion() reports nothing while a menu is visible.

  2. The shadow ghost itself. display_controller_set_menu_shadow overrides the shared autosuggestion layer (never clears it, so the history ghost survives menu-less frames) and raises menu_shadow_active, which relaxes the geometry + render ghost guards for that one case. Driven from a single chokepoint in refresh_display; recomputed every refresh, so it tracks open / filter / leave-pre-navigation / close with no per-keybinding wiring.

Curation

Gated by completion.menu_shadow_ghost: curated on in lush mode, off in bash/zsh/posix (which show the menu box alone, matching readline/zle). Settable via lushrc.toml.

Verification

  • meson test: 156/156, zero warnings (werror).
  • PTY (pyte): shadow renders inline over the menu; cursor stays at the typed-text end including a wrapping shadow (the exact geometry that collapsed the cursor before); TAB-commit transitions cleanly; bash-mode and TOML toggles disable it; history ghost intact; C-Right over a menu no longer splices history.
  • A 15-agent adversarial audit of the shared-layer ownership drove both fixes; remaining findings were a refuted false-positive and one invisible transient frame.

🤖 Generated with Claude Code

berrym added 2 commits June 25, 2026 00:25
The move-or-accept-suggestion keys -- RIGHT, END, Ctrl-E, Ctrl-F, and the
forward-word/partial-accept (Ctrl-Right, Meta-f) -- gate on has_autosuggestion(),
which reported a suggestion whenever ctx->current_suggestion was non-empty.
update_autosuggestion() fills that buffer regardless of whether a completion menu
is visible, while the display suppresses the history ghost under a menu. The two
disagreed: with a menu open, the keys spliced the invisible history suggestion
into the command line underneath the menu, leaving the menu box drawn for the old
word over a buffer that had changed.

Only the RIGHT context action carried its own menu guard. Rather than copy that
guard into every accept handler, report no autosuggestion from has_autosuggestion()
while a completion menu is visible: the history ghost is suppressed then and the
menu's candidate is committed through menu navigation, not the accept handlers.
Every accept key falls through to its normal motion instead of splicing text under
the menu; the menu's own navigation keys do not consult has_autosuggestion and are
unaffected. No-menu acceptance is unchanged.
A completion menu opened in pre-navigation keeps the command line literal and
lists candidates in the box below, but nothing previewed the candidate that a
single Right/accept would take. Offer the highlighted candidate inline as a
faint shadow ghost over the open menu -- the menu's companion to the history
autosuggestion.

The ghost and an open menu were mutually exclusive by design: three
!completion_menu_visible guards (geometry, render, and the layer store) cleared
the ghost whenever a menu was up. Relax the geometry and render guards for the
single shadow case behind menu_shadow_active, set through a new
display_controller_set_menu_shadow. The ghost wrap rows are now part of
screen_buffer geometry, so a shadow drawn over a menu composes with the menu
rows in one num_rows total -- the cursor math and prior-frame cleanup handle
command + shadow + menu uniformly, with no second accounting path (an earlier
attempt collapsed the cursor precisely because that path did not exist yet).

The shadow is driven from one point in refresh_display: the menu's current
selection is recomputed into the candidate's remainder (the splicer in accept
phase, exactly as the completion-fallback autosuggestion) every refresh, so it
tracks open, filter, the first navigation that leaves pre-navigation, and close
with no per-keybinding wiring. set_menu_shadow only overrides the shared
autosuggestion layer; it never clears it, so the history ghost survives on
menu-less frames. It clears outside pre-navigation, off the buffer end, and with
no menu.

Gated by completion.menu_shadow_ghost, curated on in lush mode and off in
bash/zsh/posix (which show the menu box alone, matching readline/zle); settable
via lushrc.toml.
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.62687% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/lle/lle_readline.c 82.69% 9 Missing ⚠️
src/display/display_controller.c 27.27% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

@berrym berrym merged commit 7e52f97 into master Jun 25, 2026
5 checks passed
@berrym berrym deleted the lle/menu-shadow-ghost branch June 25, 2026 04:41
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