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§

dialog
Interactive dialog prompts

Structs§

EmptyKeyState
An implementation of InputKeyState that stores nothing.
InputIterator
Iterate over the actions produced by ModalMachine, feeding keys into it as needed.
ModalMachine
Manage and process modal keybindings.

Enums§

EdgeEvent
What kind of input is acceptible for continuing towards a Step.
EdgeRepeat
Specifies how many times an EdgeEvent is allowed to be repeated.
EmptyKeyClass
A default InputKeyClass with no members.
EmptySequence
A default type for Step::Sequence with no members.
SequenceStatus
Different ways to include an action in the current action sequence.

Traits§

BindingMachine
Trait for objects that can process input keys using previously mapped bindings.
InputBindings
A collection of bindings that can be added to a ModalMachine.
InputKey
Trait for keys that can be used with ModalMachine.
InputKeyClass
Trait for the classes of input keys specific to a consumer.
InputKeyState
Trait for context objects used within ModalMachine.
InputState
Represents contextual information that is updated upon user input.
Mode
Trait for the input modes specific to a consumer.
ModeKeys
Key-specific behaviour associated with a Mode.
ModeSequence
Sequence-specific behaviour associated with a Mode.
Step
Trait for controlling the behaviour of ModalMachine during a sequence of input keys.

Type Aliases§

EdgePath
A description of a sequence of input keys that leads to a Step.
EdgePathPart
Part of a sequence of input keys that leads to a Step.