Templates
A live template is a text a short key expands into, as in IntelliJ. Type iter in a JavaScript file, press Tab, and a for...of loop appears with the caret in its first field. language.templates is the TemplatesFeature of an editor; the templates live in the project's TemplateRegistry.
const templates = new TemplateRegistry();
templates.set({
live: [{ id: 'own.todo', key: 'todo', body: '// TODO($1): $0', languages: ['javascript'], places: ['comment', 'code'] }]
});
const project = new ProjectLanguage(service, { templates });Live templates
A LiveTemplate has a stable id (built-ins use builtin.<language>.<key>), a key of 1 to 40 non-whitespace characters, a body in snippet syntax, an optional description and group, and the languages and places it applies to. places holds 'code', 'comment' and 'string', and defaults to ['code']. group sorts Insert Live Template and defaults to the first language. A leading tab in the body is one indentation unit of the editor. A body with $TM_SELECTED_TEXT wraps the selection.
languages are families: javascript, typescript, php, css, html, xml and other. TypeScript and TSX count as javascript and typescript, vue and xhtml as html, svg and xsl as xml, scss and less as css. Where the editor can tell, the place under the caret decides: the <script lang="ts"> of a Vue file is TypeScript, its template HTML, and the markup of a PHP file HTML.
BUILT_IN_LIVE_TEMPLATES are IntelliJ's keys for JavaScript (log, itar, ritar, iter, itin), PHP (fore, forek, pubf, prif, prof, pubsf, thr), HTML and XML (T, which wraps the selection in a tag) and XML (CD). CSS has none. BUILT_IN_POSTFIX_TEMPLATES are listed under postfix completion.
The registry
new TemplateRegistry({ builtins }) holds the built-in templates unless builtins is false. set({ live, postfix, disabled, settings }) replaces what it names and keeps the rest; disabled lists built-in ids to turn off. set drops templates that fail checkTemplate and never throws. live and postfix list the enabled built-ins, then the host's. Two templates with one key become a choice on expansion. onChange(listener) runs after every set and returns its unsubscribe function.
checkTemplate(template, kind?) returns a reason in English for the host's form, or null when the template is valid. kind is 'live' or 'postfix'; without it, a template with an expression or example counts as postfix.
TemplateSettings has three switches, each on unless false: liveInCompletion (live templates in the suggestion list), postfix (Tab expands postfix templates) and postfixInCompletion (postfix templates in the suggestion list). registry.settings returns all three. To keep template rows out of documents no service serves, set templates.set({ settings: { liveInCompletion: false } }).
Tab
Tab expands the key before the caret, as IntelliJ's TemplateManager does:
- The key is the longest text before the caret on its line that is a template key. A key starting with a letter,
_or$must not follow an identifier character, soxlogis notlog, whilex.iterisiter. A postfix template takesx.logfirst. - The template must apply where the key starts. If the longest key applies nowhere, no shorter key is tried.
- Several templates with one key open the suggestion list with only those rows.
- With several carets, every caret expands and the fields mirror. If one caret lacks the key, Tab does its usual work.
- A selection or a read-only editor leaves Tab alone.
A running snippet takes Tab first. An Emmet abbreviation starting at or before the key wins; a live template key starting before the abbreviation wins over it. language.templates.expand() runs the same decision for a menu. It returns false when there is nothing to expand or the key is an Emmet abbreviation, which language.markup.expand() expands.
Suggestions
With liveInCompletion on, the suggestion list gets a snippet row for each template that applies and whose key starts with the word before the caret. The row shows the description, with the body as documentation, and sorts after service rows of equal score. Accepting it expands the template.
Insert Live Template
language.templates.insert() (insertLiveTemplate, Mod+J) lists the templates that apply at the caret, filtered by the word before the caret when it matches keys or descriptions. Rows sort by group and key. With a selection, the template replaces it and gets it as $TM_SELECTED_TEXT. Where no template applies, the editor tells the person through host.notify.
Save as Live Template
language.templates.saveSelection() takes the selection, strips its shared indentation and escapes \, $ and }. It passes a LiveTemplateDraft (body, languages, places) to LanguageHost.saveAsLiveTemplate(draft), where the host opens its own form. It returns false without a selection or without the hook. The saveAsLiveTemplate key has no default chord.
Postfix completion
A PostfixTemplate expands after a dot and its key, around the expression before the dot, which $EXPR in the body stands for. It has a key of identifier characters, languages (javascript, typescript, php), an optional example for its row (if (expr) {}), and an expression of 'any' or 'statement'.
BUILT_IN_POSTFIX_TEMPLATES are IntelliJ's, with ids builtin.postfix.<language>.<key>:
| Language | Statements | Any expression |
|---|---|---|
| JavaScript, TypeScript | if, else, null, notnull, return, throw, const, let, forof, forin, log | not, par, arg, await, typeof |
| PHP | if, isset, empty, null, notnull, return, throw, var, foreach | not, par |
Type x.if and press Tab, and x becomes if (x) {} with the caret inside. The operands are the expressions ending right before the dot, as Editor.postfixOperands lists them: a.b(c).d.if takes the whole chain, and 1.if offers nothing. A statement template takes the widest expression, only where it starts a statement. An any template with several candidates, such as a and !a for !a.par, opens an "Expressions" list, innermost first, with a preview of each. Nothing happens in a string or comment, with several carets, in a read-only editor, or with settings.postfix off.
With postfixInCompletion on, the suggestion list after a dot gets a row per applicable template, with its example and description.
Surround With and Unwrap
language.surround is the SurroundFeature. Its commands are open() (surroundWith, Mod+Alt+T), openTemplates() (surroundWithLiveTemplate, Mod+Alt+J) and unwrap() (unwrap, Mod+Shift+Delete). Each takes its chord even where nothing applies, makes one undo step, and does nothing in a read-only editor. Some Linux desktops take Ctrl+Alt+T; reach the command from a menu through language.surround.open().
Surround With
open() surrounds the selection, or at a bare caret the trimmed content of its line. With several carets every range is surrounded and the fields mirror; overlapping ranges merge, and two ranges on one line refuse. All ranges must be in one language, as placeAt reports; otherwise host.notify gets "Couldn't find Surround With variants applicable to the current context".
surroundShape decides what applies: statement rows for whole statements, (expr) and !(expr) for one expression. The "Surround With" list numbers its rows 1 to 9, 0, then A, B and on; typing the character picks the row.
| Language | Statements | Expressions |
|---|---|---|
| JavaScript and TypeScript (also in Vue) | if, if / else, while, do / while, for, try / catch, try / finally, try / catch / finally, { }, function | (expr), !(expr) |
| PHP | if, if / else, while, do / while, for, foreach, try / catch, try / finally, try / catch / finally, function | (expr), !(expr) |
Then come two region comments in any language with comments, "region...endregion Comments" and "<editor-fold...> Comments" (//region Description, <!--region Description-->). Last is the "Live templates" group: Emmet in markup, the $TM_SELECTED_TEXT templates that apply, and "Configure live templates…" when the host has openTemplateSettings.
A chosen row inserts its body with the selection as $TM_SELECTED_TEXT, and the caret goes to the first field, such as the condition. Emmet opens the wrap field of markup. The active row previews the result.
openTemplates() opens the "Live templates" group alone, titled "Select Template".
Unwrap/Remove
unwrap() lists the constructs around the primary caret, innermost first, under "Choose the statement to unwrap/remove", with a preview. Typing narrows the list. A chosen row applies in one step.
In JavaScript, TypeScript and PHP the rows come from Editor.unwrapTargets: "Unwrap 'if...'" (drops the else chain), "Unwrap 'else...'", "Remove 'else...'", "Unwrap 'for...'" (also foreach), "Unwrap 'while...'", "Unwrap 'do...'", "Unwrap 'try...'" (drops catch and finally), "Remove 'catch...'", "Remove 'finally...'" and "Unwrap braces". Functions, classes, object literals and switch give none.
In HTML, XML, Vue and PHP markup, each enclosing element gives "Remove enclosing tag: div", which removes the two tags and outdents what was between them. Vue and PHP merge both lists, latest start first.
Host hooks
LanguageHost.templates is the project's TemplateRegistry, read through ProjectLanguage.templates; see LanguageHost for saveAsLiveTemplate and openTemplateSettings.
Words
The words come from the editor:language.templates.* and editor:language.surround.* locale keys, in English and Dutch.