Skip to content

Add with_persistent_menus option#1118

Open
maxim-uvarov wants to merge 3 commits into
nushell:mainfrom
maxim-uvarov:persistent-menus
Open

Add with_persistent_menus option#1118
maxim-uvarov wants to merge 3 commits into
nushell:mainfrom
maxim-uvarov:persistent-menus

Conversation

@maxim-uvarov

Copy link
Copy Markdown

Disclosure: the code was written by an AI agentunder my direction; I can't review Rust line-by-line myself. What was verified: the full reedline test suite plus a new 4-case test matrix (quick × persistent), cargo fmt --check / clippy with CI flags, and manual behavior checks in a live nushell build with both flag values. I've wanted this behavior for a long time and drove the design decisions. Please review accordingly.

Summary

When a menu is active and the user erases the characters they typed to filter it, the engine dismisses the menu. Two hardcoded paths in engine.rs do this: with quick completions on, any Backspace/BackspaceWord/MoveToLineStart deactivates the menu; and regardless of quick completions, any edit that leaves the line buffer empty deactivates it. Neither was configurable.

Erasing the filter is a natural part of browsing completions — narrowing too far and backing up shouldn't throw the menu away, especially since activating a menu on an empty buffer is already valid and shows all values.

Public API change: a new builder option, Reedline::with_persistent_menus(bool), default false. With the default, observable behavior is unchanged. With true, edits refilter an active menu instead of dismissing it; the menu still closes on Esc, Ctrl-C, or when a value is accepted.

Before

With a menu open, erasing input dismisses it:

  • quick completions on: the first backspace closes the menu;
  • quick completions off: the menu survives backspaces until an edit empties the line buffer, then closes.

After

Default (with_persistent_menus(false)): same as before.

with_persistent_menus(true): erasing characters (or emptying the line) sends MenuEvent::Edit like any other edit, so the menu stays open and refilters — down to the full unfiltered list on an empty buffer.

Additional notes

A follow-up nushell PR wires this as $env.config.completions.persistent_menus once a reedline release includes it.

Covered by a new rstest case matrix (quick × persistent) in src/engine.rs.

When a menu is active, two hardcoded paths in the engine dismiss it while the user is erasing input: with quick completions on, any backspace deactivates it; and regardless of quick completions, any edit that leaves the line buffer empty deactivates it. There was no way to configure either.

User reasoning: erasing the filter characters should not dismiss the menu — activating a menu on an empty buffer is already valid and shows all values, so erasing back to empty should be able to behave the same. The flag covers both paths so it also works when quick completions are enabled (the default in downstream nushell).

Add `with_persistent_menus(bool)` (default `false`, preserving current behavior). Naming: a first draft was `with_deactivate_menu_on_empty_buffer(true)` — rejected because enabling the feature read as a double negative, and once the quick-completions path was included, "empty buffer" no longer described the only trigger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fdncred

fdncred commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

I'd like to see how this work in an example so I can try it out before landing it.

@kronberger-droid

Copy link
Copy Markdown
Collaborator

Code looks good, not much to say since it plugs into the existing behavior.
If the example shows the expected behavoir its fine to land from my side.

@maxim-uvarov

maxim-uvarov commented Jul 11, 2026

Copy link
Copy Markdown
Author

@fdncred @kronberger-droid below is the video demo. And nushell/nushell#18573 is the next change that is needed to use the reedline feature in nushell

Screen.Recording.2026-07-11.at.16.33.55.mov

@fdncred

fdncred commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@maxim-uvarov This is fine but I'd like to see an example in the examples folder that demonstrates this reedline feature without nushell. I want to be able to run it myself without nushell to see how it works. Just look at the other examples as a guide on how to create one.

claude and others added 2 commits July 11, 2026 21:53
Requested by review: a runnable demonstration of `with_persistent_menus` that doesn't need the downstream nushell PR.

Uses a small inline prefix completer instead of `DefaultCompleter` because the latter returns nothing for empty input, which would make the kept-open menu invisible exactly at the empty-line moment the example wants to show.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The quick-completions arm also dismisses on BackspaceWord and non-selecting MoveToLineStart; the existing matrix only exercised Backspace. All three share one guard today, but the tests pin the contract in case the arm is ever split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@maxim-uvarov

Copy link
Copy Markdown
Author

@fdncred, this?
Added examples/persistent_menus.rs — runnable standalone:

cargo run --example persistent_menus

Type he, press Tab, then erase with Backspace past the start — the menu stays open and
refilters back up to the full list. Change with_persistent_menus(true) to false in the
example to compare with the current behavior (first Backspace dismisses the menu, since
quick completions are on).

Note: the example uses a small inline prefix completer instead of DefaultCompleter,
because DefaultCompleter returns no suggestions for empty input — the kept-open menu would
be invisible exactly at the empty-line moment the example demonstrates.

Also extended the test matrix to cover the other two commands in the quick-dismissal arm
(BackspaceWord, non-selecting MoveToLineStart).

@fdncred

fdncred commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

yup, that's what i meant. very cool. thanks!

@fdncred

fdncred commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

I'll let @kronberger-droid try it before we land it incase he sees something I missed.

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