Skip to content

Snippets API

Snippets APIは、エディタでのコードスニペット管理機能を提供します。これには、スニペットの作成、登録、挿入、およびスニペットコレクションとプロバイダーの管理が含まれます。

概要

Snippets APIにより、拡張機能は以下のことができます:

  • カスタムコードスニペットの作成と登録
  • 異なる言語のスニペットコレクションの管理
  • プロバイダーを通じた動的スニペットの提供
  • 変数解決を伴うスニペットの挿入
  • スニペットコレクションのインポートとエクスポート
  • スニペットからのファイルテンプレートの作成

基本的な使用方法

スニペットマネージャー

SnippetManagerクラスは、スニペット操作のメインエントリーポイントです:

typescript
import { SnippetManager } from 'vscode';

// スニペットマネージャーインスタンスを取得
const snippetManager = SnippetManager.getInstance();

// 組み込みスニペットを初期化
await snippetManager.initialize();

組み込みスニペット

スニペットマネージャーには、一般的なプログラミング構造のための組み込みスニペットセットが付属しています:

typescript
class SnippetManager {
    private async initializeBuiltInSnippets(): Promise<void> {
        // JavaScript/TypeScriptスニペット
        await this.registerSnippet({
            name: 'log',
            prefix: 'log',
            body: 'console.log(${1:message});',
            description: 'コンソールにログ出力',
            scope: ['javascript', 'typescript']
        });

        await this.registerSnippet({
            name: 'function',
            prefix: 'func',
            body: [
                'function ${1:name}(${2:params}) {',
                '\t${3:// body}',
                '}'
            ],
            description: '関数宣言',
            scope: ['javascript', 'typescript']
        });

        await this.registerSnippet({
            name: 'arrow-function',
            prefix: 'arrow',
            body: 'const ${1:name} = (${2:params}) => {${3:// body}};',
            description: 'アロー関数',
            scope: ['javascript', 'typescript']
        });

        await this.registerSnippet({
            name: 'async-function',
            prefix: 'async',
            body: [
                'async function ${1:name}(${2:params}) {',
                '\t${3:// body}',
                '}'
            ],
            description: '非同期関数宣言',
            scope: ['javascript', 'typescript']
        });

        await this.registerSnippet({
            name: 'class',
            prefix: 'class',
            body: [
                'class ${1:ClassName} {',
                '\tconstructor(${2:params}) {',
                '\t\t${3:// constructor body}',
                '\t}',
                '}'
            ],
            description: 'クラス宣言',
            scope: ['javascript', 'typescript']
        });

        // その他のJavaScript/TypeScriptスニペット
        await this.registerSnippet({
            name: 'interface',
            prefix: 'interface',
            body: [
                'interface ${1:InterfaceName} {',
                '\t${2:property}: ${3:type};',
                '}'
            ],
            description: 'TypeScriptインターフェース',
            scope: ['typescript']
        });

        await this.registerSnippet({
            name: 'type',
            prefix: 'type',
            body: 'type ${1:TypeName} = ${2:type};',
            description: 'TypeScript型エイリアス',
            scope: ['typescript']
        });

        // Reactスニペット
        await this.registerSnippet({
            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関数コンポーネント',
            scope: ['typescript', 'typescriptreact']
        });

        // HTMLスニペット
        await this.registerSnippet({
            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ボイラープレート',
            scope: ['html']
        });

        // CSSスニペット
        await this.registerSnippet({
            name: 'flexbox',
            prefix: 'flex',
            body: [
                'display: flex;',
                'justify-content: ${1:center};',
                'align-items: ${2:center};',
                'flex-direction: ${3:row};'
            ],
            description: 'CSSフレックスボックスレイアウト',
            scope: ['css', 'scss', 'less']
        });
    }

    // 変数リゾルバーの初期化
    private async initializeVariableResolvers(): Promise<void> {
        this.variableResolvers.set('TM_SELECTED_TEXT', () => {
            return vscode.window.activeTextEditor?.document.getText(
                vscode.window.activeTextEditor.selection
            ) || '';
        });

        this.variableResolvers.set('TM_FILENAME', () => {
            const editor = vscode.window.activeTextEditor;
            return editor ? path.basename(editor.document.fileName) : '';
        });

        this.variableResolvers.set('CURRENT_YEAR', () => new Date().getFullYear().toString());
        this.variableResolvers.set('UUID', () => {
            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);
            });
        });
    }

    // スニペット操作
    async createSnippet(definition: SnippetDefinition): Promise<Snippet> {
        const snippet: Snippet = {
            id: this.generateSnippetId(),
            name: definition.name,
            prefix: definition.prefix,
            body: Array.isArray(definition.body) ? definition.body : [definition.body],
            description: definition.description || '',
            scope: definition.scope || [],
            isFileTemplate: definition.isFileTemplate || false
        };

        return snippet;
    }

    async registerSnippet(definition: SnippetDefinition): Promise<void> {
        const snippet = await this.createSnippet(definition);
        
        for (const language of snippet.scope) {
            if (!this.snippetCollections.has(language)) {
                this.snippetCollections.set(language, new Map());
            }
            this.snippetCollections.get(language)!.set(snippet.id, snippet);
        }

        this.onDidChangeSnippets.fire();
    }

    async unregisterSnippet(snippetId: string): Promise<void> {
        for (const collection of this.snippetCollections.values()) {
            if (collection.has(snippetId)) {
                collection.delete(snippetId);
                this.onDidChangeSnippets.fire();
                break;
            }
        }
    }

    async insertSnippet(snippet: Snippet, editor: vscode.TextEditor): Promise<void> {
        const resolvedBody = await this.resolveSnippetVariables(snippet.body, editor);
        const snippetString = new vscode.SnippetString(resolvedBody.join('\n'));
        
        await editor.insertSnippet(snippetString);
    }

    private async resolveSnippetVariables(body: string[], editor: vscode.TextEditor): Promise<string[]> {
        const resolvedBody: string[] = [];
        
        for (const line of body) {
            let resolvedLine = line;
            
            // 変数を解決
            for (const [variable, resolver] of this.variableResolvers) {
                const regex = new RegExp(`\\$\\{${variable}\\}`, 'g');
                const value = await resolver();
                resolvedLine = resolvedLine.replace(regex, value);
            }
            
            resolvedBody.push(resolvedLine);
        }
        
        return resolvedBody;
    }
}

スニペットプロバイダー

動的スニペットを提供するためのプロバイダーシステム:

typescript
interface SnippetProvider {
    provideSnippets(document: vscode.TextDocument, position: vscode.Position): Promise<Snippet[]>;
}

class DynamicSnippetProvider implements SnippetProvider {
    async provideSnippets(document: vscode.TextDocument, position: vscode.Position): Promise<Snippet[]> {
        const snippets: Snippet[] = [];
        
        // 現在のコンテキストに基づいてスニペットを生成
        if (document.languageId === 'typescript') {
            snippets.push({
                id: 'dynamic-import',
                name: 'dynamic-import',
                prefix: 'dimport',
                body: [`import { \${1:component} } from '\${2:./\${1:component}}';`],
                description: '動的インポート文',
                scope: ['typescript']
            });
        }
        
        return snippets;
    }
}

// プロバイダーの登録
const snippetManager = SnippetManager.getInstance();
snippetManager.registerSnippetProvider(new DynamicSnippetProvider());

ファイルテンプレート

スニペットからファイルテンプレートを作成:

typescript
class FileTemplateManager {
    private templates: Map<string, FileTemplate> = new Map();

    async createFileFromTemplate(templateName: string, targetPath: string, variables?: Record<string, string>): Promise<void> {
        const template = this.templates.get(templateName);
        if (!template) {
            throw new Error(`テンプレート '${templateName}' が見つかりません`);
        }

        let content = template.content;
        
        // 変数を置換
        if (variables) {
            for (const [key, value] of Object.entries(variables)) {
                const regex = new RegExp(`\\$\\{${key}\\}`, 'g');
                content = content.replace(regex, value);
            }
        }

        await vscode.workspace.fs.writeFile(vscode.Uri.file(targetPath), Buffer.from(content));
    }

    registerTemplate(template: FileTemplate): void {
        this.templates.set(template.name, template);
    }
}

// テンプレートの使用例
const templateManager = new FileTemplateManager();

templateManager.registerTemplate({
    name: 'react-component',
    extension: '.tsx',
    content: `import React from 'react';

interface \${ComponentName}Props {
  // プロパティを定義
}

const \${ComponentName}: React.FC<\${ComponentName}Props> = () => {
  return (
    <div>
      <h1>\${ComponentName}</h1>
    </div>
  );
};

export default \${ComponentName};`
});

// テンプレートからファイルを作成
await templateManager.createFileFromTemplate('react-component', './src/components/MyComponent.tsx', {
    ComponentName: 'MyComponent'
});

インターフェース定義

SnippetDefinition

typescript
interface SnippetDefinition {
    name: string;
    prefix: string;
    body: string | string[];
    description?: string;
    scope?: string[];
    isFileTemplate?: boolean;
}

Snippet

typescript
interface Snippet {
    id: string;
    name: string;
    prefix: string;
    body: string[];
    description: string;
    scope: string[];
    isFileTemplate: boolean;
}

SnippetProvider

typescript
interface SnippetProvider {
    provideSnippets(document: vscode.TextDocument, position: vscode.Position): Promise<Snippet[]>;
}

FileTemplate

typescript
interface FileTemplate {
    name: string;
    extension: string;
    content: string;
    variables?: string[];
}

API リファレンス

SnippetsAPI

typescript
interface SnippetsAPI {
    // スニペット管理
    createSnippet(definition: SnippetDefinition): Promise<Snippet>;
    registerSnippet(definition: SnippetDefinition): Promise<void>;
    unregisterSnippet(snippetId: string): Promise<void>;
    getSnippet(snippetId: string): Promise<Snippet | undefined>;
    getAllSnippets(): Promise<Snippet[]>;
    getSnippetsByLanguage(language: string): Promise<Snippet[]>;
    searchSnippets(query: string): Promise<Snippet[]>;

    // スニペットコレクション
    registerSnippetCollection(language: string, snippets: Record<string, SnippetDefinition>): Promise<void>;
    unregisterSnippetCollection(language: string): Promise<void>;
    getSnippetCollection(language: string): Promise<Map<string, Snippet> | undefined>;

    // スニペット挿入
    insertSnippet(snippet: Snippet, editor?: vscode.TextEditor): Promise<void>;
    expandSnippet(snippetText: string, editor?: vscode.TextEditor): Promise<void>;

    // プロバイダー管理
    registerSnippetProvider(provider: SnippetProvider): vscode.Disposable;
    getCompletionItems(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.CompletionItem[]>;

    // インポート/エクスポート
    exportSnippets(language?: string): Promise<SnippetExport>;
    importSnippets(snippetData: SnippetExport): Promise<void>;

    // ファイルテンプレート
    createFileFromTemplate(templateName: string, targetPath: string, variables?: Record<string, string>): Promise<void>;
    registerFileTemplate(template: FileTemplate): void;
    getFileTemplates(): FileTemplate[];

    // イベント
    onDidChangeSnippets: vscode.Event<void>;
    onDidChangeLanguage: vscode.Event<string>;
}

ベストプラクティス

1. スニペットの命名

typescript
// 良い例:明確で一貫した命名
await snippetManager.registerSnippet({
    name: 'react-functional-component',
    prefix: 'rfc',
    description: 'React関数コンポーネント'
});

// 悪い例:曖昧な命名
await snippetManager.registerSnippet({
    name: 'comp',
    prefix: 'c',
    description: 'コンポーネント'
});

2. 変数の活用

typescript
// プレースホルダーとデフォルト値を使用
const snippet = {
    body: [
        'function ${1:functionName}(${2:params}) {',
        '\t${3:// 実装}',
        '\treturn ${4:undefined};',
        '}'
    ]
};

3. スコープの適切な設定

typescript
// 言語固有のスニペット
await snippetManager.registerSnippet({
    name: 'typescript-interface',
    scope: ['typescript'],
    // ...
});

// 複数言語対応
await snippetManager.registerSnippet({
    name: 'console-log',
    scope: ['javascript', 'typescript'],
    // ...
});

関連API

究極の AI 駆動 IDE 学習ガイド