Documents and edits
new DocumentModel(text?) starts a document with one caret at offset 0 and an empty history. The model keeps its text in a persistent rope, so an edit copies only the chunks it touches and an old snapshot keeps its own text.
Text and lines
getText() flattens the rope once per revision and caches it. A feature that needs a small region reads it with getLength(), getLineCount(), getLine(line) or slice(from, to) instead.
const document = new DocumentModel('first\r\nsecond');
document.getLineCount(); // 2
document.getLine(1); // { start: 7, end: 13, next: 13, text: 'second' }
document.positionAt(9); // { line: 1, column: 2 }
document.offsetAt({ line: 1, column: 99 }); // 13, the end of the lineThe model keeps the line endings it was given. A line ends at \n, and a \r\n counts as one break whose \r is not part of the line's text. A lone \r does not end a line. An empty document has one line, and a trailing break adds an empty last line.
getLine clamps a line number out of range. positionAt and offsetAt clamp to the document and to the line. slice is a plain substring and can cut a surrogate pair in half.
Selections
getSelections() returns copies in the order the carets were added. The last one is the primary caret, which getPrimary() returns: the view scrolls to it and a language feature asks about it.
setSelections(selections) clamps the offsets, moves an end out of the middle of a surrogate pair or a \r\n, and merges selections that touch or overlap. A merge that takes in the primary caret keeps the result last. An empty list becomes one caret at offset 0. A call to setSelections also ends the current typing group in the history.
const document = new DocumentModel('red blue red');
document.setSelections([
{ anchor: 0, head: 3 },
{ anchor: 9, head: 12 }
]);
document.typeText('green', { language: 'text' });
document.getText(); // 'green blue green'Edits
applyEdits(edits, options?) applies a batch of TextEdits at once. Every range is in the coordinates of the text before the batch, so the order of the list does not matter. The batch is one undo step.
const document = new DocumentModel('red blue red');
const revision = document.getRevision();
document.applyEdits(
[
{ from: 0, to: 3, text: 'green' },
{ from: 9, to: 12, text: 'green' }
],
{ expectedRevision: revision, source: 'command' }
);It returns true when the text changed. It returns false, with nothing changed, for a batch that leaves the text as it was or for an expectedRevision the document moved past. It throws a RangeError for an offset outside the document, a range with to before from, an edge inside a surrogate pair, or two edits that overlap; two different insertions at one offset count as overlapping. A non-string text throws a TypeError. Nothing changes when it throws.
DocumentEditOptions | Meaning |
|---|---|
expectedRevision | Refuse the batch unless the document is at this revision |
selections | The selections after the edit; without them the current ones move along with the edits |
source | 'input', 'command' or 'external' (the default), passed on to listeners |
historyGroup | Joins this batch to the previous undo step of the same group |
setText(text) replaces everything as one undoable external edit. It keeps the history; to start over without one, make a new model.
Revisions
getRevision() goes up by one with every change of the text, undo and redo included, and never returns to an earlier value. A selection change leaves it alone. Take the revision before asking something slow, such as a language server or a model, and pass it as expectedRevision with the answer. A false then means the person typed in between and the answer needs asking again.
History
undo() and redo() return whether there was a step to take. A new edit clears the redo stack, and the history keeps the last 200 steps.
Edits with the same historyGroup join one undo step until something closes the group: a setSelections, an undo, an edit without the group, or any other change of the text or the selections in between. typeText uses the group typing unless told otherwise, so a word typed letter by letter undoes at once.
Snapshots and listeners
getSnapshot() returns an EditorSnapshot: text, selections, revision, canUndo and canRedo. Its text is read lazily from the rope of that revision, so a snapshot kept across later edits still returns its own text.
subscribe(listener) returns a Disposable and calls the listener after every change, synchronously, with a fresh snapshot. It does not call it right away. A snapshot of a text change adds three fields:
source: what made the change.changes: one list ofDocumentChanges (from,to,insertedLength) per transaction in the step, each in the coordinates of the text before that transaction. An undo of a typing group has one list per keystroke.contentEdits: the same change asContentEdits with line and column positions, each in the text the edit before it left. That is the order a language server'sdidChangeexpects.
const subscription = document.subscribe((snapshot) => {
if (snapshot.contentEdits === undefined) {
return; // only the selection moved
}
send(snapshot.contentEdits);
});
subscription.dispose();The model has no dispose of its own. Dispose its subscriptions and drop the reference; a retained snapshot keeps the rope of its revision alive.