3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

小さなアイデアをVibe Codingで形にする(第1弾):1時間足らずでAGY IDE / VS Codeの拡張機能を作ってみた

3
Posted at

1. Google Antigravity IDE (AGY IDE) でMarkdownプレビューを開くときのちょっとした不満

私は現在、メインの作業環境として AGY IDE を愛用しています。

日頃からドキュメント作成や開発を進める中で、地味ながらどうしても気になっていたのが「Markdownプレビュー」の操作感でした。

プレビューの表示クオリティ自体に問題があるわけではありません。VS Code / AGY IDE の標準プレビュー機能は非常に優秀で、正確なテキスト装飾、明瞭なシンタックスハイライト、そして Mermaid ダイアグラムの描画にもしっかり対応しています。不満の種だったのは、Markdownのレンダリングそのものではなく、「タブの開き方」 でした。

ツールバーのプレビューボタンを押すか、ショートカットキー Ctrl + Shift + V を実行するたびに、AGY IDE は常に Preview <ファイル名>.md という 新しいタブを横に開いてしまう のです。

プロジェクト内に複数の仕様書(spec)や設計書、作業タスク計画(TODO)が存在する場合、プレビューを開くたびにタブバーが埋め尽くされてしまいます。開いているファイルを行き来する際の手数が増え、作業の集中力が削がれる原因になっていました。

デフォルトの挙動:
[ README.md ]  -->  プレビューを開く  -->  [ README.md ] [ Preview README.md ] (タブが2つに増える)

理想の挙動:
[ README.md ]  <== 1クリック / Ctrl+Shift+V ==>  [ Preview README.md ] (同一タブ内で切り替わる)

2. 現状と既存の解決策

最近の新しいバージョンの VS Code では、Microsoft によるUIの改善が進められています。しかし、AGY IDE の内部エンジンは更新されておらず、バージョン v1.107.0 に据え置かれています。

幸いなことに、このバージョンであっても VS Code / AGY IDE には「Custom Editor」の仕組みを利用して、同一タブ内でプレビューを表示する機能が標準で備わっていました。

対象の .md タブのタイトル部分を右クリック -> 「Reopen Editor With...」(他のエディターで再度開く...) -> 「Markdown Preview (built-in)」 を選択します。すると、新しいタブを作ることなく、そのタブのままプレビュー表示に切り替わります。Rawコード(編集モード)に戻したい場合は、同様に 「Text Editor」 を選ぶか、プレビュー画面上をダブルクリックするだけです。

この機能自体は非常に安定しており、まさに求めていた挙動です。しかし、切り替えるたびに毎回 3〜4 回のマウス操作が必要になるため、文章の執筆とプレビュー確認を頻繁に往復する作業フローでは思考が中断されてしまいます。


3. 拡張機能を探してみたものの……自作を決意

VS Code Marketplace で Markdown 関連の拡張機能を探してみましたが、ドンピシャなツールは見当たりませんでした。

  • ほとんどのプレビュー拡張機能は、依然として「別タブで開く」または「画面分割(Split Editor)」を前提とした仕様でした。
  • 一方、エディタをリッチテキスト化(WYSIWYG)するタイプの拡張機能は、フォントレンダリングの違和感、IDEテーマとの非同期、あるいは Mermaid 図が正しく描画されないといった別の制約を抱えていました。

私の目的はいたってシンプルでした。「VS Code 標準の優秀なレンダラーをそのまま活かしつつ、切り替え操作をワンクリックまたはショートカットキー1つに短縮すること」 です。

そこで、自分専用の小さな拡張機能をサクッと作ってみることにしました。


4. バイブコーディング(Vibe Coding)の実践プロセス

私自身、これまでに VS Code 拡張機能を作った経験はなく、VS Code の拡張 API 仕様にも精通していませんでした。しかし、昨今の生成 AI ツールを活用すれば、そのプラットフォームに関する深い事前知識がなくてもすぐに形にできます。最も重要なのは、「何が不便で、どう解決したいか」という具体的なアイデアとビジョンを言語化することです。

プロジェクト立ち上げとファーストプロトタイプ

AI に対して現状の課題と理想のゴールをプロンプトとして提示し、開発環境のセットアップ、プロジェクト構成、コード生成までを一任しました。

AIに送ったプロンプト:
"現在 Antigravity IDE(VS Code エンジン v1.107.0)を使用しています。Markdown プレビューを開く際、デフォルトでは毎回新しいタブが生成されて邪魔に感じています。VS Code の「Reopen Editor With」機能を使えば同一タブ内でプレビューを開けることは知っていますが、毎回右クリックしてメニューを辿るのが手間に感じます。
以下の仕様を満たすシンプルな拡張機能を作成してください:
1. Markdown ファイルのエディタ右上ツールバーにトグルボタンを配置し、同一タブ内で Raw ソースコードとプレビュー表示を相互に切り替えられるようにする。
2. ショートカットキー Ctrl + Shift + V を押した際も、現在のタブのまま同様のトグル切り替えを行えるようにする。"

およそ 30 分ほどで、AI はディレクトリ構成の構築、package.json の設定、必要なパッケージのインストール、そして extension.ts のロジック実装までを完遂してくれました。F5 キーを押してデバッグモードで立ち上げると、初回実行の段階で早くも要件どおりの挙動が確認できました。

次の20分間:操作性とユーザー体験のブラッシュアップ

さらに使い心地を洗練させるため、AI にいくつか追加のブラッシュアップを依頼しました。

  • 状態連動型の動的アイコン: Raw コード編集中は「プレビューアイコン」を表示し、プレビュー表示中は「コードアイコン($(code))」へと自動で切り替える。
  • デフォルトのプレビューボタンの非表示化: タイトルバーをすっきり整頓するため、AGY IDE 既定のプレビューボタンをコンテキスト変数で自動的に非表示にする。
  • フォールバック処理の実装: 一部の特殊なタブでエディタ切り替えコマンドが失敗した場合に備え、vscode.openWith で確実に開く安全策を追加する。

アイデアの着想から、実際に快適に使える拡張機能が手元で完成するまで、かかった時間はトータルで1時間足らずでした。


5. 主要な設定ファイルとソースコード

参考までに、本プロジェクトの核となるソースコードと設定ファイルを掲載します。

src/extension.ts (タブ切り替え制御ロジック)
src/extension.ts
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {
  // 二重表示を防ぐため、VS Code標準のプレビューボタンを非表示にするコンテキストフラグをセット
  vscode.commands.executeCommand('setContext', 'hasCustomMarkdownPreview', true);

  const toggle = async (contextUri?: vscode.Uri) => {
    const activeTab = vscode.window.tabGroups.activeTabGroup?.activeTab;
    const input = activeTab?.input as any;

    // 現在のアクティブタブがMarkdownプレビューモードかどうかを判定
    const isCustomPreview =
      Boolean(input && input.viewType === 'vscode.markdown.preview.editor') ||
      (!vscode.window.activeTextEditor && Boolean(input?.uri?.path?.endsWith('.md')));

    if (isCustomPreview) {
      // プレビュー表示中 -> 同じタブでテキストエディタ(Raw)に戻す
      try {
        await vscode.commands.executeCommand('reopenActiveEditorWith', 'default');
      } catch {
        await vscode.commands.executeCommand('workbench.action.reopenTextEditor');
      }
    } else {
      // テキストエディタ表示中 -> 同じタブでMarkdownプレビューに切り替え
      try {
        await vscode.commands.executeCommand(
          'reopenActiveEditorWith',
          'vscode.markdown.preview.editor'
        );
      } catch (err) {
        // フォールバック: reopenActiveEditorWithに失敗した場合はvscode.openWithを試行
        const uri =
          contextUri ||
          vscode.window.activeTextEditor?.document.uri ||
          (input && input.uri instanceof vscode.Uri ? input.uri : undefined);
        if (uri) {
          await vscode.commands.executeCommand(
            'vscode.openWith',
            uri,
            'vscode.markdown.preview.editor'
          );
        }
      }
    }
  };

  context.subscriptions.push(
    vscode.commands.registerCommand('markdownInplacePreview.toggle', toggle),
    vscode.commands.registerCommand('markdownInplacePreview.openPreview', toggle),
    vscode.commands.registerCommand('markdownInplacePreview.openRaw', toggle),
    {
      dispose: () => {
        vscode.commands.executeCommand('setContext', 'hasCustomMarkdownPreview', false);
      }
    }
  );
}

export function deactivate() {
  vscode.commands.executeCommand('setContext', 'hasCustomMarkdownPreview', false);
}
package.json (コマンド・メニュー・ショートカット定義)
package.json
{
  "name": "markdown-inplace-preview",
  "displayName": "Markdown In-Place Preview",
  "description": "Reopen between raw Markdown and built-in preview in the exact same tab (in-place) with 1 click or Ctrl+Shift+V",
  "version": "1.0.2",
  "publisher": "personal",
  "engines": {
    "vscode": "^1.107.0"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onStartupFinished",
    "onLanguage:markdown",
    "onCommand:markdownInplacePreview.toggle",
    "onCommand:markdownInplacePreview.openPreview",
    "onCommand:markdownInplacePreview.openRaw",
    "onCustomEditor:vscode.markdown.preview.editor"
  ],
  "main": "./dist/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "markdownInplacePreview.toggle",
        "title": "Toggle Markdown Preview / Raw (Same Tab)",
        "icon": "$(preview)"
      },
      {
        "command": "markdownInplacePreview.openPreview",
        "title": "Markdown: Preview in Same Tab",
        "icon": "$(preview)"
      },
      {
        "command": "markdownInplacePreview.openRaw",
        "title": "Markdown: Edit Raw in Same Tab",
        "icon": "$(code)"
      }
    ],
    "menus": {
      "editor/title": [
        {
          "command": "markdownInplacePreview.openPreview",
          "when": "(editorLangId == markdown || resourceLangId == markdown || resourceExtname == .md) && activeCustomEditorId != 'vscode.markdown.preview.editor'",
          "group": "navigation@1"
        },
        {
          "command": "markdownInplacePreview.openRaw",
          "when": "activeCustomEditorId == 'vscode.markdown.preview.editor'",
          "group": "navigation@1"
        }
      ],
      "editor/title/context": [
        {
          "command": "markdownInplacePreview.toggle",
          "when": "editorLangId == markdown || resourceLangId == markdown || resourceExtname == .md || activeCustomEditorId == 'vscode.markdown.preview.editor'",
          "group": "1_open@1"
        }
      ]
    },
    "keybindings": [
      {
        "command": "markdownInplacePreview.toggle",
        "key": "ctrl+shift+v",
        "mac": "cmd+shift+v",
        "when": "editorLangId == markdown || resourceLangId == markdown || resourceExtname == .md || activeCustomEditorId == 'vscode.markdown.preview.editor'"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run build",
    "build": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node",
    "watch": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --watch",
    "check-types": "tsc --noEmit",
    "package": "vsce package --no-dependencies"
  },
  "devDependencies": {
    "@types/node": "^20.11.0",
    "@types/vscode": "^1.107.0",
    "@vscode/vsce": "^3.0.0",
    "esbuild": "^0.20.0",
    "typescript": "^5.3.0"
  }
}
tsconfig.json (TypeScriptコンパイラ設定)
tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2022",
    "outDir": "out",
    "lib": ["ES2022"],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true
  },
  "exclude": ["node_modules", ".vscode-test"]
}

VS Code エンジンのバージョンに関する注意点:
publishernamedisplayName などの項目はお好みに応じて変更可能です。ただし、Antigravity IDE で利用する場合は、engines 内の "vscode": "^1.107.0" というバージョン指定を書き換えないように注意してください。Google は Antigravity IDE に最新の AI 機能を継続して追加していますが、コアとなる VS Code エンジンのバージョンは 1.107.0 に維持されています。この指定を安易に最新バージョンへと引き上げてしまうと、IDE が拡張機能のインストールを拒絶する原因となります。


6. セルフビルドとインストールの手順

あらかじめ配布されたパッケージファイルをダウンロードする必要はありません。AI を活用すれば、手元でのセットアップとビルドは極めてシンプルです。

  1. 空の作業フォルダを作成し、上記の設定ファイル・コードを配置します(または、上記のコードブロックをそのまま AI のプロンプトに渡してプロジェクト構造を作成させます)。

  2. 拡張機能を .vsix ファイルにパッケージングする:
    AI にプロンプトで指示すれば、すべて自動で進めてくれます:

    "上記のファイルを元に、依存関係のインストール、ビルド、そして .vsix ファイルへのパッケージングまで実行してください。"

    手動でターミナルから実行する場合も、以下の3つのコマンドを実行するだけで完了します:

    npm install
    npm run build
    npx @vscode/vsce package --no-dependencies
    

    ビルドが正常に完了すると、プロジェクトディレクトリ直下に拡張機能のインストールファイルである .vsix が生成されます。

  3. IDE へのインストール: 拡張機能ビュー(Ctrl + Shift + X)を開き、生成された .vsix ファイルを AGY IDE の画面内にドラッグ&ドロップします(またはビュー右上の ... メニューから「VSIX からのインストール...」を選択します)。

インストール後のワンポイントTips:
任意の Markdown ファイルを開くと、エディタ上部のタブツールバー右側にプレビュー切り替えアイコンが表示されます。もし以前の VS Code 標準プレビューボタンが隣に残っている場合は、そのボタンを右クリックして「非表示」に設定することで、ツールバーをさらに美しくすっきりと整頓できます。


7. さらなる拡張のアイデア

この拡張機能は Markdown ドキュメントの表示制御フローに直接アプローチしているため、個々のニーズに合わせてさらなる応用が可能です。

  • プラットフォーム固有の記法サポート: Qiita の :::note 記法や、Docusaurus / Obsidian の Admonition 構文などをパースして美しく表示するパーサーの組み込み。
  • スクロール位置の同期(Scroll Sync): Raw コード編集とプレビュー表示を往復した際にも、閲覧中だった行の位置をそのまま維持する追従機能。
  • ショートカットや操作方法のカスタマイズ: ご自身のタイピングスタイルや作業習慣に合わせたコンテキストキーや追加操作の登録。

おわりに

昨今の AI ツールや大規模言語モデルは目覚ましい進化を遂げており、手軽かつ安価に、そしてプロジェクトの文脈を深く理解してエンジニアを強力に支えてくれるようになりました。「今まで拡張機能を作った経験がない」「API の仕様を詳しく知らない」といった当初の技術的ハードルは、もはや制作を躊躇する理由にはなりません。

また、今回の手法は Antigravity IDE にとどまらず、VS Code をコアエンジンに採用しているエディタ全般(本家 VS Code、Cursor、Windsurf など)にもそのまま適用できます。

日々のコーディングやドキュメント作成の中で「ちょっとした不便さ」を感じるルーティンがあれば、専門知識の有無を心配することなく、ぜひ AI と二人三脚で自分専用のツール作りにチャレンジしてみてください。

3
1
0

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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?