# 機能設計書 20-スニペット

## 概要

本ドキュメントは、Roslynの「スニペット」（Snippets）機能の設計について記述する。定型コードパターンを素早く挿入するためのコード補完機能である。

### 本機能の処理概要

スニペット機能は、`for`、`if`、`while`などのキーワードを入力し、Tabキーを押すことで定型のコードパターンを展開・挿入する機能である。挿入後はプレースホルダーをTab/Shift+Tabで移動しながら編集できる。

**業務上の目的・背景**：繰り返し記述するコードパターンを素早く入力できることは開発効率に直結する。この機能により、構文を覚えていなくても正しいコード構造を素早く生成でき、タイプ量を削減できる。

**機能の利用シーン**：
- forループやforeachループを素早く入力したいとき
- if文やswitch文を素早く入力したいとき
- プロパティやコンストラクタを素早く入力したいとき
- try-catch文を素早く入力したいとき
- usingステートメントを素早く入力したいとき

**主要な処理内容**：
1. スニペットの有効位置判定
2. スニペットテキスト変更の生成
3. ドキュメントへの挿入とフォーマット
4. プレースホルダーの位置計算
5. キャレット位置の設定
6. インポート文の追加（必要な場合）

**関連システム・外部連携**：
- 補完システム（IntelliSense）
- フォーマッタ
- インポート追加システム
- LSP（Language Server Protocol）

**権限による制御**：特になし。すべての開発者が利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 補完リスト | 主画面 | スニペットの選択 |
| - | エディタ | 結果画面 | スニペットの展開・編集 |

## 機能種別

コード生成 / 補完

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| document | Document | Yes | 対象ドキュメント | null不可 |
| position | int | Yes | 挿入位置 | 0以上 |
| cancellationToken | CancellationToken | Yes | キャンセルトークン | - |

### 利用可能なスニペット（例）

- `for` - forループ
- `foreach` - foreachループ
- `if` - if文
- `else` - else句
- `while` - whileループ
- `do` - do-whileループ
- `switch` - switch文
- `try` - try-catch文
- `prop` - プロパティ
- `propfull` - 完全プロパティ
- `ctor` - コンストラクタ
- `using` - usingステートメント
- `lock` - lockステートメント
- `class` - クラス定義
- `interface` - インターフェース定義
- `enum` - 列挙型定義

### 入力データソース

- 補完リストからの選択
- エディタでのトリガー入力

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| snippetChange | SnippetChange | スニペット変更情報 |

### SnippetChange

| 項目名 | 型 | 説明 |
|--------|-----|------|
| TextChanges | ImmutableArray\<TextChange\> | テキスト変更 |
| Placeholders | ImmutableArray\<SnippetPlaceholder\> | プレースホルダー |
| FinalCaretPosition | int | 最終キャレット位置 |

### SnippetPlaceholder

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Identifier | string | プレースホルダー識別子 |
| Span | TextSpan | テキストスパン |

### 出力先

- エディタ（コード挿入）

## 処理フロー

### 処理シーケンス

```
1. 有効位置判定
   └─ IsValidSnippetLocationで挿入可能位置か判定
2. テキスト変更生成
   └─ GenerateSnippetTextChangesAsyncでスニペットテキストを生成
3. ドキュメント更新
   └─ スニペットテキストをドキュメントに適用
4. トリビア追加
   └─ 構文木にElasticTriviaを追加
5. フォーマット・簡略化
   └─ Formatter、Simplifier、ImportAdderを適用
6. インデント調整
   └─ ブロック内のインデントを調整
7. プレースホルダー計算
   └─ LSPスニペット用のプレースホルダー位置を計算
8. 結果返却
   └─ SnippetChangeを返却
```

### フローチャート

```mermaid
flowchart TD
    A[スニペット選択] --> B[有効位置判定]
    B --> C{有効?}
    C -->|No| Z[終了]
    C -->|Yes| D[テキスト変更生成]
    D --> E[ドキュメント更新]
    E --> F[トリビア追加]
    F --> G[フォーマット・簡略化]
    G --> H[インポート追加]
    H --> I[インデント調整]
    I --> J[プレースホルダー計算]
    J --> K[キャレット位置計算]
    K --> L[結果返却]
    L --> Z
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 非ユーザーコード除外 | コメント内、文字列内などはスニペット無効 | IsInNonUserCode |
| BR-02 | トリビア保持 | 最初と最後のトークンのトリビアは保持 | 常時 |
| BR-03 | Elastic追加 | 中間トークンにはElasticTriviaを追加 | 常時 |
| BR-04 | フォーマット | FindSnippetAnnotation付きノードをフォーマット | 常時 |
| BR-05 | 簡略化 | Simplifier.Annotationで簡略化 | 常時 |
| BR-06 | インポート | SymbolAnnotationからインポートを追加 | 必要な場合 |

### 計算ロジック

- キャレット位置: GetTargetCaretPositionで言語固有の位置を計算

## データベース操作仕様

該当なし（メモリ内処理のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 無効位置 | スニペット挿入不可の位置 | 補完リストから除外 |
| - | 構文エラー | 挿入結果が不正 | 部分的に適用 |

### リトライ仕様

特になし

## トランザクション仕様

該当なし（エディタ操作として一括適用）

## パフォーマンス要件

- レスポンス時間: 即座（Tab押下から展開まで）
- 非同期処理でUIスレッドをブロックしない

## セキュリティ考慮事項

特になし（ローカルコード生成のみ）

## 備考

- C#とVBで共通の基盤を使用
- LSPスニペット形式（$1、$2等のプレースホルダー）をサポート

---

## コードリーディングガイド

### 推奨読解順序

#### Step 1: サービスインターフェースを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ISnippetService.cs | `src/Features/Core/Portable/Snippets/ISnippetService.cs` | サービスインターフェース。GetSnippets、GetSnippetProviderを理解する |

**主要処理フロー**:
- **17行目**: GetSnippets - コンテキストに基づいて利用可能なスニペットを取得
- **23行目**: GetSnippetProvider - 識別子からプロバイダを取得

#### Step 2: サービス実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AbstractSnippetService.cs | `src/Features/Core/Portable/Snippets/AbstractSnippetService.cs` | サービス実装。プロバイダ管理、スニペット取得を理解する |

**主要処理フロー**:
- **17行目**: コンストラクタ - MEFからプロバイダを取得
- **28-32行目**: GetSnippetProvider - 識別子からプロバイダを取得
- **38-49行目**: GetSnippets - 有効なスニペットをフィルタリングして返却
- **51-69行目**: EnsureSnippetsLoaded - 遅延ロード

#### Step 3: プロバイダ基底クラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AbstractSnippetProvider.cs | `src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs` | プロバイダ基底クラス。スニペット生成フローを理解する |

**主要処理フロー**:
- **25-27行目**: Identifier、Description - スニペットの識別子と説明
- **36行目**: IsValidSnippetLocationCore - 有効位置判定（言語固有）
- **41行目**: GenerateSnippetTextChangesAsync - テキスト変更生成
- **46行目**: GetTargetCaretPosition - キャレット位置計算
- **51-53行目**: GetPlaceHolderLocationsListAsync - プレースホルダー位置計算
- **55-65行目**: IsValidSnippetLocation - 有効位置判定（共通処理）
- **72-118行目**: GetSnippetChangeAsync - スニペット変更のメイン処理
- **123-140行目**: GenerateElasticTriviaForSyntax - Elasticトリビア追加
- **142-164行目**: CleanupDocumentAsync - フォーマット・簡略化・インポート
- **170-189行目**: GetDocumentWithSnippetAndTriviaAsync - スニペット挿入とトリビア
- **201-206行目**: AddFormatAnnotationAsync - フォーマットアノテーション追加

### プログラム呼び出し階層図

```
ISnippetService.GetSnippets()
    │ (AbstractSnippetService)
    │
    ├─ EnsureSnippetsLoaded()
    │      └─ MEFプロバイダのロード
    │
    └─ foreach (provider in _snippetProviders)
           │
           └─ ISnippetProvider.IsValidSnippetLocation()
                  │
                  └─ SnippetData返却

ISnippetProvider.GetSnippetChangeAsync()
    │ (AbstractSnippetProvider)
    │
    ├─ GenerateSnippetTextChangesAsync()
    │      └─ テキスト変更生成
    │
    ├─ GetDocumentWithSnippetAsync()
    │      └─ スニペット挿入
    │
    ├─ GetDocumentWithSnippetAndTriviaAsync()
    │      └─ トリビア追加
    │
    ├─ AddFormatAnnotationAsync()
    │      └─ アノテーション追加
    │
    ├─ CleanupDocumentAsync()
    │      ├─ ImportAdder.AddImportsFromSymbolAnnotationAsync()
    │      ├─ Simplifier.ReduceAsync()
    │      └─ Formatter.FormatAsync()
    │
    ├─ AddIndentationToDocumentAsync()
    │      └─ インデント調整
    │
    ├─ GetPlaceHolderLocationsListAsync()
    │      └─ プレースホルダー計算
    │
    └─ GetTargetCaretPosition()
           └─ キャレット位置計算
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ISnippetService.cs | `src/Features/Core/Portable/Snippets/ISnippetService.cs` | ソース | サービスインターフェース |
| AbstractSnippetService.cs | `src/Features/Core/Portable/Snippets/AbstractSnippetService.cs` | ソース | サービス実装 |
| ISnippetProvider.cs | `src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs` | ソース | プロバイダインターフェース |
| AbstractSnippetProvider.cs | `src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs` | ソース | プロバイダ基底クラス |
| SnippetChange.cs | `src/Features/Core/Portable/Snippets/SnippetChange.cs` | ソース | 変更結果 |
| SnippetContext.cs | `src/Features/Core/Portable/Snippets/SnippetContext.cs` | ソース | コンテキスト |
| SnippetData.cs | `src/Features/Core/Portable/Snippets/SnippetData.cs` | ソース | スニペットデータ |
| SnippetPlaceholder.cs | `src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs` | ソース | プレースホルダー |
