Skip to main content

Module quickstart

Module quickstart 

Source
Expand description

Build a small Vim editor over a plain Rust String.

This program executes cwhello<Esc>w. on one two three. Vimlet interprets the keys and tracks the mode; the program applies its requested edits. The result is hello hello three, with the editor back in Normal mode.

§Add Vimlet to an application

You need Rust 1.90 or newer. Create an application:

cargo new vimlet-demo
cd vimlet-demo

Add this under [dependencies] in the application’s Cargo.toml:

vimlet = "0.1"

The default feature set supports strings and custom text sources. No Ropey dependency is needed.

§The complete program

Copy the following into src/main.rs. It uses only Vimlet and the standard library; all the host code is included. The same source lives in examples/quickstart.rs and runs as a doctest, so the example is checked against the public API.

This small host handles edits and cursor/selection changes. It has no undo history or external services and rejects those requests. It dispatches the built-in keys used below; the integration guide covers application shortcuts and macro replay.

use vimlet::{
    AppliedEdit, ByteOffset, DocumentVersion, DocumentView, EditingState, HostOutcome, HostResult,
    Input, Key, Mode, OperationKind, Session, Step,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut text = String::from("one two three");
    let mut cursor = ByteOffset(0);
    let mut selection = None;
    let mut version = DocumentVersion { id: 1, revision: 0 };
    let mut session = Session::new();
    let mut state = EditingState::default(); // Start in Normal mode.

    let keys =
        "cwhello"
            .chars()
            .map(Key::Char)
            .chain([Key::Escape, Key::Char('w'), Key::Char('.')]);

    for key in keys {
        let mut view = DocumentView::new(&text, version, cursor);
        view.selection = selection;
        let mut step = session.handle(&mut state, Input::key(key), view);

        loop {
            let operation = match step {
                Step::HostOperation(operation) => operation,
                Step::Consumed | Step::AwaitingInput => break,
                Step::Error(error) => return Err(error.into()),
                _ => return Err("this example only dispatches built-in editing keys".into()),
            };

            let outcome = if !operation.is_current(version) {
                HostOutcome::Rejected
            } else {
                match operation.kind {
                    OperationKind::Move {
                        cursor: next_cursor,
                        selection: next_selection,
                    } => {
                        cursor = next_cursor;
                        selection = next_selection;
                        HostOutcome::Applied {
                            edits: Vec::new(),
                            changed: false,
                        }
                    }
                    OperationKind::Edit {
                        replacements,
                        cursor: next_cursor,
                        selection: next_selection,
                        transaction: _, // This small host has no undo history.
                    } => {
                        // Capture removed text before applying any replacement.
                        let edits: Vec<_> = replacements
                            .iter()
                            .map(|replacement| AppliedEdit {
                                range: replacement.range,
                                removed: text[replacement.range.as_range()].into(),
                                inserted: replacement.text.clone(),
                            })
                            .collect();
                        let changed = edits.iter().any(|edit| edit.removed != edit.inserted);
                        // All ranges refer to the old text; start at the end.
                        for replacement in replacements.iter().rev() {
                            text.replace_range(replacement.range.as_range(), &replacement.text);
                        }
                        if changed {
                            version.revision += 1;
                        }
                        cursor = next_cursor;
                        selection = next_selection;
                        HostOutcome::Applied { edits, changed }
                    }
                    _ => HostOutcome::Rejected,
                }
            };

            let result = HostResult {
                operation: operation.id,
                before: operation.expected,
                after: version,
                cursor,
                selection,
                outcome,
            };
            let mut view = DocumentView::new(&text, version, cursor);
            view.selection = selection;
            // Dot can produce several operations from one key.
            step = session.resume(&mut state, result, view);
        }
    }

    assert_eq!(text, "hello hello three");
    assert_eq!(state.mode(), Mode::Normal);
    println!("{text}");
    Ok(())
}

§Run it

In your application directory:

cargo run

Output:

hello hello three

From the Vimlet checkout, run the same program with:

cargo run --locked --example quickstart

§Follow one command

InputEngine responseHost work
cAwaitingInputKeep the pending command and wait for its target.
wAn edit removing oneApply it and acknowledge it; Vimlet enters Insert mode.
helloAn insertion for each characterApply each edit and advance the document revision.
EscapeAn edit operation ending the insertion transactionAcknowledge it; Vimlet returns to Normal mode.
wA cursor moveMove to two and acknowledge the new cursor.
.The saved change, resolved at the new cursorKeep applying and acknowledging operations until repeat completes.

Read EditingState::mode after processing the returned steps to update your cursor shape or mode indicator. The final assertion verifies both the edited text and the return to Normal mode.

Next: connect your application’s document and event loop, then add the recipes your surface needs.