リポジトリを横断しての開発
自分は普段いくつかの(主にマイクロサービス)リポジトリを横断しつつコーディングをしています。
その際に 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 を操作してるか画像で説明します。
上記のように session の切り替えと window の切り替えを Metaキーを使って一手で実行できるようにしています。
使用頻度が高いコマンドについてはPrefixを使わずに一手で実行できるようにしましょう。体感の快適さが全く変わります。
あとは頭の中でイメージを作って、それに対して直感的なキーを振ると手が動きやすいです。
自分は上記画像のイメージで、vimに近い体型の操作をしています。
設定は以下です。
# 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 のステータスラインに現在のセッション名の表示も行っておきましょう。
set -g status-left "#[fg=colour108,bg=colour237,bold] [#S:#I:#P] "
pane 間の ctrlキーを使った一手移動
window 内での pane の移動については ctrlキーを使っています。これについてはC-hやC-kを殺したくないため zsh と連携させて多少便利に動作するようにしています(後述します)。
pane の移動の詳細については zsh, vim と組み合わせているため下で説明します。最低限の設定は以下です
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になります。
choose-tree は session, window, pane についてツリーとサムネイルの表示を行いながら選択できる機能になっています。
choose-session は session のみのリスト表示 choose-tree -w は session と window のみのリスト表示を行います。
自分は
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
# 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 fzf と yuki-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
}