僕が一番 Vim のタブページをうまく使えるんだ(GNU screen 連携編)


2012年 05月 16日

問題

僕が一番 Vim のタブページをうまく使えるんだ(カレントディレクトリ編)で述べたように、Vimのタブページはとても便利ですが、ちょっと工夫するだけでさらに便利になります。

ところで、一般に Vim を使うような方は黒い画面を眺める機会が多いと思います。
また、端末をより便利に使うために GNU screen 等の端末マルチプレクサーを使用している方も多くいるはずです。
すると、 Vim でソースコードを編集しつつ、時折端末に切り替えて作業する……ということが多発します。

この時、以下の点で不便に感じることがあります。

  • 端末をアクティブにすることが面倒(GUI 版の Vim を使用している場合)。
  • 作業用のウィンドウに切り替えることが面倒(GNU screen 内で Vim を起動している場合)。
  • シェルで作業ができる状態になったとしても、 Vim で注目しているカレントディレクトリに移動する手間がかかって面倒。

特に最後の点が大問題で、
せっかくタブページ毎にカレントディレクトリを設定できるようにして便利さを高めたのにもかかわらず、
端末を使おうとするとその便利さも片手落ちという状態です。

つまり、 作業用のシェルにフォーカスを移し、さらにシェルのカレントディレクトリを Vim で注目しているディレクトリに切り替える ことが簡単にできるようにしたいものです。
しかし、一体全体どうすればよいのでしょうか。

解答

問題は

  1. 端末をアクティブにする(GUI 版限定)。
  2. 作業用のウィンドウに切り替える。
  3. 作業用シェルのカレントディレクトリを切り替える。

の3ステップに分解できます。
一つづつ片づけていきましょう。

端末をアクティブにする

例えば Mac OS X であれば以下のコマンドで端末をアクティブにできます:

open -a Terminal

他の OS の場合や Mac OS X でも標準以外の端末エミュレーターを使用している場合のコマンドは各自の宿題とします。

作業用のウィンドウに切り替える

これを実現するために、まずどのウィンドウが「作業用」なのか定義しておく必要があります。
ここでは作業用のウィンドウのタイトルが another だとしておきましょう。

幸い、 GNU screen は他のプロセスから既に起動しているセッションに対してコマンドを送ることができます。
ですので、以下のコマンドで another ウィンドウに切り替えることができます:

screen -X eval 'select another'

(ウィンドウの切り替えだけなら eval を使う必要はありませんが、
最後の問題で eval を使うので、この段階で予め eval を使う形で書いています)

作業用シェルのカレントディレクトリを切り替える

stuff を使うと GNU screen 内の現在のウィンドウへ任意の文字列を送ることができます。
つまり以下のコマンドを実行すれば適切な cd を GNU screen から作業用シェルで行わせることが可能です:

screen -X eval 'stuff "cd somewhere\015"'

ここで \015<C-m><Return> (若者言葉で言うところの <Enter>) を意味します。

上記の設定を統合して Vim から簡単に「切り替え」できるようにする

各問題が一通り解決できたので、後はその結果をまとめるだけです。
まとめ始めると長くなるので、筆者の vimrc から必要な部分だけを抜粋します:

function! s:activate_terminal()
  if !has('gui_running')
    return
  endif

  if has('macunix')
    silent !open -a Terminal
  else
    " This platform is not supported.
  endif
endfunction

if !exists('s:GNU_SCREEN_AVAILABLE_P')
  if has('gui_running')
    let s:GNU_SCREEN_AVAILABLE_P = executable('screen')
  else
    let s:GNU_SCREEN_AVAILABLE_P = len($WINDOW) != 0
  endif
endif

function! s:SuspendWithAutomticCD()
  if s:GNU_SCREEN_AVAILABLE_P
    call s:activate_terminal()

    silent execute '!screen -X eval'
    \              '''select another'''
    \              '''stuff "cd \"'.getcwd().'\"\015"'''
    redraw!
    let s:GNU_SCREEN_AVAILABLE_P = (v:shell_error == 0)
  endif

  if !s:GNU_SCREEN_AVAILABLE_P
    suspend
  endif
endfunction

noremap <C-z>  <Nop>
noremap! <C-z>  <Nop>
nnoremap <C-z>  :<C-u>call <SID>SuspendWithAutomticCD()<Return>

ここでは:

  • GUI 版の Vim を利用しているケースは Mac OS X のみサポートしています。
  • 作業用シェルへの切り替えは <C-z> で行う形にしています。

前者は単に筆者のメイン環境が Mac OS X であることで、
後者は過去に筆者が :suspend を多用していた頃の名残です。

これで <C-z> を押下するだけで

  1. 自動的に端末がアクティブになり、
  2. 作業用のシェルにフォーカスが合い、
  3. さらには作業用のシェルのカレントディレクトリが Vim で注目中のディレクトリに切り替わる

ようになります。
やりましたね。

補足

  • 前提条件が色々とあるため、上記の設定を vimrc にコピーすれば万人の環境ですぐに使えるというものではありません。各自の環境に合わせて適宜変更してください。
  • 上記の設定は GNU screen の利用を前提としたものですが、 tmux 等の他の端末マルチプレクサーでも同様の設定ができると思います。
  • 先述の通り、いささか好みや利用環境に依存するところが大きいため、プラグインとして分離していません。誰か一般化してください。
  • 「 Vim の中でシェルを動かせばいい」というのは邪教徒の妄言なのでここでは無視しています。
  • 次回は Emacs 編です。