Help us understand the problem. What is going on with this article?

【Neovim】半透明色のウィンドウが開けるようになりました

呟いたら意外とみんな知らなかったようで結構 RT されました(Neovim 公式にまで RT されました)。まあ僕も一昨日知ったんですがね!

長くなったので最初にまとめ

前提として、ターミナル上の Neovim で、True Color を利用可能にしておく必要があります。手前味噌ですが、以下の記事を読んでいただくか、ググって設定しておいてください。

この上で、今回覚えて欲しいのは最近の Neovim に追加された以下のオプションたちです。

オプション 意味
pumblend=10 補完などに使われるポップアップメニューを半透明に表示します。
winblend=10 任意の floating windows を半透明に表示します。
set termguicolors    " ターミナルでも True Color を使えるようにする。
set pumblend=10      " 0100 が指定できます。ドキュメントによると 530 くらいが適当だそうです。

" 以下はおまけ。ここでは Denite の設定を載せていますが、
" 同様の仕組みで任意のウィンドウを半透明化できるでしょう。
augroup transparent-windows
  autocmd!
  autocmd FileType denite set winblend=10  " こちらも 530 で試してみてください。
  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 開くウィンドウの横方向の位置。

スクリーンショット 0001-06-29 15.26.58.png

スクリーンショットの例では nord というカラースキームを使っています。背景色が灰色になっていますね。これが NormalFloat です。

floating windows の背景色を変える

しかしこの NormalFloat、まだ定義されてから日が浅いので、ほとんどのカラースキームでは定義されていません(nord にも存在しません)。このため、NormalFloat がない場合は Pmenu(補完などに使うポップアップメニューのハイライト)を使うようになっています。試しに NormalFloat を定義してみましょう。

" 文字の色を黒、背景色を緑に変更
hi NormalFloat guifg=#2e3440 guibg=#a3be8c

スクリーンショット 0001-06-29 15.29.37.png

ハイライト定義はグローバルなものなので、すでに開いたウィンドウにも影響があります。スクリーンショットのように、緑地に黒の表示になるはずです。

複数の 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

スクリーンショット 0001-06-29 15.31.23.png

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')

スクリーンショット 0001-06-29 15.36.53.png

いい感じですね! あれ? ということは、なんか 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

スクリーンショット 0001-06-30 10.05.43.png

もう 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

xxfKJ4ibro.gif

一番下にあるウィンドウの文字が透けて見えていますね。赤色のウィンドウはステータスバーにも重なっています。青色と赤色のウィンドウは重なっていますが、お互いの文字が透けて見えることはないようです。

プラグインのバッファやターミナルを半透明色のウィンドウで開く

Denite の場合

Denite などの、floating windows に対応しているプラグインにもこの「半透明化機能」は適用できます。Denite の場合は FileType として denite / denite-filter という2つの値を使うようになっているので、それに合わせた autocmd を書けば良いのです。

autocmd FileType denite set winblend=30
autocmd FileType denite-filter set winblend=30

スクリーンショット 0001-06-29 21.49.21.png

バッチリ透けてますね。重ねて書いておきますが、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

スクリーンショット 0001-06-29 22.25.00.png

毎回これではちょっと大変ですね。Deol などのプラグインでターミナルを開くようにすれば少しましになるでしょう。

" Deol コマンドで開いたウィンドウは全て半透明にする。
autocmd FileType deol set winblend=30

ポップアップメニューを半透明にする

さあ、やっと本題です。実は Neovim では、補完などに使われるポップアップメニューも floating windows で実装されているのです。ここで最初に書いたオプションを(やっと)使ってみましょう。

" ポップアップメニューの半透明度を指定する
set pumblend=30

スクリーンショット 0001-06-29 16.08.50.png

スクリーンショットでは Ctrl+X Ctrl+K を押して /usr/share/dict/words からの補完候補を表示しています(詳しくは :h i_CTRL-X_CTRL-K をみてください)。

さらに、これは本記事とは直接関係ありませんが、Neovim ではコマンドラインでの補完にもポップアップウィンドウを使います。

スクリーンショット 0001-06-29 16.09.15.png | スクリーンショット 0001-06-29 16.09.38.png

まるで GUI のエディタのような見た目ですね。今までの Vim では入力候補が画面横幅にあふれていたのですが、これでだいぶ視認性が上がりました。

まとめ

最近の Neovim マジですごいよ! というお話でした。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした