⌨️

最強のターミナル環境を求めた結果、WezTermに落ち着きそうな話

2023/12/31に公開

WezTermはいいぞ。
Zellijもいいぞ。

最強のターミナル環境を作りたい

Hyperを使っていましたが、タブ/ペイン周りがもうちょっと操作しやすいといいなあ & Ctrl-Cでコピーしたいなあということで色々あさり始めた。

どんなターミナルがほしいのか

  • タブ/ペインが操作しやすい
  • Ctrl-Cでコピーできる
  • それなりにデザインがいい
  • 背景透過できる
  • 高速

Alacrittyを試してみる

  • Rust製のモダンなターミナル
  • 設定はtomlで記述
  • タブ等はない

タブの代わりにtmuxなどのターミナルマルチプレクサを組み合わせて使うらしい。
ターミナルマルチプレクサについてもいろいろ調べてみたところ、Zellijというのが初心者フレンドリーで良さそう。
さっそくZellijとAlacrittyを組み合わせて使ってみたところ、Zellijの使い心地が思った以上に良かった。Alacrittyも高速動作でいい感じだった。ただ、Ctrl-CでコピーするためにZellij側の設定でcopy_on_selectをオフにしてCtrl-Cでコピーするようなキーバインドを設定してみたところ、こんどは強制終了のCtrl-Cが使えなくなることになってしまった。

WezTermを試してみる

  • Rust製のモダンなターミナル
  • 設定はLuaで記述

Alacrittyの情報を調べていたらWezTermというのが良いという話も見かけたので、試してみることにした。環境変数にLANG=ja_JP.UTF-8を設定することで日本語のインライン入力ができた。設定がLuaなので柔軟に設定できるのも強い。
Ctrl-Cでコピーする機能も標準では無いものの

{
    key = 'c',
    mods = 'CTRL',
    action = wezterm.action_callback(function(window, pane)
        selection_text = window:get_selection_text_for_pane(pane)
        is_selection_active = string.len(selection_text) ~= 0
        if is_selection_active then
            window:perform_action(wezterm.action.CopyTo('ClipboardAndPrimarySelection'), pane)
        else
            window:perform_action(wezterm.action.SendKey{ key='c', mods='CTRL' }, pane)
        end
    end),
}

引用: wez/wezterm#606
のように、「選択文字列の文字数が0なら普通にCtrl-Cを送信、そうでなければコピー」という処理を書くことで解決できた。良すぎる。

ただ、不満な点もあった。タブのデザインを変えたいと思ったのだが、これは設定でいじれる部分にも限界があった。
そこで、いっそWezTermのタブ/ペイン機能をすべて切り、Zellijと組み合わせて使うことにした。
しかし、ここでも問題が発生した。WezTermとZellijを組み合わせて使うと、さっきのCtrl-C設定のwindow:get_selection_text_for_pane(pane)が正常に動かないらしく、常に空文字列が返ってくるようになっていた。これによって、常に選択文字列がないと判定され、コピーができなくなってしまった。
悩んだ結果、Zellij側でコピーのコマンドにAlt-Cを設定し、WezTerm側で

c.keys = {
  {
    key = 'c',
    mods = 'CTRL',
    action = wezterm.action_callback(function(window, pane)
      window:perform_action(wezterm.action.SendKey{ key='c', mods='ALT' }, pane)
      window:perform_action(wezterm.action.SendKey{ key='c', mods='CTRL' }, pane)
    end),
  }
}

とすることで「Ctrl-Cを押したらCtrl-CとAlt-Cを同時に押したことにする」という脳筋名案を思いついた。こうすることで、文字列のコピーもプロセスの強制終了も両方可能になる。もちろん、プロセスの実行中にコピーしようとするとコピーと強制終了が同時に起こってしまうが、その時だけAlt-Cを使えばいいということで妥協した。

作った設定ファイル全体
local wezterm = require('wezterm')
local c = wezterm.config_builder()

c.font = wezterm.font_with_fallback({
  {family="Roboto Mono", weight="Regular"},
  {family="Noto Sans JP", weight="Regular"},
})
c.font_size = 10.5
c.color_scheme = "OneHalfDark"
c.adjust_window_size_when_changing_font_size = false
c.default_cursor_style = "SteadyBar"
c.window_background_opacity = 0.9
c.window_close_confirmation = 'NeverPrompt'
c.use_fancy_tab_bar = false
c.show_tab_index_in_tab_bar = false
c.enable_tab_bar = false
c.window_padding = {
  left = 0,
  right = 0,
  top = 0,
  bottom = 0,
}
c.disable_default_key_bindings = true
c.default_prog = {"fish", "-l", "-c", "zellij attach --index 0 --create"}
c.keys = {
  {
    key = 'c',
    mods = 'CTRL',
    action = wezterm.action_callback(function(window, pane)
      window:perform_action(wezterm.action.SendKey{ key='c', mods='ALT' }, pane)
      window:perform_action(wezterm.action.SendKey{ key='c', mods='CTRL' }, pane)
    end),
  }
}

wezterm.on('format-window-title', function(tab, pane, tabs, panes, config)
  return "WezTerm"
end)

return c

追記

上記の設定で妥協したつもりだったが、やはりWezTermとZellijは相性が悪いらしく、何かと競合してしまい使い勝手が悪くなってしまった。
今はZellijを使わずWezTermだけで運用している
タブのデザインについてはLuaでゴリ押すことでもそれなりに解決できることがわかった。

タブ関連の設定
local wezterm = require('wezterm')
local act = wezterm.action
local c = wezterm.config_builder()
local ARROW = wezterm.nerdfonts.pl_left_hard_divider
local FILLED_BLOCK = '█'
local GREEN = "#98c379"
local GRAY = "#abb2bf"
local BLACK = "#1e222a"
local BG = "#282c34"

c.use_fancy_tab_bar = false
c.show_tab_index_in_tab_bar = false
c.show_new_tab_button_in_tab_bar = false
c.colors = {
  tab_bar = {
    background = BG,
    active_tab = {
      bg_color = GREEN,
      fg_color = BLACK,
      intensity = "Normal",
      underline = "None",
      italic = false,
      strikethrough = false,
    },
    inactive_tab = {
      bg_color = GRAY,
      fg_color = BLACK,
      intensity = "Normal",
      underline = "None",
      italic = false,
      strikethrough = false,
    },
    inactive_tab_hover = {
      bg_color = GRAY,
      fg_color = BLACK,
      intensity = "Normal",
      underline = "None",
      italic = false,
      strikethrough = false,
    },
  }
}
wezterm.on('format-tab-title', function(tab, tabs, panes, config, hover, max_width)
  local edge_background = BG
  local edge_foreground = GRAY
  local background = GRAY
  local foreground = BLACK
  if tab.is_active then
    background = GREEN
    edge_foreground = GREEN
  end
  title = 'Tab #' .. tab.tab_index
  return {
    { Background = { Color = edge_foreground } },
    { Foreground = { Color = edge_background } },
    { Text = ARROW },
    { Background = { Color = edge_background } },
    { Foreground = { Color = edge_foreground } },
    { Text = FILLED_BLOCK },
    { Background = { Color = background } },
    { Foreground = { Color = foreground } },
    { Text = title },
    { Background = { Color = edge_background } },
    { Foreground = { Color = edge_foreground } },
    { Text = FILLED_BLOCK },
    { Background = { Color = edge_background } },
    { Foreground = { Color = edge_foreground } },
    { Text = ARROW },
  }
end)

おまけ

ウィンドウを最前面に表示したい

Alt-Spaceを押したときのメニューで「最前面に維持する」を選ぶと最前面に維持できる。

コマンドでやるにはwmctrlというツールを使ってwmctrl -r :ACTIVE: -b toggle,aboveとかやってやればいい......はずなのだがなぜかこれでは動かず、
実際にはbash -c 'wmctrl -r :ACTIVE: -b $([[ $(xprop -id $(xprop -root -f _NET_ACTIVE_WINDOW 0x " \$0\\n" _NET_ACTIVE_WINDOW | awk "{print \$2}") _NET_WM_STATE) =~ "ABOVE" ]] && echo "remove" || echo "add"),above'(https://askubuntu.com/questions/1008199/wmctrl-ubuntu-17-10-toggle-above-not-toggling-down から引用)とか書いてやる必要があるらしい。無情。

とにかく、このコマンドをOSのショートカットキー機能などにぶちこんでやると好きなショートカットでこのコマンドを呼べるようになり便利。

ターミナルを任意のショートカットキーで呼び出したい

WezTerm側の設定で

wezterm.on('format-window-title', function(tab, pane, tabs, panes, config)
  return "WezTerm"
end)

としてウィンドウのタイトルを"WezTerm"に固定したうえで
OSのショートカットキー機能にwmctrl -F -a WezTerm

Discussion