pub struct Document { /* private fields */ }Expand description
An editable document: a Buffer plus the bookkeeping every edit needs
— a process-unique [DocumentId], an optional file path, version
counters, the [SelectionSet], the UndoStack and dirty tracking.
All text mutation goes through Document::apply_edit /
Document::apply_edit_batch (or Document::undo /
Document::redo); each one records its inverse for undo, carries the
selections across the change, and bumps both Document::version and
Document::text_version. Positions are the protocol’s
(line, byte-within-line) pairs, both 0-based — see Buffer.
A Document has no interior mutability and no locking; the editor keeps
it behind a single writer (the core actor). It knows nothing about
syntax, modes, LSP or rendering.
§Examples
Edit, undo, redo and dirty tracking:
use lattice_core::Document;
use lattice_core::protocol::edit::Edit;
use lattice_core::protocol::position::Position;
let mut doc = Document::from_text("hello");
assert!(!doc.dirty());
doc.apply_edit(Edit::insert(Position::new(0, 5), " world"))?;
assert_eq!(doc.text(), "hello world");
assert!(doc.dirty());
doc.undo()?;
assert_eq!(doc.text(), "hello");
assert!(!doc.dirty()); // back at the loaded state
doc.redo()?;
assert_eq!(doc.text(), "hello world");Coalescing a whole insert session into one undo step:
use lattice_core::Document;
use lattice_core::protocol::edit::Edit;
use lattice_core::protocol::position::Position;
let mut doc = Document::empty();
doc.begin_undo_group(); // `i`
doc.apply_edit(Edit::insert(Position::new(0, 0), "a"))?;
doc.apply_edit(Edit::insert(Position::new(0, 1), "b"))?;
doc.apply_edit(Edit::insert(Position::new(0, 2), "c"))?;
doc.end_undo_group(); // `<Esc>`
doc.undo()?; // one `u` removes the whole session
assert_eq!(doc.text(), "");Implementations§
Source§impl Document
impl Document
Sourcepub fn from_text(text: impl Into<String>) -> Self
pub fn from_text(text: impl Into<String>) -> Self
A pathless document holding text. It starts clean: text is
treated as the saved state.
Sourcepub fn from_buffer(buffer: Buffer) -> Self
pub fn from_buffer(buffer: Buffer) -> Self
Construct a Document around
a pre-built Buffer. Sister of Self::from_text for
callers that already hold a Rope-backed Buffer and want
to avoid the as_string() → from_text(&str) round-trip.
See DocumentBuilder::with_buffer for the rationale +
the K.4.11 consumer.
Slice: K.4.11.perf-fix (2026-06-02).
Sourcepub fn open(path: impl AsRef<Path>) -> CoreResult<Self>
pub fn open(path: impl AsRef<Path>) -> CoreResult<Self>
Read path from disk into a new clean document whose path is set.
Blocking I/O — never call it on the UI thread.
§Errors
CoreError::Io if the file cannot be read, including when it is
not valid UTF-8. A missing file is an error here; use
Self::open_or_new for vim’s :e newfile behaviour.
Sourcepub fn open_or_new(path: impl AsRef<Path>) -> CoreResult<(Self, bool)>
pub fn open_or_new(path: impl AsRef<Path>) -> CoreResult<(Self, bool)>
Open path, or — when nothing is there yet — start an empty,
unmodified document that will create it on the first save.
vim’s :e newfile (checked in 9.2: an empty buffer, &modified 0, and
:w creates the file). Only NotFound becomes a new document; any
other read failure — permissions, a non-UTF-8 file — is still an
error, because opening an empty buffer over an unreadable file would
let the first save overwrite it.
Returns whether the document is new, so a caller can tell a fresh file
from an existing one without a second stat.
Sourcepub fn id(&self) -> DocumentId
pub fn id(&self) -> DocumentId
The process-unique id assigned at construction. Never reused within a process; not stable across restarts.
Sourcepub fn path(&self) -> Option<&Path>
pub fn path(&self) -> Option<&Path>
The file this document is backed by, or None for a scratch /
synthetic document that has never been saved.
Adopt a path that is already shared. Does NOT touch the filesystem —
Document::save_as is the one that writes.
For a throwaway document assembled around an existing buffer, where the path came from the snapshot that buffer was cloned from (the host’s plugin-action gate, OM.6b). An Arc bump, so it costs the same as not carrying the path at all.
The path as a shared handle, for readers that must carry it out of
the borrow — a published DocumentSnapshot, a grammar dispatch
context. An Arc bump, never an allocation. Prefer Document::path
when a borrow will do.
Sourcepub fn version(&self) -> u64
pub fn version(&self) -> u64
Monotonic change counter: bumps on every edit, batch, undo, redo
and Self::set_selections. Use it to detect “anything changed”;
use Self::text_version when only text matters.
Sourcepub fn text_version(&self) -> u64
pub fn text_version(&self) -> u64
Monotonic counter that bumps only when the text changes (edits, batches, undo, redo) — not on selection changes or saves. The syntax cache keys reparses on it.
Sourcepub fn dirty(&self) -> bool
pub fn dirty(&self) -> bool
Whether the text differs from the last saved (or initially loaded) state, as judged by undo depth.
Undoing back to the saved state makes the document clean again.
Once an edit discards the redo entries that led back to the saved
state, no clean state is reachable and the document stays dirty
until the next Self::save / Self::save_as.
Sourcepub fn buffer(&self) -> &Buffer
pub fn buffer(&self) -> &Buffer
The underlying text. Read-only: mutation must go through the document so undo, versions and selections stay consistent.
Sourcepub fn selections(&self) -> &SelectionSet
pub fn selections(&self) -> &SelectionSet
The current selections (a single cursor at the origin for a fresh document). Edits transform them automatically.
Sourcepub fn set_selections(&mut self, selections: SelectionSet)
pub fn set_selections(&mut self, selections: SelectionSet)
Replace the selections. Bumps Self::version but not
Self::text_version. Positions are not validated against the
buffer.
Sourcepub fn text(&self) -> String
pub fn text(&self) -> String
The whole text as a String — an O(n) allocation; see
Buffer::as_string for the cheaper alternatives.
Sourcepub fn apply_edit(&mut self, edit: Edit) -> CoreResult<AppliedEdit>
pub fn apply_edit(&mut self, edit: Edit) -> CoreResult<AppliedEdit>
Apply an edit, push its inverse onto the undo stack, bump the version.
Returns a structural description of what changed (suitable for
Event::DocumentChanged). Transforms selections across the edit
so the caret survives owner writes (§4 of owner-write-caret.md).
Inside an open Self::begin_undo_group the inverse folds into the
group’s entry instead of pushing a new one. Pushing a new entry
clears the redo stack.
§Errors
Those of Buffer::apply_edit; the document is unchanged on error.
Sourcepub fn apply_edit_batch(
&mut self,
edits: Vec<Edit>,
) -> CoreResult<Vec<AppliedEdit>>
pub fn apply_edit_batch( &mut self, edits: Vec<Edit>, ) -> CoreResult<Vec<AppliedEdit>>
Apply a batch of edits as a single undoable unit. Edits are applied in order; undo reverts them all. Transforms selections across each edit so the caret survives owner writes (§4 of owner-write-caret.md).
Each edit’s range is in the coordinates produced by the edits before it, not the pre-batch text. Versions bump once for the whole batch.
§Errors
Those of Buffer::apply_edit. Not atomic: edits before the
failing one stay applied, and — because the undo entry is recorded
only after the loop — they are not on the undo stack and versions do
not bump. Callers must hand in a batch that is valid in sequence.
§Examples
use lattice_core::Document;
use lattice_core::protocol::edit::Edit;
use lattice_core::protocol::position::{Position, Range};
let mut doc = Document::from_text("one two");
doc.apply_edit_batch(vec![
// Delete "one " …
Edit::delete(Range::new(Position::new(0, 0), Position::new(0, 4))),
// … so "two" now starts at byte 0.
Edit::insert(Position::new(0, 3), "!"),
])?;
assert_eq!(doc.text(), "two!");
doc.undo()?; // the batch is one undo unit
assert_eq!(doc.text(), "one two");Sourcepub fn begin_undo_group(&mut self)
pub fn begin_undo_group(&mut self)
Open an undo-coalescing group: every edit applied until
Self::end_undo_group folds into a single undo entry rather
than pushing its own. This is how a vim insert session
(i/a/o/cw .. <Esc>) becomes one u step – typed
characters, in-session backspaces, and completion inserts all
collapse together. Re-opening an already-open group starts a
fresh coalescing run (the next edit pushes a new entry).
Sourcepub fn end_undo_group(&mut self)
pub fn end_undo_group(&mut self)
Close the group opened by Self::begin_undo_group. Subsequent
edits push their own entries again. Idempotent when no group is
open.
Sourcepub fn undo(&mut self) -> CoreResult<Vec<AppliedEdit>>
pub fn undo(&mut self) -> CoreResult<Vec<AppliedEdit>>
Revert the most recent undo entry and move it onto the redo stack. Returns the edits as applied to the buffer, in application order, so callers can emit change events. Bumps both versions.
§Errors
CoreError::NothingToUndo if the undo stack is empty.
Sourcepub fn redo(&mut self) -> CoreResult<Vec<AppliedEdit>>
pub fn redo(&mut self) -> CoreResult<Vec<AppliedEdit>>
Re-apply the most recently undone entry and move it back onto the
undo stack. The mirror of Self::undo.
§Errors
CoreError::NothingToRedo if nothing has been undone since the
last new edit.
Sourcepub fn save(&mut self) -> CoreResult<&Path>
pub fn save(&mut self) -> CoreResult<&Path>
Persist to the document’s path. Errors if no path is set.
Writes the text verbatim (blocking I/O) and marks the current undo depth as clean. Returns the path written.
§Errors
CoreError::NoPath for a pathless document; CoreError::Io if
the write fails (the document stays dirty).
Sourcepub fn save_as(&mut self, path: impl Into<PathBuf>) -> CoreResult<()>
pub fn save_as(&mut self, path: impl Into<PathBuf>) -> CoreResult<()>
Write the text to path, then adopt path as the document’s path
and mark it clean. On a write error neither the path nor the clean
state changes.
§Errors
CoreError::Io if the write fails.