Snippets API
The Snippets API provides comprehensive functionality for creating, managing, and using code snippets within the development environment.
Overview
The Snippets API enables you to:
- Create and manage custom code snippets
- Support multiple programming languages
- Use variables and placeholders in snippets
- Provide snippet completion and insertion
- Import and export snippet collections
- Support snippet transformations and conditions
- Implement snippet sharing and synchronization
- Handle snippet conflicts and versioning
Basic Usage
Snippet Management
typescript
import { TraeAPI } from '@trae/api';
// Snippet manager implementation
class SnippetManager {
private snippets: Map<string, SnippetCollection> = new Map();
private globalSnippets: Map<string, Snippet> = new Map();
private snippetProviders: Map<string, SnippetProvider> = new Map();
private transformers: Map<string, SnippetTransformer> = new Map();
private variableResolvers: Map<string, VariableResolver> = new Map();
constructor() {
this.initializeBuiltinSnippets();
this.initializeVariableResolvers();
this.initializeTransformers();
this.loadUserSnippets();
this.setupEventListeners();
}
private initializeBuiltinSnippets(): void {
// JavaScript/TypeScript snippets
const jsSnippets: SnippetDefinition[] = [
{
name: 'log',
prefix: 'log',
body: [
'console.log(${1:message});'
],
description: 'Log output to console',
scope: 'javascript,typescript'
},
{
name: 'function',
prefix: 'func',
body: [
'function ${1:name}(${2:params}) {',
'\t${3:// body}',
'}'
],
description: 'Function declaration',
scope: 'javascript,typescript'
},
{
name: 'arrow-function',
prefix: 'af',
body: [
'const ${1:name} = (${2:params}) => {',
'\t${3:// body}',
'};'
],
description: 'Arrow function',
scope: 'javascript,typescript'
},
{
name: 'async-function',
prefix: 'afunc',
body: [
'async function ${1:name}(${2:params}) {',
'\t${3:// body}',
'}'
],
description: 'Async function declaration',
scope: 'javascript,typescript'
},
{
name: 'class',
prefix: 'class',
body: [
'class ${1:ClassName} {',
'\tconstructor(${2:params}) {',
'\t\t${3:// constructor body}',
'\t}',
'',
'\t${4:// methods}',
'}'
],
description: 'Class declaration',
scope: 'javascript,typescript'
},
{
name: 'interface',
prefix: 'interface',
body: [
'interface ${1:InterfaceName} {',
'\t${2:// properties}',
'}'
],
description: 'Interface declaration',
scope: 'typescript'
},
{
name: 'type',
prefix: 'type',
body: [
'type ${1:TypeName} = ${2:type};'
],
description: 'Type alias',
scope: 'typescript'
},
{
name: 'import',
prefix: 'imp',
body: [
'import { ${2:imports} } from \'${1:module}\';'
],
description: 'Import statement',
scope: 'javascript,typescript'
},
{
name: 'export',
prefix: 'exp',
body: [
'export { ${1:exports} };'
],
description: 'Export statement',
scope: 'javascript,typescript'
},
{
name: 'try-catch',
prefix: 'try',
body: [
'try {',
'\t${1:// try block}',
'} catch (${2:error}) {',
'\t${3:// catch block}',
'}'
],
description: 'Try-catch block',
scope: 'javascript,typescript'
},
{
name: 'for-loop',
prefix: 'for',
body: [
'for (let ${1:i} = 0; ${1:i} < ${2:array}.length; ${1:i}++) {',
'\t${3:// loop body}',
'}'
],
description: 'For loop',
scope: 'javascript,typescript'
},
{
name: 'for-of',
prefix: 'forof',
body: [
'for (const ${1:item} of ${2:array}) {',
'\t${3:// loop body}',
'}'
],
description: 'For-of loop',
scope: 'javascript,typescript'
},
{
name: 'for-in',
prefix: 'forin',
body: [
'for (const ${1:key} in ${2:object}) {',
'\tif (${2:object}.hasOwnProperty(${1:key})) {',
'\t\t${3:// loop body}',
'\t}',
'}'
],
description: 'For-in loop',
scope: 'javascript,typescript'
},
{
name: 'if-statement',
prefix: 'if',
body: [
'if (${1:condition}) {',
'\t${2:// if body}',
'}'
],
description: 'If statement',
scope: 'javascript,typescript'
},
{
name: 'if-else',
prefix: 'ifelse',
body: [
'if (${1:condition}) {',
'\t${2:// if body}',
'} else {',
'\t${3:// else body}',
'}'
],
description: 'If-else statement',
scope: 'javascript,typescript'
},
{
name: 'switch',
prefix: 'switch',
body: [
'switch (${1:expression}) {',
'\tcase ${2:value}:',
'\t\t${3:// case body}',
'\t\tbreak;',
'\tdefault:',
'\t\t${4:// default body}',
'\t\tbreak;',
'}'
],
description: 'Switch statement',
scope: 'javascript,typescript'
},
{
name: 'promise',
prefix: 'promise',
body: [
'new Promise((resolve, reject) => {',
'\t${1:// promise body}',
'});'
],
description: 'Promise constructor',
scope: 'javascript,typescript'
},
{
name: 'async-await',
prefix: 'await',
body: [
'const ${1:result} = await ${2:promise};'
],
description: 'Await expression',
scope: 'javascript,typescript'
},
{
name: 'timeout',
prefix: 'timeout',
body: [
'setTimeout(() => {',
'\t${2:// timeout body}',
'}, ${1:1000});'
],
description: 'Set timeout',
scope: 'javascript,typescript'
},
{
name: 'interval',
prefix: 'interval',
body: [
'setInterval(() => {',
'\t${2:// interval body}',
'}, ${1:1000});'
],
description: 'Set interval',
scope: 'javascript,typescript'
}
];
// React snippets
const reactSnippets: SnippetDefinition[] = [
{
name: 'react-component',
prefix: 'rfc',
body: [
'import React from \'react\';',
'',
'interface ${1:ComponentName}Props {',
'\t${2:// props}',
'}',
'',
'const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = (${3:props}) => {',
'\treturn (',
'\t\t<div>',
'\t\t\t${4:// component content}',
'\t\t</div>',
'\t);',
'};',
'',
'export default ${1:ComponentName};'
],
description: 'React functional component',
scope: 'typescriptreact,javascriptreact'
},
{
name: 'react-hook',
prefix: 'rhook',
body: [
'import { ${1:useState} } from \'react\';',
'',
'const use${2:HookName} = (${3:params}) => {',
'\t${4:// hook logic}',
'',
'\treturn ${5:returnValue};',
'};',
'',
'export default use${2:HookName};'
],
description: 'Custom React hook',
scope: 'typescriptreact,javascriptreact'
},
{
name: 'use-state',
prefix: 'useState',
body: [
'const [${1:state}, set${1/(.*)/${1:/capitalize}/}] = useState${2:<${3:type}>}(${4:initialValue});'
],
description: 'useState hook',
scope: 'typescriptreact,javascriptreact'
},
{
name: 'use-effect',
prefix: 'useEffect',
body: [
'useEffect(() => {',
'\t${1:// effect logic}',
'}, [${2:dependencies}]);'
],
description: 'useEffect hook',
scope: 'typescriptreact,javascriptreact'
},
{
name: 'use-context',
prefix: 'useContext',
body: [
'const ${1:contextValue} = useContext(${2:Context});'
],
description: 'useContext hook',
scope: 'typescriptreact,javascriptreact'
},
{
name: 'use-reducer',
prefix: 'useReducer',
body: [
'const [${1:state}, ${2:dispatch}] = useReducer(${3:reducer}, ${4:initialState});'
],
description: 'useReducer hook',
scope: 'typescriptreact,javascriptreact'
}
];
// HTML snippets
const htmlSnippets: SnippetDefinition[] = [
{
name: 'html5',
prefix: 'html5',
body: [
'<!DOCTYPE html>',
'<html lang="${1:en}">',
'<head>',
'\t<meta charset="UTF-8">',
'\t<meta name="viewport" content="width=device-width, initial-scale=1.0">',
'\t<title>${2:Document}</title>',
'</head>',
'<body>',
'\t${3:<!-- content -->}',
'</body>',
'</html>'
],
description: 'HTML5 boilerplate',
scope: 'html'
},
{
name: 'div',
prefix: 'div',
body: [
'<div${1: class="${2:className}"}>',
'\t${3:content}',
'</div>'
],
description: 'Div element',
scope: 'html'
},
{
name: 'link',
prefix: 'a',
body: [
'<a href="${1:url}"${2: target="${3:_blank}"}>${4:link text}</a>'
],
description: 'Anchor link',
scope: 'html'
},
{
name: 'image',
prefix: 'img',
body: [
'<img src="${1:src}" alt="${2:alt}"${3: width="${4:width}" height="${5:height}"}/>'
],
description: 'Image element',
scope: 'html'
},
{
name: 'form',
prefix: 'form',
body: [
'<form${1: action="${2:action}" method="${3:post}"}>',
'\t${4:<!-- form content -->}',
'</form>'
],
description: 'Form element',
scope: 'html'
},
{
name: 'input',
prefix: 'input',
body: [
'<input type="${1:text}" name="${2:name}" id="${3:id}"${4: placeholder="${5:placeholder}"}${6: required}/>'
],
description: 'Input element',
scope: 'html'
},
{
name: 'button',
prefix: 'btn',
body: [
'<button type="${1:button}"${2: class="${3:className}"}>${4:Button Text}</button>'
],
description: 'Button element',
scope: 'html'
}
];
// CSS snippets
const cssSnippets: SnippetDefinition[] = [
{
name: 'flexbox',
prefix: 'flex',
body: [
'display: flex;',
'justify-content: ${1:center};',
'align-items: ${2:center};'
],
description: 'Flexbox layout',
scope: 'css,scss,less'
},
{
name: 'grid',
prefix: 'grid',
body: [
'display: grid;',
'grid-template-columns: ${1:repeat(auto-fit, minmax(250px, 1fr))};',
'gap: ${2:1rem};'
],
description: 'CSS Grid layout',
scope: 'css,scss,less'
},
{
name: 'media-query',
prefix: 'media',
body: [
'@media (${1:max-width}: ${2:768px}) {',
'\t${3:/* styles */}',
'}'
],
description: 'Media query',
scope: 'css,scss,less'
},
{
name: 'animation',
prefix: 'animation',
body: [
'animation: ${1:name} ${2:duration} ${3:ease-in-out} ${4:infinite};',
'',
'@keyframes ${1:name} {',
'\t0% {',
'\t\t${5:/* start styles */}',
'\t}',
'\t100% {',
'\t\t${6:/* end styles */}',
'\t}',
'}'
],
description: 'CSS animation',
scope: 'css,scss,less'
},
{
name: 'transition',
prefix: 'transition',
body: [
'transition: ${1:property} ${2:duration} ${3:ease-in-out};'
],
description: 'CSS transition',
scope: 'css,scss,less'
}
];
// Register snippet collections
this.registerSnippetCollection('javascript', {
name: 'JavaScript',
description: 'JavaScript code snippets',
snippets: new Map(jsSnippets.map(s => [s.name, this.createSnippet(s)]))
});
this.registerSnippetCollection('react', {
name: 'React',
description: 'React code snippets',
snippets: new Map(reactSnippets.map(s => [s.name, this.createSnippet(s)]))
});
this.registerSnippetCollection('html', {
name: 'HTML',
description: 'HTML code snippets',
snippets: new Map(htmlSnippets.map(s => [s.name, this.createSnippet(s)]))
});
this.registerSnippetCollection('css', {
name: 'CSS',
description: 'CSS code snippets',
snippets: new Map(cssSnippets.map(s => [s.name, this.createSnippet(s)]))
});
console.log('Initialized builtin snippets');
}
private initializeVariableResolvers(): void {
// Built-in variable resolvers
this.variableResolvers.set('TM_SELECTED_TEXT', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor && !editor.selection.isEmpty) {
return editor.document.getText(editor.selection);
}
return '';
}
});
this.variableResolvers.set('TM_CURRENT_LINE', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
const line = editor.document.lineAt(editor.selection.active.line);
return line.text;
}
return '';
}
});
this.variableResolvers.set('TM_CURRENT_WORD', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
const wordRange = editor.document.getWordRangeAtPosition(editor.selection.active);
if (wordRange) {
return editor.document.getText(wordRange);
}
}
return '';
}
});
this.variableResolvers.set('TM_FILENAME', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
return TraeAPI.path.basename(editor.document.fileName);
}
return '';
}
});
this.variableResolvers.set('TM_FILENAME_BASE', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
const filename = TraeAPI.path.basename(editor.document.fileName);
return TraeAPI.path.parse(filename).name;
}
return '';
}
});
this.variableResolvers.set('TM_DIRECTORY', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
return TraeAPI.path.dirname(editor.document.fileName);
}
return '';
}
});
this.variableResolvers.set('TM_FILEPATH', {
resolve: () => {
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
return editor.document.fileName;
}
return '';
}
});
this.variableResolvers.set('CLIPBOARD', {
resolve: async () => {
try {
return await TraeAPI.env.clipboard.readText();
} catch {
return '';
}
}
});
this.variableResolvers.set('WORKSPACE_NAME', {
resolve: () => {
const workspaceFolder = TraeAPI.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
return TraeAPI.path.basename(workspaceFolder.uri.fsPath);
}
return '';
}
});
this.variableResolvers.set('WORKSPACE_FOLDER', {
resolve: () => {
const workspaceFolder = TraeAPI.workspace.workspaceFolders?.[0];
if (workspaceFolder) {
return workspaceFolder.uri.fsPath;
}
return '';
}
});
// Date/time variables
this.variableResolvers.set('CURRENT_YEAR', {
resolve: () => new Date().getFullYear().toString()
});
this.variableResolvers.set('CURRENT_YEAR_SHORT', {
resolve: () => new Date().getFullYear().toString().slice(-2)
});
this.variableResolvers.set('CURRENT_MONTH', {
resolve: () => (new Date().getMonth() + 1).toString().padStart(2, '0')
});
this.variableResolvers.set('CURRENT_MONTH_NAME', {
resolve: () => new Date().toLocaleString('default', { month: 'long' })
});
this.variableResolvers.set('CURRENT_MONTH_NAME_SHORT', {
resolve: () => new Date().toLocaleString('default', { month: 'short' })
});
this.variableResolvers.set('CURRENT_DATE', {
resolve: () => new Date().getDate().toString().padStart(2, '0')
});
this.variableResolvers.set('CURRENT_DAY_NAME', {
resolve: () => new Date().toLocaleString('default', { weekday: 'long' })
});
this.variableResolvers.set('CURRENT_DAY_NAME_SHORT', {
resolve: () => new Date().toLocaleString('default', { weekday: 'short' })
});
this.variableResolvers.set('CURRENT_HOUR', {
resolve: () => new Date().getHours().toString().padStart(2, '0')
});
this.variableResolvers.set('CURRENT_MINUTE', {
resolve: () => new Date().getMinutes().toString().padStart(2, '0')
});
this.variableResolvers.set('CURRENT_SECOND', {
resolve: () => new Date().getSeconds().toString().padStart(2, '0')
});
this.variableResolvers.set('CURRENT_SECONDS_UNIX', {
resolve: () => Math.floor(Date.now() / 1000).toString()
});
// Random values
this.variableResolvers.set('RANDOM', {
resolve: () => Math.random().toString(36).substring(2, 8)
});
this.variableResolvers.set('RANDOM_HEX', {
resolve: () => Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')
});
this.variableResolvers.set('UUID', {
resolve: () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
});
console.log('Initialized variable resolvers');
}
private initializeTransformers(): void {
// Text case transformers
this.transformers.set('upcase', {
transform: (text: string) => text.toUpperCase()
});
this.transformers.set('downcase', {
transform: (text: string) => text.toLowerCase()
});
this.transformers.set('capitalize', {
transform: (text: string) => {
return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
}
});
this.transformers.set('camelcase', {
transform: (text: string) => {
return text.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
}).replace(/\s+/g, '');
}
});
this.transformers.set('pascalcase', {
transform: (text: string) => {
return text.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => {
return word.toUpperCase();
}).replace(/\s+/g, '');
}
});
this.transformers.set('snakecase', {
transform: (text: string) => {
return text.replace(/\W+/g, ' ')
.split(/ |\s/)
.map(word => word.toLowerCase())
.join('_');
}
});
this.transformers.set('kebabcase', {
transform: (text: string) => {
return text.replace(/\W+/g, ' ')
.split(/ |\s/)
.map(word => word.toLowerCase())
.join('-');
}
});
console.log('Initialized transformers');
}
private setupEventListeners(): void {
// Listen for language changes
TraeAPI.window.onDidChangeActiveTextEditor(editor => {
if (editor) {
this.updateLanguageContext(editor.document.languageId);
}
});
// Listen for snippet provider registrations
TraeAPI.languages.onDidChangeLanguages(() => {
this.refreshSnippetProviders();
});
}
private async loadUserSnippets(): Promise<void> {
try {
// Load user snippets from workspace settings
const userSnippets = TraeAPI.workspace.getConfiguration('snippets').get<{ [key: string]: SnippetDefinition[] }>('user', {});
for (const [language, snippets] of Object.entries(userSnippets)) {
const collection = this.snippets.get(language) || {
name: language,
description: `User ${language} snippets`,
snippets: new Map()
};
snippets.forEach(snippetDef => {
const snippet = this.createSnippet(snippetDef);
collection.snippets.set(snippet.name, snippet);
});
this.snippets.set(language, collection);
}
console.log('Loaded user snippets');
} catch (error) {
console.error('Failed to load user snippets:', error);
}
}
// Core snippet operations
createSnippet(definition: SnippetDefinition): Snippet {
return {
name: definition.name,
prefix: definition.prefix,
body: definition.body,
description: definition.description,
scope: definition.scope ? definition.scope.split(',').map(s => s.trim()) : [],
isFileTemplate: definition.isFileTemplate || false,
when: definition.when,
sortText: definition.sortText,
insertText: definition.insertText || definition.body.join('\n'),
createdAt: Date.now(),
usageCount: 0
};
}
registerSnippet(snippet: Snippet, language?: string): void {
if (language) {
// Register to specific language collection
let collection = this.snippets.get(language);
if (!collection) {
collection = {
name: language,
description: `${language} snippets`,
snippets: new Map()
};
this.snippets.set(language, collection);
}
collection.snippets.set(snippet.name, snippet);
} else {
// Register as global snippet
this.globalSnippets.set(snippet.name, snippet);
}
console.log(`Registered snippet: ${snippet.name}`);
}
unregisterSnippet(name: string, language?: string): boolean {
if (language) {
const collection = this.snippets.get(language);
if (collection) {
return collection.snippets.delete(name);
}
} else {
return this.globalSnippets.delete(name);
}
return false;
}
registerSnippetCollection(language: string, collection: SnippetCollection): void {
this.snippets.set(language, collection);
console.log(`Registered snippet collection: ${collection.name}`);
}
getSnippetsForLanguage(language: string): Snippet[] {
const snippets: Snippet[] = [];
// Add global snippets
snippets.push(...Array.from(this.globalSnippets.values()));
// Add language-specific snippets
const collection = this.snippets.get(language);
if (collection) {
snippets.push(...Array.from(collection.snippets.values()));
}
// Add snippets from other collections that support this language
for (const [, coll] of this.snippets) {
for (const snippet of coll.snippets.values()) {
if (snippet.scope.length === 0 || snippet.scope.includes(language)) {
snippets.push(snippet);
}
}
}
// Remove duplicates and sort
const uniqueSnippets = Array.from(new Map(snippets.map(s => [s.name, s])).values());
return uniqueSnippets.sort((a, b) => {
// Sort by usage count (descending) then by name
if (a.usageCount !== b.usageCount) {
return (b.usageCount || 0) - (a.usageCount || 0);
}
return a.name.localeCompare(b.name);
});
}
searchSnippets(query: string, language?: string): Snippet[] {
const lowercaseQuery = query.toLowerCase();
let snippets: Snippet[];
if (language) {
snippets = this.getSnippetsForLanguage(language);
} else {
snippets = this.getAllSnippets();
}
return snippets.filter(snippet => {
return (
snippet.name.toLowerCase().includes(lowercaseQuery) ||
snippet.prefix.toLowerCase().includes(lowercaseQuery) ||
(snippet.description && snippet.description.toLowerCase().includes(lowercaseQuery)) ||
snippet.body.some(line => line.toLowerCase().includes(lowercaseQuery))
);
});
}
getAllSnippets(): Snippet[] {
const snippets: Snippet[] = [];
// Add global snippets
snippets.push(...Array.from(this.globalSnippets.values()));
// Add all collection snippets
for (const collection of this.snippets.values()) {
snippets.push(...Array.from(collection.snippets.values()));
}
return snippets;
}
// Snippet insertion and expansion
async insertSnippet(snippet: Snippet, editor?: TraeAPI.TextEditor): Promise<boolean> {
try {
const targetEditor = editor || TraeAPI.window.activeTextEditor;
if (!targetEditor) {
return false;
}
// Resolve variables and transform text
const expandedText = await this.expandSnippetText(snippet.insertText);
// Create snippet string with placeholders
const snippetString = new TraeAPI.SnippetString(expandedText);
// Insert snippet
const success = await targetEditor.insertSnippet(snippetString);
if (success) {
// Update usage statistics
snippet.usageCount = (snippet.usageCount || 0) + 1;
snippet.lastUsed = Date.now();
console.log(`Inserted snippet: ${snippet.name}`);
}
return success;
} catch (error) {
console.error(`Failed to insert snippet ${snippet.name}:`, error);
return false;
}
}
async expandSnippetText(text: string): Promise<string> {
let expandedText = text;
// Replace variables
const variableRegex = /\$\{([^}]+)\}/g;
const matches = Array.from(text.matchAll(variableRegex));
for (const match of matches) {
const [fullMatch, variableExpr] = match;
const expandedValue = await this.expandVariable(variableExpr);
expandedText = expandedText.replace(fullMatch, expandedValue);
}
return expandedText;
}
private async expandVariable(variableExpr: string): Promise<string> {
// Parse variable expression
const parts = variableExpr.split(':');
const variableName = parts[0];
const defaultValue = parts[1] || '';
// Check for transformations
const transformMatch = variableName.match(/^(.+)\/(.+)\/$/);
if (transformMatch) {
const [, baseVar, transformName] = transformMatch;
const baseValue = await this.resolveVariable(baseVar) || defaultValue;
const transformer = this.transformers.get(transformName);
if (transformer) {
return transformer.transform(baseValue);
}
return baseValue;
}
// Resolve variable
const value = await this.resolveVariable(variableName);
return value !== undefined ? value : defaultValue;
}
private async resolveVariable(variableName: string): Promise<string | undefined> {
const resolver = this.variableResolvers.get(variableName);
if (resolver) {
try {
return await resolver.resolve();
} catch (error) {
console.error(`Error resolving variable ${variableName}:`, error);
return undefined;
}
}
// Check for numbered placeholders
if (/^\d+$/.test(variableName)) {
return `\${${variableName}}`; // Keep as placeholder
}
return undefined;
}
// Snippet providers
registerSnippetProvider(language: string, provider: SnippetProvider): TraeAPI.Disposable {
this.snippetProviders.set(`${language}:${Date.now()}`, provider);
return {
dispose: () => {
// Remove provider
for (const [key, p] of this.snippetProviders) {
if (p === provider) {
this.snippetProviders.delete(key);
break;
}
}
}
};
}
async getSnippetCompletions(document: TraeAPI.TextDocument, position: TraeAPI.Position): Promise<TraeAPI.CompletionItem[]> {
const completions: TraeAPI.CompletionItem[] = [];
const language = document.languageId;
// Get snippets for current language
const snippets = this.getSnippetsForLanguage(language);
// Get word at position for prefix matching
const wordRange = document.getWordRangeAtPosition(position);
const prefix = wordRange ? document.getText(wordRange) : '';
// Filter snippets by prefix
const matchingSnippets = snippets.filter(snippet => {
return snippet.prefix.startsWith(prefix) || prefix === '';
});
// Convert to completion items
for (const snippet of matchingSnippets) {
const item = new TraeAPI.CompletionItem(snippet.prefix, TraeAPI.CompletionItemKind.Snippet);
item.detail = snippet.description || snippet.name;
item.documentation = new TraeAPI.MarkdownString().appendCodeblock(snippet.body.join('\n'), language);
item.insertText = new TraeAPI.SnippetString(snippet.insertText);
item.sortText = snippet.sortText || snippet.prefix;
completions.push(item);
}
// Get completions from providers
for (const provider of this.snippetProviders.values()) {
try {
const providerCompletions = await provider.provideCompletionItems(document, position);
if (providerCompletions) {
completions.push(...providerCompletions);
}
} catch (error) {
console.error('Error getting snippets from provider:', error);
}
}
return completions;
}
// Import/Export
exportSnippets(language?: string): SnippetExport {
const snippets: { [key: string]: SnippetDefinition[] } = {};
if (language) {
const collection = this.snippets.get(language);
if (collection) {
snippets[language] = Array.from(collection.snippets.values()).map(this.snippetToDefinition);
}
} else {
// Export all snippets
for (const [lang, collection] of this.snippets) {
snippets[lang] = Array.from(collection.snippets.values()).map(this.snippetToDefinition);
}
// Add global snippets
if (this.globalSnippets.size > 0) {
snippets['global'] = Array.from(this.globalSnippets.values()).map(this.snippetToDefinition);
}
}
return {
version: '1.0.0',
exportedAt: new Date().toISOString(),
snippets
};
}
async importSnippets(exportData: SnippetExport): Promise<boolean> {
try {
for (const [language, snippetDefs] of Object.entries(exportData.snippets)) {
if (language === 'global') {
// Import as global snippets
snippetDefs.forEach(def => {
const snippet = this.createSnippet(def);
this.globalSnippets.set(snippet.name, snippet);
});
} else {
// Import to language collection
let collection = this.snippets.get(language);
if (!collection) {
collection = {
name: language,
description: `Imported ${language} snippets`,
snippets: new Map()
};
this.snippets.set(language, collection);
}
snippetDefs.forEach(def => {
const snippet = this.createSnippet(def);
collection!.snippets.set(snippet.name, snippet);
});
}
}
console.log('Imported snippets successfully');
return true;
} catch (error) {
console.error('Failed to import snippets:', error);
return false;
}
}
private snippetToDefinition(snippet: Snippet): SnippetDefinition {
return {
name: snippet.name,
prefix: snippet.prefix,
body: snippet.body,
description: snippet.description,
scope: snippet.scope.join(','),
isFileTemplate: snippet.isFileTemplate,
when: snippet.when,
sortText: snippet.sortText,
insertText: snippet.insertText
};
}
// Utility methods
private updateLanguageContext(languageId: string): void {
// Update context for snippet filtering
console.log(`Language context updated: ${languageId}`);
}
private refreshSnippetProviders(): void {
// Refresh snippet providers when languages change
console.log('Refreshing snippet providers');
}
dispose(): void {
this.snippets.clear();
this.globalSnippets.clear();
this.snippetProviders.clear();
this.transformers.clear();
this.variableResolvers.clear();
}
}
// Interfaces
interface SnippetDefinition {
name: string;
prefix: string;
body: string[];
description?: string;
scope?: string;
isFileTemplate?: boolean;
when?: string;
sortText?: string;
insertText?: string;
}
interface Snippet {
name: string;
prefix: string;
body: string[];
description?: string;
scope: string[];
isFileTemplate: boolean;
when?: string;
sortText?: string;
insertText: string;
createdAt: number;
lastUsed?: number;
usageCount?: number;
}
interface SnippetCollection {
name: string;
description: string;
snippets: Map<string, Snippet>;
}
interface SnippetProvider {
provideCompletionItems(document: TraeAPI.TextDocument, position: TraeAPI.Position): Promise<TraeAPI.CompletionItem[] | undefined>;
}
interface VariableResolver {
resolve(): Promise<string> | string;
}
interface SnippetTransformer {
transform(text: string): string;
}
interface SnippetExport {
version: string;
exportedAt: string;
snippets: { [language: string]: SnippetDefinition[] };
}
// Initialize snippet manager
const snippetManager = new SnippetManager();File Templates
typescript
// File template functionality
class FileTemplateManager {
private templates: Map<string, FileTemplate> = new Map();
constructor(private snippetManager: SnippetManager) {
this.initializeDefaultTemplates();
}
private initializeDefaultTemplates(): void {
const templates: FileTemplateDefinition[] = [
{
name: 'React Component',
extension: 'tsx',
content: [
'import React from \'react\';',
'',
'interface ${TM_FILENAME_BASE}Props {',
'\t// Props definition',
'}',
'',
'const ${TM_FILENAME_BASE}: React.FC<${TM_FILENAME_BASE}Props> = (props) => {',
'\treturn (',
'\t\t<div>',
'\t\t\t{/* Component content */}',
'\t\t</div>',
'\t);',
'};',
'',
'export default ${TM_FILENAME_BASE};'
],
description: 'React functional component template'
},
{
name: 'Node.js Module',
extension: 'js',
content: [
'\'use strict\';',
'',
'/**',
' * ${TM_FILENAME_BASE} module',
' * @author ${USER}',
' * @date ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}',
' */',
'',
'module.exports = {',
'\t// Module exports',
'};'
],
description: 'Node.js module template'
},
{
name: 'HTML5 Document',
extension: 'html',
content: [
'<!DOCTYPE html>',
'<html lang="en">',
'<head>',
'\t<meta charset="UTF-8">',
'\t<meta name="viewport" content="width=device-width, initial-scale=1.0">',
'\t<title>${TM_FILENAME_BASE}</title>',
'</head>',
'<body>',
'\t<!-- Content -->',
'</body>',
'</html>'
],
description: 'HTML5 document template'
},
{
name: 'CSS Stylesheet',
extension: 'css',
content: [
'/**',
' * ${TM_FILENAME_BASE} stylesheet',
' * @author ${USER}',
' * @date ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE}',
' */',
'',
'/* Reset and base styles */',
'* {',
'\tbox-sizing: border-box;',
'}',
'',
'/* Component styles */'
],
description: 'CSS stylesheet template'
}
];
templates.forEach(template => {
this.registerTemplate(this.createTemplate(template));
});
}
createTemplate(definition: FileTemplateDefinition): FileTemplate {
return {
name: definition.name,
extension: definition.extension,
content: definition.content.join('\n'),
description: definition.description,
createdAt: Date.now(),
usageCount: 0
};
}
registerTemplate(template: FileTemplate): void {
this.templates.set(template.name, template);
console.log(`Registered file template: ${template.name}`);
}
getTemplatesForExtension(extension: string): FileTemplate[] {
return Array.from(this.templates.values())
.filter(template => template.extension === extension)
.sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0));
}
async createFileFromTemplate(template: FileTemplate, filePath: string): Promise<boolean> {
try {
// Expand template content
const expandedContent = await this.snippetManager.expandSnippetText(template.content);
// Write file
await TraeAPI.workspace.fs.writeFile(
TraeAPI.Uri.file(filePath),
Buffer.from(expandedContent, 'utf8')
);
// Update usage statistics
template.usageCount = (template.usageCount || 0) + 1;
template.lastUsed = Date.now();
console.log(`Created file from template: ${template.name}`);
return true;
} catch (error) {
console.error(`Failed to create file from template ${template.name}:`, error);
return false;
}
}
getAllTemplates(): FileTemplate[] {
return Array.from(this.templates.values());
}
}
interface FileTemplateDefinition {
name: string;
extension: string;
content: string[];
description?: string;
}
interface FileTemplate {
name: string;
extension: string;
content: string;
description?: string;
createdAt: number;
lastUsed?: number;
usageCount?: number;
}
// Initialize file template manager
const fileTemplateManager = new FileTemplateManager(snippetManager);API Reference
Core Interfaces
typescript
interface SnippetsAPI {
// Snippet management
createSnippet(definition: SnippetDefinition): Snippet;
registerSnippet(snippet: Snippet, language?: string): void;
unregisterSnippet(name: string, language?: string): boolean;
// Collections
registerSnippetCollection(language: string, collection: SnippetCollection): void;
getSnippetsForLanguage(language: string): Snippet[];
getAllSnippets(): Snippet[];
// Search and query
searchSnippets(query: string, language?: string): Snippet[];
// Insertion
insertSnippet(snippet: Snippet, editor?: TraeAPI.TextEditor): Promise<boolean>;
expandSnippetText(text: string): Promise<string>;
// Providers
registerSnippetProvider(language: string, provider: SnippetProvider): TraeAPI.Disposable;
getSnippetCompletions(document: TraeAPI.TextDocument, position: TraeAPI.Position): Promise<TraeAPI.CompletionItem[]>;
// Import/Export
exportSnippets(language?: string): SnippetExport;
importSnippets(exportData: SnippetExport): Promise<boolean>;
// File templates
registerTemplate(template: FileTemplate): void;
getTemplatesForExtension(extension: string): FileTemplate[];
createFileFromTemplate(template: FileTemplate, filePath: string): Promise<boolean>;
}Best Practices
- Snippet Organization: Group snippets by language and functionality
- Variable Usage: Use built-in variables for dynamic content
- Placeholder Design: Create logical tab stops for user input
- Scope Definition: Define appropriate scopes for snippet availability
- Performance: Optimize snippet loading and expansion
- User Experience: Provide clear descriptions and examples
- Customization: Allow users to create and modify snippets
- Sharing: Support snippet import/export for team collaboration
Related APIs
- Completion API - For snippet completion integration
- Language Services API - For language-specific features
- Editor API - For snippet insertion
- Settings API - For snippet configuration