| Crates.io | crossterm-actions |
| lib.rs | crossterm-actions |
| version | 1.0.1 |
| created_at | 2026-01-10 17:05:37.859636+00 |
| updated_at | 2026-01-10 17:52:42.303148+00 |
| description | Hierarchical TUI action/keybinding management for crossterm applications |
| homepage | |
| repository | https://github.com/JonathanTroyer/crossterm-actions |
| max_upload_size | |
| id | 2034441 |
| size | 153,170 |
Hierarchical TUI action/keybinding management for crossterm applications.
TuiEvent::Navigation(NavigationEvent::Left)ActionBinding::builder().action(event).key(key.with_ctrl()).build()KeyEvent to TuiEventuse crossterm_actions::{
ActionBinding, ActionConfig, EditingMode, EventDispatcher,
TuiEvent, AppEvent, NavigationEvent, keys,
};
// Create a dispatcher with emacs defaults
let dispatcher = EventDispatcher::default();
// Or build custom bindings
let mut config = ActionConfig::new(EditingMode::Emacs);
config.bind(
ActionBinding::builder()
.action(TuiEvent::App(AppEvent::Quit))
.key(keys::char('q'))
.key(keys::char('c').with_ctrl())
.description("Exit the application")
.build(),
);
config.bind(
ActionBinding::builder()
.action(TuiEvent::Navigation(NavigationEvent::Left))
.key(keys::LEFT)
.key(keys::char('h'))
.build(),
);
config.compile(); // Build lookup table
let dispatcher = EventDispatcher::new(config);
bind_action! MacroFor more concise binding definitions, use the bind_action! macro:
use crossterm_actions::{
bind_action, ActionConfig, EditingMode, EventDispatcher,
TuiEvent, AppEvent, NavigationEvent, keys,
};
let mut config = ActionConfig::new(EditingMode::Emacs);
// Single key
bind_action!(config, TuiEvent::App(AppEvent::Quit), keys::char('q'));
// Single key with description
bind_action!(config, TuiEvent::App(AppEvent::Quit), keys::char('c').with_ctrl(), "Force quit");
// Multiple keys
bind_action!(config, TuiEvent::Navigation(NavigationEvent::Left), [keys::LEFT, keys::char('h')]);
// Multiple keys with description
bind_action!(
config,
TuiEvent::App(AppEvent::Help),
[keys::char('?'), keys::f(1)],
"Show help"
);
config.compile();
let dispatcher = EventDispatcher::new(config);
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
use crossterm_actions::{EventDispatcher, TuiEvent, AppEvent};
let dispatcher = EventDispatcher::default();
// Create a key event (normally from crossterm::event::read())
let event = KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
};
match dispatcher.dispatch(&event) {
Some(TuiEvent::App(AppEvent::Quit)) => {
println!("User wants to quit");
}
Some(other) => {
println!("Got action: {:?}", other);
}
None => {
// Key not bound to any action
}
}
use crossterm_actions::{TuiEvent, NavigationEvent, InputEvent, SelectionEvent, AppEvent};
// All available events
let events: Vec<TuiEvent> = vec![
// Navigation
TuiEvent::Navigation(NavigationEvent::Left),
TuiEvent::Navigation(NavigationEvent::Right),
TuiEvent::Navigation(NavigationEvent::Up),
TuiEvent::Navigation(NavigationEvent::Down),
TuiEvent::Navigation(NavigationEvent::Home),
TuiEvent::Navigation(NavigationEvent::End),
TuiEvent::Navigation(NavigationEvent::PageUp),
TuiEvent::Navigation(NavigationEvent::PageDown),
// Input/Editing
TuiEvent::Input(InputEvent::Confirm),
TuiEvent::Input(InputEvent::Cancel),
TuiEvent::Input(InputEvent::Delete),
TuiEvent::Input(InputEvent::Backspace),
// Selection
TuiEvent::Selection(SelectionEvent::Next),
TuiEvent::Selection(SelectionEvent::Prev),
TuiEvent::Selection(SelectionEvent::Toggle),
// Application
TuiEvent::App(AppEvent::Quit),
TuiEvent::App(AppEvent::Help),
TuiEvent::App(AppEvent::Refresh),
TuiEvent::App(AppEvent::Search),
];
| Action | Keys |
|---|---|
| Left | ←, Ctrl+B |
| Right | →, Ctrl+F |
| Up | ↑, Ctrl+P |
| Down | ↓, Ctrl+N |
| Home | Home, Ctrl+A |
| End | End, Ctrl+E |
| Quit | q, Ctrl+C |
| Help | ?, F1 |
| Action | Keys |
|---|---|
| Left | ←, h |
| Right | →, l |
| Up | ↑, k |
| Down | ↓, j |
| Home | Home, 0, ^ |
| End | End, $ |
| Quit | q |
Use your own action types by implementing Clone, Eq, and Hash:
use crossterm_actions::{ActionBinding, ActionConfig, EditingMode, EventDispatcher, keys};
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
enum MyAction {
Save,
Undo,
Redo,
}
let mut config = ActionConfig::new(EditingMode::Emacs);
config.bind(
ActionBinding::builder()
.action(MyAction::Save)
.key(keys::char('s').with_ctrl())
.build(),
);
config.bind(
ActionBinding::builder()
.action(MyAction::Undo)
.key(keys::char('z').with_ctrl())
.build(),
);
config.compile();
let dispatcher = EventDispatcher::new(config);
Use map_actions to wrap the built-in defaults in your own action type:
use crossterm_actions::{ActionBinding, emacs_defaults, TuiEvent, AppEvent, keys};
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
enum AppAction {
Tui(TuiEvent),
CodePreview,
ToggleDarkMode,
}
// Transform emacs defaults from ActionConfig<TuiEvent> to ActionConfig<AppAction>
let mut config = emacs_defaults().map_actions(AppAction::Tui);
// Add custom bindings alongside the defaults
config.bind(
ActionBinding::builder()
.action(AppAction::CodePreview)
.key(keys::char('p'))
.build(),
);
config.compile();
// 'q' still triggers quit (wrapped), 'p' triggers your custom action
assert_eq!(config.get(&keys::char('q')), Some(AppAction::Tui(TuiEvent::App(AppEvent::Quit))));
assert_eq!(config.get(&keys::char('p')), Some(AppAction::CodePreview));
Enable the inputrc feature (on by default) to load keybindings from ~/.inputrc:
use crossterm_actions::{defaults, load_inputrc};
let mut config = defaults::emacs_defaults();
// Apply user's inputrc customizations
if let Err(e) = load_inputrc(&mut config) {
eprintln!("Warning: failed to load inputrc: {}", e);
}
MIT