皆さんはVSCodeで作業してて、
- 「ターミナル消しちゃったけど、さっき実行したコマンドなんだっけ」
- 「意外と処理に時間かかるなぁ。時間計っておけばよかった」
- 「実行終わるまでちょっと休憩してこよ」
なんて思うことありませんか。私はあります。
そんなわけで、VSCode拡張機能でターミナル出力を取得して、外部にログを通知できるものを作りました。
実装計画は次のようになっています。
- コマンドの出力を取得する
- 取得した出力を外部に通知する(今回はdiscordのAPIを叩く)
実装した拡張機能のソースコードは、次のリポジトリで公開しています。
実行環境
- TypeScript
- VS Code Extension API
- Node.js
Shell Integration APIでコマンドを検知する
VS Codeには、統合ターミナル上のコマンド実行を扱うためのAPIがあります。Shell Integration APIというらしい。
今回使用したイベントは次の2つです。
時間を測る場合には、これらのイベントが発火したタイミングを確認するとよいと思います。
| イベント | 発生するタイミング |
|---|---|
onDidStartTerminalShellExecution |
コマンドの実行開始時 |
onDidEndTerminalShellExecution |
コマンドの実行終了時 |
出力ストリームは、 TerminalShellExecution の event から read() メソッドで読み取ります。
しかし、この時点で取得できるのはコマンドごとの出力ではなく、実行中にストリームで届くデータなので、awaitを使用した非同期処理で取得します。一旦配列に格納し、コマンド実行終了後に join('') しましょう。
const startDisposable =
vscode.window.onDidStartTerminalShellExecution(async (event) => {
const execution = event.execution;
const stream = execution.read();
for await (const data of stream) {
// do something
}
});
実行してみた
実行コマンド:echo hello
得られた文字列:
]633;Chello
[1m[7m%[27m[1m[0m
何かついてますね。どうやら制御文字が含まれているようです。
調べてみると、VS CodeのShell Integrationが有効なターミナルでは、OSC 633と呼ばれるシーケンスも含まれるようです。
それに加えて、ANSIエスケープシーケンスも含まれてるので、これらを除去してみましょう。
ANSIエスケープシーケンスを除去する
ANSIのCSI(Control Sequence Introducer)シーケンスは、一般にESCと [ から始まります。
今回の実装では、次の正規表現で除去します。
text.replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, '');
正規表現を分解すると次のようになります。
| パターン | 対象 |
|---|---|
\u001b\[ |
ESCに続く [
|
[0-9;?]* |
数値などのパラメーター |
[ -/]* |
中間バイト |
[@-~] |
シーケンスの終端文字 |
これにより、文字色、背景色、カーソル移動などに使われる代表的なCSIシーケンスを取り除けます。
VS CodeのOSC 633を除去する
text.replace(
/\u001b\]633;.*?(?:\u0007|\u001b\\)/g,
''
);
完成
無事にdiscordでターミナルのログを確認することができました。
出力が少し見づらいのでdiscordのembedも試しましたが、どうやらテキストファイルの方が表示の優先度が高いようで、出力の方が先に表示されるようになりました。

改善点
- VSCodeが起動した時の仮想環境を起動する際の
sourceコマンドで発火してしまう - 文字数制限の影響か、ログが多いコマンドでたまに動作しない
まとめ
今回の拡張機能は、VS Codeの統合ターミナル上で実行されたコマンドの出力をdiscordに送ることができるようになりました。
