Skip to content

Completion API

Completion APIは、インテリジェントなコード補完機能を提供し、ユーザーが入力する際にコンテキストを認識した提案を提供します。

概要

Completion APIでは以下のことができます:

  • インテリジェントなコード補完の提供
  • カスタム補完プロバイダーの作成
  • 異なる補完トリガーの処理
  • 言語サービスとの統合
  • スニペット補完のサポート
  • ドキュメントと型情報の提供

基本的な使用方法

シンプルな補完プロバイダー

typescript
import { TraeAPI } from '@trae/api';

// 基本的な補完プロバイダーを登録
const provider = TraeAPI.languages.registerCompletionItemProvider(
  { scheme: 'file', language: 'typescript' },
  {
    async provideCompletionItems(document, position, token, context) {
      const lineText = document.lineAt(position.line).text;
      const linePrefix = lineText.substring(0, position.character);

      // シンプルなキーワード補完
      if (linePrefix.endsWith('console.')) {
        return [
          {
            label: 'log',
            kind: TraeAPI.CompletionItemKind.Method,
            insertText: 'log($1)',
            insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
            documentation: 'コンソールにメッセージを出力します'
          },
          {
            label: 'error',
            kind: TraeAPI.CompletionItemKind.Method,
            insertText: 'error($1)',
            insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
            documentation: 'コンソールにエラーメッセージを出力します'
          }
        ];
      }

      return [];
    }
  },
  '.', // トリガー文字
  ' '
);

高度な補完プロバイダー

typescript
class AdvancedCompletionProvider implements TraeAPI.CompletionItemProvider {
  async provideCompletionItems(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position,
    token: TraeAPI.CancellationToken,
    context: TraeAPI.CompletionContext
  ): Promise<TraeAPI.CompletionItem[]> {
    const completions: TraeAPI.CompletionItem[] = [];
    const lineText = document.lineAt(position.line).text;
    const wordRange = document.getWordRangeAtPosition(position);
    const word = wordRange ? document.getText(wordRange) : '';

    // コンテキストを認識した補完
    const fileContent = document.getText();
    const imports = this.extractImports(fileContent);
    const functions = this.extractFunctions(fileContent);
    const variables = this.extractVariables(fileContent, position);

    // 関数補完を追加
    functions.forEach(func => {
      completions.push({
        label: func.name,
        kind: TraeAPI.CompletionItemKind.Function,
        insertText: `${func.name}(${func.parameters.map((p, i) => `\${${i + 1}:${p}}`).join(', ')})`,
        insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
        documentation: func.documentation,
        detail: func.signature,
        sortText: '1' + func.name // 高い優先度
      });
    });

    // 変数補完を追加
    variables.forEach(variable => {
      completions.push({
        label: variable.name,
        kind: TraeAPI.CompletionItemKind.Variable,
        insertText: variable.name,
        documentation: variable.type,
        detail: `${variable.type} ${variable.name}`,
        sortText: '2' + variable.name
      });
    });

    // Add import completions
    if (lineText.trim().startsWith('import')) {
      const availableModules = await this.getAvailableModules();
      availableModules.forEach(module => {
        completions.push({
          label: module.name,
          kind: TraeAPI.CompletionItemKind.Module,
          insertText: module.name,
          documentation: module.description,
          detail: module.version
        });
      });
    }

    return completions;
  }

  async resolveCompletionItem(
    item: TraeAPI.CompletionItem,
    token: TraeAPI.CancellationToken
  ): Promise<TraeAPI.CompletionItem> {
    // Add additional information when item is selected
    if (item.kind === TraeAPI.CompletionItemKind.Function) {
      const documentation = await this.getFunctionDocumentation(item.label);
      item.documentation = {
        kind: TraeAPI.MarkupKind.Markdown,
        value: documentation
      };
    }

    return item;
  }

  private extractImports(content: string): Array<{ name: string; path: string }> {
    const importRegex = /import\s+.*?from\s+['"]([^'"]+)['"]/g;
    const imports: Array<{ name: string; path: string }> = [];
    let match;

    while ((match = importRegex.exec(content)) !== null) {
      imports.push({
        name: match[1].split('/').pop() || match[1],
        path: match[1]
      });
    }

    return imports;
  }

  private extractFunctions(content: string): Array<{
    name: string;
    parameters: string[];
    signature: string;
    documentation?: string;
  }> {
    const functionRegex = /(?:function\s+|const\s+|let\s+|var\s+)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=:]?\s*(?:function)?\s*\(([^)]*)\)/g;
    const functions: Array<{
      name: string;
      parameters: string[];
      signature: string;
      documentation?: string;
    }> = [];
    let match;

    while ((match = functionRegex.exec(content)) !== null) {
      const name = match[1];
      const params = match[2].split(',').map(p => p.trim()).filter(p => p);
      
      functions.push({
        name,
        parameters: params,
        signature: `${name}(${params.join(', ')})`,
        documentation: `Function ${name}`
      });
    }

    return functions;
  }

  private extractVariables(content: string, position: TraeAPI.Position): Array<{
    name: string;
    type: string;
  }> {
    const lines = content.split('\n');
    const variables: Array<{ name: string; type: string }> = [];
    const variableRegex = /(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*:\s*([^=]+))?/g;

    // Only look at variables declared before current position
    for (let i = 0; i < Math.min(position.line, lines.length); i++) {
      let match;
      while ((match = variableRegex.exec(lines[i])) !== null) {
        variables.push({
          name: match[1],
          type: match[2] || 'any'
        });
      }
    }

    return variables;
  }

  private async getAvailableModules(): Promise<Array<{
    name: string;
    description: string;
    version: string;
  }>> {
    // In a real implementation, this would query npm registry or local node_modules
    return [
      { name: 'lodash', description: 'A modern JavaScript utility library', version: '4.17.21' },
      { name: 'axios', description: 'Promise based HTTP client', version: '1.6.0' },
      { name: 'react', description: 'A JavaScript library for building user interfaces', version: '18.2.0' }
    ];
  }

  private async getFunctionDocumentation(functionName: string): Promise<string> {
    // In a real implementation, this would fetch from documentation sources
    return `## ${functionName}\n\nDetailed documentation for ${functionName} function.`;
  }
}

// Register the advanced provider
const advancedProvider = new AdvancedCompletionProvider();
TraeAPI.languages.registerCompletionItemProvider(
  { scheme: 'file', language: 'typescript' },
  advancedProvider,
  '.', '(', ' '
);

Snippet Completions

Creating Snippet Providers

typescript
class SnippetCompletionProvider implements TraeAPI.CompletionItemProvider {
  private snippets = {
    'for': {
      prefix: 'for',
      body: [
        'for (let ${1:i} = 0; ${1:i} < ${2:array}.length; ${1:i}++) {',
        '\t${3:// code}',
        '}'
      ],
      description: 'For loop'
    },
    'if': {
      prefix: 'if',
      body: [
        'if (${1:condition}) {',
        '\t${2:// code}',
        '}'
      ],
      description: 'If statement'
    },
    'func': {
      prefix: 'func',
      body: [
        'function ${1:name}(${2:params}) {',
        '\t${3:// code}',
        '\treturn ${4:value};',
        '}'
      ],
      description: 'Function declaration'
    },
    'class': {
      prefix: 'class',
      body: [
        'class ${1:ClassName} {',
        '\tconstructor(${2:params}) {',
        '\t\t${3:// constructor code}',
        '\t}',
        '',
        '\t${4:// methods}',
        '}'
      ],
      description: 'Class declaration'
    }
  };

  async provideCompletionItems(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position
  ): Promise<TraeAPI.CompletionItem[]> {
    const lineText = document.lineAt(position.line).text;
    const linePrefix = lineText.substring(0, position.character);
    const word = this.getCurrentWord(linePrefix);

    const completions: TraeAPI.CompletionItem[] = [];

    // Add snippet completions
    Object.entries(this.snippets).forEach(([key, snippet]) => {
      if (snippet.prefix.startsWith(word) || word === '') {
        completions.push({
          label: snippet.prefix,
          kind: TraeAPI.CompletionItemKind.Snippet,
          insertText: snippet.body.join('\n'),
          insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
          documentation: snippet.description,
          detail: 'Snippet',
          sortText: '0' + snippet.prefix // Highest priority
        });
      }
    });

    return completions;
  }

  private getCurrentWord(linePrefix: string): string {
    const match = linePrefix.match(/[a-zA-Z_$][a-zA-Z0-9_$]*$/);
    return match ? match[0] : '';
  }
}

// Register snippet provider
const snippetProvider = new SnippetCompletionProvider();
TraeAPI.languages.registerCompletionItemProvider(
  { scheme: 'file', language: 'typescript' },
  snippetProvider
);

AI-Powered Completions

Intelligent Code Suggestions

typescript
class AICompletionProvider implements TraeAPI.CompletionItemProvider {
  async provideCompletionItems(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position,
    token: TraeAPI.CancellationToken,
    context: TraeAPI.CompletionContext
  ): Promise<TraeAPI.CompletionItem[]> {
    // Get context around cursor
    const contextRange = new TraeAPI.Range(
      Math.max(0, position.line - 10),
      0,
      Math.min(document.lineCount - 1, position.line + 10),
      0
    );
    const contextText = document.getText(contextRange);
    const currentLine = document.lineAt(position.line).text;
    const prefix = currentLine.substring(0, position.character);

    // Use AI to generate completions
    const aiSuggestions = await TraeAPI.ai.codeGeneration.complete({
      document: contextText,
      position,
      language: document.languageId,
      maxSuggestions: 5,
      context: {
        prefix,
        fileName: document.fileName,
        projectContext: await this.getProjectContext(document.uri)
      }
    });

    const completions: TraeAPI.CompletionItem[] = [];

    aiSuggestions.suggestions.forEach((suggestion, index) => {
      completions.push({
        label: suggestion.label || suggestion.code.split('\n')[0],
        kind: this.getCompletionKind(suggestion.type),
        insertText: suggestion.code,
        insertTextFormat: suggestion.isSnippet ? 
          TraeAPI.InsertTextFormat.Snippet : 
          TraeAPI.InsertTextFormat.PlainText,
        documentation: {
          kind: TraeAPI.MarkupKind.Markdown,
          value: suggestion.explanation || 'AI-generated suggestion'
        },
        detail: `AI Suggestion (${Math.round(suggestion.confidence * 100)}% confidence)`,
        sortText: `ai${index.toString().padStart(2, '0')}`,
        filterText: suggestion.filterText || suggestion.code,
        additionalTextEdits: suggestion.additionalEdits?.map(edit => ({
          range: new TraeAPI.Range(
            edit.range.start.line,
            edit.range.start.character,
            edit.range.end.line,
            edit.range.end.character
          ),
          newText: edit.newText
        }))
      });
    });

    return completions;
  }

  private getCompletionKind(type: string): TraeAPI.CompletionItemKind {
    switch (type) {
      case 'function': return TraeAPI.CompletionItemKind.Function;
      case 'method': return TraeAPI.CompletionItemKind.Method;
      case 'variable': return TraeAPI.CompletionItemKind.Variable;
      case 'class': return TraeAPI.CompletionItemKind.Class;
      case 'interface': return TraeAPI.CompletionItemKind.Interface;
      case 'module': return TraeAPI.CompletionItemKind.Module;
      case 'property': return TraeAPI.CompletionItemKind.Property;
      case 'keyword': return TraeAPI.CompletionItemKind.Keyword;
      case 'snippet': return TraeAPI.CompletionItemKind.Snippet;
      default: return TraeAPI.CompletionItemKind.Text;
    }
  }

  private async getProjectContext(uri: TraeAPI.Uri): Promise<any> {
    const workspaceFolder = TraeAPI.workspace.getWorkspaceFolder(uri);
    if (!workspaceFolder) {
      return {};
    }

    // Analyze project structure and dependencies
    const packageJsonUri = TraeAPI.Uri.joinPath(workspaceFolder.uri, 'package.json');
    try {
      const packageJsonContent = await TraeAPI.workspace.fs.readFile(packageJsonUri);
      const packageJson = JSON.parse(packageJsonContent.toString());
      
      return {
        dependencies: packageJson.dependencies || {},
        devDependencies: packageJson.devDependencies || {},
        scripts: packageJson.scripts || {},
        projectType: this.detectProjectType(packageJson)
      };
    } catch {
      return {};
    }
  }

  private detectProjectType(packageJson: any): string {
    if (packageJson.dependencies?.react) return 'react';
    if (packageJson.dependencies?.vue) return 'vue';
    if (packageJson.dependencies?.angular) return 'angular';
    if (packageJson.dependencies?.express) return 'express';
    if (packageJson.dependencies?.next) return 'nextjs';
    return 'javascript';
  }
}

// Register AI completion provider
const aiProvider = new AICompletionProvider();
TraeAPI.languages.registerCompletionItemProvider(
  { scheme: 'file' },
  aiProvider,
  '.', '(', ' ', '='
);

Language-Specific Completions

TypeScript Completions

typescript
class TypeScriptCompletionProvider implements TraeAPI.CompletionItemProvider {
  async provideCompletionItems(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position
  ): Promise<TraeAPI.CompletionItem[]> {
    const completions: TraeAPI.CompletionItem[] = [];
    const lineText = document.lineAt(position.line).text;
    const linePrefix = lineText.substring(0, position.character);

    // Type annotations
    if (linePrefix.includes(': ')) {
      const typeCompletions = [
        'string', 'number', 'boolean', 'object', 'array', 'function',
        'Promise', 'Date', 'RegExp', 'Error', 'any', 'unknown', 'never', 'void'
      ];

      typeCompletions.forEach(type => {
        completions.push({
          label: type,
          kind: TraeAPI.CompletionItemKind.TypeParameter,
          insertText: type,
          documentation: `TypeScript ${type} type`
        });
      });
    }

    // Interface completions
    if (linePrefix.trim().startsWith('interface ')) {
      completions.push({
        label: 'interface template',
        kind: TraeAPI.CompletionItemKind.Snippet,
        insertText: [
          '${1:InterfaceName} {',
          '\t${2:property}: ${3:type};',
          '}'
        ].join('\n'),
        insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
        documentation: 'Interface template'
      });
    }

    // Generic completions
    if (linePrefix.includes('<')) {
      completions.push({
        label: 'T',
        kind: TraeAPI.CompletionItemKind.TypeParameter,
        insertText: 'T',
        documentation: 'Generic type parameter'
      });
    }

    return completions;
  }
}

React Completions

typescript
class ReactCompletionProvider implements TraeAPI.CompletionItemProvider {
  private reactSnippets = {
    'rfc': {
      label: 'React Functional Component',
      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:// JSX content}',
        '\t\t</div>',
        '\t);',
        '};',
        '',
        'export default ${1:ComponentName};'
      ]
    },
    'useState': {
      label: 'useState Hook',
      body: [
        'const [${1:state}, set${1/(.*)/${1:/capitalize}/}] = useState${2:<${3:type}>}(${4:initialValue});'
      ]
    },
    'useEffect': {
      label: 'useEffect Hook',
      body: [
        'useEffect(() => {',
        '\t${1:// effect}',
        '\treturn () => {',
        '\t\t${2:// cleanup}',
        '\t};',
        '}, [${3:dependencies}]);'
      ]
    }
  };

  async provideCompletionItems(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position
  ): Promise<TraeAPI.CompletionItem[]> {
    const completions: TraeAPI.CompletionItem[] = [];
    const lineText = document.lineAt(position.line).text;
    const linePrefix = lineText.substring(0, position.character);

    // Check if we're in a React file
    const isReactFile = document.getText().includes('import React') || 
                       document.fileName.includes('.jsx') || 
                       document.fileName.includes('.tsx');

    if (!isReactFile) {
      return completions;
    }

    // Add React snippets
    Object.entries(this.reactSnippets).forEach(([key, snippet]) => {
      if (key.startsWith(linePrefix.trim()) || linePrefix.trim() === '') {
        completions.push({
          label: key,
          kind: TraeAPI.CompletionItemKind.Snippet,
          insertText: snippet.body.join('\n'),
          insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
          documentation: snippet.label,
          detail: 'React Snippet'
        });
      }
    });

    // JSX attribute completions
    if (linePrefix.includes('<') && !linePrefix.includes('>')) {
      const commonProps = [
        'className', 'style', 'onClick', 'onChange', 'onSubmit',
        'id', 'key', 'ref', 'children', 'disabled', 'placeholder'
      ];

      commonProps.forEach(prop => {
        completions.push({
          label: prop,
          kind: TraeAPI.CompletionItemKind.Property,
          insertText: `${prop}={$1}`,
          insertTextFormat: TraeAPI.InsertTextFormat.Snippet,
          documentation: `React ${prop} prop`
        });
      });
    }

    return completions;
  }
}

Completion Configuration

Provider Settings

typescript
interface CompletionConfiguration {
  enabled: boolean;
  triggerCharacters: string[];
  maxSuggestions: number;
  sortOrder: 'alphabetical' | 'relevance' | 'frequency';
  includeSnippets: boolean;
  includeAISuggestions: boolean;
  aiConfidenceThreshold: number;
  cacheResults: boolean;
  cacheDuration: number;
}

class CompletionManager {
  private config: CompletionConfiguration;
  private providers: Map<string, TraeAPI.CompletionItemProvider> = new Map();
  private cache: Map<string, { items: TraeAPI.CompletionItem[]; timestamp: number }> = new Map();

  constructor(config: CompletionConfiguration) {
    this.config = config;
    this.setupProviders();
  }

  private setupProviders() {
    // Register providers based on configuration
    if (this.config.includeSnippets) {
      this.registerProvider('snippets', new SnippetCompletionProvider());
    }

    if (this.config.includeAISuggestions) {
      this.registerProvider('ai', new AICompletionProvider());
    }

    // Language-specific providers
    this.registerProvider('typescript', new TypeScriptCompletionProvider());
    this.registerProvider('react', new ReactCompletionProvider());
  }

  registerProvider(name: string, provider: TraeAPI.CompletionItemProvider) {
    this.providers.set(name, provider);
    
    TraeAPI.languages.registerCompletionItemProvider(
      { scheme: 'file' },
      provider,
      ...this.config.triggerCharacters
    );
  }

  async getCompletions(
    document: TraeAPI.TextDocument,
    position: TraeAPI.Position
  ): Promise<TraeAPI.CompletionItem[]> {
    if (!this.config.enabled) {
      return [];
    }

    const cacheKey = `${document.uri.toString()}:${position.line}:${position.character}`;
    
    // Check cache
    if (this.config.cacheResults) {
      const cached = this.cache.get(cacheKey);
      if (cached && Date.now() - cached.timestamp < this.config.cacheDuration) {
        return cached.items;
      }
    }

    // Collect completions from all providers
    const allCompletions: TraeAPI.CompletionItem[] = [];
    
    for (const [name, provider] of this.providers) {
      try {
        const items = await provider.provideCompletionItems(
          document,
          position,
          new TraeAPI.CancellationTokenSource().token,
          { triggerKind: TraeAPI.CompletionTriggerKind.Invoke }
        );
        
        if (items) {
          allCompletions.push(...(Array.isArray(items) ? items : items.items));
        }
      } catch (error) {
        console.error(`Error in completion provider ${name}:`, error);
      }
    }

    // Filter and sort completions
    let filteredCompletions = allCompletions;
    
    // Filter by AI confidence threshold
    if (this.config.includeAISuggestions) {
      filteredCompletions = filteredCompletions.filter(item => {
        if (item.detail?.includes('AI Suggestion')) {
          const confidenceMatch = item.detail.match(/(\d+)% confidence/);
          if (confidenceMatch) {
            const confidence = parseInt(confidenceMatch[1]);
            return confidence >= this.config.aiConfidenceThreshold;
          }
        }
        return true;
      });
    }

    // Limit number of suggestions
    if (filteredCompletions.length > this.config.maxSuggestions) {
      filteredCompletions = filteredCompletions.slice(0, this.config.maxSuggestions);
    }

    // Sort completions
    this.sortCompletions(filteredCompletions);

    // Cache results
    if (this.config.cacheResults) {
      this.cache.set(cacheKey, {
        items: filteredCompletions,
        timestamp: Date.now()
      });
    }

    return filteredCompletions;
  }

  private sortCompletions(completions: TraeAPI.CompletionItem[]) {
    switch (this.config.sortOrder) {
      case 'alphabetical':
        completions.sort((a, b) => a.label.localeCompare(b.label));
        break;
      case 'relevance':
        completions.sort((a, b) => (a.sortText || a.label).localeCompare(b.sortText || b.label));
        break;
      case 'frequency':
        // Would need usage statistics
        break;
    }
  }

  updateConfiguration(newConfig: Partial<CompletionConfiguration>) {
    this.config = { ...this.config, ...newConfig };
    this.cache.clear(); // Clear cache when config changes
  }
}

// Initialize completion manager
const completionManager = new CompletionManager({
  enabled: true,
  triggerCharacters: ['.', '(', ' ', '=', '<', ':', '"', "'"],
  maxSuggestions: 20,
  sortOrder: 'relevance',
  includeSnippets: true,
  includeAISuggestions: true,
  aiConfidenceThreshold: 70,
  cacheResults: true,
  cacheDuration: 5000 // 5 seconds
});

API Reference

Core Interfaces

typescript
interface CompletionItemProvider {
  provideCompletionItems(
    document: TextDocument,
    position: Position,
    token: CancellationToken,
    context: CompletionContext
  ): ProviderResult<CompletionItem[] | CompletionList>;

  resolveCompletionItem?(
    item: CompletionItem,
    token: CancellationToken
  ): ProviderResult<CompletionItem>;
}

interface CompletionItem {
  label: string;
  kind?: CompletionItemKind;
  detail?: string;
  documentation?: string | MarkupContent;
  sortText?: string;
  filterText?: string;
  insertText?: string;
  insertTextFormat?: InsertTextFormat;
  range?: Range;
  commitCharacters?: string[];
  additionalTextEdits?: TextEdit[];
  command?: Command;
}

enum CompletionItemKind {
  Text = 0,
  Method = 1,
  Function = 2,
  Constructor = 3,
  Field = 4,
  Variable = 5,
  Class = 6,
  Interface = 7,
  Module = 8,
  Property = 9,
  Unit = 10,
  Value = 11,
  Enum = 12,
  Keyword = 13,
  Snippet = 14,
  Color = 15,
  File = 16,
  Reference = 17,
  Folder = 18,
  EnumMember = 19,
  Constant = 20,
  Struct = 21,
  Event = 22,
  Operator = 23,
  TypeParameter = 24
}

Best Practices

  1. Performance: Keep completion providers fast and responsive
  2. Relevance: Provide contextually relevant suggestions
  3. Caching: Cache expensive computations and API calls
  4. Error Handling: Handle errors gracefully without breaking the completion flow
  5. User Experience: Provide clear labels and helpful documentation
  6. Filtering: Implement proper filtering based on user input
  7. Sorting: Sort suggestions by relevance and frequency
  8. Debouncing: Debounce rapid completion requests

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