Edited at

tmuxを効率よく使って開発を爆速にする最高の設定


リポジトリを横断しての開発

自分は普段いくつかの(主にマイクロサービス)リポジトリを横断しつつコーディングをしています。

その際に tmux + zsh + neovim を使っているのですが、 tmux (とzsh)を使って複数のリポジトリを横断する最高の設定を使っているので紹介します。

まず前提として、複数リポジトリのマイクロサービスを立ち上げるとめちゃくちゃコンソールが増えると思います。

自分はプロジェクト毎にローカルサーバで1-2個・エディタ1つ・シェルで1つ・REPLで1つくらいは平気で使います。ついでに一時的な検証をするワークスペースを作って5-7個くらいは平気でプロジェクトを横断することがあります。

これを tmux の window と pane だけで管理するのは辛いのでやめましょう。

tmux には session という便利な機能があるのでこれを使います。

簡単に説明すると window はブラウザにおけるタブで session はブラウザの別ウィンドウみたいな感じです。

ブラウザ1枚で複数のプロジェクトの情報が混在したまま作業をするのは大変ですよね?

今すぐプロジェクト毎に別の session に切り分けて管理しましょう。

iTermなどの高機能なアプリケーションを使えば別のアプローチで解決するのかもしれませんが、特定の端末アプリケーションへの依存をしたくないため行っていません。(ssh先で使えないなどの問題もあります)

というか最近4k近くの高解像度でvimを使うとiTermでは描画が重すぎて実用に耐えないので最低限の機能だけ実装されたGPUで描画される端末のAlacrittyを使っています。


session と window の一手移動

では自分がどのように tmux を操作してるか画像で説明します。

tmux-move-session-and-window.png

tmux-with-meta-key.png

上記のように session の切り替えと window の切り替えを Metaキーを使って一手で実行できるようにしています。

使用頻度が高いコマンドについてはPrefixを使わずに一手で実行できるようにしましょう。体感の快適さが全く変わります。

あとは頭の中でイメージを作って、それに対して直感的なキーを振ると手が動きやすいです。

自分は上記画像のイメージで、vimに近い体型の操作をしています。

設定は以下です。


tmux.conf

# window の作成, 移動

bind -n M-c new-window -c "#{pane_current_path}"
bind -n M-j next-window
bind -n M-k previous-window

# session の作成, 移動
bind -n M-C new-session
bind -n M-l switch-client -n
bind -n M-h switch-client -p
 
# pane の分割
bind -n M-v split-window -h -c "#{pane_current_path}"
bind -n M-s split-window -v -c "#{pane_current_path}"


ついでに以下のような設定をして tmux のステータスラインに現在のセッション名の表示も行っておきましょう。


tmux.conf

set -g status-left "#[fg=colour108,bg=colour237,bold] [#S:#I:#P] "



pane 間の ctrlキーを使った一手移動

window 内での pane の移動については ctrlキーを使っています。これについてはC-hやC-kを殺したくないため zsh と連携させて多少便利に動作するようにしています(後述します)。

pane-with-ctrl.png

pane の移動の詳細については zsh, vim と組み合わせているため下で説明します。

最低限の設定は以下です


tmux.conf

bind -n C-h select-pane -L

bind -n C-j select-pane -D
bind -n C-k select-pane -U
bind -n C-l select-pane -R


全ての session, window, pane を俯瞰するコマンド choose-tree, choose-session

上記の session の移動を行っていると、 session の順序が分からなくなり、無駄に移動を繰り返すことがあります。

それを解決するために、 tmux には choose-tree と choose-session というコマンドが存在します。

以下が choose-tree のGIFになります。

2018-10-15 16-17-43.2018-10-15 16_26_15.gif

choose-tree は session, window, pane についてツリーとサムネイルの表示を行いながら選択できる機能になっています。

choose-session は session のみのリスト表示 choose-tree -w は session と window のみのリスト表示を行います。

自分は


tmux.conf

bind -n M-a choose-tree

bind -n M-e choose-session
bind -n M-w choose-tree -w

と設定しています。


tmux, zsh, vim を連携させた pane の移動

シェル, エディタ, Fuzzy Finder などで入力をしている際には、 C-h や C-k はそれらのプロセス上で動作して欲しいことが多いと思います。

そこで、C-h,j,k,l での pane の移動については tmux でシェルのプロセスを確認した上で、 pane の移動について処理の分岐をしています。

zsh, vim, fzf, pecoの場合はC-h や C-k をそれらのプロセスに送り、状態次第で tmux に send-key で処理を戻すといった実装をしています。

以下がそのコードです。


tmux


tmux.conf

# Vim Tmux Navigator

is_zsh="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE 'Ss\\+\\s*-zsh$'"
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE 'S\\+\\s*?g?(view|n?vim?x?)(diff)?$'"
is_fzf="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE 'S\\+\\s*fzf$'"
is_peco="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE 'S\\+\\s*peco$'"
bind -n C-h run "($is_zsh && tmux send-keys C-h) || ($is_vim && tmux send-keys C-h) || ($is_fzf && tmux send-keys C-h) || ($is_peco && tmux send-keys C-h) || tmux select-pane -L"
bind -n C-j run "($is_zsh && tmux send-keys C-j) || ($is_vim && tmux send-keys C-j) || ($is_fzf && tmux send-keys C-j) || ($is_peco && tmux send-keys C-j) || tmux select-pane -D"
bind -n C-k run "($is_zsh && tmux send-keys C-k) || ($is_vim && tmux send-keys C-k) || ($is_fzf && tmux send-keys C-k) || ($is_peco && tmux send-keys C-k) || tmux select-pane -U"
bind -n C-l if-shell "$is_vim" "send-keys C-l" "select-pane -R"


zsh

function _left-pane() {

tmux select-pane -L
}
zle -N left-pane _left-pane

function _down-pane() {
tmux select-pane -D
}
zle -N down-pane _down-pane

function _up-pane() {
tmux select-pane -U
}
zle -N up-pane _up-pane

function _right-pane() {
tmux select-pane -R
}
zle -N right-pane _right-pane

function _backspace-or-left-pane() {
if [[ $#BUFFER -gt 0 ]]; then
zle backward-delete-char
elif [[ ! -z ${TMUX} ]]; then
zle left-pane
fi
}
zle -N backspace-or-left-pane _backspace-or-left-pane

function _kill-line-or-up-pane() {
if [[ $#BUFFER -gt 0 ]]; then
zle kill-line
elif [[ ! -z ${TMUX} ]]; then
zle up-pane
fi
}
zle -N kill-line-or-up-pane _kill-line-or-up-pane

function _accept-line-or-down-pane() {
if [[ $#BUFFER -gt 0 ]]; then
zle accept-line
elif [[ ! -z ${TMUX} ]]; then
zle down-pane
fi
}
zle -N accept-line-or-down-pane _accept-line-or-down-pane

bindkey '^k' kill-line-or-up-pane
bindkey '^l' right-pane
bindkey '^h' backspace-or-left-pane
bindkey '^j' accept-line-or-down-pane


vim

vim には Vim Tmux Navigator

プラグインを導入します。


fzf で tmux の session を管理する

tmuxの操作を選択的UIでインタラクティブにする の一部を抜き出して書き直させていただき、 yuki-ycino/tms: tmux session manager for fzfyuki-ycino/tmk: tmux session killer for fzf を使っています。

ありがとうございます。


ghq と fzf でのプロジェクトの管理と移動

以下の関数を使うことでプロジェクト毎のセッションを作成と移動をしています。

ghq で管理しているプロジェクトを選択し、自動で tmux の session を作成とリネームを行います。

既にそのプロジェクトの session が存在する場合は、 対象の session に移動します。

function f() {

local project dir repository session current_session
project=$(ghq list | fzf --prompt='Project >')

if [[ $project == "" ]]; then
return 1
elif [[ -d ~/repos/${project} ]]; then
dir=~/repos/${project}
elif [[ -d ~/.go/src/${project} ]]; then
dir=~/.go/src/${project}
fi

if [[ ! -z ${TMUX} ]]; then
repository=${dir##*/}
session=${repository//./-}
current_session=$(tmux list-sessions | grep 'attached' | cut -d":" -f1)

if [[ $current_session =~ ^[0-9]+$ ]]; then
cd $dir
tmux rename-session $session
else
tmux list-sessions | cut -d":" -f1 | grep -e "^$session\$" > /dev/null
if [[ $? != 0 ]]; then
tmux new-session -d -c $dir -s $session
fi
tmux switch-client -t $session
fi
else
cd $dir
fi
}