koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

Cygwinのvimを快適に使う

今日は、Cygwinvimについて書いてみようと思う。
内容としては、以下の4点である。

1. 使用ターミナルによる挙動の違い
 1-1. minttyとコマンドプロンプト
 1-2. Terminal type(オプション:termの値)による違い
2. Altキーと併用するキーバインド
3. 挿入モード時のカーソル形状の変更
4. 通常のgVimに似せる(mintty自体の設定を変更)
 4-1. 背景透過
 4-2. IMEの状態によるカーソル色の変更



1. 使用ターミナルによる挙動の違い

1-1. minttyとコマンドプロンプト

以前のCygwinであれば、Cygwinコマンドプロンプト上で動作するものであったが、最近のCygwinは、minttyというターミナルエミュレータが標準で付いてきて、その上でCygwinを動作させるようになっている。
実は、CygwinVimコマンドプロンプト上で動作させる場合と、mintty上で動作させる場合とで、いくつか注意しておくべきことがある。
結論から言うと、コマンドプロンプト上でCygwinVimを動かすべきではない。


コマンドプロンプト上でVimを動作させた場合の問題は、以下の2点である。

  1. 8色ターミナルとして扱われる
  2. IMEをオンにした状態でvimを起動すると、入力や表示がおかしくなる


このような何があるため、コマンドプロンプト上で使用すべきではないだろう。

1-2. Terminal type(オプション:termの値)による違い

minttyはターミナルタイプを変更することができる。
以下の画像の赤丸部分をクリックしていけば、切り替えることができる。
f:id:koturn:20130813014441p:plain
f:id:koturn:20130813014455p:plain


さて、CygwinVimでは、ターミナル毎に、以下のようにオプションの値が設定される。

Terminal type オプション:termの値 ターミナルの色数(t_Co)
コマンドプロンプト cygwin 8
minttyでxterm xterm 8
minttyでxterm-256color xterm-256 256
minttyでxterm-vt220 xterm-vt220 8
minttyでvt100 vt100 存在しない
minttyでvt220 vt220 存在しない

これを元に、話を進める。
なお、オプションの値の確認は、
set [オプション名]?
もしくは、
echo &[オプション名]
で確認できる。

上記の表で、ターミナルの色数について言及したのは、最近話題のvim-airlineが8色ターミナルをサポートしていないためである。
まともに使用するとなると、最低でも16色ターミナルである必要がある。
(もし、16色以上のターミナルでないなら、smartuslineというプラグインを使用するのがよいだろう)


しかし、256色ターミナルでなくとも、ターミナルの種類によっては、
set t_Co=256
とすることで、256色ターミナルを使用することが可能となる。


このことについて、各ターミナルの種類について述べていこう。
まず、コマンドプロンプト上でのCygwin-Vimについて述べる。
ターミナルカラーは8色であり、
set t_Co=256
としても効果が無いため、色数については諦めるしかない。
(ちなみに、windows向けにコンパイルされたVimであれば、ターミナルの色数は16色である。)


次に、xterm系(xterm, xterm-256color, xterm-vt220)について述べる。
xtermとxterm-220は8色ターミナルであるが、
set t_Co=256
とすることで、Vimを使用中のみ、256色ターミナルに変更可能なのである。
これにより、vim-airlineをまともに使用することが可能となる。


最後に、vt系(vt100, vt220)について述べる。
これらは、ターミナルのカラーの補助を受けることができない。
そのため、Vimの色機能を活かすことはできない。


以上のことを踏まえると、以下のコードを.vimrcに書くと、vimの色関係のことが改善される。
ステータスライン系のプラグインについては、NeoBundleで管理している。

if has('win32unix') && &term =~# '^xterm' && &t_Co < 256
  set t_Co=256  " Extend cygwin terminal color
endif

if &t_Co >= 16
  NeoBundle 'bling/vim-airline'
elseif
  set laststatus=2
  set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
  NeoBundle 'molok/vim-smartusline'
endif

2. Altキーと併用するキーバインド

よくgVimを使用しているユーザは、Alt(Meta)キーを併用キーマッピングを行っている人もいる。
しかし、mintty上でVimを使用している場合、<M-hoge> といったキーマッピングがうまく機能しない。

実は、<Esc>hogeに対しマッピングを行えば、Alt(Meta)キーを併用したキーマッピングが有効になる。
だが、gVim上では、<Esc>hogeと書いても、Alt(Meta)キーを併用したキーマッピングが有効になるわけではない。
そのため、CUIGUIに対応しようと思えば、2つ書かなければならないということになる。

" gVim用のキーマッピング
cnoremap <M-h>   <Left>
cnoremap <M-l>   <Right>
" CUIのvim用のキーマッピング
cnoremap <Esc>h  <Left>
cnoremap <Esc>l  <Right>

少ない量を書くのであればいいが、多くのAlt(Meta)キーを併用したキーマッピングを記述しようとしたときに面倒である。
それを解決するのに、以下の記事は参考になる
端末の Vim でも Alt キーを使う - 永遠に未完成

ただし、条件判断部分にCygwinを加えるために、一工夫しておいたほうが良いだろう。
以下、上記の記事のコードを少し改変したものである。

if !has('win16') && !has('win32') && !has('win64') && !has('gui_running')
  for s:ch in map(
        \   range(char2nr('a'), char2nr('z'))
        \ + range(char2nr('A'), char2nr('N'))
        \ + range(char2nr('P'), char2nr('Z'))
        \ + range(char2nr('0'), char2nr('9'))
        \ , 'nr2char(v:val)')
    exec 'nmap <ESC>' . s:ch '<M-' . s:ch . '>'
  endfor
  unlet s:ch
  map  <NUL>  <C-Space>
  map! <NUL>  <C-Space>
endif

3. 挿入モード時のカーソル形状の変更

これは、以下の記事の内容をそのまま伝える。
mintty ターミナル上の vim で、モードに応じてカーソル形状を変える - Qiita
これは、オプションt_ti, t_SI, t_EI, t_teの値に追記することで実現可能である。

前述の参考サイトのように、
通常はブロック型点滅カーソル
挿入モード時はライン型点滅カーソル
とする場合、以下をvimrcに記述すればよい。

if has('win32unix')
  let &t_ti .= "\e[1 q"  " 端末を termcap モードにする
  let &t_SI .= "\e[5 q"  " 挿入モード開始(バー型のカーソル)
  let &t_EI .= "\e[1 q"  " 挿入モード終了(ブロック型カーソル)
  let &t_te .= "\e[0 q"  " termcap モードから抜ける
endif

".="とは、Vim scriptにおける文字列連結演算子"."の、再帰代入バージョンである。
このようにして、各オプションの末尾に制御シーケンスの文字列を追記しているのであるなお、"\e"は、Escを表す特殊文字であるため、制御シーケンスはシングルクオートではなく、ダブルクオートで囲わなければならない。
カーソルを他の形状にしたい場合は、上記参考サイトを参照するとよいだろう。



4. 通常のgVimに似せる(mintty自体の設定を変更)

4-1. 背景透過

KaoriYa版のgVimでは、背景透過が可能である。
CygwinVimにおいては、予めminttyに対し、透過設定を行っておくことで実現する。
以下の画像中の赤丸の部分をクリックしていけば、透過度を変更することができるだろう。
f:id:koturn:20130813014501p:plain


ちなみに、.minttyrcを直接書き換えることでも、背景透過は実現できるし、透過度を細かく設定することができる。
.minttyrcにTransparencyに関する項目があるので(なければ自分で書き加えること)、例えば、

Transparency=220

とすれば、アルファ値を220に設定することができる。
この値は、4 ~ 254まで設定することができるようだ。

以下、minttyのマニュアルより引用

Numeric transparency values ranging from 4 to 254 can be specified in config files or on the command line. (Values below 4 are multiplied by 16, for backward compatibility reasons.)

4-2. IMEの状態によるカーソル色の変更

これは、minttyの隠しオプションの機能であり、直接.minttyrcを書き換えることでしか実現できない。
例えば、IMEがオンの場合、カ-ソルの色を赤に変更するには、以下のようにIMECursorColourに関する項目を書き加えるとよい。

IMECursorColour=255,0,0

以下、minttyのマニュアルより引用

The cursor colour can be set to change when the Input Method Editor (IME) for entering characters not available directly on the keyboard is active. The setting is a RGB triplet such as 255,0,0 for bright red.
By default, this is unset, which means that the cursor colour does not change. The colour can also be changed using xterm’s OSC 4 control sequence with colour number 262.

この項目を見る限り、特殊なシーケンスを用いれば、VimからIMEの状態によってカーソル色を変更することが可能だと思われるのだが、私は端末事情に詳しくないので、このあたりは読者各位が頑張ってほしい。



コードのまとめ

今回の記事を簡単にまとめると、以下のコードを.vimrcに書くと幸せになれる(笑)

let s:is_windows =  has('win16') || has('win32') || has('win64')
let s:is_cygwin  =  has('win32unix')
let s:is_cui     = !has('gui_running')

if s:is_cygwin
  if &term =~# '^xterm' && &t_Co < 256
    set t_Co=256  " Extend terminal color of xterm
  endif
  if &term !=# 'cygwin'  " not in command prompt
    " Change cursor shape depending on mode
    let &t_ti .= "\e[1 q"
    let &t_SI .= "\e[5 q"
    let &t_EI .= "\e[1 q"
    let &t_te .= "\e[0 q"
  endif
endif

if &t_Co >= 16
  NeoBundle 'bling/vim-airline'
elseif
  set laststatus=2
  set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
  NeoBundle 'molok/vim-smartusline'
endif

if !s:is_windows && s:is_cui
  for s:ch in map(
        \   range(char2nr('a'), char2nr('z'))
        \ + range(char2nr('A'), char2nr('N'))
        \ + range(char2nr('P'), char2nr('Z'))
        \ + range(char2nr('0'), char2nr('9'))
        \ , 'nr2char(v:val)')
    exec 'nmap <ESC>' . s:ch '<M-' . s:ch . '>'
  endfor
  unlet s:ch
  map  <NUL>  <C-Space>
  map! <NUL>  <C-Space>
endif