Crate keybindings

source ·
Expand description

§keybindings

§Overview

This crate provides environment-agnostic interfaces for tracking and processing modal keybindings.

The ModalMachine component allows consumers to build input processors that support multiple modes of input, similar to applications descended from vi.

ModalMachine maintains a graph of Step-containing nodes, and follows edges with each InputKey it receives. Consumers populate the graph by mapping an EdgePath that describes a series of acceptible inputs to reach a Step.

When a new node is reached, Step::step is used to determine whether we can produce any actions, and whether we need to transition to another Mode. If any actions are produced, the keybinding is considered fully entered and the current InputState is taken. If the Step transitions to another Mode, then future input keys will be processed there. Otherwise, future keys will continue to be processed from the top of the currently entered Mode.

§Customization

For straightforward keybindings, consumers only need an action type and a Mode. More complex setups might require doing one or more of the following:

§Example

Here is a program that builds keybindings with a Normal mode for sending commands to the program, and an Insert mode for typing text. The Normal mode supports a “qq” sequence to quit the program, and the Insert mode supports Escape to return to Normal mode.

use keybindings::{
    BindingMachine,
    EmptyKeyClass,
    EmptyKeyState,
    InputBindings,
    InputKey,
    InputState,
    ModalMachine,
    Mode,
    ModeKeys,
    Step,
};

const ESC: char = '\u{1B}';

#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
enum ProgMode {
    Normal,
    Insert
}

#[derive(Clone, Debug, Eq, PartialEq)]
enum ProgAction {
    Type(char),
    NoOp,
    Quit
}

impl Default for ProgAction {
    fn default() -> Self {
        ProgAction::NoOp
    }
}

#[derive(Default)]
struct ProgBindings { }

impl Default for ProgMode {
    fn default() -> ProgMode {
        ProgMode::Normal
    }
}

impl Mode<ProgAction, EmptyKeyState> for ProgMode { }

impl<K: InputKey> ModeKeys<K, ProgAction, EmptyKeyState> for ProgMode {
    fn unmapped(&self, key: &K, _: &mut EmptyKeyState) -> (Vec<ProgAction>, Option<ProgMode>) {
        match self {
            ProgMode::Normal => {
                return (vec![], None);
            },
            ProgMode::Insert => {
                if let Some(c) = key.get_char() {
                    return (vec![ProgAction::Type(c)], None);
                }

                return (vec![], None);
            },
        }
    }
}

impl InputBindings<char, ProgStep> for ProgBindings {
    fn setup(&self, machine: &mut ProgMachine) {
        use keybindings::EdgeRepeat::Once;
        use keybindings::EdgeEvent::Key;

        // Insert mode mappings
        machine.add_mapping(ProgMode::Insert, &vec![
            (Once, Key(ESC))
        ], &(None, Some(ProgMode::Normal)));

        // Normal mode mappings
        machine.add_mapping(ProgMode::Normal, &vec![
            (Once, Key('i'))
        ], &(None, Some(ProgMode::Insert)));
        machine.add_mapping(ProgMode::Normal, &vec![
            (Once, Key('q')),
            (Once, Key('q')),
        ], &(Some(ProgAction::Quit), None));
    }
}

type ProgStep = (Option<ProgAction>, Option<ProgMode>);
type ProgMachine = ModalMachine<char, ProgStep>;

fn main() {
    let mut pm = ProgMachine::from_bindings::<ProgBindings>();
    let ctx = EmptyKeyState::default();

    // We begin in the Default mode, Normal.
    assert_eq!(pm.mode(), ProgMode::Normal);

    // Pressing "i" takes us to Insert mode.
    pm.input_key('i');
    assert_eq!(pm.pop(), Some((ProgAction::NoOp, ctx.clone())));
    assert_eq!(pm.mode(), ProgMode::Insert);

    // "q" is unmapped in Insert mode, and types a key.
    pm.input_key('q');
    assert_eq!(pm.pop(), Some((ProgAction::Type('q'), ctx.clone())));
    assert_eq!(pm.mode(), ProgMode::Insert);

    // Escape takes us back to Normal mode.
    pm.input_key(ESC);
    assert_eq!(pm.pop(), Some((ProgAction::NoOp, ctx.clone())));
    assert_eq!(pm.mode(), ProgMode::Normal);

    // A single "q" does nothing.
    pm.input_key('q');
    assert_eq!(pm.pop(), None);
    assert_eq!(pm.mode(), ProgMode::Normal);

    // A second "q" produces the Quit action.
    pm.input_key('q');
    assert_eq!(pm.pop(), Some((ProgAction::Quit, ctx.clone())));
    assert_eq!(pm.mode(), ProgMode::Normal);
}

Modules§

  • Interactive dialog prompts

Structs§

Enums§

Traits§

Type Aliases§