概要
VSCode の機能拡張でエクスプローラー上からコンテキストメニュー経由で実行するコマンドを実装した際、呼び出されるコマンドが受け取る引数の説明が公式ドキュメントに無く、またそのパターンもやや特殊であった。
公式リファレンスには package.json
の menu
に設定する値として explorer/context
が使えることは書かれているが、そのコンテキストメニューからコマンドが呼び出されたときの引数についての説明がない。
ここではその調査結果をまとめる。
調査時点での VSCode のバージョン
Version: 1.96.2 (Universal)
引数について
基本的には第1引数に vscode.Uri
型の値、第2引数に vscode.Uri[]
型の値が渡される。
つまりコマンドを実行する関数の定義は以下の様な感じになる。
export function my_command_from_explorer(uri: vscode.Uri, selectedFiles?: vscode.Uri[])
{
console.log( uri.fsPath );
}
vscode.Uri
はクラス。詳細は公式リファレンスを参照。
引数のパターン
分かりにくい仕様として条件によって渡される引数のパターンが異なる問題があった。具体的には以下の条件が影響する。
- エクスプローラー上のどこをクリックしてコンテキストメニューを開いたか
- クリックしたファイル・ディレクトリの選択状態
- 他のファイル・ディレクトリの選択状態(無視されたり無視されなかったりする)
これらのパターンの調査結果は以下の様になる。
コンテキストメニューを開いた場所 | 当該アイテムの選択状態 | 他の選択状態 | 第1引数 | 第2引数 |
---|---|---|---|---|
空白部分 | - | 無視される | ワークスペースフォルダー | [] |
ファイル | 選択状態 | 無し | 当該ファイル | ["当該ファイル"] |
〃 | 非選択状態 | 無し | 当該ファイル | ["当該ファイル"] |
〃 | 選択状態 | 有り | 当該ファイル | ["当該ファイル", ..."選択中のファイルやフォルダ"] |
〃 | 非選択状態 | 有り | 当該ファイル | ["当該ファイル"] |
※表中の「ファイル」は「ディレクトリ」の場合も同様であるため「ファイルまたはディレクトリ」と読み替え可能
表だと分かりにくいため、以下特徴的な仕様について説明する。
1. エクスプローラーの空白部分 == ワークスペースフォルダ、という判定
エクスプローラーの空白部分でコンテキストメニューを開いた場合、ワークスペースフォルダ自体が処理対象となる。
エクスプローラーの空白部分とは下図の赤点線で囲んだ部分のこと。
この時エクスプローラー上でファイルやフォルダが選択状態にあっても無視され、ルートフォルダのパスが渡される。ユーザーが GUI の状態からこの内部仕様を予測するのは難しい。
もし実行するコマンドが破壊的な処理1を行うならばユーザーの期待を裏切った上に、大きな損失を与える事になるだろう。
破壊的1なコマンドを提供するならばユーザーに事前に適切なダイアログを提示すべきだろう。
エクスプローラーの空白部分でコンテキストメニューが開かれた事を判定する条件
調査の結果から以下の条件を満たすとき、エクスプローラーの空白部分からコンテキストメニューが起動されたと判定出来る。
- 第1引数がワークスペースフォルダを指している
- 第2引数が空の配列である
プログラム側はこの条件を元に警告を出すことが出来る。
悩みどころ
- そもそも第2引数が空のリストになるため、第2引数だけを参照する様にすれば「処理対象無し」とすることも出来る
- しかし、ルートフォルダ全体にコマンドを適用したいケースもある場合、第2引数だけを見る方法は採用できない
2. 選択状態のファイルが存在してもクリックした位置で渡されるファイルが異なる
例えばエクスプローラーの選択状態が下図の状態を例とする。
この時 memo.md
を右クリックしてコンテキストメニューからコマンドを実行した場合、第1引数にも第2引数にも memo.md
しか渡されない。
つまり以下の様になる。
-
第1引数:
memo.md
-
第2引数:
memo.md
しかし edited-state.diff
の上でコンテキストメニューを開いた場合以下の様になる。
-
第1引数:
edited-state.diff
-
第2引数:
sub-dir/buzz.md
edited-state.diff
in-padding-lines.md
※ 読みやすさのために相対パスにしているが実際には絶対パスで渡される
この仕様は比較的理解できる仕様であり、また破壊的な処理1であってもポインタが指し示すファイル・ディレクトリのみが対象となるためユーザーの意図に反することは無いだろう。
選択された他のファイル・ディレクトリが対象とならないことについての疑問も、再度の選択されたファイル・ディレクトリ上から実行すれば処理対象となるため、比較的短期間で経験的な理解を獲得すると思われる。
この仕様についてまとめると
右クリックしたファイル・ディレクトリの状態が: | 第2引数の値 |
---|---|
選択状態である | 他の選択状態のファイル・ディレクトリも含んだリストになる |
非選択状態である | 右クリックしたファイル・ディレクトリのみからなるリストになる |
プログラムからはユーザーがいずれの状態で操作を行ったのかを判定することは出来無い2。
結論
- 第1引数がワークスペースフォルダを指している場合、コマンドの性質によってはユーザーに告知すべき(第2引数だけを参照して無視する手段もあり得る)
- それ以外のケースでは通常第2引数を見れば対象とすべきファイル・ディレクトリのリストが得られ、それを対象に処理を行えば良い
- そもそも単一のファイル・フォルダ向けのコマンドである場合は第1引数のみを参照すれば良い(ただしワークスペースフォルダの場合だけは注意)