Skip to content

refactor(engine): leave the menu accept rule in one place - #1210

Merged
kronberger-droid merged 1 commit into
nushell:mainfrom
shreeve:menu-accept-is-the-one-rule
Sep 13, 2026
Merged

kronberger-droid merged 1 commit into
nushell:mainfrom
shreeve:menu-accept-is-the-one-rule

Conversation

@shreeve

@shreeve shreeve commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The follow-up from #1175, as suggested there.

Enter grew a guard that accepts an open menu's selection, and #1203 landed
MenuAccept carrying the same rule a second time. They are not even spelled the
same way: the guard filters on is_active() && !get_values().is_empty(), while
MenuAccept finds the first active menu and checks emptiness afterwards. Those
disagree when the first active menu is the empty one — unreachable today, since
ReedlineEvent::Menu refuses to activate while active_menu() is Some, but it is
the drift worth removing rather than documenting.

Enter, Submit and SubmitOrNewline now try MenuAccept first and carry on to
the submit path when it declines, which is what the guard was arranging for. The
guarded arm goes away, the rule lives in the MenuAccept arm, and each submit arm
says plainly that a menu gets first refusal.

Behavior

Unchanged. MenuAccept reports Inapplicable for exactly the cases the guard
excluded — no menu open, or an open menu with nothing to accept — and the
delegation sits at the top of each arm, ahead of the bashisms and abbreviation
expansion, so the ordering is what it was.

Worth noting the guard covered Submit and SubmitOrNewline as well as Enter,
and nothing tested that. It does now.

One thing this surfaced, not fixed here

MenuAccept does not check results_are_provisional(), so accepting over stale
suggestions splices nothing — the span belongs to another line and is refused
downstream — while Deactivate still runs and the arm still reports Handled. A
dead key, reachable with an async completer. decide_menu_completion and MenuNext
both guard this explicitly and their comments say exactly why.

This is not a regression: the deleted guard had no provisional check either, so
Enter behaved this way already. But this PR is what makes MenuAccept the single
path, and the rule it now owns is missing a check its two siblings have. Happy to fix
it in a follow-up, or fold it in here if you would rather it not land as-is.

Tests

Two rstest cases over all three events:

  • every_submit_event_accepts_an_open_menu — the menu takes the keypress, closes,
    and its selection reaches the buffer
  • every_submit_event_passes_an_empty_menu — once the menu has nothing to offer,
    the line runs

Both pass on the two-rule version as well, which is the point: they pin the
behavior, and this does not move it.

cargo fmt --check and cargo clippy --all-targets --all-features -- -D warnings
are clean; the suite passes with default and with all features.

Comment thread src/engine.rs Outdated
// itself, which is what leaves `Enter` free to reach the line
// instead of appearing dead.
match self.menus.iter_mut().find(|menu| menu.is_active()) {
Some(menu) if !menu.get_values().is_empty() => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fold the provisional check in here, since this PR makes MenuAccept the only accept path.
With provisional results the splice is refused downstream, but Deactivate still runs and the arm reports Handled, so Enter closes the menu and does nothing else.
Decline when menu.results_are_provisional(), the way MenuNext and decide_menu_completion already refuse a stale accept.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folded in. The guard now also declines when menu.results_are_provisional(), so the arm refuses anything it cannot actually splice — an empty menu and a stale one alike — and Enter reaches the line instead of dying against a menu that was never going to accept.

Two cases cover it: MenuAccept over a stale menu reports Inapplicable and leaves the menu open, and Enter over the same menu runs the line as typed. Both fail without the check.

@shreeve
shreeve force-pushed the menu-accept-is-the-one-rule branch from 2a043ad to a809898 Compare September 12, 2026 02:27
@shreeve

shreeve commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main, CI green. #1209 is rebased and green as well.

No rush on either — just noting this one is ready whenever you have a window, since it is the collapse you sketched on #1175.

`Enter` grew a guard that accepts an open menu's selection, and `MenuAccept`
landed with the same rule written a second way: the guard filters on
`is_active() && !get_values().is_empty()`, `MenuAccept` finds the first
active menu and checks emptiness after. Nothing can tell them apart today,
since `ReedlineEvent::Menu` will not activate a second menu while one is
open, but the two are already spelled differently and only one of them is
the one anybody reads.

`Enter`, `Submit` and `SubmitOrNewline` now offer an open menu first refusal
and carry on to the submit path when it declines, which is what the guard
was arranging for. The rule lives in the `MenuAccept` arm, and the reasoning
that used to sit above `Enter` describing the guard sits with it.

That arm is now the only accept path, so it also refuses a provisional
answer, the way `MenuNext` and `decide_menu_completion` already do. A stale
value is rejected downstream because its span belongs to another line, and
accepting one would still deactivate the menu and report the key handled —
closing the menu over a completion that never happened, and spending an
`Enter` on nothing. Declining hands the key back to the line.

The new cases pin all of it from both sides: three submit events accept an
open menu and run the line once the menu has nothing to offer, and a stale
menu is refused through both `MenuAccept` and `Enter`.
@shreeve
shreeve force-pushed the menu-accept-is-the-one-rule branch from a809898 to 0774bc0 Compare September 12, 2026 05:20
@kronberger-droid

Copy link
Copy Markdown
Collaborator

Nice, looks good to land for me.

@kronberger-droid
kronberger-droid merged commit d9db2f7 into nushell:main Sep 13, 2026
7 checks passed
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.

2 participants