65
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

アイレット株式会社Advent Calendar 2024

Day 17

Gemini APIを使用して愛犬がコードレビューしてくれるVSCode拡張機能を自作する

Last updated at Posted at 2024-12-16

はじめに

賢すぎる愛犬にいっそコードレビューを頼みたいと思ったことはありませんか?私はあります。
この記事では、Gemini APIを使用して愛犬がコードレビューしてくれるVSCode拡張機能を作成します。

動作イメージ

  1. VSCodeのサイドバーに愛犬の画像とテキストエリアを表示
  2. コードを選択してコマンドを実行すると、犬っぽい口調でレビューを返してくれる
  3. レビュー中やレビュー完了時は専用の画像に切り替わる演出付き

プロジェクトの作成

公式ドキュメントを参考に、Yeomanでプロジェクトを作成します。

npm install --global yo generator-code
yo code

コマンドを実行するといくつか質問されるので、対話形式で以下のように回答します。

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------´   │        generator!        │
    ( _´U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? dog-reviewer
? What's the identifier of your extension? dog-reviewer
? What's the description of your extension?
? Initialize a git repository? No
? Which bundler to use? webpack
? Which package manager to use? npm

これでmy-pets-reviewerフォルダが作成され、拡張機能のプロジェクトが出来上がりました。

今回はGoogle AI Gemini APIを使用するため、APIキーとライブラリも用意します。

npm install @google/generative-ai

WebViewとコマンドの登録

拡張機能の起動時には、src/extension.ts内のactivate関数が呼び出されます。
activate関数に以下のコードを記述します。

src/extensions.ts
export function activate(context: vscode.ExtensionContext): void {
  const provider = new WebViewProvider(context);

  // サイドバーのWebView登録
  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider("dogReviewerSidebar", provider)
  );

  // コードレビューコマンドの登録
  const codeReviewCommand = vscode.commands.registerCommand(
    "extension.codeReview",
    async () => {
      await handleCodeReview(provider);
    }
  );

  context.subscriptions.push(codeReviewCommand);
}

VSCodeのサイドバーにWebView(HTML)を表示するため、WebViewProviderインスタンスを作成し、vscode.window.registerWebviewViewProvider関数を使ってサイドバー(dogReviewerSidebarというIDのビュー)へ登録しています。

vscode.commands.registerCommand関数では、コマンドextension.codeReviewを登録しています。これにより、エディタ上で選択したテキストに対して、ショートカットメニューから「コードレビュー」コマンドを実行できるようになります。

続いて、拡張機能の設定をpackage.jsonに追記します。

package.json
  "contributes": {
    "views": {
      "explorer": [
        {
          "type": "webview",
          "id": "dogReviewerSidebar",
          "name": "DogReviewer"
        }
      ]
    },
    "commands": [
      {
        "command": "extension.codeReview",
        "title": "コードレビュー"
      }
    ],
    "menus": {
      "editor/context": [
        {
          "command": "extension.codeReview",
          "when": "editorHasSelection",
          "group": "navigation"
        }
      ]
    },
    "configuration": {
      "type": "object",
      "title": "Dog Reviewer",
      "properties": {
        "dogReviewer.apiKey": {
          "type": "string",
          "default": "",
          "description": "Gemini API Key"
        }
      }
    }
  },

contributesでは、WebViewやコマンドの識別子を定義します。viewsでサイドバー(エクスプローラー)にWebViewを追加し、commandsで「コードレビュー」コマンドを登録、menusでメニューにコマンドを表示する設定をしています。
また、ユーザー設定からGemini APIキーを取得できるように、configurationで設定項目を定義しています。

コマンド実行時の処理

extension.codeReviewコマンドが呼ばれた際に実行される処理を実装します。

src/extensions.ts
async function handleCodeReview(provider: WebViewProvider): Promise<void> {
  const editor = vscode.window.activeTextEditor;
  if (!editor) {
    vscode.window.showInformationMessage("アクティブなエディタがありません。");
    return;
  }

  // 設定から API キーを取得
  const config = vscode.workspace.getConfiguration("dogReviewer");
  const apiKey = config.get<string>("apiKey");

  if (!apiKey) {
    vscode.window.showErrorMessage("APIキーが設定されていません。拡張機能の設定からAPIキーを入力してください。");
    return;
  }

  const selection = editor.selection;
  const selectedText = editor.document.getText(selection);

  // プログレスインジケータの表示中にレビューを実行
  await vscode.window.withProgress(
    {
      location: vscode.ProgressLocation.Notification,
      title: "コードをレビュー中...",
      cancellable: false,
    },
    async () => {
      provider.setImage("Progress.png");

      const genAI = new GoogleGenerativeAI(apiKey);
      const prompt = `あなたは賢いボーダーコリーです。以下のコードの問題点を犬の口調で簡潔にレビューしてください。\n\n${selectedText}`;
      const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });

      // プロンプトを送信して結果を取得
      const result = await model.generateContent(prompt);
      const response = result.response;
      const text = response.text();

      // 結果をWebViewに表示
      provider.setReviewText(text);
      provider.setImage("Success.png"); 
    }
  );
}

アクティブなエディタから選択中のテキストを取得し、withProgressで進捗インジケータを表示しながら処理を行っています。
犬にコードレビューしてもらう必要があるので、promptとして「犬の口調で簡潔にレビューしてください」という指示とともにコードを渡します。

WebViewの表示

src/extensions.ts
class WebViewProvider implements vscode.WebviewViewProvider {
  private _view?: vscode.WebviewView;
  private _reviewText: string = "";
  private _currentImage: string = "Wendy.jpg";

  constructor(private readonly context: vscode.ExtensionContext) {}

  // WebViewの初期化処理
  resolveWebviewView(webviewView: vscode.WebviewView) {
    this._view = webviewView;

    webviewView.webview.options = {
      enableScripts: true,
      localResourceRoots: [this.context.extensionUri],
    };

    this.updateWebviewContent();
  }
  
  // WebViewを更新する
  private updateWebviewContent() {
    if (!this._view) {
      return;
    }
    
    // _currentImageで指定した画像ファイルへのURIを取得
    const imageUri = this._view.webview.asWebviewUri(
      vscode.Uri.joinPath(
        this.context.extensionUri,
        "media",
        this._currentImage
      )
    );
    
    // CSSファイルへのURIを取得
    const cssUri = this._view.webview.asWebviewUri(
      vscode.Uri.joinPath(this.context.extensionUri, "media", "style.css")
    );

    // WebViewに表示するHTML
    this._view.webview.html = `<!DOCTYPE html>
          <html lang="ja">
          <head>
              <meta charset="UTF-8">
              <link rel="stylesheet" href="${cssUri}">
          </head>
          <body>
              <div class="image-container">
                  <img src="${imageUri}" style="max-width: 100%; height: auto;">
              </div>
              <div class="review-container">${this._reviewText}</div>
          </body>
          </html>`;
  }

  // Geminiから取得したコードレビューをWebViewに反映する
  public setReviewText(text: string) {
    this._reviewText = text;
    this.updateWebviewContent();
  }

  // 表示画像を更新する
  public setImage(imageName: string) {
    this._currentImage = imageName;
    this.updateWebviewContent();
  }
}

WebviewViewProviderを利用して、サイドバーに愛犬の画像とレビュー結果を表示します。
_reviewText_currentImageの変更に合わせてupdateWebviewContentを呼び出すことで、WebViewを更新しています。
注意点として、asWebviewUri()を使わないと、ディレクトリ内の画像やCSSファイルに直接アクセスできません。

動作確認

F5を押して拡張機能をデバッグ実行します。
新しいVSCodeウィンドウが立ち上がり、サイドバーに愛犬の画像が表示されました。
image.png

次に、拡張機能の設定でGemini APIキーを入力します。
image.png

適当なファイルを開き、コードを選択した状態でショートカットメニューから「コードレビュー」を実行します。

生成中は画像が切り替わり・・・
image.png

数秒後、レビュー文が返ってきました。
ちゃんと犬っぽい口調でコードの解説と問題点の指摘をしてくれています。
image.png

まとめ

以上で完成です。お疲れ様でした!🎉
VSCodeの拡張機能は公式のドキュメントやサンプルがとても充実しているので、比較的簡単に作ることができます。
皆さんもぜひ作ってみてください!

65
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
65
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?