Vim
Zsh
tmux
fzf
Ghq

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 dir repository session current_session
  dir=$(ghq root)/$(ghq list | fzf --prompt='Project >')

  if [[ $dir != "$(ghq root)/" ]]; then
    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 $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
  fi
}