Search API 
Search APIは、開発環境内でファイル、シンボル、コンテンツを包括的に検索する機能を提供します。
概要 
Search APIを使用すると、以下のことができます:
- ファイル名とパスでファイルを検索
- ファイル全体でテキストコンテンツを検索
- シンボル(関数、クラス、変数)を検索
- 正規表現ベースの検索を実行
- 特定のスコープとディレクトリ内で検索
- 検索候補とオートコンプリートを提供
- 検索結果のインデックス化とキャッシュ
- ファジーマッチングとランキングをサポート
基本的な使用方法 
Search Manager 
import { TraeAPI } from '@trae/api';
// 検索マネージャー
class SearchManager {
  private fileIndex: Map<string, FileIndexEntry> = new Map();
  private symbolIndex: Map<string, SymbolIndexEntry[]> = new Map();
  private contentIndex: Map<string, ContentIndexEntry[]> = new Map();
  private searchHistory: SearchHistoryEntry[] = [];
  private eventEmitter = new TraeAPI.EventEmitter<SearchEvent>();
  private indexingInProgress = false;
  private watchers: TraeAPI.FileSystemWatcher[] = [];
  constructor() {
    this.initializeIndexing();
    this.setupFileWatchers();
  }
  // インデックス化の初期化
  private async initializeIndexing(): Promise<void> {
    if (this.indexingInProgress) return;
    
    this.indexingInProgress = true;
    console.log('検索インデックス化を開始しています...');
    this.eventEmitter.fire({ type: 'indexingStarted' });
    
    try {
      await this.buildFileIndex();
      await this.buildSymbolIndex();
      await this.buildContentIndex();
      
      console.log('検索インデックス化が完了しました');
      this.eventEmitter.fire({ type: 'indexingCompleted' });
    } catch (error) {
      console.error('検索インデックス化に失敗しました:', error);
      this.eventEmitter.fire({ type: 'indexingFailed', error: error as Error });
    } finally {
      this.indexingInProgress = false;
    }
  }
  // ファイルインデックスの構築
  private async buildFileIndex(): Promise<void> {
    const workspaceFolders = TraeAPI.workspace.workspaceFolders;
    if (!workspaceFolders) return;
    for (const folder of workspaceFolders) {
      await this.indexDirectory(folder.uri.fsPath);
    }
  }
  private async indexDirectory(dirPath: string): Promise<void> {
    try {
      const entries = await TraeAPI.workspace.fs.readDirectory(TraeAPI.Uri.file(dirPath));
      
      for (const [name, type] of entries) {
        const fullPath = TraeAPI.path.join(dirPath, name);
        
        if (type === TraeAPI.FileType.Directory) {
          // インデックス化すべきでない一般的なディレクトリをスキップ
          if (this.shouldSkipDirectory(name)) {
            continue;
          }
          await this.indexDirectory(fullPath);
        } else if (type === TraeAPI.FileType.File) {
          await this.indexFile(fullPath);
        }
      }
    } catch (error) {
      console.error(`ディレクトリ ${dirPath} のインデックス化に失敗しました:`, error);
    }
  }
  private shouldSkipDirectory(name: string): boolean {
    const skipDirs = [
      'node_modules', '.git', '.svn', '.hg',
      'dist', 'build', 'out', 'target',
      '.vscode', '.idea', '__pycache__',
      'coverage', '.nyc_output'
    ];
    return skipDirs.includes(name) || name.startsWith('.');
  }
  private async indexFile(filePath: string): Promise<void> {
    try {
      const stat = await TraeAPI.workspace.fs.stat(TraeAPI.Uri.file(filePath));
      const fileName = TraeAPI.path.basename(filePath);
      const extension = TraeAPI.path.extname(filePath);
      const relativePath = TraeAPI.workspace.asRelativePath(filePath);
      
      const entry: FileIndexEntry = {
        path: filePath,
        relativePath,
        name: fileName,
        extension,
        size: stat.size,
        lastModified: stat.mtime,
        language: this.getLanguageFromExtension(extension),
        searchTerms: this.generateFileSearchTerms(fileName, relativePath)
      };
      
      this.fileIndex.set(filePath, entry);
    } catch (error) {
      console.error(`ファイル ${filePath} のインデックス化に失敗しました:`, error);
    }
  }
  private getLanguageFromExtension(extension: string): string {
    const languageMap: { [key: string]: string } = {
      '.js': 'javascript',
      '.ts': 'typescript',
      '.jsx': 'javascriptreact',
      '.tsx': 'typescriptreact',
      '.py': 'python',
      '.java': 'java',
      '.cpp': 'cpp',
      '.c': 'c',
      '.cs': 'csharp',
      '.php': 'php',
      '.rb': 'ruby',
      '.go': 'go',
      '.rs': 'rust',
      '.swift': 'swift',
      '.kt': 'kotlin',
      '.scala': 'scala',
      '.html': 'html',
      '.css': 'css',
      '.scss': 'scss',
      '.less': 'less',
      '.json': 'json',
      '.xml': 'xml',
      '.yaml': 'yaml',
      '.yml': 'yaml',
      '.md': 'markdown',
      '.txt': 'plaintext'
    };
    
    return languageMap[extension.toLowerCase()] || 'plaintext';
  }
  private generateFileSearchTerms(fileName: string, relativePath: string): string[] {
    const terms = new Set<string>();
    
    // 拡張子なしのファイル名を追加
    const nameWithoutExt = TraeAPI.path.parse(fileName).name;
    terms.add(nameWithoutExt.toLowerCase());
    
    // 完全なファイル名を追加
    terms.add(fileName.toLowerCase());
    
    // パスセグメントを追加
    const pathSegments = relativePath.split(TraeAPI.path.sep);
    pathSegments.forEach(segment => {
      if (segment) {
        terms.add(segment.toLowerCase());
      }
    });
    
    // camelCaseとsnake_caseの分割を追加
    const camelCaseSplit = nameWithoutExt.split(/(?=[A-Z])/);
    camelCaseSplit.forEach(part => {
      if (part) {
        terms.add(part.toLowerCase());
      }
    });
    
    const snakeCaseSplit = nameWithoutExt.split(/[_-]/);
    snakeCaseSplit.forEach(part => {
      if (part) {
        terms.add(part.toLowerCase());
      }
    });
    
    return Array.from(terms);
  }
  // シンボルインデックスの構築
  private async buildSymbolIndex(): Promise<void> {
    for (const [filePath, fileEntry] of this.fileIndex) {
      if (this.isCodeFile(fileEntry.language)) {
        await this.indexFileSymbols(filePath);
      }
    }
  }
  private isCodeFile(language: string): boolean {
    const codeLanguages = [
      'javascript', 'typescript', 'javascriptreact', 'typescriptreact',
      'python', 'java', 'cpp', 'c', 'csharp', 'php', 'ruby',
      'go', 'rust', 'swift', 'kotlin', 'scala'
    ];
    return codeLanguages.includes(language);
  }
  private async indexFileSymbols(filePath: string): Promise<void> {
    try {
      const document = await TraeAPI.workspace.openTextDocument(TraeAPI.Uri.file(filePath));
      const symbols = await TraeAPI.commands.executeCommand<TraeAPI.DocumentSymbol[]>(
        'vscode.executeDocumentSymbolProvider',
        document.uri
      );
      
      if (symbols) {
        const symbolEntries = this.flattenSymbols(symbols, filePath);
        this.symbolIndex.set(filePath, symbolEntries);
      }
    } catch (error) {
      console.error(`${filePath} のシンボルインデックス化に失敗しました:`, error);
    }
  }
  private flattenSymbols(symbols: TraeAPI.DocumentSymbol[], filePath: string, parent?: string): SymbolIndexEntry[] {
    const entries: SymbolIndexEntry[] = [];
    
    for (const symbol of symbols) {
      const fullName = parent ? `${parent}.${symbol.name}` : symbol.name;
      
      entries.push({
        name: symbol.name,
        fullName,
        kind: symbol.kind,
        filePath,
        range: symbol.range,
        selectionRange: symbol.selectionRange,
        searchTerms: this.generateSymbolSearchTerms(symbol.name, fullName)
      });
      
      // 子要素を再帰的に処理
      if (symbol.children) {
        entries.push(...this.flattenSymbols(symbol.children, filePath, fullName));
      }
    }
    
    return entries;
  }
  private generateSymbolSearchTerms(name: string, fullName: string): string[] {
    const terms = new Set<string>();
    
    terms.add(name.toLowerCase());
    terms.add(fullName.toLowerCase());
    
    // camelCaseの分割を追加
    const camelCaseSplit = name.split(/(?=[A-Z])/);
    camelCaseSplit.forEach(part => {
      if (part) {
        terms.add(part.toLowerCase());
      }
    });
    
    // snake_caseの分割を追加
    const snakeCaseSplit = name.split(/[_-]/);
    snakeCaseSplit.forEach(part => {
      if (part) {
        terms.add(part.toLowerCase());
      }
    });
    
    return Array.from(terms);
  }
  // コンテンツインデックスの構築
  private async buildContentIndex(): Promise<void> {
    for (const [filePath, fileEntry] of this.fileIndex) {
      if (this.shouldIndexContent(fileEntry)) {
        await this.indexFileContent(filePath);
      }
    }
  }
  private shouldIndexContent(fileEntry: FileIndexEntry): boolean {
    // バイナリファイルと非常に大きなファイルをスキップ
    if (fileEntry.size > 1024 * 1024) { // 1MBの制限
      return false;
    }
    
    const textExtensions = [
      '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c',
      '.cs', '.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala',
      '.html', '.css', '.scss', '.less', '.json', '.xml', '.yaml',
      '.yml', '.md', '.txt', '.log', '.config', '.ini'
    ];
    
    return textExtensions.includes(fileEntry.extension.toLowerCase());
  }
  private async indexFileContent(filePath: string): Promise<void> {
    try {
      const document = await TraeAPI.workspace.openTextDocument(TraeAPI.Uri.file(filePath));
      const content = document.getText();
      const lines = content.split('\n');
      
      const entries: ContentIndexEntry[] = [];
      
      lines.forEach((line, lineNumber) => {
        const trimmedLine = line.trim();
        if (trimmedLine.length > 0) {
          entries.push({
            filePath,
            lineNumber: lineNumber + 1,
            content: line,
            trimmedContent: trimmedLine,
            searchTerms: this.generateContentSearchTerms(trimmedLine)
          });
        }
      });
      
      this.contentIndex.set(filePath, entries);
    } catch (error) {
      console.error(`${filePath} のコンテンツインデックス化に失敗しました:`, error);
    }
  }
  private generateContentSearchTerms(content: string): string[] {
    const terms = new Set<string>();
    
    // 一般的な区切り文字で分割
    const words = content.toLowerCase().split(/[\s\W]+/);
    words.forEach(word => {
      if (word.length > 2) { // 非常に短い単語をスキップ
        terms.add(word);
      }
    });
    
    return Array.from(terms);
  }
  // ファイル検索
  async searchFiles(query: string, options: FileSearchOptions = {}): Promise<FileSearchResult[]> {
    const results: FileSearchResult[] = [];
    const queryLower = query.toLowerCase();
    const maxResults = options.maxResults || 100;
    
    for (const [filePath, entry] of this.fileIndex) {
      if (results.length >= maxResults) break;
      
      // フィルターを適用
      if (options.includePatterns && !this.matchesPatterns(entry.relativePath, options.includePatterns)) {
        continue;
      }
      
      if (options.excludePatterns && this.matchesPatterns(entry.relativePath, options.excludePatterns)) {
        continue;
      }
      
      if (options.fileTypes && !options.fileTypes.includes(entry.extension)) {
        continue;
      }
      
      // マッチスコアを計算
      const score = this.calculateFileMatchScore(entry, queryLower);
      if (score > 0) {
        results.push({
          file: entry,
          score,
          matches: this.getFileMatches(entry, queryLower)
        });
      }
    }
    
    // スコア順にソート(降順)
    results.sort((a, b) => b.score - a.score);
    
    // 検索履歴に追加
    this.addToSearchHistory({
      type: 'file',
      query,
      timestamp: Date.now(),
      resultCount: results.length
    });
    
    return results;
  }
  private calculateFileMatchScore(entry: FileIndexEntry, query: string): number {
    let score = 0;
    
    // 完全なファイル名マッチ(最高スコア)
    if (entry.name.toLowerCase() === query) {
      score += 100;
    }
    
    // ファイル名がクエリで始まる
    if (entry.name.toLowerCase().startsWith(query)) {
      score += 80;
    }
    
    // ファイル名にクエリが含まれる
    if (entry.name.toLowerCase().includes(query)) {
      score += 60;
    }
    
    // パスにクエリが含まれる
    if (entry.relativePath.toLowerCase().includes(query)) {
      score += 40;
    }
    
    // 検索用語のマッチ
    for (const term of entry.searchTerms) {
      if (term === query) {
        score += 50;
      } else if (term.startsWith(query)) {
        score += 30;
      } else if (term.includes(query)) {
        score += 20;
      }
    }
    
    // ファジーマッチボーナス
    const fuzzyScore = this.calculateFuzzyScore(entry.name.toLowerCase(), query);
    score += fuzzyScore * 10;
    
    return score;
  }
  private calculateFuzzyScore(text: string, query: string): number {
    if (query.length === 0) return 0;
    if (text.length === 0) return 0;
    
    let score = 0;
    let queryIndex = 0;
    let lastMatchIndex = -1;
    
    for (let i = 0; i < text.length && queryIndex < query.length; i++) {
      if (text[i] === query[queryIndex]) {
        score += 1;
        
        // 連続マッチのボーナス
        if (i === lastMatchIndex + 1) {
          score += 0.5;
        }
        
        lastMatchIndex = i;
        queryIndex++;
      }
    }
    
    // スコアを正規化
    return queryIndex === query.length ? score / query.length : 0;
  }
  private getFileMatches(entry: FileIndexEntry, query: string): FileMatch[] {
    const matches: FileMatch[] = [];
    
    // ファイル名マッチをチェック
    const nameIndex = entry.name.toLowerCase().indexOf(query);
    if (nameIndex !== -1) {
      matches.push({
        type: 'filename',
        text: entry.name,
        startIndex: nameIndex,
        length: query.length
      });
    }
    
    // パスマッチをチェック
    const pathIndex = entry.relativePath.toLowerCase().indexOf(query);
    if (pathIndex !== -1) {
      matches.push({
        type: 'path',
        text: entry.relativePath,
        startIndex: pathIndex,
        length: query.length
      });
    }
    
    return matches;
  }
  private matchesPatterns(path: string, patterns: string[]): boolean {
    return patterns.some(pattern => {
      // シンプルなglobパターンマッチング
      const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
      return regex.test(path);
    });
  }
  // シンボル検索
  async searchSymbols(query: string, options: SymbolSearchOptions = {}): Promise<SymbolSearchResult[]> {
    const results: SymbolSearchResult[] = [];
    const queryLower = query.toLowerCase();
    const maxResults = options.maxResults || 100;
    
    for (const [filePath, symbols] of this.symbolIndex) {
      if (results.length >= maxResults) break;
      
      // ファイルフィルターを適用
      const fileEntry = this.fileIndex.get(filePath);
      if (!fileEntry) continue;
      
      if (options.fileTypes && !options.fileTypes.includes(fileEntry.extension)) {
        continue;
      }
      
      for (const symbol of symbols) {
        if (results.length >= maxResults) break;
        
        // シンボル種別フィルターを適用
        if (options.symbolKinds && !options.symbolKinds.includes(symbol.kind)) {
          continue;
        }
        
        // マッチスコアを計算
        const score = this.calculateSymbolMatchScore(symbol, queryLower);
        if (score > 0) {
          results.push({
            symbol,
            file: fileEntry,
            score,
            matches: this.getSymbolMatches(symbol, queryLower)
          });
        }
      }
    }
    
    // スコア順にソート(降順)
    results.sort((a, b) => b.score - a.score);
    
    // 検索履歴に追加
    this.addToSearchHistory({
      type: 'symbol',
      query,
      timestamp: Date.now(),
      resultCount: results.length
    });
    
    return results;
  }
  private calculateSymbolMatchScore(symbol: SymbolIndexEntry, query: string): number {
    let score = 0;
    
    // 完全な名前マッチ
    if (symbol.name.toLowerCase() === query) {
      score += 100;
    }
    
    // 名前がクエリで始まる
    if (symbol.name.toLowerCase().startsWith(query)) {
      score += 80;
    }
    
    // 名前にクエリが含まれる
    if (symbol.name.toLowerCase().includes(query)) {
      score += 60;
    }
    
    // 完全名にクエリが含まれる
    if (symbol.fullName.toLowerCase().includes(query)) {
      score += 40;
    }
    
    // 検索用語のマッチ
    for (const term of symbol.searchTerms) {
      if (term === query) {
        score += 50;
      } else if (term.startsWith(query)) {
        score += 30;
      } else if (term.includes(query)) {
        score += 20;
      }
    }
    
    // シンボル種別ボーナス(関数とクラスは高いスコア)
    if (symbol.kind === TraeAPI.SymbolKind.Function || symbol.kind === TraeAPI.SymbolKind.Method) {
      score += 10;
    } else if (symbol.kind === TraeAPI.SymbolKind.Class) {
      score += 15;
    }
    
    return score;
  }
  private getSymbolMatches(symbol: SymbolIndexEntry, query: string): SymbolMatch[] {
    const matches: SymbolMatch[] = [];
    
    // 名前マッチをチェック
    const nameIndex = symbol.name.toLowerCase().indexOf(query);
    if (nameIndex !== -1) {
      matches.push({
        type: 'name',
        text: symbol.name,
        startIndex: nameIndex,
        length: query.length
      });
    }
    
    // 完全名マッチをチェック
    const fullNameIndex = symbol.fullName.toLowerCase().indexOf(query);
    if (fullNameIndex !== -1 && symbol.fullName !== symbol.name) {
      matches.push({
        type: 'fullName',
        text: symbol.fullName,
        startIndex: fullNameIndex,
        length: query.length
      });
    }
    
    return matches;
  }
  // コンテンツ検索
  async searchContent(query: string, options: ContentSearchOptions = {}): Promise<ContentSearchResult[]> {
    const results: ContentSearchResult[] = [];
    const maxResults = options.maxResults || 1000;
    const isRegex = options.isRegex || false;
    const isCaseSensitive = options.isCaseSensitive || false;
    
    let searchRegex: RegExp;
    try {
      if (isRegex) {
        searchRegex = new RegExp(query, isCaseSensitive ? 'g' : 'gi');
      } else {
        const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        searchRegex = new RegExp(escapedQuery, isCaseSensitive ? 'g' : 'gi');
      }
    } catch (error) {
      throw new Error(`無効な検索パターン: ${error}`);
    }
    
    for (const [filePath, contentEntries] of this.contentIndex) {
      if (results.length >= maxResults) break;
      
      // ファイルフィルターを適用
      const fileEntry = this.fileIndex.get(filePath);
      if (!fileEntry) continue;
      
      if (options.includePatterns && !this.matchesPatterns(fileEntry.relativePath, options.includePatterns)) {
        continue;
      }
      
      if (options.excludePatterns && this.matchesPatterns(fileEntry.relativePath, options.excludePatterns)) {
        continue;
      }
      
      if (options.fileTypes && !options.fileTypes.includes(fileEntry.extension)) {
        continue;
      }
      
      for (const contentEntry of contentEntries) {
        if (results.length >= maxResults) break;
        
        const matches = Array.from(contentEntry.content.matchAll(searchRegex));
        if (matches.length > 0) {
          results.push({
            file: fileEntry,
            lineNumber: contentEntry.lineNumber,
            content: contentEntry.content,
            matches: matches.map(match => ({
              startIndex: match.index || 0,
              length: match[0].length,
              text: match[0]
            }))
          });
        }
      }
    }
    
    // 検索履歴に追加
    this.addToSearchHistory({
      type: 'content',
      query,
      timestamp: Date.now(),
      resultCount: results.length
    });
    
    return results;
  }
  // 検索候補
  async getSearchSuggestions(query: string, type: SearchType): Promise<string[]> {
    const suggestions = new Set<string>();
    const queryLower = query.toLowerCase();
    
    // 検索履歴から候補を取得
    const historySuggestions = this.searchHistory
      .filter(entry => entry.type === type && entry.query.toLowerCase().startsWith(queryLower))
      .map(entry => entry.query)
      .slice(0, 5);
    
    historySuggestions.forEach(suggestion => suggestions.add(suggestion));
    
    // タイプに基づいて候補を取得
    switch (type) {
      case 'file':
        // ファイル名とパスを提案
        for (const entry of this.fileIndex.values()) {
          if (suggestions.size >= 10) break;
          
          if (entry.name.toLowerCase().startsWith(queryLower)) {
            suggestions.add(entry.name);
          }
          
          for (const term of entry.searchTerms) {
            if (term.startsWith(queryLower)) {
              suggestions.add(term);
            }
          }
        }
        break;
        
      case 'symbol':
        // シンボル名を提案
        for (const symbols of this.symbolIndex.values()) {
          if (suggestions.size >= 10) break;
          
          for (const symbol of symbols) {
            if (symbol.name.toLowerCase().startsWith(queryLower)) {
              suggestions.add(symbol.name);
            }
            
            for (const term of symbol.searchTerms) {
              if (term.startsWith(queryLower)) {
                suggestions.add(term);
              }
            }
          }
        }
        break;
        
      case 'content':
        // コンテンツから一般的な単語を提案
        for (const contentEntries of this.contentIndex.values()) {
          if (suggestions.size >= 10) break;
          
          for (const entry of contentEntries) {
            for (const term of entry.searchTerms) {
              if (term.startsWith(queryLower)) {
                suggestions.add(term);
              }
            }
          }
        }
        break;
    }
    
    return Array.from(suggestions).slice(0, 10);
  }
  // 検索履歴管理
  private addToSearchHistory(entry: SearchHistoryEntry): void {
    // 重複エントリを削除
    this.searchHistory = this.searchHistory.filter(
      existing => !(existing.type === entry.type && existing.query === entry.query)
    );
    
    // 新しいエントリを先頭に追加
    this.searchHistory.unshift(entry);
    
    // 履歴サイズを制限
    if (this.searchHistory.length > 100) {
      this.searchHistory = this.searchHistory.slice(0, 100);
    }
  }
  getSearchHistory(type?: SearchType): SearchHistoryEntry[] {
    if (type) {
      return this.searchHistory.filter(entry => entry.type === type);
    }
    return [...this.searchHistory];
  }
  clearSearchHistory(type?: SearchType): void {
    if (type) {
      this.searchHistory = this.searchHistory.filter(entry => entry.type !== type);
    } else {
      this.searchHistory = [];
    }
  }
  // ファイルシステムウォッチャー
  private setupFileWatchers(): void {
    const workspaceFolders = TraeAPI.workspace.workspaceFolders;
    if (!workspaceFolders) return;
    
    for (const folder of workspaceFolders) {
      const pattern = new TraeAPI.RelativePattern(folder, '**/*');
      const watcher = TraeAPI.workspace.createFileSystemWatcher(pattern);
      
      watcher.onDidCreate(uri => this.onFileCreated(uri.fsPath));
      watcher.onDidChange(uri => this.onFileChanged(uri.fsPath));
      watcher.onDidDelete(uri => this.onFileDeleted(uri.fsPath));
      
      this.watchers.push(watcher);
    }
  }
  private async onFileCreated(filePath: string): Promise<void> {
    await this.indexFile(filePath);
    
    const fileEntry = this.fileIndex.get(filePath);
    if (fileEntry) {
      if (this.isCodeFile(fileEntry.language)) {
        await this.indexFileSymbols(filePath);
      }
      
      if (this.shouldIndexContent(fileEntry)) {
        await this.indexFileContent(filePath);
      }
    }
    
    this.eventEmitter.fire({ type: 'fileIndexed', filePath });
  }
  private async onFileChanged(filePath: string): Promise<void> {
    const fileEntry = this.fileIndex.get(filePath);
    if (!fileEntry) return;
    
    // ファイルエントリを更新
    await this.indexFile(filePath);
    
    // コードファイルの場合はシンボルを更新
    if (this.isCodeFile(fileEntry.language)) {
      await this.indexFileSymbols(filePath);
    }
    
    // 該当する場合はコンテンツを更新
    if (this.shouldIndexContent(fileEntry)) {
      await this.indexFileContent(filePath);
    }
    
    this.eventEmitter.fire({ type: 'fileUpdated', filePath });
  }
  private onFileDeleted(filePath: string): void {
    this.fileIndex.delete(filePath);
    this.symbolIndex.delete(filePath);
    this.contentIndex.delete(filePath);
    
    this.eventEmitter.fire({ type: 'fileRemoved', filePath });
  }
  // ユーティリティメソッド
  getIndexStats(): IndexStats {
    const symbolCount = Array.from(this.symbolIndex.values())
      .reduce((total, symbols) => total + symbols.length, 0);
    
    const contentLineCount = Array.from(this.contentIndex.values())
      .reduce((total, entries) => total + entries.length, 0);
    
    return {
      fileCount: this.fileIndex.size,
      symbolCount,
      contentLineCount,
      isIndexing: this.indexingInProgress
    };
  }
  async refreshIndex(): Promise<void> {
    this.fileIndex.clear();
    this.symbolIndex.clear();
    this.contentIndex.clear();
    
    await this.initializeIndexing();
  }
  // イベント処理
  onDidChangeIndex(listener: (event: SearchEvent) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(listener);
  }
  dispose(): void {
    this.watchers.forEach(watcher => watcher.dispose());
    this.watchers = [];
    
    this.fileIndex.clear();
    this.symbolIndex.clear();
    this.contentIndex.clear();
    this.searchHistory = [];
    
    this.eventEmitter.dispose();
  }
}
// 検索マネージャーを初期化
const searchManager = new SearchManager();検索UI統合 
// 検索UIプロバイダー
class SearchUIProvider {
  private searchPanel: TraeAPI.WebviewPanel | null = null;
  private currentSearchType: SearchType = 'file';
  private currentQuery = '';
  private searchResults: any[] = [];
  constructor(private searchManager: SearchManager) {
    this.setupCommands();
    this.setupEventListeners();
  }
  private setupCommands(): void {
    // 検索コマンドを登録
    TraeAPI.commands.registerCommand('search.showPanel', () => {
      this.showSearchPanel();
    });
    
    TraeAPI.commands.registerCommand('search.searchFiles', async () => {
      const query = await TraeAPI.window.showInputBox({
        prompt: 'ファイルを検索',
        placeHolder: 'ファイル名またはパターンを入力...'
      });
      
      if (query) {
        await this.performSearch(query, 'file');
      }
    });
    
    TraeAPI.commands.registerCommand('search.searchSymbols', async () => {
      const query = await TraeAPI.window.showInputBox({
        prompt: 'シンボルを検索',
        placeHolder: 'シンボル名を入力...'
      });
      
      if (query) {
        await this.performSearch(query, 'symbol');
      }
    });
    
    TraeAPI.commands.registerCommand('search.searchContent', async () => {
      const query = await TraeAPI.window.showInputBox({
        prompt: 'ファイル内を検索',
        placeHolder: '検索テキストを入力...'
      });
      
      if (query) {
        await this.performSearch(query, 'content');
      }
    });
  }
  private async showSearchPanel(): Promise<void> {
    if (this.searchPanel) {
      this.searchPanel.reveal();
      return;
    }
    this.searchPanel = TraeAPI.window.createWebviewPanel(
      'search',
      '検索',
      TraeAPI.ViewColumn.One,
      {
        enableScripts: true,
        retainContextWhenHidden: true
      }
    );
    this.searchPanel.webview.html = this.getSearchHTML();
    this.setupWebviewMessageHandling();
    this.searchPanel.onDidDispose(() => {
      this.searchPanel = null;
    });
  }
  private getSearchHTML(): string {
    return `
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>検索</title>
        <style>
          body {
            margin: 0;
            padding: 20px;
            background: #1e1e1e;
            color: #d4d4d4;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            font-size: 13px;
          }
          
          .search-container {
            max-width: 800px;
            margin: 0 auto;
          }
          
          .search-header {
            margin-bottom: 20px;
          }
          
          .search-tabs {
            display: flex;
            margin-bottom: 15px;
            border-bottom: 1px solid #3e3e42;
          }
          
          .search-tab {
            padding: 8px 16px;
            background: none;
            border: none;
            color: #cccccc;
            cursor: pointer;
            border-bottom: 2px solid transparent;
          }
          
          .search-tab.active {
            color: #ffffff;
            border-bottom-color: #007acc;
          }
          
          .search-input-container {
            display: flex;
            margin-bottom: 15px;
          }
          
          .search-input {
            flex: 1;
            padding: 8px 12px;
            background: #2d2d30;
            border: 1px solid #3e3e42;
            color: #d4d4d4;
            border-radius: 3px;
            font-size: 14px;
          }
          
          .search-input:focus {
            outline: none;
            border-color: #007acc;
          }
          
          .search-button {
            margin-left: 8px;
            padding: 8px 16px;
            background: #0e639c;
            border: none;
            color: #ffffff;
            border-radius: 3px;
            cursor: pointer;
          }
          
          .search-button:hover {
            background: #1177bb;
          }
          
          .search-options {
            display: flex;
            gap: 15px;
            margin-bottom: 20px;
            font-size: 12px;
          }
          
          .search-option {
            display: flex;
            align-items: center;
            gap: 5px;
          }
          
          .search-option input[type="checkbox"] {
            margin: 0;
          }
          
          .search-results {
            border-top: 1px solid #3e3e42;
            padding-top: 15px;
          }
          
          .search-stats {
            margin-bottom: 15px;
            color: #cccccc;
            font-size: 12px;
          }
          
          .search-result {
            margin-bottom: 10px;
            padding: 10px;
            background: #2d2d30;
            border-radius: 3px;
            cursor: pointer;
          }
          
          .search-result:hover {
            background: #37373d;
          }
          
          .result-title {
            font-weight: 500;
            margin-bottom: 5px;
          }
          
          .result-path {
            color: #cccccc;
            font-size: 11px;
            margin-bottom: 5px;
          }
          
          .result-content {
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 12px;
            color: #d4d4d4;
            white-space: pre-wrap;
          }
          
          .result-match {
            background: #515c6a;
            color: #ffffff;
            padding: 1px 2px;
            border-radius: 2px;
          }
          
          .no-results {
            text-align: center;
            color: #cccccc;
            padding: 40px;
          }
          
          .loading {
            text-align: center;
            color: #cccccc;
            padding: 20px;
          }
          
          .suggestions {
            position: absolute;
            top: 100%;
            left: 0;
            right: 0;
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-top: none;
            border-radius: 0 0 3px 3px;
            max-height: 200px;
            overflow-y: auto;
            z-index: 1000;
          }
          
          .suggestion {
            padding: 8px 12px;
            cursor: pointer;
            border-bottom: 1px solid #3e3e42;
          }
          
          .suggestion:hover {
            background: #37373d;
          }
          
          .suggestion:last-child {
            border-bottom: none;
          }
        </style>
      </head>
      <body>
        <div class="search-container">
          <div class="search-header">
            <div class="search-tabs">
              <button class="search-tab active" data-type="file">ファイル</button>
              <button class="search-tab" data-type="symbol">シンボル</button>
              <button class="search-tab" data-type="content">コンテンツ</button>
            </div>
            
            <div class="search-input-container" style="position: relative;">
              <input type="text" class="search-input" id="searchInput" placeholder="検索クエリを入力...">
              <button class="search-button" onclick="performSearch()">検索</button>
              <div class="suggestions" id="suggestions" style="display: none;"></div>
            </div>
            
            <div class="search-options">
              <div class="search-option">
                <input type="checkbox" id="caseSensitive">
                <label for="caseSensitive">大文字小文字を区別</label>
              </div>
              <div class="search-option">
                <input type="checkbox" id="useRegex">
                <label for="useRegex">正規表現を使用</label>
              </div>
              <div class="search-option">
                <input type="checkbox" id="wholeWord">
                <label for="wholeWord">単語全体</label>
              </div>
            </div>
          </div>
          
          <div class="search-results">
            <div class="search-stats" id="searchStats"></div>
            <div id="resultsContainer">
              <div class="no-results">検索クエリを入力して開始してください</div>
            </div>
          </div>
        </div>
        
        <script>
          const vscode = acquireVsCodeApi();
          let currentSearchType = 'file';
          let searchTimeout = null;
          
          // イベントリスナーを設定
          document.addEventListener('DOMContentLoaded', () => {
            const searchInput = document.getElementById('searchInput');
            const tabs = document.querySelectorAll('.search-tab');
            
            // タブ切り替え
            tabs.forEach(tab => {
              tab.addEventListener('click', () => {
                tabs.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                currentSearchType = tab.dataset.type;
                updatePlaceholder();
                clearResults();
              });
            });
            
            // 検索入力
            searchInput.addEventListener('input', (e) => {
              const query = e.target.value;
              
              // 検索をデバウンス
              clearTimeout(searchTimeout);
              searchTimeout = setTimeout(() => {
                if (query.length > 0) {
                  getSuggestions(query);
                  if (query.length >= 2) {
                    performSearch();
                  }
                } else {
                  hideSuggestions();
                  clearResults();
                }
              }, 300);
            });
            
            searchInput.addEventListener('keydown', (e) => {
              if (e.key === 'Enter') {
                performSearch();
                hideSuggestions();
              } else if (e.key === 'Escape') {
                hideSuggestions();
              }
            });
            
            // 外側をクリックしたときに候補を非表示
            document.addEventListener('click', (e) => {
              if (!e.target.closest('.search-input-container')) {
                hideSuggestions();
              }
            });
            
            updatePlaceholder();
          });
          
          function updatePlaceholder() {
            const input = document.getElementById('searchInput');
            const placeholders = {
              file: '名前でファイルを検索...',
              symbol: 'シンボル(関数、クラスなど)を検索...',
              content: 'ファイル内のテキストを検索...'
            };
            input.placeholder = placeholders[currentSearchType];
          }
          
          function performSearch() {
            const query = document.getElementById('searchInput').value.trim();
            if (!query) return;
            
            showLoading();
            
            const options = {
              isCaseSensitive: document.getElementById('caseSensitive').checked,
              isRegex: document.getElementById('useRegex').checked,
              wholeWord: document.getElementById('wholeWord').checked
            };
            
            vscode.postMessage({
              type: 'search',
              searchType: currentSearchType,
              query,
              options
            });
          }
          
          function getSuggestions(query) {
            vscode.postMessage({
              type: 'getSuggestions',
              searchType: currentSearchType,
              query
            });
          }
          
          function showSuggestions(suggestions) {
            const suggestionsEl = document.getElementById('suggestions');
            
            if (suggestions.length === 0) {
              hideSuggestions();
              return;
            }
            
            suggestionsEl.innerHTML = suggestions.map(suggestion => 
              \`<div class="suggestion" onclick="selectSuggestion('\${suggestion}')">\${suggestion}</div>\`
            ).join('');
            
            suggestionsEl.style.display = 'block';
          }
          
          function hideSuggestions() {
            document.getElementById('suggestions').style.display = 'none';
          }
          
          function selectSuggestion(suggestion) {
            document.getElementById('searchInput').value = suggestion;
            hideSuggestions();
            performSearch();
          }
          
          function showLoading() {
            document.getElementById('resultsContainer').innerHTML = 
              '<div class="loading">検索中...</div>';
          }
          
          function clearResults() {
            document.getElementById('resultsContainer').innerHTML = 
              '<div class="no-results">検索クエリを入力して開始してください</div>';
            document.getElementById('searchStats').textContent = '';
          }
          
          function showResults(results, stats) {
            const container = document.getElementById('resultsContainer');
            const statsEl = document.getElementById('searchStats');
            
            statsEl.textContent = \`\${results.length}件の結果が\${stats.searchTime}msで見つかりました\`;
            
            if (results.length === 0) {
              container.innerHTML = '<div class="no-results">結果が見つかりませんでした</div>';
              return;
            }
            
            container.innerHTML = results.map(result => {
              switch (currentSearchType) {
                case 'file':
                  return createFileResult(result);
                case 'symbol':
                  return createSymbolResult(result);
                case 'content':
                  return createContentResult(result);
                default:
                  return '';
              }
            }).join('');
          }
          
          function createFileResult(result) {
            return \`
              <div class="search-result" onclick="openFile('\${result.file.path}')">
                <div class="result-title">\${result.file.name}</div>
                <div class="result-path">\${result.file.relativePath}</div>
              </div>
            \`;
          }
          
          function createSymbolResult(result) {
            return \`
              <div class="search-result" onclick="openSymbol('\${result.file.path}', \${result.symbol.range.start.line})">
                <div class="result-title">\${result.symbol.name}</div>
                <div class="result-path">\${result.file.relativePath}:\${result.symbol.range.start.line + 1}</div>
              </div>
            \`;
          }
          
          function createContentResult(result) {
            let content = result.content;
            
            // マッチをハイライト
            result.matches.forEach(match => {
              const before = content.substring(0, match.startIndex);
              const matchText = content.substring(match.startIndex, match.startIndex + match.length);
              const after = content.substring(match.startIndex + match.length);
              content = before + \`<span class="result-match">\${matchText}</span>\` + after;
            });
            
            return \`
              <div class="search-result" onclick="openFile('\${result.file.path}', \${result.lineNumber})">
                <div class="result-path">\${result.file.relativePath}:\${result.lineNumber}</div>
                <div class="result-content">\${content}</div>
              </div>
            \`;
          }
          
          function openFile(filePath, lineNumber) {
            vscode.postMessage({
              type: 'openFile',
              filePath,
              lineNumber
            });
          }
          
          function openSymbol(filePath, lineNumber) {
            vscode.postMessage({
              type: 'openFile',
              filePath,
              lineNumber
            });
          }
          
          // 拡張機能からのメッセージを処理
          window.addEventListener('message', event => {
            const message = event.data;
            
            switch (message.type) {
              case 'searchResults':
                showResults(message.results, message.stats);
                break;
              case 'suggestions':
                showSuggestions(message.suggestions);
                break;
            }
          });
        </script>
      </body>
      </html>
    `;
  }
  private setupWebviewMessageHandling(): void {
    if (!this.searchPanel) return;
    this.searchPanel.webview.onDidReceiveMessage(async message => {
      switch (message.type) {
        case 'search':
          await this.handleSearchRequest(message);
          break;
        case 'getSuggestions':
          await this.handleSuggestionsRequest(message);
          break;
        case 'openFile':
          await this.handleOpenFile(message);
          break;
      }
    });
  }
  private async handleSearchRequest(message: any): Promise<void> {
    const startTime = Date.now();
    let results: any[] = [];
    try {
      switch (message.searchType) {
        case 'file':
          results = await this.searchManager.searchFiles(message.query, message.options);
          break;
        case 'symbol':
          results = await this.searchManager.searchSymbols(message.query, message.options);
          break;
        case 'content':
          results = await this.searchManager.searchContent(message.query, message.options);
          break;
      }
    } catch (error) {
      console.error('検索に失敗しました:', error);
      results = [];
    }
    const searchTime = Date.now() - startTime;
    this.sendMessage({
      type: 'searchResults',
      results,
      stats: { searchTime }
    });
  }
  private async handleSuggestionsRequest(message: any): Promise<void> {
    try {
      const suggestions = await this.searchManager.getSearchSuggestions(
        message.query,
        message.searchType
      );
      this.sendMessage({
        type: 'suggestions',
        suggestions
      });
    } catch (error) {
      console.error('候補の取得に失敗しました:', error);
    }
  }
  private async handleOpenFile(message: any): Promise<void> {
    try {
      const uri = TraeAPI.Uri.file(message.filePath);
      const document = await TraeAPI.workspace.openTextDocument(uri);
      const editor = await TraeAPI.window.showTextDocument(document);
      if (message.lineNumber) {
        const line = message.lineNumber - 1; // 0ベースに変換
        const position = new TraeAPI.Position(line, 0);
        editor.selection = new TraeAPI.Selection(position, position);
        editor.revealRange(new TraeAPI.Range(position, position));
      }
    } catch (error) {
      console.error('ファイルのオープンに失敗しました:', error);
      TraeAPI.window.showErrorMessage(`ファイルのオープンに失敗しました: ${message.filePath}`);
    }
  }
  private async performSearch(query: string, type: SearchType): Promise<void> {
    this.currentQuery = query;
    this.currentSearchType = type;
    try {
      let results: any[] = [];
      switch (type) {
        case 'file':
          results = await this.searchManager.searchFiles(query);
          break;
        case 'symbol':
          results = await this.searchManager.searchSymbols(query);
          break;
        case 'content':
          results = await this.searchManager.searchContent(query);
          break;
      }
      this.searchResults = results;
      await this.showSearchResults(results, type);
    } catch (error) {
      console.error('検索に失敗しました:', error);
      TraeAPI.window.showErrorMessage(`検索に失敗しました: ${error}`);
    }
  }
  private async showSearchResults(results: any[], type: SearchType): Promise<void> {
    if (results.length === 0) {
      TraeAPI.window.showInformationMessage('結果が見つかりませんでした');
      return;
    }
    // 結果のクイックピックを表示
    const items = results.slice(0, 50).map(result => {
      switch (type) {
        case 'file':
          return {
            label: result.file.name,
            description: result.file.relativePath,
            detail: `スコア: ${result.score}`,
            result
          };
        case 'symbol':
          return {
            label: result.symbol.name,
            description: `${result.file.relativePath}:${result.symbol.range.start.line + 1}`,
            detail: `${this.getSymbolKindName(result.symbol.kind)} - スコア: ${result.score}`,
            result
          };
        case 'content':
          return {
            label: result.content.trim(),
            description: `${result.file.relativePath}:${result.lineNumber}`,
            detail: `${result.matches.length}件のマッチ`,
            result
          };
        default:
          return { label: '', description: '', result };
      }
    });
    const selected = await TraeAPI.window.showQuickPick(items, {
      placeHolder: `${results.length}件の検索結果から選択`,
      matchOnDescription: true,
      matchOnDetail: true
    });
    if (selected) {
      await this.openSearchResult(selected.result, type);
    }
  }
  private async openSearchResult(result: any, type: SearchType): Promise<void> {
    let filePath: string;
    let lineNumber: number | undefined;
    switch (type) {
      case 'file':
        filePath = result.file.path;
        break;
      case 'symbol':
        filePath = result.file.path;
        lineNumber = result.symbol.range.start.line;
        break;
      case 'content':
        filePath = result.file.path;
        lineNumber = result.lineNumber - 1; // 0ベースに変換
        break;
      default:
        return;
    }
    try {
      const uri = TraeAPI.Uri.file(filePath);
      const document = await TraeAPI.workspace.openTextDocument(uri);
      const editor = await TraeAPI.window.showTextDocument(document);
      if (lineNumber !== undefined) {
        const position = new TraeAPI.Position(lineNumber, 0);
        editor.selection = new TraeAPI.Selection(position, position);
        editor.revealRange(new TraeAPI.Range(position, position));
      }
    } catch (error) {
      console.error('検索結果のオープンに失敗しました:', error);
      TraeAPI.window.showErrorMessage(`ファイルのオープンに失敗しました: ${filePath}`);
    }
  }
  private getSymbolKindName(kind: TraeAPI.SymbolKind): string {
    const kindNames: { [key: number]: string } = {
      [TraeAPI.SymbolKind.File]: 'ファイル',
      [TraeAPI.SymbolKind.Module]: 'モジュール',
      [TraeAPI.SymbolKind.Namespace]: '名前空間',
      [TraeAPI.SymbolKind.Package]: 'パッケージ',
      [TraeAPI.SymbolKind.Class]: 'クラス',
      [TraeAPI.SymbolKind.Method]: 'メソッド',
      [TraeAPI.SymbolKind.Property]: 'プロパティ',
      [TraeAPI.SymbolKind.Field]: 'フィールド',
      [TraeAPI.SymbolKind.Constructor]: 'コンストラクタ',
      [TraeAPI.SymbolKind.Enum]: '列挙型',
      [TraeAPI.SymbolKind.Interface]: 'インターフェース',
      [TraeAPI.SymbolKind.Function]: '関数',
      [TraeAPI.SymbolKind.Variable]: '変数',
      [TraeAPI.SymbolKind.Constant]: '定数',
      [TraeAPI.SymbolKind.String]: '文字列',
      [TraeAPI.SymbolKind.Number]: '数値',
      [TraeAPI.SymbolKind.Boolean]: 'ブール値',
      [TraeAPI.SymbolKind.Array]: '配列',
      [TraeAPI.SymbolKind.Object]: 'オブジェクト',
      [TraeAPI.SymbolKind.Key]: 'キー',
      [TraeAPI.SymbolKind.Null]: 'Null',
      [TraeAPI.SymbolKind.EnumMember]: '列挙型メンバー',
      [TraeAPI.SymbolKind.Struct]: '構造体',
      [TraeAPI.SymbolKind.Event]: 'イベント',
      [TraeAPI.SymbolKind.Operator]: '演算子',
      [TraeAPI.SymbolKind.TypeParameter]: '型パラメータ'
    };
    
    return kindNames[kind] || '不明';
  }
  private sendMessage(message: any): void {
    if (this.searchPanel) {
      this.searchPanel.webview.postMessage(message);
    }
  }
  private setupEventListeners(): void {
    this.searchManager.onDidChangeIndex(event => {
      // 検索パネルが開いている場合は更新
      if (this.searchPanel && this.currentQuery) {
        // 現在の検索を再実行して更新された結果を取得
        this.performSearch(this.currentQuery, this.currentSearchType);
      }
    });
  }
}
// 検索UIを初期化
const searchUIProvider = new SearchUIProvider(searchManager);インターフェース 
interface FileIndexEntry {
  path: string;
  relativePath: string;
  name: string;
  extension: string;
  size: number;
  lastModified: number;
  language: string;
  searchTerms: string[];
}
interface SymbolMatch {
  type: 'name' | 'fullName';
  text: string;
  startIndex: number;
  length: number;
}
interface ContentMatch {
  startIndex: number;
  length: number;
  text: string;
}
interface IndexStats {
  fileCount: number;
  symbolCount: number;
  contentLineCount: number;
  isIndexing: boolean;
}
interface SearchEvent {
  type: 'indexingStarted' | 'indexingCompleted' | 'indexingFailed' | 'fileIndexed' | 'fileUpdated' | 'fileRemoved';
  filePath?: string;
  error?: Error;
}
type SearchType = 'file' | 'symbol' | 'content';APIリファレンス 
SearchManager 
メソッド 
- searchFiles(query: string, options?: FileSearchOptions): Promise<FileSearchResult[]>- ファイル名とパスでファイルを検索
- マッチ情報付きのランク付けされた結果を返す
 
- searchSymbols(query: string, options?: SymbolSearchOptions): Promise<SymbolSearchResult[]>- シンボル(関数、クラス、変数)を検索
- シンボル種別とファイルタイプによるフィルタリングをサポート
 
- searchContent(query: string, options?: ContentSearchOptions): Promise<ContentSearchResult[]>- ファイル内のテキストコンテンツを検索
- 正規表現パターンと大文字小文字の区別をサポート
 
- getSearchSuggestions(query: string, type: SearchType): Promise<string[]>- クエリとタイプに基づいて検索候補を取得
- 履歴とインデックスから関連する候補を返す
 
- getSearchHistory(type?: SearchType): SearchHistoryEntry[]- 検索履歴エントリを取得
- オプションで検索タイプによるフィルタリング
 
- clearSearchHistory(type?: SearchType): void- 検索履歴をクリア
- オプションで特定のタイプのみクリア
 
- getIndexStats(): IndexStats- 現在のインデックス統計を取得
- ファイル、シンボル、コンテンツの数を返す
 
- refreshIndex(): Promise<void>- 検索インデックス全体を再構築
- 大きなファイルシステム変更後に有用
 
- onDidChangeIndex(listener: (event: SearchEvent) => void): TraeAPI.Disposable- インデックス変更イベントをリッスン
- リッスンを停止するためのDisposableを返す
 
- dispose(): void- リソースをクリーンアップしてファイルウォッチャーを停止
 
SearchUIProvider 
メソッド 
- showSearchPanel(): Promise<void>- 検索Webviewパネルを表示
- 存在しない場合はパネルを作成
 
- performSearch(query: string, type: SearchType): Promise<void>- 検索を実行して結果を表示
- 検索結果でUIを更新
 
ベストプラクティス 
パフォーマンス最適化 
// より良いパフォーマンスのために検索結果を制限
const results = await searchManager.searchFiles(query, {
  maxResults: 50
});
// 特定のファイルタイプフィルターを使用
const jsResults = await searchManager.searchSymbols(query, {
  fileTypes: ['.js', '.ts'],
  symbolKinds: [TraeAPI.SymbolKind.Function, TraeAPI.SymbolKind.Class]
});
// より良いパフォーマンスのために除外パターンを使用
const contentResults = await searchManager.searchContent(query, {
  excludePatterns: ['node_modules/**', 'dist/**', '*.min.js']
});検索クエリの最適化 
// より良い結果のために特定のクエリを使用
const specificResults = await searchManager.searchFiles('UserService.ts');
// 部分マッチにファジーマッチングを使用
const fuzzyResults = await searchManager.searchFiles('usrserv');
// 複雑なパターンに正規表現を使用
const regexResults = await searchManager.searchContent('function\\s+\\w+\\(', {
  isRegex: true
});インデックス管理 
// インデックス状態を監視
searchManager.onDidChangeIndex(event => {
  switch (event.type) {
    case 'indexingStarted':
      console.log('検索インデックス化が開始されました');
      break;
    case 'indexingCompleted':
      console.log('検索インデックス化が完了しました');
      break;
    case 'indexingFailed':
      console.error('検索インデックス化に失敗しました:', event.error);
      break;
  }
});
// インデックス統計をチェック
const stats = searchManager.getIndexStats();
console.log(`${stats.fileCount}ファイル、${stats.symbolCount}シンボルをインデックス化しました`);
// 必要に応じてインデックスを更新
if (majorChangesDetected) {
  await searchManager.refreshIndex();
}エラーハンドリング 
try {
  const results = await searchManager.searchContent(userQuery, {
    isRegex: true
  });
  displayResults(results);
} catch (error) {
  if (error.message.includes('無効な検索パターン')) {
    TraeAPI.window.showErrorMessage('無効な正規表現パターンです。検索クエリを確認してください。');
  } else {
    TraeAPI.window.showErrorMessage(`検索に失敗しました: ${error.message}`);
  }
}メモリ管理 
// 不要になったときに検索マネージャーを破棄
class MyExtension {
  private searchManager: SearchManager;
  
  activate() {
    this.searchManager = new SearchManager();
  }
  
  deactivate() {
    this.searchManager.dispose();
  }
}
// 大きなファイルのコンテンツインデックス化を制限
const shouldIndex = (fileEntry: FileIndexEntry) => {
  return fileEntry.size < 1024 * 1024; // 1MBの制限
};関連API 
- Workspace API - ファイルシステム操作
- Editor API - テキストエディター統合
- Commands API - 検索コマンド登録
- UI API - 検索UIコンポーネント
- Language Services API - シンボルプロバイダー