scrive
A from-scratch code-editor widget for iced — a headless
editing engine plus an iced::advanced::Widget that renders it.

Recorded deterministically, headless — the demo renders itself, no external tools:
cargo run -p scrive-iced --example record_showcase --release -- docs/showcase.gif
Two crates, with the dependency pointing one way:
scrive-core— the headless engine: a rope-backed buffer, atomic multi-range transactions with mechanically-derived undo/redo, selections and multi-cursor, tracked-range decorations (diagnostics, find matches, snippet stops), an incremental syntax-highlight cache, folding, and the language-intelligence controllers. Imports no GUI crate.scrive-iced— aniced::advanced::Widgetwrapping the core: gutter and line numbers, N-caret selections, syntax highlighting, bracket-pair colors and matching, indent guides, diagnostic squiggles, code folding, and the completion / hover / signature-help popups. Depends onscrive-core; the dependency never points back.
Features
- Rope-backed buffer with an O(1) snapshot for background work; the document-size envelope is bounded only by the u32 offset space, not a load cap.
- Atomic transactions with undo/redo — every edit is one transaction with a mechanically-derived inverse, and undo/redo flow through the same view-rebase path as forward edits.
- Multi-cursor editing, column (box) selection, and word-wise motion.
- Syntax highlighting via an incremental, viewport-windowed syntect cache; the app supplies the grammar and theme (the core ships neither).
- Bracket-pair colorization, matching, and indent guides — optionally comment/string-aware, so brackets inside line comments, strings, and char literals are skipped (opt-in per language; line-local).
- Code folding — foldable ranges, gutter chevrons, nested and inline (sub-line) collapse, and fold-aware movement.
- Find and replace — literal, whole-word, or regex search (with
$1capture groups in the replacement), find-in-selection, case-preserving replace, and replace / replace-all as a single undo step. The match set is repaired per edit, so results never go stale. - Language intelligence — completion, snippets, signature help, and hover, exposed as trait seams the integrating application implements.
- Diagnostics — squiggles, a diagnostic hover, and scrollbar overview marks.
Every derived position — a caret, a find match, a diagnostic, a snippet stop — is moved through a windowed or O(log n) update per keystroke, so no editing operation scales with the size of the document.
Design
The load-bearing idea is one fact, one owner: every derived position moves through a single mapping function on every edit, and every change is one atomic transaction with a mechanically-derived inverse — so the class of bug where two copies of one fact drift apart cannot occur, on the forward path or on undo.
The editor is a direct iced::advanced::Widget rather than a canvas::Program,
because only the low-level widget API participates in iced's focus/operation
protocol. The low-level Editor is a controlled widget: it borrows the
Document immutably for drawing and emits semantic actions (Type, Move,
Undo, …); the application owns the Document and applies the actions, so the
widget never mutates state behind the app's back. The CodeEditor tier wraps
that widget, owns the Document itself, and runs the plumbing for you.
Quick start
CodeEditor owns the document and runs highlighting, find, focus, and language
intelligence internally — integrating is three wires plus registering the
bundled font:
use ;
use ;
Attach a grammar with .language(grammar) (highlighting is coloured at load, no
scroll needed) and override defaults with .theme(..), .find(..),
.completions(..), .hover(..), and .signature(..). For full control, drop to
the low-level Editor widget. The host must register the bundled font —
iced::application(..).font(scrive_iced::CODICON_FONT) — so the fold chevrons
and find-bar icons render.
Examples
scratch opens a real editor over a sample Rust document — type, select, find
(Ctrl+F), fold, and undo/redo.
License
MIT.