VimのPopup windowとwindow-IDについて

Vim 8.1からPopup windowが実装されました。 詳しくは :h popup を参照して欲しいですがちょっと凝った使い方をしようとし、filterとcallbackを実装しますが結構ノウハウ、具体的にはwindow-IDの理解が必要になってくるのでここでまとめてみようと思います。

window numberとwindow-ID

Popup windowを作成する関数は戻り値としてwindow-IDを返し、また、Popup windowのfilterとcallbackのコールバック関数の第一引数にはwindow-IDが渡ってきます。そのため、Popup windowを扱ううえでwindow-IDの理解が必須になってくるのです。

まず、Vimにはもともとwindow numberというものがありますがこれはwindow-IDとは全く異なるものになります。

window number

window numberは現在Vim画面上に表示されているウィンドウを左上から順にナンバリングした値になります。ここで気を付けたいのはwindow numberは完全に一意な値にならないことです。 例えばtabpageが複数になる場合、window numberはそれぞれのtabpageで左上から順にナンバリングされるため、Vim全体としてwindow numberが重複することになります。

tabpage:1                        tabpage:2
+-------------+----------------+ +-------------+----------------+
|             |                | |             |                |
|     1       |        2       | |     1       |        2       |
|             |                | |             |                |
+-------------+----------------+ +-------+-----+------+---------+
|                              | |       |            |         |
|             3                | |   3   |     4      |    5    |
|                              | |       |            |         |
+------------------------------+ +-------+------------+---------+

window-ID

window-IDはwindow numberとは違いウィンドウに完全に一意な値を振ったものになります。そのため、ウィンドウを作成する度に新しいwindow-IDが生成されます。

tabpage:1                        tabpage:2
+-------------+----------------+ +-------------+----------------+
|             |                | |             |                |
|     1000    |     1002       | |    1023     |        1020    |
|             |                | |             |                |
+-------------+----------------+ +-------+-----+------+---------+
|                              | |       |            |         |
|            1031              | | 1030  |    1004    |  1050   |
|                              | |       |            |         |
+------------------------------+ +-------+------------+---------+
※ 環境よってwindow-IDは異なる場合があります。

window-IDの重要性

window-IDですがそもそもなぜwindow-IDが重要かというとcallbackやfilterのコールバック関数の中では既存の関数やコマンドを使うと Popup windowに対してではなく、カレントウィンドウに対して適用されます。 このため、このコールバック関数内で唯一のPopup windowに関する情報であるwindow-IDはかなり重要で、これから出来ることを知っておく必要があります。

let winid = popup_menu(['aaa', 'bbb'], {
    \ 'filter' : function('s:filter'),
    \ 'callback' : function('s:callback'),
    \ })
echo winid
" 1001

function! s:filter(id, key) abort
    " a:idがそのPopup windowのwindow-IDになります。
    echo a:id
    " 1001

    " getline()でPopup windowの行を取得したいところですが、実際はカレントウィンドウの行を取得しようとします。
    echo getline(2)

    " Popup windowに行番号を表示するのではなく、カレントウィンドウに行番号を表示しようとします。
    set number

    return popup_filter_menu(a:id, a:key)
endfunction

function! s:callback(id, key) abort
    " a:idがそのPopup windowのwindow-IDになります。
    echo a:id
    " 1001

    " getline()でPopup windowの行を取得したいところですが、実際はカレントウィンドウの行を取得しようとします。
    echo getline(2)

    " Popup windowに行番号を表示するのではなく、カレントウィンドウに行番号を表示しようとします。
    set number
endfunction

window-IDから出来ること

Popup windowのcallbackやfilterのコールバック関数内でそのPopup windowに対しての操作が必要になってきます。 で、必要になってくるのがwinbufnr()win_execute()です。これはwindow-IDからそのウィンドウに表示されているbuffer numberを取得、window-IDに対してコマンドを適用できます。 このbuffer numberはPopup windowに表示されているバッファに対するものになりますので、既存の関数がいろいろ使えます。

" Popup windowのcallbackやfilterのコールバック関数
function! s:callback_or_filter(id, key) abort
    " winbufnr()でwindow-IDからそのウィンドウに表示されているbuffer numberを取得できます。
    let bnr = winbufnr(a:id)

    " buffer numberがあれば既存のsetbufline()やgetbufline()でバッファ内容設定/取得できます。
    call setbufline(bnr, 1, 'xxx')

    " window-IDのウィンドウに対して、コマンドを実行することができます。
    call win_execute(a:id, 'set number')

    " Popup windowのカーソル行位置を取得しようとline(".")を単純に使いたいところですが、
    " そのまま使うとPopup windowに対してではなく、カレントウィンドウに対してのカーソル行位置
    " が取得できてしまいますので、以下のような2ステップを踏まないとカーソル行位置は取得できません。
    call win_execute(a:id, 'let w:lnum = line(".")')
    echo getwinvar(a:id, 'lnum', 0)

    return popup_filter_menu(a:id, a:key)
endfunction

その他

もしfilterとcallbackを実装方法がわからなかったら、私は何個かのPopup windowに関するVimプラグインを作成したりしてますのでご参考に。