はじめに
WezTermはクロスプラットフォームで動作する高度なターミナルエミュレータです。プログラミング言語のLuaを使って設定を記述するため、条件に応じた柔軟かつ高度な設定ができます。
その中で本記事は、「WezTermでタブバーとウィンドウに表示するテキストを自分の好きなように変更する方法」にテーマを絞って記載します。
※ WezTermのセットアップ方法の説明は割愛させていただきます。必要な方はDownload - Wez's Terminal Emulator等をご参照ください。
本記事の背景:ターミナルのプロンプトに沢山の情報を表示させたくない
ターミナルでコマンド操作する際に、「Gitの現在のブランチ」「Kubernetesの現在のコンテキスト」「Pythonの仮想環境」等をいつでも確認したくて、Starship等のツールを使ってプロンプトにそれらを表示する設定をされている方も多いと思います。
「これは便利そう!」と思って私もやってみたのですが、コマンドを打つ度にプロンプトにほぼ同じ情報が長い文字列で冗長に表示されてしまうのが受け容れられず(あくまで私個人の趣向です)、使うのを断念した経緯がありました。一方で、間違ったGitブランチやk8sコンテキストで操作してしまう事故を防ぐために「必要な情報は常に見えるようにしておきたい」という思いも持っていました。
そんな折に知ったのが、WezTermという興味深いターミナルツール。数ヶ月ほど使って、「WezTermだったら必要な情報をプロンプトではなくて、タブバーやタイトルに表示できるんじゃない!?」と思いつきました。
実現したいこと
- タブのタイトル
- 各タブのターミナルセッションにおける、Gitの現ブランチとディレクトリ名を表示
- ディレクトリがGitリポジトリでなければ、ディレクトリ名のみ表示
- ウインドウのタイトル
- 現在表示されているターミナルセッションの、Gitの現ブランチとディレクトリ名を表示
- ディレクトリがGitリポジトリでなければ、ディレクトリ名のみ表示
- タブバーの左側
- Kubernetesの現在のコンテキストを表示
言葉ではわかりにくいと思うので、下の図で目指す状態を示します。
(タブバーの位置はデフォルトだと上ですが、私はtab_bar_at_bottomをtrue
にして下に設定しています)
図の表示内容を言葉で説明すると以下です。
- git-repoというディレクトリはGitリポジトリで現ブランチがmainなので、タブに
main:git-repo
と表示 - non-git-repoはGitリポジトリではないので、タブに
non-git-repo
と表示 - Kubernetesの現在のコンテキスト タブバーの左側に
kind-cluster
と表示
wezterm.lua
に実装
それでは実装してみましょう。
紹介するソースコードは、今回やりたいことを実現するのに必要最低限の内容しか書いていません。WezTermのコンフィグで共通に必要な記述はQuick Start等を参考にしてください。
タブバーの左側に、Kubernetesの現在のコンテキストを表示
まずは実装がわりと簡単な、タブバーの左側にテキストを追加するソースコードを紹介します。
-- 現在の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のコンテキストが出力されるようになります。
参考情報
-
wezterm.run_child_process
のドキュメント -
wezterm.on('update-status', callback)
のドキュメント -
wezterm.on(event_name, callback)
のドキュメント
タブとウィンドウのタイトルに、現ブランチとディレクトリ名を表示
こちらを実現するには一工夫必要です。
タブのタイトルとウィンドウのタイトルを変更するには、先ほども出てきた wezterm.on
関数を使います。それぞれの第一引数には "format-tab-title"
,"format-window-title"
を指定して、第二引数のコールバック関数で何らかの処理をして出力したい文字列を返します。以下はその簡単な例です。
-- タブのタイトルを変更
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
上記はあくまでもお試しで、改めて実現したいことを確認すると以下になります。
- 現ディレクトリがGitリポジトリの場合 -> 現ブランチとディレクトリ名を表示
- 現ディレクトリがGitリポジトリでない場合 -> ディレクトリ名のみ表示
これを実現するためには、wezterm.on
のコールバック関数で現在のディレクトリ情報を取得したり、Gitのブランチ名を取得したりする必要があります。これらは "format-tab-title"
と "format-window-title"
で共通に使うので、最初にその関数を作っておきましょう。
-- 現ディレクトリと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", ...
でやってみます。
-- タブのタイトルを変更
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のドキュメント を見ると、「"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
というのは「タブをさらに分割した領域」を意味します。
-- 現ディレクトリと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)
ここまでできれば、以下のようにタブとウィンドウに「ブランチ名:ディレクトリ名」が表示されるはずです!