💭

ChatGPT-4でVSCode Extensionを作る【プロンプト付き】

2023/03/19に公開

概要

大事なことなので、最初と最後に2回この記事のまとめを書いておきます。

  1. 自然言語による要件の入力から、期待するJavaScriptのコードを生成できた。
  2. VSCode Extensionの作成という、パッケージ管理からコードの生成までを含めた複雑な手順をAIが示すことができた。
  3. 当初対応していなかった機能のサポートを後出しで依頼したところ、既存のコードを「修正」した。
  4. それ以前のやり取りで作成済みだった関数について「あれをここにコピーしてください」と示して、「流用」してきた。
  5. Markdownの階層構造をMermaid.jsのグラフで表現するという割と難しい概念を、ChatGPT-4があっさり理解した。

追記

この記事を書く中で生まれた仮説を元に、もう一本記事を書きました。
https://zenn.dev/u_motion_tech/articles/638cdcea4f5db9

作ったもの: VSCode Extension

少ない記述量でMermaid.jsダイアグラムを生成する

Mermaid.jsはMarkdownベースの文書にダイアグラムを挿入できて便利なんですが、エンティティの数が増えてくると記述量が増えて管理が大変になりますよね。
このエクステンションを使うと、Markdownの「見出し」の階層構造をMermaidのダイアログに変換することができます。

Mermaid.jsのコードを直接編集するのと比べて大幅に記述量を減らすことができ、「Markdownの見出し」の階層構造を利用することで、多くの人がすぐに使い始められる容易さを実現しています。

筆者は常々このツールを作りたいと思っていたのですが、VSCode Extensionを作成したことがなく先延ばしにしていました。ここに来て「ChatGPTがちゃんと仕事してくれる」という声がたくさん聞こえてきたので、じゃあChatGPTに頼んでみよう!とやってみたのが以下の顛末になります。

作り方:上から目線の「お手並み拝見」から「こいつ、動くぞ」まで

まずはシンプルなコマンドライン・ツールを依頼

このツールもともとはChatGPT上で実行していたものでした。依頼書を書いてダイアグラムに変換してほしいMarkdownの見出しテキストを「入力」として与えていたのですが、毎回実行すると出力に時間がかかってしまいます。ふと「作業の内容を理解してるなら、同じ作業をするコードも書けるのでは?」と考えて依頼書の中身を書き換えてみました。

実際に入力したプロンプトは下記の通りです。
まあ、それっぽいコードは出せても実際は動かないだろうと思っていました。この時までは...

markdownファイルを読み込んで階層構造をMermaidの階層グラフのコードに変換するコードを出力してください。
サンプルに入力されるデータのサンプルを示します

【制約】
- JavaScriptで記述してください
- 名前の文字列は""で囲ってください
- 必要な場合は文末にセミコロンを追加してください
- graph TDを使ってください
- 異なる見出しの子要素は別のオブジェクトとして扱ってください
- 全てのエンティティにIDを設定してください
- IDは `{エンティティ名の英語}_{番号}` のフォーマットにしてください

サンプルとしてこちらも入力します。

【サンプル】
# ChatGPT 株式会社
## 開発部門
### バックエンド開発部
#### DB 開発部
##### SQL デザイン課
###### メンバー
- エンジニア一号
- エンジニア二号
##### Index せってい課
###### メンバー
- エンジニア三号
- エンジニア四号
#### サーバ開発部
- エンジニア五号
- エンジニア六号
#### SRE 部
### フロントエンド開発部
## 営業部門
### 第一営業部
### 第二営業部

ChatGPT-4の出してきた最初のコード

以下に、指定された制約に基づいて、Markdownファイルの階層構造をMermaidの階層グラフのコードに変換するJavaScriptコードを示します。

function markdownToMermaid(markdown) {
  const lines = markdown.split('\n');
  let mermaidCode = 'graph TD\n';
  const idCounter = {};
  const parents = [];

  lines.forEach(line => {
    const headingLevel = line.match(/^#+/)?.[0]?.length;
    if (!headingLevel) return;

    const name = line.slice(headingLevel + 1).trim();
    const englishName = name.replace(/[^a-zA-Z0-9]/g, '');
    idCounter[englishName] = (idCounter[englishName] || 0) + 1;
    const id = `${englishName}_${idCounter[englishName]}`;

    mermaidCode += `  ${id}("${name}")\n`;
    if (headingLevel > 1) {
      const parentId = parents[headingLevel - 2];
      mermaidCode += `  ${parentId} --> ${id}\n`;
    }
    parents[headingLevel - 1] = id;
  });

  return mermaidCode;
}

// サンプルのMarkdownテキスト
const markdown = `# ChatGPT 株式会社
## 開発部門
### バックエンド開発部
#### DB 開発部
##### SQL デザイン課
###### メンバー
- エンジニア一号
- エンジニア二号
##### Index せってい課
###### メンバー
- エンジニア三号
- エンジニア四号
#### サーバ開発部
- エンジニア五号
- エンジニア六号
#### SRE 部
### フロントエンド開発部
## 営業部門
### 第一営業部
### 第二営業部`;

console.log(markdownToMermaid(markdown));

おっと考慮漏れが

階層を示すのに「見出し」とともに「-」を併用していたのですが、この点が考慮されていませんでした。
指摘してみます。

筆者: 「-」で列挙されたアイテムが処理されていません
ChatGPT-4: 申し訳ありません。列挙されたアイテムに対応するようにコードを修正しました。

diff --git a/markdown2mermaid.js b/markdown2mermaid.js
index 028592c..f0dc1b3 100644
--- a/markdown2mermaid.js
+++ b/markdown2mermaid.js
@@ -3,10 +3,29 @@ function markdownToMermaid(markdown) {
   let mermaidCode = 'graph TD\n';
   const idCounter = {};
   const parents = [];
+  let inList = false;
 
   lines.forEach(line => {
     const headingLevel = line.match(/^#+/)?.[0]?.length;
-    if (!headingLevel) return;
+    if (!headingLevel) {
+      if (line.startsWith('-')) {
+        const name = line.slice(1).trim();
+        const englishName = name.replace(/[^a-zA-Z0-9]/g, '');
+        idCounter[englishName] = (idCounter[englishName] || 0) + 1;
+        const id = `${englishName}_${idCounter[englishName]}`;
+        const parentId = parents[parents.length - 1];
+
+        mermaidCode += `  ${id}("${name}")\n`;
+        mermaidCode += `  ${parentId} --> ${id}\n`;
+        inList = true;
+      }
+      return;
+    }
+
+    if (inList) {
+      parents.pop();
+      inList = false;
+    }
 
     const name = line.slice(headingLevel + 1).trim();
     const englishName = name.replace(/[^a-zA-Z0-9]/g, '');

指摘を受けてコードが修正された

「-」で始まる行が考慮されました(階層構造が深いケースがサポートされてませんが、ひとまず要望は満たしているので進めます)。

const headingLevel = line.match(/^#+/)?.[0]?.length;
    if (!headingLevel) {
      if (line.startsWith('-')) {

そして、この「修正」を追加したコードを、 markdown2mermaid.js として保存

node markdown2mermaid.js

で実行すると期待した動作になりました。

調子にのってVSCode Extension化もお願いしてみる。

しかしこうなると「いちいちコマンドラインで実行するのは面倒だな」と思うのが人というものです。いつもVSCodeでMarkdownを書いているので「Markdownを選択してコマンド・パレットから変換を実行できるといいな」ということで、さらにプロンプトをアップデートします。

diff --git a/PROMPT b/PROMPT
index 12177d0..ce22666 100644
--- a/PROMPT
+++ b/PROMPT
@@ -1,4 +1,4 @@
-markdownファイルを読み込んで階層構造をMermaidの階層グラフのコードに変換するコードを出力してください。サンプルに入力されるデータのサンプルを示します
+VSCodeエディタ内で選択されたmarkdownテキストをパースして階層構造をMermaidの階層グラフのコードに変換するVSCode Extensionを出力してください。サンプルに入力されるデータのサンプルを示します
 
 【制約】
 - JavaScriptで記述してください

ChatGPT-4が出してきたコード

以下のコードは、VSCodeエディタで選択されたMarkdownテキストをパースし、Mermaidの階層グラフのコードに変換するVSCode Extensionです。

まず、Visual Studio Code Extensionを作成するには、次の手順に従ってください。

  1. Node.jsをインストールします。
  2. npm install -g yo generator-codeを実行して、YeomanとVS Code Extensionジェネレータをインストールします。
  3. yo codeを実行して、新しい拡張機能のプロジェクトを作成します。
  4. 適切なオプションを選択し、拡張機能のテンプレートを作成します。

次に、以下のコードをextension.jsに追加します。

const vscode = require('vscode');

function activate(context) {
  let disposable = vscode.commands.registerCommand('extension.parseMarkdownToMermaid', async function () {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
      vscode.window.showErrorMessage('No active text editor found');
      return;
    }

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

    const mermaidCode = parseMarkdownToMermaid(text);
    const panel = vscode.window.createWebviewPanel('mermaidPreview', 'Mermaid Preview', vscode.ViewColumn.Two, {
      enableScripts: true
    });

    panel.webview.html = `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Mermaid Preview</title>
        <script src="https://unpkg.com/mermaid@8.13.5/dist/mermaid.min.js"></script>
        <style>
          body {
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
          }
          #graph {
            padding: 1rem;
          }
        </style>
        <script>
          mermaid.initialize({ startOnLoad: true });
        </script>
      </head>
      <body>
        <div id="graph">
          <div class="mermaid">${mermaidCode}</div>
        </div>
      </body>
      </html>
    `;
  });

  context.subscriptions.push(disposable);
}

function parseMarkdownToMermaid(markdown) {
  // 以下のコードは、先程提供したMarkdownパーサのコードを貼り付けます。
  // ...
}

exports.activate = activate;

function deactivate() {}

module.exports = {
  activate,
  deactivate
};

このコードをextension.jsとして保存し、package.jsonファイルを作成して、以下の内容を追加してください。

{
    "name": "markdown-to-mermaid",
    "displayName": "Markdown to Mermaid",
    "description": "Convert selected Markdown text to Mermaid hierarchical graph code.",
    "version": "1.0.0",
    "publisher": "your-publisher-name",
    "engines": {
        "vscode": "^1.62.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "onCommand:extension.convertToMermaid"
    ],
    "main": "./extension",
    "contributes": {
        "commands": [
            {
                "command": "extension.convertToMermaid",
                "title": "Convert to Mermaid"
            }
        ]
    },
    "scripts": {
        "vscode:prepublish": "npm run compile",
        "compile": "tsc -p ./",
        "watch": "tsc -watch -p ./",
        "postinstall": "node ./node

ChatGPT、すでに提供済みのコードを省略してコピペを要求してくる

VSCode Extensionの作成手順とコードが示されたことも結構びっくりだったのですが、一番驚いたのはこの部分でした。
ChatGPT-4は事前にコマンド・ライン・ツールを作ったときに作った「parseMardkdownToMermaid」関数のことを「覚えて」いて、ここは同じで動くからコピペしてねと言ってきたのです。

function parseMarkdownToMermaid(markdown) {
  // 以下のコードは、先程提供したMarkdownパーサのコードを貼り付けます。
  // ...
}

事前のやりとりを「コンテキスト」として次の推論に利用する、ChatGPT-4ならではのレスポンスだと思いました。

ChatGPT-4に教わりながら、VSCode Extensionをビルドしてインストールしてみる

筆者: 開発中のVSCode extensionをVScodeにインストールする方法を教えて下さい
ChatGPT-4: 開発中のVisual Studio Code Extensionをインストールするには、次の手順に従ってください。

↓以下、ChatGPT-4の出力です。

まず、拡張機能のプロジェクトフォルダで、Terminal(ターミナル)を開きます。

ターミナルで次のコマンドを実行して、拡張機能のパッケージ(VSIXファイル)を作成します。

vsce package

このコマンドは、vsce CLIツールを使用しています。vsceがインストールされていない場合は、次のコマンドを実行してインストールしてください。

npm install -g vsce

上記のコマンドを実行すると、プロジェクトフォルダにVSIXファイルが作成されます。例えば、my-extension-0.0.1.vsixのような名前が付いています。

次に、Visual Studio Codeを開きます。
左サイドバーの拡張機能アイコンをクリックして拡張機能ビューを表示します。
右上隅にあるメニューアイコン(歯車アイコンの横にある3つの点)をクリックし、「VSIXからインストール」を選択します。

ファイルダイアログが表示されるので、先程作成したVSIXファイルを選択し、[開く]をクリックします。

これで拡張機能がインストールされ、有効化されます。拡張機能が正常にインストールされたことを確認するために、拡張機能ビューで検索してみてください。

注意: 開発中の拡張機能は、更新や公開前のものであるため、動作が不安定である場合があります。安定版の拡張機能をインストールする場合は、拡張機能ビューで検索してインストールしてください。

これも動いた! さらに機能修正を依頼してみる。

このVSCode Extension、いくつかインストールに手こずる問題(ファイルからのインストールでだVSCodeのダイアログが開かない)がありましたが直接本記事と関係ないのでスキップします。ちなみにその問題もChatGPT-4に質問して解消しました。

これでVSCode上にMarkdownで記述した階層構造から、Mermaid.jsのグラフのプレビューを作成できるようになりました。せっかくなので「生成されたMermaid.jsのコードをクリップボードにコピーする」機能をリクエストしてみます。

筆者: markdown2mermaid エクステンションに、mermaid.js形式のコードをそのまま出力する機能を追加してください。別ウィンドウで作成されたmermaid.jsのコードを表示して、同時にクリップボードにコピーしてください

ChatGPT-4: エクステンションの機能を更新して、別ウィンドウで生成されたMermaid.js形式のコードを表示し、同時にクリップボードにコピーするように変更しました。以下に変更後のエクステンションのコードを示します。

↓以下、ChatGPT-4のレスポンスです。

まず、package.jsonファイルに新しいコマンドを追加してください。

{
  "commands": [
    {
      "command": "markdown2mermaid.parseAndCopyToClipboard",
      "title": "Parse Markdown to Mermaid and Copy to Clipboard"
    }
  ]
}

そして、extension.jsファイルに新しい関数を追加し、既存のコードを少し修正します。

const vscode = require('vscode');
const { parseMarkdownToMermaid } = require('./parser');

function activate(context) {
  let disposable = vscode.commands.registerCommand('markdown2mermaid.parseMarkdownToMermaid', function () {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
      vscode.window.showErrorMessage('No active text editor found.');
      return;
    }

    const markdownText = editor.document.getText(editor.selection);
    if (!markdownText) {
      vscode.window.showErrorMessage('No selected text found.');
      return;
    }

    const mermaidCode = parseMarkdownToMermaid(markdownText);

    if (mermaidCode) {
      const panel = vscode.window.createWebviewPanel('markdown2mermaid', 'Markdown to Mermaid', vscode.ViewColumn.Beside, {});
      panel.webview.html = `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Markdown to Mermaid</title>
        </head>
        <body>
            <pre>${mermaidCode}</pre>
        </body>
        </html>
      `;
    } else {
      vscode.window.showErrorMessage('Unable to parse the selected text.');
    }
  });

  let copyToClipboard = vscode.commands.registerCommand('markdown2mermaid.parseAndCopyToClipboard', function () {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
      vscode.window.showErrorMessage('No active text editor found.');
      return;
    }

    const markdownText = editor.document.getText(editor.selection);
    if (!markdownText) {
      vscode.window.showErrorMessage('No selected text found.');
      return;
    }

    const mermaidCode = parseMarkdownToMermaid(markdownText);

    if (mermaidCode) {
      vscode.env.clipboard.writeText(mermaidCode);
      vscode.window.showInformationMessage('Mermaid code copied to clipboard.');

      const panel = vscode.window.createWebviewPanel('markdown2mermaid', 'Markdown to Mermaid', vscode.ViewColumn.Beside, {});
      panel.webview.html = `
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Markdown to Mermaid</title>
        </head>
        <body>
            <pre>${mermaidCode}</pre>
        </body>
        </html>
  `;
} else {
  vscode.window.showErrorMessage('Unable to parse the selected text.');
}
});

context.subscriptions.push(disposable);
context.subscriptions.push(copyToClipboard);
}

function deactivate() {}

module.exports = {
activate,
deactivate
};

この変更により、新しいコマンド「Parse Markdown to Mermaid and Copy to Clipboard」が追加されました。
選択されたMarkdownテキストをパースしてMermaid.js形式のコードに変換し、別ウィンドウで表示すると同時にクリップボードにコピーします。

機能追加も問題なく動いた! 欲しかったVSCode Extensionが完成

いわれるがままにコードを修正すると、クリップボードにコードをコピーする機能が追加され、問題なく動作しました。

まとめと驚きポイントの振り返り

ChatGPT-4と対話を重ねることで、VSCode Extensionを作成して自分が望む機能をエディタに追加することができました。筆者はVSCode Extensionを作った経験がないので、実際にやったらそれなりの日数がかかっていたと思います。AIにお願いして1時間ほどで要望する機能を開発することができて、それが実際に自分の作業の役に立っているというのがなかなかの衝撃です。

せっかくなので、今回のChatGPT-4とのやりとりを通じて感じたことをメモに残しておきます。
今回驚いたのは下記の点です。

1. 自然言語による要件の入力から、期待するJavaScriptのコードを生成できた。
2. VSCode Extensionの作成という、パッケージ管理からコードの生成までを含めた複雑な手順をAIが示すことができた。
3. 当初対応していなかった機能のサポートを後出しで依頼したところ、既存のコードを「修正」した。
4. それ以前のやり取りで作成済みだった関数について「あれをここにコピーしてください」と示して、「流用」してきた。
5. Markdownの階層構造をMermaid.jsのグラフで表現するという割と難しい概念を、ChatGPT-4があっさり理解した。

1回目の生成までは、エンジニアの経験から「まあ、やれるかな」ぐらいの期待値を持っていましたが、修正やライブラリ・コードの流用、そしてそもそもあまり一般的ではない要件を理解した理解力など、全くもって驚きとしかいいようのない能力を示しました。

これが大規模言語モデルが統計的に生成した「最も確からしいやりとり」に過ぎないことは分かってはいるのですが、ここまで出来るならそもそも我々が考えていた「理解」とはなんだったのか?という気もしてきます。

Discussion