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

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">NeoVim の pumblend オプションで補完などに使われる pop-up menu が半透明になる。ターミナルでここまでできるとは…… スクリーンショットでは 30% に指定してるけど、実際は 10% くらいが見やすい。 <a href="https://t.co/O5D4HDcDNE">pic.twitter.com/O5D4HDcDNE</a></p>&mdash; delphinus (@delphinus35) <a href="https://twitter.com/delphinus35/status/1144406808628301824?ref_src=twsrc%5Etfw">2019年6月28日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">やってみた。set winblend=30 でターミナル開いたところ。floating windows でターミナル開くと下のソースが全く見えなくて困ることがあったのでこれは便利そう。 <a href="https://t.co/xMoQrWTt2n">pic.twitter.com/xMoQrWTt2n</a></p>&mdash; delphinus (@delphinus35) <a href="https://twitter.com/delphinus35/status/1144436863182049280?ref_src=twsrc%5Etfw">June 28, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

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

[RT]: https://twitter.com/Neovim/status/1145446583573594112

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

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

* [tmux on iTerm2 で斜体 & True Color を使う - Qiita](https://qiita.com/delphinus/items/b8c1a8d3af9bbacb85b8)

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

| オプション    | 意味                                                         |
|---------------|--------------------------------------------------------------|
| `pumblend=10` | 補完などに使われるポップアップメニューを半透明に表示します。 |
| `winblend=10` | 任意の floating windows を半透明に表示します。               |

```vim
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][pr] がマージされ、`set wildoptions=pum,tagfile` がデフォルト値に設定されました。そのため、上に書いていた `wildoptions` の設定は特に必要なくなりました。

[pr]: https://github.com/neovim/neovim/pull/10384

----

これで上記のスクリーンショットのように、ポップアップメニューやプラグインの floating windows を半透明に表示することができるようになります。以下のスクリーンショットでは効果がわかりやすいように 30% 程度の値を設定していますが、実用上は 10% 程度の値を設定した方が見やすいです。

では以下、floating windows とその背景色の仕組みから追っていきます。

# floating windows の概要

従来の Vim は[タイル型ウィンドウマネージャ][tailing]を採用しており、ウィンドウ同士を重ねて表示することができませんでした。これを解決するために floating windows という機能が追加され、2019/06 現在では master ブランチから直接インストールすると利用可能です。

[tailing]: https://ja.wikipedia.org/wiki/タイル型ウィンドウマネージャ

```sh
# master の HEAD をインストール
brew install neovim --HEAD
```

しかしこれ、GUI でのウィンドウと違って「枠」も「影」もありません。そのままだと後ろのウィンドウと区別がつきづらいです。このため、標準では floating windows のハイライトとして `NormalFloat` が使われます。

これだけでは何のことかわからないと思うので、試してみましょう。

```vim
" 返り値は 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`  | 開いたウィンドウにフォーカスを移動するか否か。                                                           |
|            |             | 第３引数は様々なオプションを指定する辞書です。完全なリストはヘルプ（[:h nvim_open_win][nvim_open_win]）を見てください。 |
| `relative` |  `'cursor'` | カーソルからの相対位置に表示する。他にも `'editor'`, `'win'` が指定可能です。                                                                       |
| `height`   |      3      | 開くウィンドウの高さ。                                                                                   |
| `width`    |      10     | 開くウィンドウの幅。                                                                                     |
| `row`      |      1      | 開くウィンドウの縦方向の位置（この場合はカーソルからの距離）。                                           |
| `col`      |      1      | 開くウィンドウの横方向の位置。                                                                           |

[nvim_open_win]: https://neovim.io/doc/user/api.html#nvim_open_win()

<img width="255" alt="スクリーンショット 0001-06-29 15.26.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/20a5d4d6-3f73-e742-e333-79cde0e9c32a.png">

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

[nord]: https://github.com/arcticicestudio/nord-vim

# floating windows の背景色を変える

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

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

<img width="255" alt="スクリーンショット 0001-06-29 15.29.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/c15693f1-2b4a-56ea-2544-f433e09b96e4.png">

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

# 複数の floating windows で色を変える

`NormalFloat` だけでは floating windows を色分けすることができません。これを解決するために、`winhighlight` オプションが追加されました。これはちょっと特殊な書式になっています。

```vim
" このウィンドウでは Normal の代わりに Hoge を使う。
" 複数指定したい場合はコンマ区切りで指定できる。
set winhighlight=Normal:Hoge
```

`winhighlight` オプションはウィンドウローカルです。上記の場合、このウィンドウでは `Normal` の代わりに（`NormalFloat` ではなく）`Hoge` を使う、ということを意味します。

紛らわしいのは、この、`Hoge` が存在しないと、このオプションは何も意味をなさないことです。つまり、以下のようにハイライトを定義してから、それを指定しないといけません。

```vim
" 新しい floating window を開いて、そのウィンドウにフォーカスを移す。
" 第２引数が 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
```

<img width="256" alt="スクリーンショット 0001-06-29 15.31.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/a8bd1555-98dd-e66e-b1f3-8ec0d91b3957.png">

`set` コマンドはそのウィンドウにフォーカスが移っていないと使えないので、Vimscript では扱いにくいです。これを解決するため、関数版の `nvim_win_set_option()` も用意されています。

```vim
" 予め返り値の 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 に違った色をつけることができます。

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

<img width="264" alt="スクリーンショット 0001-06-29 15.36.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/7e222d82-8139-e8e7-7d61-61e6671ed728.png">

いい感じですね！ あれ？ ということは、なんか Neovim 上でお絵かきができる気がしてきましたね！ 試しに Color Wheel（色選択画面に出るあれ）を描いてみましょう。

（長くなったので完全版は [gist][] を見てください）

[gist]: https://gist.github.com/delphinus/8b05cd9ad6e0f8f8e9be0d02b28f35df

```vim
" ターミナルの文字は縦長の正方形なので座標を補正します。
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
  " 横方向は２倍に引き伸ばして描画します。
  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
```

<img width="800" alt="スクリーンショット 0001-06-30 10.05.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/659c2334-7d99-05c5-750b-a49e68b9d020.png">

もう Vim で動くドローイングソフトが出るのも時間の問題ですね！

# ウィンドウの背景色を半透明にする

さて、これで自在にウィンドウが開けるようになったわけですが、開いたウィンドウに重なった部分が見えなくなってしまいます。GUI ならマウスでウィンドウをずらせば良いのですが、ターミナルの画面でそれはなかなか難しいです。最新の Neovim ではこれを解決するために、擬似的な半透明色を導入しました。

```vim
" 現在のウィンドウの半透明度を指定する。
set winblend=30
```

「擬似的な」と書いたのには理由があります。半透明とは言っても、本当に下が透けて見えている訳ではないのです。あくまで、背景色が重なる場合に、上下の色をブレンドして表示する機能です。そのため、上のウィンドウで文字が表示されていると下の重なった文字は見えませんし、（現時点では）floating windows 同士の背景色も重なりません。

百聞は一見に如かず。見てみましょう。

```vim
" 一つ目のウィンドウを青色の背景色で開く。
" 第２引数に 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/66c2fc39-709f-618a-fc98-895dcaf55233.gif)

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

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

## Denite の場合

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

[Denite]: https://github.com/Shougo/denite.nvim

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

<img width="645" alt="スクリーンショット 0001-06-29 21.49.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/d33c4c8b-1997-164a-077e-c68304c085cc.png">

バッチリ透けてますね。重ねて書いておきますが、30% だとちょっとうるさいので、実際は 10% くらいの値が良さそうですね。

他のプラグインも、特殊な FileType を使っていたりするものは同様に設定できるでしょう。

## ターミナルの場合

ターミナルを半透明のウィンドウで開くのはちょっと大変です。

```vim
" 空のバッファを作る
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
```

<img width="270" alt="スクリーンショット 0001-06-29 22.25.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/223118f6-e742-3dc9-a0fd-0a3057fe0005.png">

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

[Deol]: https://github.com/Shougo/deol.nvim

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

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

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

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

<img width="262" alt="スクリーンショット 0001-06-29 16.08.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/aabe9160-aa0e-95ed-07eb-7fef6af0493d.png">

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

[dict]: https://vim-jp.org/vimdoc-ja/insert.html#i_CTRL-X_CTRL-K

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

<img width="262" alt="スクリーンショット 0001-06-29 16.09.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/f5140d24-7245-52f0-d56b-4637dd0fe646.png"> | <img width="262" alt="スクリーンショット 0001-06-29 16.09.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8880/acde2b84-99b6-ba7e-f126-21c082db39a8.png">

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

# まとめ

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