VSCode でブランチごとにタブ状態を復元する拡張

gitswitch <branch> したときに VSCode のタブ状態がブランチごとに保存/復元できたら,切替時に楽だな〜と思った.のでそういう拡張機能を作ろうと思った.

たとえば

  • feature-add-user ブランチでは: User.pm, User.t, UserRepository.pm
  • feature-add-work ブランチでは: Work.pm, Work.t, WorkRepository.pm

を開いて作業するのが心地よい,とかザラにあると思われる.

そんなときに毎回切替時に全タブ閉じて,必要なファイルを Cmd+P で開いてとか面倒の極みだな,と思っていた.開きっぱだとごちゃごちゃしてきて精神衛生上よくない.

git でファイルの状態は変わるのなら,タブの状態も変わっていいだろうと思った.

結果

できあがった拡張機能がこちら:

marketplace.visualstudio.com

github.com

ブランチごとにタブ状態の保存/復元機能

現在いるブランチのタブ状態のセッションをコマンドから保存/復元できる機能:

https://raw.githubusercontent.com/mangano-ito/git-branch-wise-session/master/assets/save-and-restore.gif

これがないと話にならない.

(UPDATE) コマンドはこの記事を書いたあとで Git Branch-Wise Session: の prefix をつけるようにしたので,とりあえず Ctrl + P or ⌘ + P して > Git Branch とか入れれば候補が出てくるようになった.

  • セッションの保存: Ctrl + P / ⌘ + P → 入力 「> Save Session for Current Branch
  • 今いるブランチのセッションの復元: Ctrl + P / ⌘ + P → 入力 「> Restore Saved Session for Current Branch

セッションは > Clear Session とか打つと出てくるコマンドで消せる.

ブランチ切り替わったら自動で復元してくれる機能

git switch でブランチが切り替わったら,保存したセッションがあれば自動復元してくれる機能:

https://raw.githubusercontent.com/mangano-ito/git-branch-wise-session/master/assets/auto-restore.gif

要・不要わかれそうなのでオプションでデフォルト OFF にした.

Ctrl + P / ⌘ + P → 「> Open Settings」 でワークスペースごと or グローバルで設定変更できる.

なやみポイント 1

VSCodeAPI には今開いてる全タブ (vscode.TextEditor) を取得する API がないということ.

なので,「次のタブに切り替え」アクション (workbench.action.nextEditor) を発行しまくって,今のタブっぽいのに戻ってくるまで繰り返して,全部のタブを入れるという激ゴリハックが提案されていた:

github.com

こういう感じになる:

import * as vscode from 'vscode';

async function *getOpenedTabs(): AsyncGenerator<vscode.TextEditor> {
    const active = vscode.window.activeTextEditor;
    if (!active) {
        return;
    }
    yield active;

    const equals = (lhs: vscode.TextEditor, rhs: vscode.TextEditor) => {
        return lhs.document.uri === rhs.document.uri
            && lhs.viewColumn === rhs.viewColumn;
    };

    while (true) {
        await vscode.commands.executeCommand<vscode.TextEditor>('workbench.action.nextEditor');
        const editor = vscode.window.activeTextEditor;
        if (!editor || equals(active, editor)) {
            break;
        }
        yield editor;
    }
}

嘘やろ,って思いますよね.この Issue 2016 年 からあるというので謎です.

なやみポイント 2

VSCodevscode.Memento というインタフェースがあって,これはワークスペース (or グローバル)に設定を保存しておける.(Memento パターンとかの Memento ですね).

これは Map<string, any> みたいな感じで KVS 感覚で保存できる.ので楽だけど,保存されているすべてのキーを enumerate することはできない.

なので,結局のところ適当な単位でキーに複数の値を詰め込むように JSON.stringify して保存することになる.

なやみポイント 3

この拡張は別のビルトイン拡張 vscode.git に依存しているので,vscode.git の初期化を待たないといけない.

最初は vscode.extension.onDidChange で初期化のイベントこないじゃん…って絶望して,

await new Promise(
    (resolve) => {
        const check = () => {
            setTimeout(() => {
                if (vscode.extensions.getExtension('vscode.git')) {
                    return resolve();
                }
                check();
            }, 100);
        };
        check();
    }
);

みたいなすごくダサいゴリゴリ待つコードを書いていた.

その後調べたら,package.json の manifest で依存している拡張を指定できることに気づいて解決した:

{
    "extensionDependencies": [
        "vscode.git"
    ]
}

なやみポイント4

VSCodeAPI ではタブがどのカラムに表示されているかは vscode.TextEditor.viewColumn: vscode.ViewColumn で取得できる.

が,この vscode.ViewColumnnumberエイリアスであって,普通に番号なのだった.

で,この番号は絶対的なペインの番号とかではなくて,左から 1, 2, 3, ... みたいに振られているということにデバッグしていて気づいた.

だから,そのタブが Window 下部のペインにアタッチされていたとしても 2 だし,右側にアタッチされていても 2 みたいなケースがあって,復元時にペインの状態を完全に元通りにできないという課題がある.

もしかしたら別の API でレイアウト状態を取れるかもしれない.

しかしながら,

まあいいか,と思った.ブランチごとに復元できることが目的であって,ペインの状態はめんどうだけど手で戻すこともできる.

そんなこといったらこの拡張機能の意義は,となって,なにもかも破壊したい気分になってきた.