Tmuxinatorの代替ツールをDenoで作った
Tmuxinator の代替CLIツール qtmut *1 を作りました。
1. これはなに
Tmuxのセッションを新規に作成する際の、windowやpaneの初期設定をYAML形式のファイルで記述できるツールです。
2. 使い方
セッションを開始するディレクトリ (-c
オプションで指定されたディレクトリ、未指定の場合はカレントディレクトリ) に以下のような .qtmut.yaml
ファイルを作成すると、そのファイルに記述されたようにTmuxのwindowやpaneが作成されます。
# .qtmut.yaml active: window: main pane: 1 windows: - name: main layout: even-horizontal panes: - command: - command: vim - layout: tile panes: - command: bundle exec rails s - command: tail -f logs/development.log
3. なぜ自作したか
ツールを自作したモチベーション、およびTmuxinatorなどの既存ツールとの差異について説明するために、私の普段のTmuxの利用方法について以下で述べます。
3.1. 普段のTmuxの利用方法
私は普段以下のようなZLEウィジェットとキーバインドを用いて、GitリポジトリごとにTmuxセッションを立ち上げ、または切り替えています。
select-ghq-session() { local root="$(ghq root)" local selected="$(ghq list | sort | fzf --exit-0 --preview="fzf-preview-git ${(q)root}/{}" --preview-window="right:60%")" if [ -z "$selected" ]; then return fi local repo_dir="$(ghq list --exact --full-path "$selected")" local session_name="$(sed -E 's/[:. ]/-/g' <<<"$selected")" if [ -z "$TMUX" ]; then BUFFER="tmux new-session -A -s ${(q)session_name} -c ${(q)repo_dir}" zle accept-line elif [ "$(tmux display-message -p "#S")" = "$session_name" ] && [ "$PWD" != "$repo_dir" ]; then BUFFER="cd ${(q)repo_dir}" zle accept-line else tmux new-session -d -s "$session_name" -c "$repo_dir" 2>/dev/null tmux switch-client -t "$session_name" fi zle -R -c # refresh screen } zle -N select-ghq-session bindkey "^G" select-ghq-session # C-g
このZLEウィジェットは、ghq で管理されたGitリポジトリを FZF を用いて選択し、選択されたリポジトリに対応するTmuxセッションにアタッチします (セッションが存在しない場合は新規に立ち上げられます)。
3.2. Tmuxinator の導入
ここで、「特定のリポジトリを開いたときはペインの分割やコマンドの実行を自動的に行いたい」という要求が発生しました。 その種の、Tmuxセッションの立ち上げを設定ファイル化するための最も有名なツールに Tmuxinator があります。
通常 Tmuxinator はセッションのコンフィグを特定のディレクトリ ($XDG_CONFIG_HOME/tmuxinator/
または ~/.tmuxinator/
) に保存する必要があります。
しかしながら、私の場合コンフィグを複数のプロジェクト間や異なるPC間で共有するようなことは全く無いため、.envrc
などと同じようにプロジェクトのディレクトリ内に配置したいと考えました。
そこで、<プロジェクトディレクトリ>/.tmuxinator.yml
が存在する場合、それを用いてTmuxinatorを実行するように上記のZLEウィジェットを変更しました。
select-ghq-session() { local root="$(ghq root)" local selected="$(ghq list | sort | fzf --exit-0 --preview="fzf-preview-git ${(q)root}/{}" --preview-window="right:60%")" if [ -z "$selected" ]; then return fi local repo_dir="$(ghq list --exact --full-path "$selected")" local session_name="$(sed -E 's/[:. ]/-/g' <<<"$selected")" + local tmuxinator_config="$repo_dir/.tmuxinator.yml" if [ -z "$TMUX" ]; then - BUFFER="tmux new-session -A -s ${(q)session_name} -c ${(q)repo_dir}" + if [ -f "$tmuxinator_config" ]; then + BUFFER="tmuxinator start -a -n ${(q)session_name} -p ${(q)tmuxinator_config}" + else + BUFFER="tmux new-session -A -s ${(q)session_name} -c ${(q)repo_dir}" + fi zle accept-line - elif [ "$(tmux display-message -p "#S")" = "$session_name" ] && [ "$PWD" != "$repo_dir" ]; then + elif [ "$(tmux display-message -p "#S")" != "$session_name" ]; then + if [ -f "$tmuxinator_config" ]; then + tmuxinator start -a -n "$session_name" -p "$tmuxinator_config" + else + tmux new-session -d -s "$session_name" -c "$repo_dir" 2>/dev/null + tmux switch-client -t "$session_name" + fi + elif [ "$PWD" != "$repo_dir" ]; then BUFFER="cd ${(q)repo_dir}" zle accept-line - else - tmux new-session -d -s "$session_name" -c "$repo_dir" 2>/dev/null - tmux switch-client -t "$session_name" fi zle -R -c # refresh screen }
これは概ね期待通りの動作をしてくれましたが、一方でTmuxinatorの想定している用途からやや離れるために、コンフィグファイル (.tmuxinator.yml
) の設定に若干の冗長さがある、ZLEウィジェットの条件分岐が複雑になるなどの不満点がありました。
また、個人的に gem install
を可能な限りしたくありません。
そこで、以下の記事を参考に簡単なTmuxinatorの代替ツールを作成することにしました。
3.3. Tmuxinator と qtmut の思想の違い
Tmuxinator が単一の設定ディレクトリ内に存在する複数の設定ファイルからTmuxセッションを立ち上げるのに対し、qtmut はカレントディレクトリ (または -c
オプションを用いて指定される作業ディレクトリ) に設置された .qtmut.yaml
ファイルを用いてTmuxセッションを作成します。
つまり、私のニーズによく適合するように設計されています。
また、設定ファイルの構造もTmuxinatorに比べ自然に感じるように構成しました。
また、カレントディレクトリ (あるいは指定された作業ディレクトリ) に .qtmut.yaml
が存在しない場合は単にTmuxセッションの立ち上げのみを行うため、ZLEウィジェットも幾分か簡略化されました。
select-ghq-session() { local root="$(ghq root)" local selected="$(ghq list | sort | fzf --exit-0 --preview="fzf-preview-git ${(q)root}/{}" --preview-window="right:60%")" if [ -z "$selected" ]; then return fi local repo_dir="$(ghq list --exact --full-path "$selected")" local session_name="$(sed -E 's/[:. ]/-/g' <<<"$selected")" - local tmuxinator_config="$repo_dir/.tmuxinator.yml" if [ -z "$TMUX" ]; then - if [ -f "$tmuxinator_config" ]; then - BUFFER="tmuxinator start -a -n ${(q)session_name} -p ${(q)tmuxinator_config}" - else - BUFFER="tmux new-session -A -s ${(q)session_name} -c ${(q)repo_dir}" - fi + BUFFER="qtmut -s ${(q)session_name} -c ${(q)repo_dir}" zle accept-line elif [ "$(tmux display-message -p "#S")" != "$session_name" ]; then - if [ -f "$tmuxinator_config" ]; then - tmuxinator start -a -n "$session_name" -p "$tmuxinator_config" - else - tmux new-session -d -s "$session_name" -c "$repo_dir" 2>/dev/null - tmux switch-client -t "$session_name" - fi + qtmut -s "$session_name" -c "$repo_dir" elif [ "$PWD" != "$repo_dir" ]; then BUFFER="cd ${(q)repo_dir}" zle accept-line fi zle -R -c # refresh screen }
2. なぜDenoを選択したか
qtmut は Deno を用いて作成しました。
特にTypeScriptはシェルスクリプトに比べ、YAMLなどの構造化されたデータ構造を扱うのが容易です。
Denoは処理系のインストールが簡単 (deno
の実行可能ファイルをPATHの通ったディレクトリに設置するだけ) で依存関係も自動的に解決されるため、小さなツールであればシェルスクリプト代わりに書いていくのもありかなと思いDenoを選択しました。
3. まとめ
Denoを用い、Tmuxinatorとは若干思想を異にする小さな代替ツール qtmut を作成しました。
Rustを使う必要があるほど速度の要求されない用途であれば、ZSHプラグイン周りでも今後はDenoが選択肢に入ってくるのではないでしょうか?
*1:qtμt というマンガから取りました。https://manga.line.me/product/periodic?id=Z0000153