ratatui-which-key
An application-level input handler and focus manager for ratatui applications with an API and popup widget inspired by folke's which-key.nvim.

ratatui-which-key is designed to handle all of the keyboard input for your application. You route input events to it from the main event loop, and it returns an Action to perform based on your configured keybinds.
Check out the docs for more info.
There is also a sample application that you can run with cargo run --example demo which shows how to perform bindings and set up ratatui-which-key for usage in an app.
How It Works
ratatui-which-key requires three data types be defined in your application.
Scopes
The scope is what part of your application is currently "in focus":
// To change focus to another pane/window/section/area/etc:
app.which_key.set_scope
Actions
ratatui-which-key returns an Action when a keybind is triggered:
// Must implement Display to show the names in the which-key popup.
Categories
The ratatui-which-key popup displays a header for each category available for a scope and then lists the associated keybinds under that category.
// Must implement Display to show the category names in the which-key popup.
Keymap Configuration
You'll need to put a WhichKeyState<KeyEvent, Scope, Action, Category> at the top-level of your application (like in App). Then at program start, configure your keybinds by creating a new Keymap. The code comments explain the different ways of performing keybindings.
use KeyEvent;
let mut keymap = new;
keymap
// Keys can be bound individually by specifying both the category and scope.
.bind
// Sequences are supported. This binds to sequence "sg".
.bind
// "describe_group" adds a description to groups. Display will default to "..." if
// no group description is found.
.describe_group // (key sequence, description)
.describe_group
// Bindings can be added to a specific group while also providing a description.
.group
// However, using `.scope` is recommended in most cases since scopes represent whatever is
// currently "in focus" for your app.
.scope
.scope
// Helper method if you want to bind based on category.
.category
// Helper method if you want to bind based on both scope and category.
.scope_and_category;
// Create new state with a keymap and initial scope.
app.which_key = new;
Input Handling
To route events to ratatui-which-key:
use CrosstermStateExt;
if let Some = app.which_key.handle_event.into_action
Mouse and Terminal Event Handlers
You can register handlers for mouse, resize, and focus events on your keymap. The current Scope is provided as part of the handler, so you can choose to return actions globally or confine them to specific scopes:
use CrosstermKeymapExt;
let mut keymap = new;
keymap
// `on_mouse` receives a `crossterm::event::MouseEvent` and the current scope
.on_mouse
// `on_resize` receives the new terminal dimensions (cols, rows) and the current scope
.on_resize
// `on_focus_gained` and `on_focus_lost` receive the current scope
.on_focus_gained
.on_focus_lost;
Rendering
To render:
// (in your top-level render function)
if app.which_key.active