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-demoAdd 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 runOutput:
hello hello threeFrom the Vimlet checkout, run the same program with:
cargo run --locked --example quickstart§Follow one command
| Input | Engine response | Host work |
|---|---|---|
c | AwaitingInput | Keep the pending command and wait for its target. |
w | An edit removing one | Apply it and acknowledge it; Vimlet enters Insert mode. |
hello | An insertion for each character | Apply each edit and advance the document revision. |
| Escape | An edit operation ending the insertion transaction | Acknowledge it; Vimlet returns to Normal mode. |
w | A cursor move | Move to two and acknowledge the new cursor. |
. | The saved change, resolved at the new cursor | Keep 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.