Thanks to visit codestin.com
Credit goes to docs.rs

Skip to main content

Crate ratatui_which_key

Crate ratatui_which_key 

Source
Expand description

A which-key popup widget for ratatui applications.

This crate provides a popup widget that displays available keybindings, similar to Neovim’s which-key plugin.

§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”:

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Scope {
    Global,
    TextInputBox,
    SearchPanel,
    // ....
}

// When changing focus to another pane/window/etc:
app.which_key.set_scope(Scope::TextInputBox)

§Actions

ratatui-which-key returns an Action when a keybind is triggered:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Action {
    Quit,
    ToggleHelp,
    MoveUp,
    MoveDown,
    Save,
    OpenFile,
    SearchFiles,
    SearchBuffers,
    // ...
}

// Must implement Display to show descriptions in the which-key popup.
impl std::fmt::Display for Action {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Action::Quit => write!(f, "quit"),
            Action::ToggleHelp => write!(f, "toggle help"),
            Action::MoveUp => write!(f, ""),
            Action::MoveDown => write!(f, "move down"),
            Action::Save => write!(f, "save"),
            Action::OpenFile => write!(f, "open file"),
            Action::SearchFiles => write!(f, "search files"),
            Action::SearchBuffers => write!(f, "search buffers"),
        }
    }
}

// In your input handler:
if let Some(action) = app.which_key.handle_event(key).into_action() {
    match action {
        Action::ToggleHelp => app.which_key.toggle(),
        Action::Quit => (), // logic here
        Action::MoveUp => (),
        Action::MoveDown => (),
        Action::Save => (),
        Action::OpenFile => (),
        Action::SearchFiles => (),
        Action::SearchBuffers => (),
    }
}

§Categories

The ratatui-which-key popup displays keybinds sorted by category:

#[derive(derive_more::Display, Debug, Clone, Copy, PartialEq, Eq)]
enum Category {
    General,
    Navigation,
    Search,
    // ...
}

§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.

struct App {
    which_key: WhichKeyState<KeyEvent, Scope, Action, Category>,
}

let mut keymap = Keymap::new();
keymap
    // Keys can be bound individually by specifying both the category and scope.
    .bind("?", Action::ToggleHelp, Category::General, Scope::Global)
    // Sequences are supported. This binds to sequence "sg".
    .bind("sg", Action::SearchGrep, Category::General, Scope::Global)
    // "describe_group" used to add a description to groups. Display will default to "..." if
    // no group description is found.
    .describe_group("<space>", "<leader>") // (key sequence, description)
    .describe_group("<leader>g", "general")
    // Bindings can be added to a specific group while also providing a description.
    .group("s", "search", |g| {
        // "sf" binding
        g.bind("f", Action::SearchFiles, Category::General, Scope::SearchPanel)
         // "sb" binding
         .bind("b", Action::SearchBuffers, Category::General, Scope::SearchPanel);
    })
    // However, using `.scope` is recommended in most cases since scopes represent whatever is
    // currently "in focus" for your app.
    .scope(Scope::Global, |global| {
        global
            .bind("?", Action::ToggleHelp, Category::General)
            .bind("j", Action::MoveDown, Category::Navigation)
            // control keys supported
            .bind("<c-c>", Action::Quit, Category::General)
            // f-keys supported
            .bind("<F1>", Action::ToggleHelp, Category::General)
            // sequences supported
            .bind("<leader>w", Action::Save, Category::General)
            // sequences can start with any key
            .bind("gof", Action::OpenFile, Category::General);
    })
    .scope(Scope::Insert, |insert| {
        // While in the `Insert` scope, all keys will be routed to this handler.
        insert.catch_all(|key| {
            // You can filter the keys here
            use crossterm::event::{KeyCode, KeyModifiers};
            match key {
                k if matches!(k, KeyEvent { code: KeyCode::Char(_), .. }) => {
                    if let KeyCode::Char(ch) = key.code {
                        Some(Action::InsertModePrintableChar(ch))
                    } else {
                        None
                    }
                }
                k if matches!(k, KeyEvent { code: KeyCode::Esc, .. }) => Some(Action::ToNormalMode),
                _ => None
            }
        });
    })
    // Helper method if you want to bind based on category.
    .category(Category::Navigation, |nav| {
        nav
            .bind("k", Action::MoveUp, Scope::Global)
            .bind("j", Action::MoveDown, Scope::Global);
    })
    // Helper method if you want to bind based on both scope and category.
    .scope_and_category(Scope::Global, Category::Navigation, |g| {
       g.bind("<leader>gg", Action::MoveUp)
        .bind("<leader>gd", Action::MoveDown);
    });

let app = App { which_key: WhichKeyState::new(keymap, Scope::Global) };

§Example

use crossterm::event::KeyEvent;
use ratatui_which_key::{Keymap, WhichKey, WhichKeyState};

// Define your action type
#[derive(Debug, Clone)]
enum Action { Quit, Save }

impl std::fmt::Display for Action {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Action::Quit => write!(f, "quit"),
            Action::Save => write!(f, "save"),
        }
    }
}

// Define your scope type
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Scope { Global, Insert }

// Define keybind categories
#[derive(derive_more::Display, Debug, Clone, PartialEq)]
enum Category { General, Navigation }

// Build the keymap
let mut keymap: Keymap<KeyEvent, Scope, Action, Category> = Keymap::new();
keymap.bind("q", Action::Quit, Category::General, Scope::Global);

// Create state
let mut state = WhichKeyState::new(keymap, Scope::Global);

// In your event loop, handle keys:
if let Some(action) = state.handle_key(key) {
    // dispatch action
}

// If handling more than keys (like mouse or terminal events):
use ratatui_which_key::CrosstermStateExt;
if let Some(action) = state.handle_event(event).into_action() {
    // dispatch action
}

// When rendering
let widget = WhichKey::new();
widget.render(&mut buf, &mut state);

§Feature Flags

  • crossterm (default): Provides KeyEvent implementation and event handlers

Structs§

Binding
A key binding with an associated description.
BindingGroup
A group of related key bindings under a shared category.
CategoryBuilder
Builder for creating keybindings within a specific category.
DisplayBinding
A key binding prepared for display, including category information.
GroupBuilder
A builder for binding key sequences under a shared prefix.
KeyChild
A key binding with its associated key and node.
Keymap
A hierarchical keymap that maps key sequences to actions with scope and category support.
LeafBinding
A flattened key binding for display purposes.
LeafEntry
A single action entry in a leaf node.
ScopeAndCategoryBuilder
A builder for binding key sequences with a pre-configured scope and category.
ScopeBuilder
Builder for creating keybindings within a specific scope.
WhichKey
Configuration for the which-key widget.
WhichKeyState
State for the which-key widget.

Enums§

DisplayMode
How bindings are organized in the popup.
EventResult
Result of handling a crossterm event.
KeyNode
A node in the keybinding tree, either a leaf with actions or a branch with children.
LayoutStrategy
How the popup distributes content across columns.
NodeResult
The result of traversing a node in the key binding tree.
PopupPosition
Position of the which-key popup on screen.

Traits§

CrosstermKeymapExt
Extension trait for Keymap to add crossterm event handlers.
CrosstermStateExt
Extension trait for WhichKeyState to handle crossterm events.
Key
A trait for abstracting keyboard key representation.

Functions§

parse_key_sequence
Parses a key sequence string into a vector of keys.