denite.nvimのfilterをかっこよくする


TL;DR

denite.nvimのdenite-filterをこんな感じにできます。

はじめに

Vim/Neovimにおける高機能なファジーファインダーShougo/denite.nvim
拡張性の高さが大きな特徴で、さまざまな記事で多彩なdenite.nvimの「使い方」が解説されています。
きっとみなさんも、.vimrcdein.tomlにゴリゴリと設定を書き込んで使っておられることでしょう。

参考:
【vim】denite.nvim v3のマイベスト設定 - Qiita

そこでこの記事では、denite.nvimの「見た目」、中でも最近新しく追加されたdenite-filterウィンドウをかっこよくした話をしようと思います。

denite.nvimのfilterについて

もともと非常に高い拡張性を誇っていたdenite.nvimですが、2019年にはプロンプトを排し、Vimのネイティブなバッファを使用するdenite v3へとアップデートされました。
現在のdenite.nvimでは一般的なファジーファインダーと異なり、このように絞り込みを「denite-filter」と呼ばれる別ウィンドウで行うようになっています。

参考:
【Vim】新しい Denite に爆速で対応する - Qiita

filterからできる操作が限定されてしまったので操作感は大きく変わりましたが、動作はより警戒に、キーマッピングはvimと共通したより自然な形で設定できるようになり、フィルタ中で補完が効くようになるなど、慣れればアドバンテージの方が多いかと思います。
しかし、気になる点がひとつ。

filterがvimのウィンドウとして開くようになったゆえに、statuslineまでついてきてしまうのです。
filterは候補の絞り込みにのみ使われるバッファですので、このstatuslineを活用できる要素は (私には) 思いつきません。

もちろんstatuslineがあるからといってdenite.nvimの機能に悪影響があるわけではありません。
ありませんが、いかんせんフィルタウィンドウはdenite.nvimを使うならパカパカと何度も開くものなので、毎回付いてくる余計な1行が次第に気にかかるようになってきました。
denite.nvim v2のときは、Pythonで実装されたlambdalisue/neovim-promptによるいい感じのプロンプトで絞り込みを行っていたので、それと比較する部分もあります。

外観というのは実際重要なファクターです。
もし最新のdenite.nvimでもオシャレなプロンプトが使えるようになれば、テンションも上がり、進捗も鰻登りに違いありません。

floating windowの可能性

ところで、 Neovimにはfloating Windowと呼ばれる、バッファの上に仮想のウィンドウを重ねて開くこと機能があります。
追従するように本家vimにも似たような性質 (同一ではない) を持つPopup windowが取り入れられたことからも、Neovimにおけるキラーフィーチャーであることが分かります。
denite.nvimでもこの新機能を活用することが可能で、deniteのバッファをfloating windowで開くことができます。
おまけに、この設定を使うと、filterのstatuslineは表示されなくなり外観がサッパリしたいい感じになります。

しかし、バッファのステータルラインも表示されなくなるので、現在開いている候補の数やパスなどが見られなくなってしまいます。(実は表示できます。詳しくは後述。)
また、deniteウィンドウ内でパスを移動するときにウィンドウがフリックするのが個人的に気になるところで、deniteバッファは安定して動作するようにしたいという思いがあり、見た目のカッコよさを惜しみつつも通常のウィンドウを使っていました。

気付き

そんな感じでdenite.nvimを使っていたところ、突然、

filterだけfloating Windowで開けばいい感じになりそうでは?

とふっと思いつきました。突然。

それでソースコードを見てみたところ、ほんの少しコードを追加するだけで簡単に実現できることがわかりました。
まあもとからあったものを組み合せただけですので……。

そうして思いつきでIssueを立て、PRを送った結果がこれ。

(figのstatuslineはlightline.vimでカスタマイズしたものです。デフォルトではただのインアクティブなstatuslineが開くので注意)

私がこのPRで行ったのは、もともと存在したfilterの開く方向を指定する-filter-split-directionオプションに、=floatingの設定を追加したこと。
この設定を指定すると、denite-filter だけがfloating windowとしてstatuslineの一行下に開きます。
floating window機能を使っているので、Neovim限定、かつNeovimのバージョンが0.4.0+である必要があります。

ほとんど頑張っていない割には、かなりいい感じの見た目になったと自分でも思います。
もちろんvimのバッファであることには変わりないので、機能はまったく同様です。

しかし、少し問題点も。
Neovimのfloating Windowはコマンドラインを上書きできないようで、deniteのウィンドウが最下段にあるときはfilterウィンドウがstatuslineを上書きしてしまいます。

filterを抜けた後は普通にstatuslineを表示してくれますが、これでは減っていく候補数を横目に見ながら絞り込んでいく……みたいなムーブができません。
そこで、denite.nvimの-statuslineオプションが設定してあり、かつNeovimがset=titleであれば、denite.nvimがfloating Windowを使用中はtitlestringにdeniteのステータスが表示されるようにしています。
便利ですね。

画像上部に注目。iTerm2のタイトルバーにdeniteのステータスが表示されています。

これは作者のShougo氏にIssueで言及されて初めて知りました。
便利なんですがあまり知られていない気がします (私の中で) 。

これでdenite-fileterがかっこよくなりましたね!
で終わらせてもいいのですが、denite.nvimはユーザー側でもとことんこだわって設定できるのが特徴。

せっかくなので、もうちょっと凝った設定を探してみることにしました。

winblendとlightline.vimでfilterをもっとかっこよくする

私は基本的にデフォルトの-direction=botrightでdeniteを使っているので、filterが開いている間はステータスラインが隠れてしまっています。
ここでfilterウィンドウをもう一度眺めてみると、右側に使われていない大きなスペースがありますね。
filterを開いている間もこのスペースにdeniteのステータスを表示させることができれば、もっといい感じになりそうな気がしませんか?
そこで、-direction=botrightでdeniteを開いた際、filterウィンドウがステータスラインを隠してしまうのを逆手に取ることにしました。

ところで、Neovimにはfloating windowの透過率を設定するウィンドウローカルなオプションwinblendがあります。
透過率といっても擬似的なものではあるのですが、floating windowの下にあるバッファをうっすら表示できるようにする機能です。
半透明ないい感じの見た目のfloating windowを作るためのオプションですね。
0から100の範囲で設定できるのですが、docには0から30の範囲で使うことが推奨されています。

そうです、このwinblend悪用活用します。
winblendはウィンドウの背景色を透過させますが、あくまで擬似的な透過なので、文字はそのまま表示されるのです。つまり、
filterがstatuslineに重なってしまうのならば、逆にfilterを透明にしてやれば、statuslineの情報をfilterと共に表示させることができるというわけです。

denite-filterにsetlocal winblend=100して完全に透過させたgifがこちら。


やや見辛いですが、denite-filterを開きつつも、後ろのステータスラインが見えるようになりました。
しかし、インプットした文字がファイル名にかぶってしまっていますね。

denite-filterと重なっているステータスラインは、deniteバッファを開いているウィンドウ非アクティブ時の表示です。
なのでそのウィンドウのステータスラインの表示を、左側のファイル名を消しつつ右側にdeniteの情報を表示するように設定すればよさそうです。

ここでは、そのようにステータスラインの表示をカスタマイズするためにitchyny/lightline.vimを使います。
というか私はlightlineしか使ったことがないので、vim-airlineなどの他のステータスライン改造プラグインでも実現できるのかは不明です。

デフォルトの状態から、denite.nvimのステータスを表示するまでの簡単な設定を並べていきます。
なお、lightline.vimの設定のしかたは作者のitchynyさんの解説を見るのが一番だと思います。

let g:lightline={}
let g:lightline.inactive = {
    \ 'left': [ [ 'filename' ] ],
    \ 'right': [ [ 'lineinfo' ],
    \            [ 'percent' ] , ['denite'] ]}
let g:lightline.component_function = {}
let g:lightline.component_function['filename'] = 'LLfilename'
let g:lightline.component_function['denite'] = 'LLDenite'

lightlineは、ひとつの大きな辞書型変数g:lightlineに書き込んでいくことで設定を行います。
ここでは非アクティブ時のステータスラインを設定したいので、g:lightilne.inactiveに書き足していきましょう。

ファイル名を表示しているコンポーネントが'filename'ですね。
この部分をfiletypedeniteの時にだけ表示させないようにしたいわけです。

通常filenameg:lightline.componentで記述されています。
この下に指定されるオプションはset=statuslineでのみ機能するフラグと同様のフラグが使えますが、場合分けをする場合は評価値を返すためこのフラグが使えません (と私は理解しています) 。
lightlineでステータスラインの表示内容を設定する際は、基本的に関数を作ってそこからcomponent_functionを介して返させるのがおすすめです。

function! LLfilename() abort
    if &filetype isnot# 'denite'
    " g:lightline.component['filename'] = '%t'  と同義
        return expand('%:t')
    else
        return ''
    endif
endfunction

右側に表示する内容はg:lightline.inactive['right']に追加します。
deniteのステータス情報はdenite.nvimの関数denite#get_status()を使い引っ張ってきましょう。
pathの表示が長くて表示を圧迫するので、ちょっと手を加えて短めにしています。

" deniteを開いたときだけ表示する
 function! LLDenite() abort
     if &filetype isnot# 'denite'
         return ''
     else
         return s:denite_statusline()
     endif
 endfunction

 function! s:denite_statusline() abort
    let p =denite#get_status('path')
    " 飾りをはずす
    let p = substitute(p, '\(\[\|\]\)', '', 'g')
    " パスをホームディレクトリからの相対パスに変換する
    let p = fnamemodify(p,':~')
    " パスが長い時は強引に切り詰める
    if strlen(p) > 40
        let p = '.../' . fnamemodify(p,':h:h:t'). '/'
            \ . fnamemodify(p,':h:t'). '/'. fnamemodify(p, ':t')
    endif
    let path =  '[' . p . ']'
    let buf = 'buffer:' . denite#get_status('buffer_name')
    let source = denite#get_status('sources')
    return  buf . ' ' . source . ' ' . path
 endfunction

せっかくなのでアクティブなdeniteウィンドウにもステータス情報を表示させておきましょう。
filetypefileencodingfileformatを表示している部分をひとつの関数にまとめて、&ft='denite'のときはその関数をdeniteステータスに置換します。

let g:lightline.active = {
    \ 'left': [ [ 'mode', 'paste' ],
    \           [ 'readonly', 'filename', 'modified' ] ],
    \ 'right': [ [ 'lineinfo' ],
    \            [ 'percent' ],
    \            ['filestatus', 'denite']] }

let g:lightline.component_function['filestatus'] = 'LLfilestatus'
" 
function! LLfilestatus() abort
    if &filetype isnot# 'denite'
        let fenc = &fenc!=#""?&fenc:&enc
        let ft = &ft!=#""?&ft:"no ft"
        return &ff . ' | ' . ft . ' | ' . fenc
    else
        return ''
    endif
endfunction

設定前はこんな感じ。

設定後はこんな感じ。

いい感じになりましたね。

おわりに

今回紹介したdenite.nvimとlightline.vimは、どちらも自分の気が済むまでとことん設定を書き込んでいける、とても使い甲斐のあるプラグインだと思います。
この記事に触発されて.vimrcに5万行書き足しちゃうような人が現れてくれれば幸いです。

記事を書いててPRに結構粗があることに気付いたので、また直してもらうかもしれません。
あとカラースキームがコロコロ変わって見辛い。

追記

TL;DRを追加(19/12/16)