Snippets API
Overview
The Snippets API allows extensions to programmatically manage code snippets that appear in the editor's bottom bar. Snippets are small, reusable code templates that users can insert with a single tap. Each snippet is scoped to one or more file extensions (e.g., .js, .html, .dart) and supports cursor positioning and multi-stop tab navigation within the inserted text.
Extensions interact with snippets through three RPC methods: snippets.add, snippets.delete, and snippets.list. All snippet IDs created by an extension are automatically namespaced with the extension's ID to prevent collisions between extensions.
When a user opens a file, the editor's bottom bar displays character buttons and snippet buttons that match the file's extension. Snippets added by extensions appear alongside the editor's built-in defaults and any user-created snippets.
Snippet Model
Each snippet consists of the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | A unique identifier for the snippet. The system automatically prefixes this with <extensionId>_ to create a namespaced ID (e.g., an extension with ID react-snippets adding a snippet with ID useState produces the stored ID react-snippets_useState). |
label | string | Yes | The display text shown on the snippet button in the bottom bar. Keep this short -- typically 2-8 characters (e.g., "func", "class", "log"). |
template | string | Yes | The code template to insert. Supports cursor markers and tab stop syntax (see Template Syntax below). |
fileExtension | string | Yes | The file extension this snippet applies to, including the leading dot (e.g., ".js", ".html", ".py"). |
Template Syntax
Snippet templates support two cursor positioning systems. The editor detects which system a template uses and handles insertion accordingly.
Tab Stop Syntax (Recommended)
Use $1, $2, ..., $9 to define numbered tab stops. After insertion, the cursor is placed at $1. The user can press Tab to advance through $2, $3, and so on in order. Use $0 to define the final cursor position; if $0 is omitted, the end of the inserted text is used as the final stop.
function $1($2) {\n $0\n}
After insertion, the cursor lands on $1 (where the function name goes). Pressing Tab moves to $2 (the parameters), then to $0 (the function body).
Legacy Cursor Marker
Use a single | character to mark where the cursor should be placed after insertion. This is simpler but only supports a single cursor position.
console.log(|);
After insertion, the cursor is placed between the parentheses.
No Marker
If a template contains neither tab stops nor a | marker, the text is inserted as-is and the cursor is placed at the end.
Special Characters
\n-- line break\t-- tab / indentation
Examples
| Template | Result (cursor shown as ▌) |
|---|---|
console.log(|); | console.log(▌); |
<div>|</div> | <div>▌</div> |
if ($1) {\n $0\n} | if (▌) { } (cursor at condition, Tab moves to body) |
for (let $1 = 0; $1 < $2; $1++) {\n $0\n} | Loop with tab stops for variable name, limit, and body |
import | | import ▌ |
RPC Methods
snippets.add
Adds a new snippet to the editor for a specific file extension. The snippet appears in the bottom bar whenever a file with the matching extension is open.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fileExtension | string | Yes | The file extension to associate the snippet with (e.g., ".js"). Must include the leading dot. |
id | string | Yes | A unique identifier for the snippet within your extension. Will be prefixed with <extensionId>_ automatically. |
label | string | Yes | The display text for the snippet button. |
template | string | Yes | The code template with optional cursor markers. |
Returns: { id: string } -- the full namespaced snippet ID.
SDK usage:
import editor from "@srcnexus/ext-sdk";
const result = await editor.snippets.add({
fileExtension: '.js',
id: 'arrow',
label: 'arrow',
template: 'const $1 = ($2) => {\n $0\n};',
});
console.log(result.id); // "my-extension_arrow"
snippets.delete
Removes a snippet previously added by this extension. Only snippets owned by the calling extension can be deleted (the system automatically applies the extension's namespace prefix).
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fileExtension | string | Yes | The file extension the snippet is associated with. |
id | string | Yes | The snippet ID (without the extension namespace prefix). |
Returns: void
SDK usage:
import editor from "@srcnexus/ext-sdk";
await editor.snippets.delete({
fileExtension: '.js',
id: 'arrow',
});
snippets.list
Lists all snippets, optionally filtered by file extension. Returns snippets from all sources: built-in defaults, user-created snippets, and extension-registered snippets.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
fileExtension | string | No | If provided, only snippets for this file extension are returned. If omitted, all snippets are returned grouped by file extension. |
Returns:
When fileExtension is provided:
[
{ id: "js_console_log", label: "log", template: "console.log(|);" },
{ id: "js_arrow_function", label: "arrow", template: "const | = () => {\n \n};" },
{ id: "my-extension_useState", label: "useState", template: "const [$1, set$1] = useState($2);$0" }
]
When fileExtension is omitted, the result is an object keyed by file extension:
{
".js": [
{ id: "js_console_log", label: "log", template: "console.log(|);" },
...
],
".html": [
{ id: "html_div", label: "div", template: "<div>|</div>" },
...
]
}
SDK usage:
import editor from "@srcnexus/ext-sdk";
// List snippets for a specific extension
const jsSnippets = await editor.snippets.list({ fileExtension: '.js' });
// List all snippets grouped by file extension
const allSnippets = await editor.snippets.list();
Editor Integration
Bottom Bar
Snippets appear as tappable buttons in the editor's bottom bar, alongside quick-access character buttons. The bottom bar is visible whenever a text file is open and adapts to the current file's extension.
- Tap a snippet button to insert the template at the current cursor position.
- Long-press a snippet button to preview the full template in a dialog before inserting.
- Character buttons support press-and-hold for rapid repeat insertion; snippet buttons do not repeat on hold.
Snippet buttons are visually distinguished from character buttons: they use the primary container color with a rounded pill shape, while character buttons use the surface color with a more compact style.
Tab Stop Navigation
When a snippet with tab stop syntax ($1, $2, etc.) is inserted, the editor enters a tab stop session. The cursor is placed at $1, and pressing Tab advances through subsequent stops in order. The session ends when the cursor reaches the final stop ($0, or end-of-text if $0 is absent). Tab stop positions are automatically adjusted as the user types between stops so they remain accurate even after the document changes.
Snippet Ordering and Visibility
The editor merges snippets from three sources in this priority:
- Built-in defaults -- language-specific snippets provided by the editor (e.g.,
log,func,iffor JavaScript). - User customizations -- snippets added or modified by the user through the Settings > Snippet Manager UI.
- Extension snippets -- snippets added programmatically through the Snippets API.
User and extension snippets with the same ID as a default snippet override the default. Users can also hide specific snippets and reorder them through the Snippet Manager.
Supported File Extensions with Built-in Snippets
The editor ships with default snippets for these file types:
.html,.htm-- HTML tags (div,span,p,a,img,input,button).css,.scss,.sass,.less-- CSS properties (flex,grid,pos,margin,padding,border,bg).js,.jsx-- JavaScript constructs (log,func,arrow,if,for,forEach,const,let).ts,.tsx-- TypeScript constructs (all JS snippets plusinterface,type).dart-- Dart constructs (class,final,const,void,future,print,if).py-- Python constructs (def,class,if,for,print,import).json-- JSON structures ({},[],str)
Extensions can add snippets for any file extension, including those not listed above.
JavaScript Examples
Adding Language Snippets on Activation
A common pattern is to register snippets when the extension loads, providing language-specific templates for the user:
import editor from "@srcnexus/ext-sdk";
editor.onLoad(async () => {
// Add React-specific snippets for JSX files
await editor.snippets.add({
fileExtension: '.jsx',
id: 'component',
label: 'comp',
template: 'function $1() {\n return (\n <div>\n $0\n </div>\n );\n}',
});
await editor.snippets.add({
fileExtension: '.jsx',
id: 'useState',
label: 'state',
template: 'const [$1, set$1] = useState($2);$0',
});
await editor.snippets.add({
fileExtension: '.jsx',
id: 'useEffect',
label: 'effect',
template: 'useEffect(() => {\n $0\n}, [$1]);',
});
});
Conditionally Adding Snippets Based on Project Content
An extension can inspect the project and add relevant snippets:
import editor from "@srcnexus/ext-sdk";
editor.onLoad(async () => {
try {
const activeFile = await editor.workspace.getActiveFile();
if (!activeFile) return;
// Add Express.js snippets for JavaScript files
await editor.snippets.add({
fileExtension: '.js',
id: 'express_route',
label: 'route',
template: "app.$1('/$2', (req, res) => {\n $0\n});",
});
await editor.snippets.add({
fileExtension: '.js',
id: 'express_middleware',
label: 'mw',
template: 'function $1(req, res, next) {\n $0\n next();\n}',
});
} catch (err) {
console.error('Failed to add snippets:', err);
}
});
Listing and Filtering Snippets
An extension can query existing snippets and act on the results:
import editor from "@srcnexus/ext-sdk";
async function showSnippetCount() {
const jsSnippets = await editor.snippets.list({ fileExtension: '.js' });
await editor.window.showToast(`${jsSnippets.length} JavaScript snippets available`);
}
async function listAllSnippetLanguages() {
const all = await editor.snippets.list();
const languages = Object.keys(all);
await editor.window.showToast(`Snippets available for: ${languages.join(', ')}`);
}
Cleaning Up Snippets
Extensions do not need to manually remove their snippets when deactivated. The editor automatically removes all snippets namespaced to an extension when it is uninstalled or disabled. However, an extension can remove specific snippets at any time:
import editor from "@srcnexus/ext-sdk";
// Remove a specific snippet
await editor.snippets.delete({
fileExtension: '.jsx',
id: 'useState',
});
A Complete Snippet Provider Extension
Here is a full example of an extension that provides Go language snippets:
import editor from "@srcnexus/ext-sdk";
const goSnippets = [
{ id: 'func', label: 'func', template: 'func $1($2) $3 {\n\t$0\n}' },
{ id: 'iferr', label: 'iferr', template: 'if err != nil {\n\t$0\n}' },
{ id: 'struct', label: 'struct', template: 'type $1 struct {\n\t$0\n}' },
{ id: 'interface', label: 'iface', template: 'type $1 interface {\n\t$0\n}' },
{ id: 'goroutine', label: 'go', template: 'go func() {\n\t$0\n}()' },
{ id: 'select', label: 'select', template: 'select {\ncase $1:\n\t$0\n}' },
{ id: 'test', label: 'test', template: 'func Test$1(t *testing.T) {\n\t$0\n}' },
];
editor.onLoad(async () => {
for (const s of goSnippets) {
await editor.snippets.add({
fileExtension: '.go',
id: s.id,
label: s.label,
template: s.template,
});
}
await editor.window.showToast(`Registered ${goSnippets.length} Go snippets`);
});
Namespace and Ownership
All snippet IDs are automatically namespaced. When an extension with ID my-ext calls snippets.add with id: "func", the stored snippet ID becomes my-ext_func. This namespacing has several implications:
- No collisions -- two extensions can both define a snippet with
id: "func"without conflict, since the stored IDs will beext-a_funcandext-b_func. - Scoped deletion --
snippets.deleteautomatically prepends the calling extension's ID, so an extension can only delete its own snippets. - Transparent listing --
snippets.listreturns the full namespaced IDs, which is useful for debugging but does not affect the user-facing label.
When an extension is uninstalled or disabled, the editor automatically removes all snippets whose ID starts with <extensionId>_.