256色なvimと戯れるための.vimrc

最近訳あってTerminal.appにおさらばして、iTermにこんにちはしました。
Terminal.appはすごく素敵で好きなんだけどxterm-256colorになってくれないのがすごく残念。
ことjabannerだとかターミナルでプレゼンだとかやっちゃう自分には256色のターミナルはすごく魅力的で...。
スクロールすると文字が壊れたり、ウィンドウを巡回のディフォルトのショートカットがすごく押しづらかったり、なんだけどiTerm使用中now!


で、折角256色出るターミナルなのでvimも256色化したい所。
色々調べて、ググって、一応覚え書き程度のまとめ。


まず、vimを256色に対応させる。
関連helpは :help xterm-color あたり。


t_Coだとかt_AF, t_ABというオプションをいじるのも一つの手らしいが、もうすこしスマートなのはTERM環境変数をいじる方法。
iTermならBookmarks -> Manage Profiles... を開いて、Terminal Profilesから現在使っている物を選択ないしdupしてTypeをxterm-256colorに変更!
ウィンドウを開き直して、echo $TERMでxterm-256colorが出ればok


vimを起動して、適当なスタイルをいじってみて256色になっているかチェキ(古
手っ取り早く確かめるには、 :set number して、 :highlight LineNr ctermfg=196 で行番号が真っ赤になればok
もとから赤だったから分からないよ!という奇特な色環境の方は :highlight LineNr ctermfg=21 で真っ青になればok


色番号が255まで使えるようになり、15までが基本色、231までがカラーキューブ色、それ以降がグレースケールとなっているようです。
id:kakurasan:20080703 に詳しく書かれているので、こちらの方を見た方が早いと思います。感謝感謝。
ポイントとしてはカラーキューブ色は赤緑青がそれぞれ6階調で、かつ実は等間隔でないという点。
...その昔書いた画像からESC+ASCIIに変化するプログラムは等間隔で書いてしまった気がするorz


いずれにせよ数字のままだと使い勝手がよろしくないので、RGBと色番号の変換する関数を用意します。
全部1から書くのは面倒なので人様のコードをぱくりいんすぱいやしつつ書いていきます。
id:y_yanbe:20080611 にかなりよさげなコードがあったので抜粋しつつ微妙に書き換え。
蛇足的なのでy_yanbe氏のをそのまま使った方がよいかと。

let s:colourcube_values = [ 0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF ]
let s:base16_values = 	[ [ 0x00, 0x00, 0x00 ]
\			, [ 0xCD, 0x00, 0x00 ]
\			, [ 0x00, 0xCD, 0x00 ]
\			, [ 0xCD, 0xCD, 0x00 ]
\			, [ 0x00, 0x00, 0xEE ]
\			, [ 0xCD, 0x00, 0xCD ]
\			, [ 0x00, 0xCD, 0xCD ]
\			, [ 0xE5, 0xE5, 0xE5 ]
\			, [ 0x7F, 0x7F, 0x7F ]
\			, [ 0xFF, 0x00, 0x00 ]
\			, [ 0x00, 0xFF, 0x00 ]
\			, [ 0xFF, 0xFF, 0x00 ]
\			, [ 0x5C, 0x5C, 0xFF ]
\			, [ 0xFF, 0x00, 0xFF ]
\			, [ 0x00, 0xFF, 0xFF ]
\			, [ 0xFF, 0xFF, 0xFF ] ]
function! s:abs( n )
	if a:n > 0
		return a:n
	else
		return (0 - a:n)
	end
endfunction

function! ESC2RGB( esc )
	let esc = a:esc
	if esc < 16
		return s:base16_values[a:esc]
	endif
	let esc = esc - 16
	if esc < 216
		let r = s:colourcube_values[(esc / 36) % 6]
		let g = s:colourcube_values[(esc / 6) % 6]
		let b = s:colourcube_values[esc % 6]
		return [r,g,b]
	endif
	let esc = esc - 216
	if esc < 24
		let y = 8 + esc * 10
		return [y,y,y]
	endif
	let esc = esc - 24
	echom "unknown esc code: " (esc+256)
	return
endfunction

let s:esc2rgbDict = {}
for i in range( 0, 255 )
	let s:esc2rgbDict[i] = ESC2RGB(i)
endfor

function! RGB2ESC( rgb )
	let rgb = a:rgb
	if rgb[0] ==? "#"
		let rgb = rgb[1:]
	endif
	if strlen( rgb ) == 6
		let r = str2nr(rgb[0] . rgb[1], 16 )
		let g = str2nr(rgb[2] . rgb[3], 16 )
		let b = str2nr(rgb[4] . rgb[5], 16 )
	elseif strlen( rgb ) == 3
		let r = str2nr(rgb[0] . rgb[0], 16 )
		let g = str2nr(rgb[1] . rgb[1], 16 )
		let b = str2nr(rgb[2] . rgb[2], 16 )
	else
		echom "format error for: " . a:rgb
		return
	endif

	let mindiff = 20
	let diff = 0xff * 3
	let index = 0
	for i in range( 0, 255 )
		let d	= s:abs( s:esc2rgbDict[i][0] - r )
\			+ s:abs( s:esc2rgbDict[i][1] - g )
\			+ s:abs( s:esc2rgbDict[i][2] - b )
		if d < mindiff
			return i
		elseif d < diff
			let diff = d
			let index = i
		endif
	endfor
	return index
endfunction

元の方では誤差を求めるのに二乗を使っているのですが、そんな厳密に距離求めんでもと手を抜いてabsを使ってます。
あとdictionarysを使ってますが、listとどっちが早いのやら*1。
s:base16_valuesはなんか微妙に値が調整されているのですが、これは使っているターミナルにあわせればいいのかな?


変更点としては先に挙げたabsとdictionarysの他、rgbからの変換では#rrggbbの他、rrggbb、#rgb、rgbを許容。
あと、mindiffとして妥協する誤差を追加。
さして重いコードではないのでmindiffは0でもいいかも。
ちなみに mindiff = 20 は結構でかいです。 カラーキューブ色の階調の差が40なのでその半分を使っているのですが、
例えばグレースケールの245は#8A8A8Aなのですが、RGBそれぞれ3番目の階調、つまり、色番号16+2*36+2*6+2=102が、#878787と、#8A8A8Aとかなり近く、その誤差は9で、#8A8A8Aはぴったりあう色番号があるにもかかわらず、245が使われずに102が使われます。
まぁ、どうせ赤緑青それぞれ6階調なんだし、さほど気になる様な誤差ではないかな、と。


使い方としては :exe "highlight LineNr ctermfg=" . RGB2ESC( "#ff0000" ) のようにコマンドラインで使うほか、
例えば$VIMRUNTIME/syntax/xpm.vimを

--- xpm.vim.backup      2009-08-12 16:06:44.000000000 +0900
+++ xpm.vim     2009-08-12 16:25:11.000000000 +0900
@@ -18,7 +18,7 @@
 syn region  xpmComment         start="/\*"  end="\*/"  contains=xpmTodo
 syn region  xpmPixelString     start=+"+  skip=+\\\\\|\\"+  end=+"+  contains=@xpmColors
 
-if has("gui_running")
+if has("gui_running") || &t_Co == 256
 
 let color  = ""
 let chars  = ""
@@ -106,6 +106,7 @@
         exe 'syn cluster xpmColors add=xpmColor'.n
 
         " if no color or color = "None" show background
+        if has("gui_running")
         if color == ""  ||  substitute(color, '.*', '\L&', '') == 'none'
            exe 'hi xpmColor'.n.' guifg=bg'
            exe 'hi xpmColor'.n.' guibg=NONE'
@@ -113,6 +114,15 @@
            exe 'hi xpmColor'.n." guifg='".color."'"
            exe 'hi xpmColor'.n." guibg='".color."'"
         endif
+        else
+           if color == ""  ||  substitute(color, '.*', '\L&', '') == 'none'
+              exe 'hi xpmColor'.n.' ctermfg=black'
+              exe 'hi xpmColor'.n.' ctermbg=NONE'
+           elseif color !~ "'"
+              exe 'hi xpmColor'.n." ctermfg='".RGB2ESC( color )."'"
+              exe 'hi xpmColor'.n." ctermbg='".RGB2ESC( color )."'"
+           endif
+        endif
         let n = n + 1
       else
         break          " no more color string

みたいにサクっとかきかえてしまうなどすると、gVimと同様xpmを色を見ながら編集できたりしちゃいます♪
参考にさせていただいたid:y_yanbe:20080611ではcssの色をプレビューしつつ編集したりと、guifg/guibg=#rrggbbな部分をcterm=RGB2ESC("#rrggbb")にして、gui_runningの他に&t_Co==256を条件に付け加えるだけでvimライフがより便利になっていきます!


enjoy! vim life!

*1:dictionarysだと数字を文字列に変える手間があるやも