WezTermのタブバーとウィンドウの表示テキストを変更する

# はじめに

WezTermはクロスプラットフォームで動作する高度なターミナルエミュレータです。プログラミング言語のLuaを使って設定を記述するため、条件に応じた柔軟かつ高度な設定ができます。

その中で本記事は、「WezTermでタブバーとウィンドウに表示するテキストを自分の好きなように変更する方法」にテーマを絞って記載します。

※ WezTermのセットアップ方法の説明は割愛させていただきます。必要な方は[Download - Wez's Terminal Emulator](https://wezfurlong.org/wezterm/installation.html)等をご参照ください。

# 本記事の背景：ターミナルのプロンプトに沢山の情報を表示させたくない

ターミナルでコマンド操作する際に、「Gitの現在のブランチ」「Kubernetesの現在のコンテキスト」「Pythonの仮想環境」等をいつでも確認したくて、Starship等のツールを使ってプロンプトにそれらを表示する設定をされている方も多いと思います。
「これは便利そう！」と思って私もやってみたのですが、コマンドを打つ度にプロンプトにほぼ同じ情報が長い文字列で冗長に表示されてしまうのが受け容れられず（あくまで私個人の趣向です）、使うのを断念した経緯がありました。一方で、間違ったGitブランチやk8sコンテキストで操作してしまう事故を防ぐために「必要な情報は常に見えるようにしておきたい」という思いも持っていました。

そんな折に知ったのが、WezTermという興味深いターミナルツール。数ヶ月ほど使って、「WezTermだったら必要な情報をプロンプトではなくて、タブバーやタイトルに表示できるんじゃない！？」と思いつきました。

# 実現したいこと

* タブのタイトル
    * 各タブのターミナルセッションにおける、Gitの現ブランチとディレクトリ名を表示
    * ディレクトリがGitリポジトリでなければ、ディレクトリ名のみ表示
* ウインドウのタイトル
    * 現在表示されているターミナルセッションの、Gitの現ブランチとディレクトリ名を表示
    * ディレクトリがGitリポジトリでなければ、ディレクトリ名のみ表示
* タブバーの左側
    * Kubernetesの現在のコンテキストを表示

言葉ではわかりにくいと思うので、下の図で目指す状態を示します。
（タブバーの位置はデフォルトだと上ですが、私は[tab_bar_at_bottom](https://wezfurlong.org/wezterm/config/lua/config/tab_bar_at_bottom.html)を``true``にして下に設定しています）

![screenshot 915.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/6aa96620-fb26-0ba9-3756-a5e7712c087c.png)

図の表示内容を言葉で説明すると以下です。
* git-repoというディレクトリはGitリポジトリで現ブランチがmainなので、タブに ``main:git-repo`` と表示
* non-git-repoはGitリポジトリではないので、タブに ``non-git-repo`` と表示
* Kubernetesの現在のコンテキスト タブバーの左側に ``kind-cluster`` と表示

# ``wezterm.lua`` に実装

それでは実装してみましょう。

:::note warn
紹介するソースコードは、今回やりたいことを実現するのに必要最低限の内容しか書いていません。WezTermのコンフィグで共通に必要な記述は[Quick Start](https://wezfurlong.org/wezterm/config/files.html#quick-start)等を参考にしてください。
:::


## タブバーの左側に、Kubernetesの現在のコンテキストを表示

まずは実装がわりと簡単な、タブバーの左側にテキストを追加するソースコードを紹介します。

```lua:wezterm.luaの一部
-- 現在のKubernetesコンテキストを取得
local function get_kube_context()

  -- kubectl は絶対パスで指定しないと動かなかった
  local success, stdout, stderr = wezterm.run_child_process({ "/usr/local/bin/kubectl", "config", "current-context"})

  if success then
    -- "\27[36m" で表示色をシアンに設定しているので、お好みで変更する
    return "\27[36m" .. stdout .. "\27[0m\n"
  else
    return "No context"
  end

end

-- WezTermのタブバーやウィンドウの情報を定期的に更新する
wezterm.on('update-status', function(window, pane)

  -- 現在のKubernetesコンテキストを取得して、ステータスバーに設定
  local kube_context = get_kube_context()

  -- 取得したコンテキスト情報をタブバーの左側に設定
  window:set_left_status(kube_context)

end)
```

この記述で、タブバーの左側にKubernetesのコンテキストが出力されるようになります。

![screenshot 915のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/58d7ca14-19bd-8330-8c3f-eea27476ff8d.png)

### 参考情報

* ``wezterm.run_child_process`` の[ドキュメント](https://wezfurlong.org/wezterm/config/lua/wezterm/run_child_process.html)
* ``wezterm.on('update-status', callback)`` の[ドキュメント](https://wezfurlong.org/wezterm/config/lua/window-events/update-status.html?h=update+status)
* ``wezterm.on(event_name, callback)`` の[ドキュメント](https://wezfurlong.org/wezterm/config/lua/wezterm/on.html)

## タブとウィンドウのタイトルに、現ブランチとディレクトリ名を表示

こちらを実現するには一工夫必要です。

タブのタイトルとウィンドウのタイトルを変更するには、先ほども出てきた ``wezterm.on``関数を使います。それぞれの第一引数には ``"format-tab-title"``,``"format-window-title"``を指定して、第二引数のコールバック関数で何らかの処理をして出力したい文字列を返します。以下はその簡単な例です。

```lua:wezterm.luaの一部
-- タブのタイトルを変更
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)
  return 'hoge'
end)

-- ウィンドウのタイトルを変更
wezterm.on("format-window-title", function(tab, pane, tabs, panes, config)
  return 'fuga'
end)
```

試しに上のように書いて保存すると、本当にタイトルが hoge と fuga に変わってしまいますw

![screenshot 919.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/c2ba727d-a166-48a8-ddd9-7d44117d7799.png)

上記はあくまでもお試しで、改めて実現したいことを確認すると以下になります。

* 現ディレクトリがGitリポジトリの場合 -> 現ブランチとディレクトリ名を表示
* 現ディレクトリがGitリポジトリでない場合 -> ディレクトリ名のみ表示

これを実現するためには、``wezterm.on`` のコールバック関数で現在のディレクトリ情報を取得したり、Gitのブランチ名を取得したりする必要があります。これらは ``"format-tab-title"`` と ``"format-window-title"``で共通に使うので、最初にその関数を作っておきましょう。

```lua
-- 現ディレクトリとgitブランチ名を取得
local function set_title(pane)

  local cwd_uri = pane:get_current_working_dir()

  local cwd_uri_string = wezterm.to_string(cwd_uri)
  local cwd = cwd_uri_string:gsub("^file://", "")

  if (not cwd) then
    return nil
  end

  -- Gitのブランチ名を取得
  local success, stdout, stderr = wezterm.run_child_process({
    "git", "-C", cwd, "branch", "--show-current"
  })

  local current_dir = cwd:match("^.*/(.*)$")

  local ret = current_dir

  -- Gitブランチ名を取得できたら「ブランチ名:ディレクトリ名」と表示できるようにする
  if success then
    local branch = stdout:gsub("%s+", "")
    ret = branch .. ':' .. current_dir
  end

  return ret

end
```

あとは、``wezterm.on`` から上の関数を呼べばいけるはず！
試しに ``wezterm.on("format-tab-title", ...`` でやってみます。

```lua
-- タブのタイトルを変更
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)

  local pane = tab.active_pane
  local title = set_title(pane)

  if title then
    return title
  else
    return tab.active_pane.title
  end

end)
```

ところが、これが動いてくれません...
何でだろうと思って [format-tab-titleのドキュメント](https://wezfurlong.org/wezterm/config/lua/window-events/format-tab-title.html) を見ると、「``"format-tab-title"`` は同期的(synchronous)な処理を行うため、``wezterm.run_child_process``のような非同期的(asynchronous)な関数を呼ぶことができない」と書いてあります。 ところがGitの現在のブランチを知るためには、``wezterm.run_child_process`` を使う必要があります。さてどうしたものか...

ChatGPTの助けを借りたところ、次のように回答がありました。

* (1) 各タブの「ブランチ名:ディレクトリ名」を格納するテーブル型の変数を用意する
* (2) ``set_title``関数を非同期処理の ``wezterm.on("update-status", callback)`` 関数で呼び出し、(1)のテーブルに ``set_title`` の結果を格納していく
* (3) ``wezterm.on``の``"format-tab-title"``,``"format-window-title"``で、(2)で格納したテーブルの値を参照して「ブランチ名:ディレクトリ名」を取得する

ざっくり言えば、「各タブの情報をテーブルに記憶させておいて、それを使ってタブやウィンドウを更新すればいい」みたいです。

これを踏まえて書いたソースコードが以下です。
コード中で出てくる ``pane`` というのは「タブをさらに分割した領域」を意味します。

```lua:wezterm.luaの一部
-- 現ディレクトリとgitブランチ名を取得
local function set_title(pane)
-- (既にコードを載せているので省略)
end

-- 各タブの「ブランチ名:ディレクトリ名」を記憶しておくテーブル
local title_cache = {}

-- 各タブ（正確にはpane）に「ブランチ名:ディレクトリ名」を記憶させる
wezterm.on("update-status", function(window, pane)

  local title = set_title(pane)
  local pane_id = pane:pane_id()
  
  title_cache[pane_id] = title
  
end)

-- タブのタイトルを変更
wezterm.on("format-tab-title", function(tab, tabs, panes, config, hover, max_width)

  local pane = tab.active_pane
  local pane_id = pane.pane_id

  -- 記憶させていた「ブランチ名:ディレクトリ名」を取り出す
  if title_cache[pane_id] then
    return title_cache[pane_id]
  else
    return tab.active_pane.title
  end

end)

-- ウィンドウのタイトルを変更
wezterm.on("format-window-title", function(tab, pane, tabs, panes, config)

  --[[
     以下サイトの書き方に従う
     https://wezfurlong.org/wezterm/config/lua/window-events/format-window-title.html
  ]]
  local zoomed = ''
  if tab.active_pane.is_zoomed then
    zoomed = '[Z] '
  end

  local index = ''
  if #tabs > 1 then
    index = string.format('[%d/%d] ', tab.tab_index + 1, #tabs)
  end

  local title = tab.active_pane.title

  local pane_id = pane.pane_id

  -- 記憶させていた「ブランチ名:ディレクトリ名」を取り出す
  if title_cache[pane_id] then
    title = title_cache[pane_id]
  end

  return zoomed .. index .. title

end)

```

ここまでできれば、以下のようにタブとウィンドウに「ブランチ名:ディレクトリ名」が表示されるはずです！

![screenshot 915のコピー2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370519/a037384b-3089-1a6e-4036-098b071189fa.png)