NeoVim の pumblend オプションで補完などに使われる pop-up menu が半透明になる。ターミナルでここまでできるとは…… スクリーンショットでは 30% に指定してるけど、実際は 10% くらいが見やすい。 pic.twitter.com/O5D4HDcDNE
— delphinus (@delphinus35) 2019年6月28日
やってみた。set winblend=30 でターミナル開いたところ。floating windows でターミナル開くと下のソースが全く見えなくて困ることがあったのでこれは便利そう。 pic.twitter.com/xMoQrWTt2n
— delphinus (@delphinus35) June 28, 2019
呟いたら意外とみんな知らなかったようで結構 RT されました(Neovim 公式にまで RT されました)。まあ僕も一昨日知ったんですがね!
長くなったので最初にまとめ
前提として、ターミナル上の Neovim で、True Color を利用可能にしておく必要があります。手前味噌ですが、以下の記事を読んでいただくか、ググって設定しておいてください。
この上で、今回覚えて欲しいのは最近の Neovim に追加された以下のオプションたちです。
オプション | 意味 |
---|---|
pumblend=10 |
補完などに使われるポップアップメニューを半透明に表示します。 |
winblend=10 |
任意の floating windows を半透明に表示します。 |
set termguicolors " ターミナルでも True Color を使えるようにする。
set pumblend=10 " 0 〜 100 が指定できます。ドキュメントによると 5 〜 30 くらいが適当だそうです。
" 以下はおまけ。ここでは Denite の設定を載せていますが、
" 同様の仕組みで任意のウィンドウを半透明化できるでしょう。
augroup transparent-windows
autocmd!
autocmd FileType denite set winblend=10 " こちらも 5 〜 30 で試してみてください。
autocmd FileType denite-filter set winblend=10
augroup END
(追記 2019/7/2)
今日付けでこの PR がマージされ、set wildoptions=pum,tagfile
がデフォルト値に設定されました。そのため、上に書いていた wildoptions
の設定は特に必要なくなりました。
これで上記のスクリーンショットのように、ポップアップメニューやプラグインの floating windows を半透明に表示することができるようになります。以下のスクリーンショットでは効果がわかりやすいように 30% 程度の値を設定していますが、実用上は 10% 程度の値を設定した方が見やすいです。
では以下、floating windows とその背景色の仕組みから追っていきます。
floating windows の概要
従来の Vim はタイル型ウィンドウマネージャを採用しており、ウィンドウ同士を重ねて表示することができませんでした。これを解決するために floating windows という機能が追加され、2019/06 現在では master ブランチから直接インストールすると利用可能です。
# master の HEAD をインストール
brew install neovim --HEAD
しかしこれ、GUI でのウィンドウと違って「枠」も「影」もありません。そのままだと後ろのウィンドウと区別がつきづらいです。このため、標準では floating windows のハイライトとして NormalFloat
が使われます。
これだけでは何のことかわからないと思うので、試してみましょう。
" 返り値は window ID
let winid = nvim_open_win(bufnr(''), v:false, {'relative': 'cursor', 'height': 3, 'width': 10, 'row': 1, 'col': 1})
nvim_open_win()
は floating windows を開くためのコマンドです。ここでは以下のようなオプションを指定しています。
オプション | 設定値 | 意味 |
---|---|---|
{buffer} |
bufnr('') |
開いたウィンドウで使うバッファー番号。ここでは現在表示しているバッファーをそのまま表示しています。 |
{enter} |
v:false |
開いたウィンドウにフォーカスを移動するか否か。 |
第3引数は様々なオプションを指定する辞書です。完全なリストはヘルプ(:h nvim_open_win)を見てください。 | ||
relative |
'cursor' |
カーソルからの相対位置に表示する。他にも 'editor' , 'win' が指定可能です。 |
height |
3 | 開くウィンドウの高さ。 |
width |
10 | 開くウィンドウの幅。 |
row |
1 | 開くウィンドウの縦方向の位置(この場合はカーソルからの距離)。 |
col |
1 | 開くウィンドウの横方向の位置。 |
スクリーンショットの例では nord というカラースキームを使っています。背景色が灰色になっていますね。これが NormalFloat
です。
floating windows の背景色を変える
しかしこの NormalFloat
、まだ定義されてから日が浅いので、ほとんどのカラースキームでは定義されていません(nord にも存在しません)。このため、NormalFloat
がない場合は Pmenu
(補完などに使うポップアップメニューのハイライト)を使うようになっています。試しに NormalFloat
を定義してみましょう。
" 文字の色を黒、背景色を緑に変更
hi NormalFloat guifg=#2e3440 guibg=#a3be8c
ハイライト定義はグローバルなものなので、すでに開いたウィンドウにも影響があります。スクリーンショットのように、緑地に黒の表示になるはずです。
複数の floating windows で色を変える
NormalFloat
だけでは floating windows を色分けすることができません。これを解決するために、winhighlight
オプションが追加されました。これはちょっと特殊な書式になっています。
" このウィンドウでは Normal の代わりに Hoge を使う。
" 複数指定したい場合はコンマ区切りで指定できる。
set winhighlight=Normal:Hoge
winhighlight
オプションはウィンドウローカルです。上記の場合、このウィンドウでは Normal
の代わりに(NormalFloat
ではなく)Hoge
を使う、ということを意味します。
紛らわしいのは、この、Hoge
が存在しないと、このオプションは何も意味をなさないことです。つまり、以下のようにハイライトを定義してから、それを指定しないといけません。
" 新しい floating window を開いて、そのウィンドウにフォーカスを移す。
" 第2引数が v:true になっているのでフォーカスが移ります。
call nvim_open_win(bufnr(''), v:true, {'relative': 'cursor', 'height': 3, 'width': 10, 'row': 1, 'col': 1})
" Hoge を定義(黄色地に黒文字になります)
hi Hoge guifg=#2e3440 guibg=#ebcb8b
" Hoge をデフォルトのハイライトにする(Normal の代わりに使う)。
set winhighlight=Normal:Hoge
set
コマンドはそのウィンドウにフォーカスが移っていないと使えないので、Vimscript では扱いにくいです。これを解決するため、関数版の nvim_win_set_option()
も用意されています。
" 予め返り値の window ID を保存しておく
let winid = nvim_open_win(bufnr(''), v:true, {'relative': 'cursor', 'height': 3, 'width': 10, 'row': 1, 'col': 1})
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Hoge')
これらを利用すると、複数の floating windows に違った色をつけることができます。
let winid = nvim_open_win(bufnr(''), v:true, {'relative': 'win', 'height': 3, 'width': 5, 'col': 1, 'row': 1})
hi Sample1 guibg=#8fbcbb
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample1')
let winid = nvim_open_win(bufnr(''), v:true, {'relative': 'win', 'height': 3, 'width': 5, 'col': 7, 'row': 1})
hi Sample2 guibg=#bf616a
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample2')
let winid = nvim_open_win(bufnr(''), v:true, {'relative': 'win', 'height': 3, 'width': 5, 'col': 13, 'row': 1})
hi Sample3 guibg=#ebcb8b
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample3')
let winid = nvim_open_win(bufnr(''), v:true, {'relative': 'win', 'height': 3, 'width': 5, 'col': 19, 'row': 1})
hi Sample4 guibg=#b48ead
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample4')
いい感じですね! あれ? ということは、なんか Neovim 上でお絵かきができる気がしてきましたね! 試しに Color Wheel(色選択画面に出るあれ)を描いてみましょう。
(長くなったので完全版は gist を見てください)
" ターミナルの文字は縦長の正方形なので座標を補正します。
const s:pixel_ratio = 2.0 / 1.0
" color wheel を画面中央に描きます
function! ColorWheel() abort
" 円の中心の位置
const [center_x, center_y] = [&columns / 2.0, &lines / 2.0]
" 描く円の半径
const radius = min([&columns, &lines]) / 8.0 * 3
" 横方向は2倍に引き伸ばして描画します。
let col = center_x - radius * s:pixel_ratio
while col < center_x + radius * s:pixel_ratio
let row = center_y - radius
while row < center_y + radius
" 中心からの座標
let [x, y] = [(col - center_x) / s:pixel_ratio, center_y - row]
" 中心からの距離
let r = sqrt(x * x + y * y) / radius
if r <= 1.0
let [i_col, i_row] = [float2nr(col), float2nr(row)]
" 大きさ 1x1 のウィンドウを開く
let winid = nvim_open_win(bufnr(''), v:false, {
\ 'relative': 'win',
\ 'width': 1, 'height': 1,
\ 'col': i_col, 'row': i_row,
\ })
let hl_name = 'CircleBG' . i_col . i_row
" PositionToRGB() は座標から RGB 形式の色を算出します。
" 実装は gist の方をみてください。
execute 'hi' hl_name 'guibg=' . PositionToRGB(x, y, r)
" ウィンドウに色をセットします。
call nvim_win_set_option(winid, 'winhighlight', 'Normal:' . hl_name)
endif
let row += 1
endwhile
let col += 1
endwhile
endfunction
もう Vim で動くドローイングソフトが出るのも時間の問題ですね!
ウィンドウの背景色を半透明にする
さて、これで自在にウィンドウが開けるようになったわけですが、開いたウィンドウに重なった部分が見えなくなってしまいます。GUI ならマウスでウィンドウをずらせば良いのですが、ターミナルの画面でそれはなかなか難しいです。最新の Neovim ではこれを解決するために、擬似的な半透明色を導入しました。
" 現在のウィンドウの半透明度を指定する。
set winblend=30
「擬似的な」と書いたのには理由があります。半透明とは言っても、本当に下が透けて見えている訳ではないのです。あくまで、背景色が重なる場合に、上下の色をブレンドして表示する機能です。そのため、上のウィンドウで文字が表示されていると下の重なった文字は見えませんし、(現時点では)floating windows 同士の背景色も重なりません。
百聞は一見に如かず。見てみましょう。
" 一つ目のウィンドウを青色の背景色で開く。
" 第2引数に v:false を指定しているので、フォーカスが移りません。
let winid = nvim_open_win(bufnr(''), v:false, {'relative': 'win', 'height': 5, 'width': 10, 'col': 1, 'row': 1})
" 青地に白太字で文字を書くようなハイライトを定義します。
hi Sample1 guifg=#eceff4 guibg=#5e81ac gui=bold
" 作成したウィンドウの winid を指定して winhighlight オプションを設定します。
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample1')
" 背景色の半透明度を 30% にします。
call nvim_win_set_option(winid, 'winblend', 30)
" バッファーを再利用せず、新しいものを開きます。
enew
" 二つ目のウィンドウを赤色の背景色で開く。
let winid = nvim_open_win(bufnr(''), v:false, {'relative': 'win', 'height': 5, 'width': 10, 'col': 5, 'row': 3})
" 背景色を赤色にするようなハイライトを定義します。
hi Sample2 guibg=#bf616a
call nvim_win_set_option(winid, 'winhighlight', 'Normal:Sample2')
call nvim_win_set_option(winid, 'winblend', 30)
enew
一番下にあるウィンドウの文字が透けて見えていますね。赤色のウィンドウはステータスバーにも重なっています。青色と赤色のウィンドウは重なっていますが、お互いの文字が透けて見えることはないようです。
プラグインのバッファやターミナルを半透明色のウィンドウで開く
Denite の場合
Denite などの、floating windows に対応しているプラグインにもこの「半透明化機能」は適用できます。Denite の場合は FileType として denite
/ denite-filter
という2つの値を使うようになっているので、それに合わせた autocmd
を書けば良いのです。
autocmd FileType denite set winblend=30
autocmd FileType denite-filter set winblend=30
バッチリ透けてますね。重ねて書いておきますが、30% だとちょっとうるさいので、実際は 10% くらいの値が良さそうですね。
他のプラグインも、特殊な FileType を使っていたりするものは同様に設定できるでしょう。
ターミナルの場合
ターミナルを半透明のウィンドウで開くのはちょっと大変です。
" 空のバッファを作る
let buf = nvim_create_buf(v:false, v:true)
" そのバッファを使って floating windows を開く
call nvim_open_win(buf, v:true, {'relative': 'win', 'height': 5, 'width': 30, 'col': 1, 'row': 1})
" 半透明にする
set winblend=30
" ターミナルを開く
terminal
毎回これではちょっと大変ですね。Deol などのプラグインでターミナルを開くようにすれば少しましになるでしょう。
" Deol コマンドで開いたウィンドウは全て半透明にする。
autocmd FileType deol set winblend=30
ポップアップメニューを半透明にする
さあ、やっと本題です。実は Neovim では、補完などに使われるポップアップメニューも floating windows で実装されているのです。ここで最初に書いたオプションを(やっと)使ってみましょう。
" ポップアップメニューの半透明度を指定する
set pumblend=30
スクリーンショットでは Ctrl+X Ctrl+K を押して /usr/share/dict/words
からの補完候補を表示しています(詳しくは :h i_CTRL-X_CTRL-K をみてください)。
さらに、これは本記事とは直接関係ありませんが、Neovim ではコマンドラインでの補完にもポップアップウィンドウを使います。
まるで GUI のエディタのような見た目ですね。今までの Vim では入力候補が画面横幅にあふれていたのですが、これでだいぶ視認性が上がりました。
まとめ
最近の Neovim マジですごいよ! というお話でした。