vscode拡張機能でGUIベースのアプリ配置ツールを作るための機能名
vscodeでアプリの配置を変更する拡張機能を作ろうと思ったときにAIに質問した内容をメモとして残します
要件
・macOS上で機能するvscode拡張機能を提供する
・web版のvscodeで動作する
・HTMLベースのGUIを実装する
・.pycetra.mdのファイルを開いたとき、自動的にGUIを開く
・.pycetra.mdのファイルを開いたとき、そのファイルのデータをGUIに表示する
・GUI側からvscode拡張側へデータを送る
・起動しているアプリの座標を取得する
・起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
・アプリの使用中に、ターミナルを開かない
技術選定
・GUI の E2E テストを行うためのライブラリについて
・コマンドを実行する環境について
要件
アプリのGUIとしてweb viewを使用
・要件:HTMLベースのGUIを実装する
const panel = vscode.window.createWebviewPanel
web版のvscodeでアプリが動くようにする設定
・要件: web版のvscodeで動作する
package.json
"browser": "./dist/extension.js",
テキストドキュメントが開かれたときに発生するイベント
・要件:.pycetra.mdのファイルを開いたとき、自動的にGUIを開く
vscode.workspace.onDidOpenTextDocument
vscode拡張側からGUI側へデータを送る
・要件:.pycetra.mdのファイルを開いたとき、そのファイルのデータをGUIに表示する
const panel = vscode.window.createWebviewPanel
panel.webview.postMessage
apple scriptの結果をpythonへ渡す
・要件:起動しているアプリの座標を取得する
・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
const { exec } = require('child_process');
async function runAppleScript(scriptPath) {
return new Promise((resolve, reject) => {
exec(`osascript ${scriptPath}`, (error, stdout, stderr) => {
if (error) {
reject(`エラー: ${error.message}`);
} else if (stderr) {
reject(`標準エラー: ${stderr}`);
} else {
resolve(stdout.trim());
}
});
});
}
async function runPythonScript(scriptPath, arg) {
return new Promise((resolve, reject) => {
exec(`python ${scriptPath} "${arg}"`, (error, stdout, stderr) => {
if (error) {
reject(`エラー: ${error.message}`);
} else if (stderr) {
reject(`標準エラー: ${stderr}`);
} else {
resolve(stdout.trim());
}
});
});
}
const appleScriptResult = await runAppleScript('path/to/your/example.scpt');
console.log(`AppleScript Result: ${appleScriptResult}`);
const pythonResult = await runPythonScript('path/to/your/script.py', appleScriptResult);
console.log(`Python Script Result: ${pythonResult}`);
(Pythonの処理の中でアプリの座標を計算して座標を更新する。)
ファイルの内容を同期的に読み取る
fs.readFileSync
macで特定のアプリを移動する
・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
例: Finder
osascript -e 'tell application "Finder" to set bounds of window 3 to {100, 100, 800, 600}'
起動中のアプリ名を取得
・要件:起動しているアプリの座標を取得する
例1: すべてのアプリ
osascript -e 'tell application "System Events" to get {name} of (every process whose background only is false)'
{name} のみでなく、{name, index}など複数の情報にするとアクセシビリティが必要になってしまうためNG
例2: Finder
osascript -e 'tell application "Finder" to get {index, name} of windows'
操作スペースをまたいでアプリを移動
・要件:起動しているアプリの座標を更新することで、操作スペース(Spaces)内/間をアプリが移動する
操作スペース(Spaces)を跨いで移動する機能をosascriptに見つけられなかった。
他のツールですが、yabaiを使うとアプリをスペースを跨いで移動させることができました。
brew install koekeishiya/formulae/yabai
brew services start yabai
brew install jq
// yabaiへアクセシビリティを許可しておく
yabai -m query --windows | jq -r '.[] | select(.app == "Finder") | .id' | xargs -I{} yabai -m window {} --space 3
ターミナルを開かずにコマンドを実行
・要件:アプリの使用中に、ターミナルを開かない
const { execSync, exec } = require('child_process');
web view側からvscode側へpostMessage
・要件:GUI側からvscode拡張側へデータを送る
document.addEventListener('DOMContentLoaded', (event) => {
vscode.postMessage({
command: 'init_message',
});
});
技術選定
基本設計 - 初期化とサイクルについて -
後述のe2dのテストを検討する上で必要になる、vscode拡張の設計をまとめます。
AIにmermaid.js形式で作ってもらいましたが、うまく描画ができなかったのでパワポで作成
mermaid版
E2E (エンドツーエンド) テストについて
"基本設計 - 初期化とサイクルについて"の内容をもとにe2eのテストを作成
-
E2E: ユーザ操作 Event1 フロー
- 目的: ユーザがイベント Event1 を発生させた際、VSCode拡張機能内の WebView が正しくレンダリング (R1) されるかを検証する
-
シナリオ例:
- ユーザインターフェースで Event1 イベントをトリガーする
- ".md"ファイルを読みこむ
- WebView が起動し、期待するコンテンツ(".md")がレンダリングされることを確認する
-
E2E: ユーザ操作 Event2 と Notebook サイクル フロー
- 目的: ユーザがイベント Event2 を発生させた場合、notebook API が起動され、連続した処理(ステータス取得 ST → アプリの起動 AP → 書き込み WR → 最終レンダリング R2)が実行され、最終結果が WebView に反映されるかを検証する
-
シナリオ例:
- 前提: ユーザ操作 Event1 フロー が完了後の画面を想定
- ユーザが Event2 イベントを発生させる
- notebook API を介して内部処理が開始される
- notebook サイクル(ST → AP → WR)が正しい順序で実行される
- 最終的に WebView (R2) で結果が正しく表示される
GUI の E2E テストを行うためのライブラリについて
-
環境の評価項目
- VSCode 拡張の起動から、WebView 上の操作までを一連の流れとしてテストしたい。
- vscode api(ローカルファイルの読み/書き)
- webviewの操作
-
yabai
コマンドの実行を含む macOS 固有の外部処理
- テスト駆動開発
- コード修正からテストまでの待ち時間が短いこと
- VSCode 拡張の起動から、WebView 上の操作までを一連の流れとしてテストしたい。
VSCode 拡張 + WebView E2E テスト 構成案 4 選
vscode-test-web |
vscode-test |
open vscode server |
code serve-web |
Playwright |
Remote Jupyter Server |
|
---|---|---|---|---|---|---|
案1 Web版 + Remote Jupyter Server |
○ | - | - | - | ○ | ○ |
案2 Desktop版 |
- | ○ | - | - | ○ | - |
案3 open vscode server |
- | - | ○ | - | ○ | - |
案4 VS Code Server |
- | - | - | ○ | ○ | - |
評価
テスト環境評価項目 | VSCode API (ローカルファイルの読み書き) | WebView の操作 | macOS 固有の外部処理 (yabai コマンド) | コード修正からテストまでの待ち時間が短いこと |
---|---|---|---|---|
案1 Web版 + Remote Jupyter Server |
○ | ○ | ○ (Jupyterを介して) |
○ (ホットリロード可能) |
案2 Desktop版 |
○ | × (Playwrightによる操作について公式サポートなし) |
- | 不明 |
案3 open vscode server |
○ | ○ | ○ | △(ホットリロード不可 |
案4 VS Code Server |
○ | ○ | ○ | △(ホットリロード不可 |
まとめ
-
案1 Web版 + Remote Jupyter Server : 一連の処理をe2eでテスト可能。かつvscode拡張のホットリロードが可能なため開発体験よし。
- ↑ 採用!(開発用)
- 案2 Desktop版: webviewのテストをする方法を見つけらえれず断念
-
案3 open vscode server: desktop版のセットアップと、web版では制限のあったAPIのテストを担当
- ↑ 採用!(リリース判定のテストで使用)
-
案4 VS Code Server: Microsoft 公式のリモート VS Code。 公式サービスのため、安定性や拡張性の面では案3 より優位な面も多い。ただし、利用枠や課金モデル(Codespaces など) に注意が必要。
- ↑ 案3の代替として現在検討中
コマンドを実行する環境について
-
Python Interactive Window
- 公式の安定したAPIが存在しない(※2025年1月時点): Python拡張機能やJupyter拡張機能の内部APIを利用して実現する方法はあるが、これらはドキュメント未整備または変更リスクが高い場合がある。
- 内部APIや非公式な方法への依存リスク: 仕様変更や非推奨化が起こった場合、拡張機能側で大規模な修正が必要になる可能性がある。
- セッション状態管理の難しさ: インタラクティブウィンドウ内での変数・環境が拡張機能からは把握しにくく、意図しない状態変化や衝突が起こる可能性がある。
-
Jupyter Notebook
- .ipynb形式特有の運用上の課題: バイナリ形式を含むJSONベースのファイルは差分や競合を扱いにくく、チーム開発でのバージョン管理に注意が必要。
- Jupyterサーバーの環境構築の複雑化: ms-python, ms-toolsai.jupyterの拡張機能が必要。
-
ターミナル
- データの受け渡しに追加の手間が発生: 複数のスクリプトやプロセスを連結する際、ファイルや標準出力・標準入力などを介してデータをやり取りする必要があり、管理が煩雑になる。
- 標準出力ベースの結果取得の制限: 出力されたテキストをパースするなど、拡張機能側で独自の処理を行わなければ途中経過の取得やエラー解析が難しい。
まとめ
- Python Interactive Window:VS Code拡張機能で直接利用するための公式かつ安定したAPIは乏しく、内部APIの変更に左右される可能性がある。
- Jupyter Notebook:名前空間の共有やノートブック形式固有の管理上の課題がある。
- ↑採用!
- ターミナル:シンプルな仕組みだが、プロセスやデータ管理を拡張機能側で細かく制御する必要があり、スクリプトの連携や長時間実行などが複雑化しやすい。
キーワード
- ウィンドウマネージャ: yabai, Amethyst, xmonad
- ステージマネージャ: macOS Venturaの組み込みのウィンドウマネージャ
- 画面分割ツール: ShiftIt, Spectacle, Magnet, BetterSnapTool
Discussion