1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! Connect Vimlet to your document, event loop and application services.
//!
//! The [quickstart](super::quickstart) shows a synchronous string host. This guide
//! explains the contracts to preserve when replacing it with your application's
//! storage, mutation pipeline and services.
//!
//! # Keep state at the right scope
//!
//! Use one [`EditingState`](crate::EditingState) per editing surface. It stores the
//! mode, pending counts and operators, and any outstanding host operation. Recreating
//! it for every key loses compound commands such as `2dw`.
//!
//! A [`Session`](crate::Session) holds registers, macro state and the last successful
//! change for dot. Share a session when those should be shared between surfaces;
//! create separate sessions when they should be independent. Dispatch each outstanding
//! operation back to the surface that requested it.
//!
//! Your application retains its document, cursor, Visual selection and undo history.
//! Build a fresh [`DocumentView`](crate::DocumentView) for every call to `handle` or
//! `resume`. It borrows the current text and describes that exact revision.
//!
//! # Dispatch input and drain the returned steps
//!
//! Normalize an application event into a [`Key`](crate::Key), then call
//! [`Session::handle`](crate::Session::handle) with the input and current view.
//! [`Input::key`](crate::Input::key) uses Vimlet's built-in bindings. For a custom
//! keymap, consult [`EditingState::binding_context`](crate::EditingState::binding_context)
//! before assigning the input's binding; see the [mapping recipe](super::recipes#custom-keymaps).
//!
//! ```text
//! key + current document view
//! |
//! Session::handle
//! |
//! Step::HostOperation ----> check version, apply or reject
//! ^ |
//! | HostResult + fresh view
//! | |
//! +----------------------- Session::resume
//!
//! Stop when the step needs input, is consumed/unhandled, or reports an error.
//! Macro replay returns input to the application's normal dispatch path.
//! ```
//!
//! Both `handle` and `resume` return [`Step`](crate::Step). Handle every variant:
//!
//! | Step | Host responsibility |
//! | --- | --- |
//! | `HostOperation` | Execute or reject the request, then call `resume` and process its return value. |
//! | `AwaitingInput` | Keep the pending state and wait for the next key in the command. |
//! | `Consumed` | Finish this input. During macro playback, request the next macro key. |
//! | `Unhandled` | Route the original input to application shortcuts. |
//! | `Replay` | Send the recorded input through ordinary application dispatch, preserving its origin. |
//! | `Error` | Report or handle the error; do not count the command as a successful edit. |
//!
//! A single dot-repeat command can generate several host operations. Stopping after
//! the first acknowledgement leaves the remaining change unapplied. A prefix can also
//! span macro keys: after a replayed key completes, request the next key even when the
//! interpreter returned `AwaitingInput`.
//!
//! Do not send another key to a surface with an outstanding host operation. If a
//! search prompt or plugin call is asynchronous, retain the operation and acknowledge
//! it when the service finishes. Queue later input or explicitly cancel the pending
//! operation with a lifecycle transition; a cancelled operation's eventual result
//! must not be applied or resumed.
//!
//! # Apply edits and acknowledge actual results
//!
//! Route [`OperationKind::Edit`](crate::OperationKind::Edit) through the application's
//! normal mutation pipeline. This is where undo storage, marks, change notifications,
//! language-server synchronization and rendering should observe the change.
//!
//! 1. Check [`HostOperation::is_current`](crate::HostOperation::is_current) immediately
//! before mutation. Check again after any asynchronous work.
//! 2. Capture the text that will be removed. Every replacement range refers to the
//! **pre-operation revision**, so apply the replacements from end to start.
//! 3. Update the document revision if content changed, then set the actual cursor and
//! selection. A cursor move alone need not advance the content revision.
//! 4. Build [`HostResult`](crate::HostResult), echoing the operation's `id` and
//! `expected` version as `operation` and `before`. Report the actual resulting
//! version, cursor and selection in both the result and the fresh view.
//! 5. Call `resume` and process the next step.
//!
//! [`HostOutcome::Applied`](crate::HostOutcome::Applied) contains the actual
//! [`AppliedEdit`](crate::AppliedEdit) values, sorted and disjoint in original-document
//! order. Report host filtering or range expansion accurately: register contents and
//! repeat depend on the removed and inserted text. Set `changed` to whether content
//! changed; a true value requires a strictly newer revision.
//!
//! For [`OperationKind::Move`](crate::OperationKind::Move), update the cursor and
//! selection and acknowledge with no edits and `changed: false`. Moves also require
//! acknowledgement because they can complete a mode transition.
//!
//! # Coordinate and revision rules
//!
//! [`ByteOffset`](crate::ByteOffset) counts UTF-8 bytes from the start of the document.
//! For example, moving right across `é` advances by two bytes. It does not count
//! Unicode scalars, graphemes or display columns. Cursor and selection positions must
//! lie on grapheme boundaries; EOF is a valid coordinate.
//!
//! Edit ranges are half-open (`start..end`). Visual selection anchors and cursors
//! are inclusive and can run in either direction. Keep the selection's
//! [`Shape`](crate::Shape) when reporting it back to the engine.
//!
//! Give each document a stable [`DocumentVersion::id`](crate::DocumentVersion::id).
//! Its revision must advance for content changes made anywhere in the application,
//! including external edits and undo. Restoring older text must not restore an older
//! revision number. A new draft or document should get a new identity.
//!
//! If an operation is stale, leave the document untouched and acknowledge it with
//! [`HostOutcome::Rejected`](crate::HostOutcome::Rejected). Keep `before` equal to the
//! operation's expected version and report the current version as `after`. If the
//! surface has already switched documents, cancel its pending operation through
//! [`Session::lifecycle`](crate::Session::lifecycle) and discard the old continuation.
//!
//! # Group undo transactions
//!
//! Vimlet describes undo boundaries; the host stores and restores history.
//! [`Transaction`](crate::Transaction) accompanies every edit operation:
//!
//! | Phase | Host action |
//! | --- | --- |
//! | `Atomic` | Record a complete command as one undo group. |
//! | `Begin` | Open a group for a change or Insert/Replace session. |
//! | `Continue` | Append to the current group. |
//! | `End` | Close the group when leaving Insert/Replace. |
//! | `BreakInsert` | Close the group while retaining an insertion-point cursor. |
//!
//! An edit with no replacements can still open or close a transaction. The deletion
//! and typed replacement in `cwhello<Esc>` belong to the same undo group.
//! [`OperationKind::History`](crate::OperationKind::History) asks the host to execute
//! undo, redo or line undo. Report the resulting document revision and surface state
//! through the same acknowledgement protocol.
//!
//! # Host services
//!
//! Some commands need information or policies that belong to the application:
//!
//! | Operation | Host implementation |
//! | --- | --- |
//! | `Resolve` | Search, marks, syntax targets, display geometry or block projection. |
//! | `Transform` | Indentation, outdentation or formatting over resolved ranges. |
//! | `History` | Undo, redo and line undo. |
//! | `Host` | An application command identified by a stable numeric ID. |
//!
//! Resolve requests produce [`HostOutcome::Target`](crate::HostOutcome::Target) with
//! ranges against the requested revision. A target lookup does not mutate the document.
//! To repeat a saved search or syntax query at a new cursor, return
//! [`HostOutcome::RememberedTarget`](crate::HostOutcome::RememberedTarget) and retain
//! the query needed to interpret its replay request. See the
//! [search recipe](super::recipes#search-and-dot-repeat).
//!
//! Reject unsupported or cancelled operations. Do not acknowledge a service as applied
//! when no implementation ran. A service can finish asynchronously; the revision
//! check still belongs immediately before mutation or accepting its resolved ranges.
//!
//! # Finish or replace an editing context
//!
//! Use [`Lifecycle`](crate::Lifecycle) to distinguish cancelling a prefix, leaving
//! modal interaction, switching documents, and starting or replacing a draft.
//! [`Session::lifecycle`](crate::Session::lifecycle) clears transient state and queued
//! continuations while retaining session registers and the last successful change.
//!
//! Finish an active Insert/Replace transaction by dispatching Escape and completing
//! its host operations before changing contexts. Lifecycle calls do not mutate or
//! close host undo history. For draft reset behavior, see the
//! [composer recipe](super::recipes#composer-drafts).
//!
//! # Troubleshooting
//!
//! | Symptom | Check |
//! | --- | --- |
//! | `cw` or a count never completes | Reuse the same editing state between keys. |
//! | Text changes but the mode does not | Acknowledge the operation and read the mode after `resume`. |
//! | Dot applies only part of the change | Drain every step returned by `resume`. |
//! | `AwaitingHost` | A new key arrived before the previous operation was acknowledged. |
//! | `InvalidDocument` | Check byte offsets, grapheme boundaries and sorted, disjoint opaque spans. |
//! | `UnexpectedResult` | Check the operation token and that the acknowledgement matches the outstanding operation kind. |
//! | `StaleResult` | Compare the result's versions, cursor and selection with the fresh view; advance the revision for content changes. |
//! | Unicode cursor movement is wrong | Convert host character indices to UTF-8 byte offsets at the adapter boundary. |
//! | Undo splits one insertion into many steps | Honor `Begin`/`Continue`/`End` rather than creating a group per character. |
//!
//! The executable `examples/support/mod.rs` host demonstrates history and macro
//! dispatch. `examples/support/rope.rs` demonstrates converting byte ranges for rope
//! mutation. Both are small example adapters; your application's services stay in
//! its own host implementation.