Snippets
A completion item with insertTextFormat: 2 is a snippet: text with stops to fill, in the LSP snippet syntax. Live templates and Emmet insert through the same session.
Fields and stops
Tab and Shift+Tab walk the stops, $0 last. The session ends at the last stop, when the caret leaves, on undo or on Escape (which keeps only the primary caret). Stops with the same index mirror each other, also across several carets. The session announces each field visit as Field N of M; see accessibility.
The session visits nested placeholders in numeric order, and replacing a parent removes its nested stops. Variables resolve per insertion. A known variable without a value inserts its default; an unknown one becomes a placeholder holding its name, visited before $0. A choice inserts its first value and opens a picker: Enter or a click applies a value, Tab moves on, typing edits the field, and the first Escape closes the picker. Variable transforms run before insertion and field transforms when Tab leaves a field, both only with a worker. An unsupported transform keeps the untransformed value.
Parsing a snippet
import { parseSnippet, tabOrder } from '@adecore/editor-react/models';
const snippet = parseSnippet('print(${1:value}, ${2:count})$0');
snippet.text; // 'print(value, count)'
tabOrder(snippet).map((stop) => stop.index); // [1, 2, 0]parseSnippet returns a ParsedSnippet with text, stops and optional fields, transforms and limited. A SnippetField groups the UTF-16 SnippetRange offsets of one index, with its primary occurrence, linked child copies, parents and choices. SnippetTransform is the pattern, flags and format of an occurrence. tabOrder returns one SnippetStop per index, in visiting order. Oversized snippets insert their source text without a session.
Variables and indentation
parseSnippet(source, resolve?) takes a synchronous SnippetVariableResolver. compileSnippet(source) returns a reusable SnippetTemplate; its render(resolve?, variableTransforms?, options?) resolves each variable once per render. Values are literal text, so a $ in a filename never makes a stop. insertionOf returns the Insertion of a completion item and takes SnippetInsertionOptions with the resolver, a compiled template, indentationBefore and the variableTransforms results. { indentVariables: true } indents the later lines of a multi-line value to the line the variable starts on, as VS Code does with TM_SELECTED_TEXT.
Completion resolves the usual LSP variables: selection, line and word, line and cursor numbers, file and workspace paths, date and time, random values and UUID. A synchronous LanguageHost.resolveSnippetVariable(name, context) overrides them or adds names, comment markers and clipboard text; the editor never reads the clipboard itself. SnippetVariableContext describes the insertion: URI, language, position, selection, line and word, cursor index and count, workspace folder and path. A throwing override falls back to the built-ins.
const project = new ProjectLanguage(service, {
folder,
resolveSnippetVariable: (name, context) => (name === 'AUTHOR' ? currentUser.name : undefined)
});insertTextMode: 2 indents every later line by the whitespace before the insertion; 1 or none keeps the text as is. Completion refuses an oversized or limited result before any edit and tells the host. See the LSP indentation modes.
Completion inside a snippet
Completion inside an active field updates its mirrors and keeps the outer stops. A completion with fields of its own starts a nested session: Tab visits its fields, then the next outer field. Shift+Tab from its first field selects the parent field. Escape and undo end every level. The session leaves out malformed items and ranges.
snippets.token changes on every field visit. snippets.applyEdits(changes, token, extras?) applies a completion or choice edit inside the session; extras lists imports outside it. A stale token ends the session first. beginInstances(insertions, { nested: true }) keeps the enclosing session; begin starts fresh. Too deep a stack starts fresh. PickSpec.onTab(backwards) lets a pick list close and pass Tab on.
Inside a nested snippet, a $0 generated after unlinking sorts after a stop of the enclosing snippet at the same point.
Isolated transforms
Transforms (${1/(.*)/${1:/upcase}/}) run server regular expressions, so they run in a worker, never on the page. LanguageHost.snippetTransformWorker supplies the worker factory, and the worker calls serveSnippetTransforms(self). Without a worker, or on a timeout, values stay untransformed. A syntax error, an oversized batch or a crashed worker refuses the insertion with a notice. A full queue refuses it with a busy notice. Editors of one project share one client and must inject the same clock.
// snippet-transforms.worker.ts
import { serveSnippetTransforms } from '@adecore/editor-react/snippet-transform-worker';
serveSnippetTransforms(self);@adecore/editor-react/snippet-transform-worker exports serveSnippetTransforms, createSnippetTransformClient, compileSnippetTransformFormat, SnippetTransformError and their protocol, job, client and endpoint types, and imports no React:
| Name | What it is |
|---|---|
SnippetTransformJob | A value, pattern, flags (gimsu) and SnippetTransformFormatNode format |
SnippetTransformCase | upcase, downcase, capitalize, camelcase, pascalcase |
SnippetTransformInput, SnippetTransformOutput | The request and reply unions |
SnippetTransformWorkerEndpoint | The port the worker serves |
SnippetTransformClientOptions | The worker factory, deadline (1,500 ms) and schedule |
SnippetTransformClient | transform(jobs) returns the strings; cancel, release, dispose |
SnippetTransformErrorKind | Protocol error, timeout or lifecycle failure |
A timeout terminates the worker. release frees it and keeps the client usable; dispose is final. Keep this client apart from the one find uses. Insertion.variableTransforms holds the jobs of an insertion.
Field-exit transforms
A transformed mirror shows the raw value while its field is active. Tab or Shift+Tab transforms it, and navigation goes on without waiting. Applying the result is its own undo step: the first undo restores the raw text. Escape, undo and other edits cancel a pending exit. A failed field-exit transform leaves the raw value without a notice.