Skip to content

Don't let a menu with no suggestions swallow Enter - #1175

Merged
kronberger-droid merged 1 commit into
nushell:mainfrom
shreeve:menu-must-not-swallow-enter
Sep 9, 2026
Merged

kronberger-droid merged 1 commit into
nushell:mainfrom
shreeve:menu-must-not-swallow-enter

Conversation

@shreeve

@shreeve shreeve commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

The symptom

With a completion menu registered, Enter sometimes appears dead: the line doesn't run, nothing is inserted, and a second Enter is needed. Repro:

  1. Type a word with matches, press Tab — the menu opens.
  2. Keep typing until the word under the cursor matches nothing (the menu shows "NO RECORDS FOUND" — or the user has long since scrolled on and can't see it).
  3. Press Enter.

The mechanism

The menu stays active from the first Tab until Esc/Enter/empty-buffer, and the Enter | Submit | SubmitOrNewline arm consumes the event for any active menu — including one with zero filtered values, where replace_in_buffer has nothing to accept. The keypress is spent deactivating an invisible menu.

The change

  • The arm's guard now ignores active menus whose values are empty, so the event falls through to the normal submit path.
  • submit_buffer deactivates menus, so a menu that let a submit through can't stay active into the next line's editing.

Two tests: the empty-menu case directly, and the full reported shape (Tab mid-line, type on, Enter runs the line). cargo fmt --all, cargo clippy --locked --all-targets --all-features (0 warnings), and the test suite pass; the pre-existing macOS-only parallel flake in the system-clipboard tests is unrelated.

Found while building a SQL REPL on reedline (duckdb-harbor's pilot), where a stale menu turned end-of-statement Enters into no-ops. Diagnosed and written with AI assistance; verified by the suite here and by daily use downstream.

kronberger-droid added a commit that referenced this pull request Sep 8, 2026
…1203)

* feat(menu): add MenuAccept to take a completion without submitting

`Enter` over an open menu accepts the highlighted item, but with no menu
it submits the line, so nothing can be bound to "accept, then keep
typing". #965 tried a `SubmitOrSpace` event for a zsh-style space and
had to bolt the space onto it; #821 needed the same accept for an
always-open menu. One event that stops after the accept covers both as
a keybinding, `Multiple([MenuAccept, InsertChar ' '])`.

An empty menu reports inapplicable instead of closing, since spending
the keypress on an invisible menu is the bug #1175 is fixing in the
`Enter` arm.

* test(menu): pin MenuAccept under UntilFound next to the Multiple case
@kronberger-droid

Copy link
Copy Markdown
Collaborator

Just needs a rebase, then I am fine to land it.
Thanks!

The completion menu stays active while the user types past it, and any
Enter/Submit/SubmitOrNewline while a menu is active is routed to the
menu. A menu whose filtered suggestions are empty has nothing to
accept, so the keypress simply died: the line did not run and nothing
visibly happened, leaving Enter to work only on the second press.

The guard now ignores active menus with no values, so the event falls
through to the normal submit path, and submit_buffer closes any menu
that let a submit through so it cannot stay active into the next
line's editing.
@shreeve
shreeve force-pushed the menu-must-not-swallow-enter branch from b64bd30 to f03d265 Compare September 9, 2026 11:25
@shreeve

shreeve commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main. The only conflict was the arm body — #1181 had replaced the loop with find(|menu| menu.is_active()), so the empty-menu predicate now lives in the find. fmt/clippy/tests clean.

Nice to see #1203 land, incidentally — same reasoning on the empty menu.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Yeah.
The Enter arm and MenuAccept now carry the same emptiness rule and the same body, so the two can collapse: Enter tries MenuAccept first and falls through to the submit path when it reports inapplicable, which drops the guard and leaves the rule in one place instead of two that can drift apart.
Feel free to take that as a follow-up if you want it, I have it on my mind anyways.

@kronberger-droid
kronberger-droid merged commit dba4621 into nushell:main Sep 9, 2026
7 checks passed
@shreeve

shreeve commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Happy to take that.

Worth noting the two are not quite identical today: the guard filters on is_active() && !get_values().is_empty(), while MenuAccept finds the first active menu and checks emptiness after. They disagree if the first active menu is the empty one — unreachable in practice, since ReedlineEvent::Menu refuses to activate while active_menu() is Some, but that is the drift you are describing, already written down twice. Collapsing removes the chance.

I will send it as a follow-up.

@shreeve

shreeve commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Sent it: #1210. The Submit and SubmitOrNewline half of the old guard turned out to have no test at all, so the two new cases cover all three events — and they pass on the two-rule version too, so they pin the behavior rather than describe the refactor.

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