Vim script で画面に表示中の行番号だけ取得する


表示している範囲の行だけ欲しい

Vimで画面に表示している範囲の内容だけ欲しいので、表示している行番号のリストがとれればなーとか思ったのですが、なかなかスマートな取得方法に辿り着かなかったので、共有も兼ねて記しておきます。

何が問題か

echo getline('w0', 'w$')

上記のようなw0w$という便利な記法があるのですが、w0w$を指定することで、表示されている一番上の行番号と下の行番号を指定することができるのです。

ですが、これで表示している範囲だけ取れると思って使っていると、折りたたみ(fold)があると不都合が起きます。

下記のような折りたたみがあるファイルでの結果は

test.vim
" vim: foldmethod=marker

aaaa                    

" {{{                   

bbbb                    

" }}}                   

cccc                                      

(↓↓↓結果のスクショ。最下行がecho getline('w0', 'w$')の結果)

結果
['" vim: foldmethod=marker', '', 'aaaa', '', '" {{{', '', 'bbbb', '', '" }}}', '', 'cccc']

foldで非表示になっている内容も取れてしまうんですよね。これだと、折りたたみの行数が大きいほど無駄に内容取得されてしまって困っちゃいます。

(私はここで、getline('w1')getline('w2')...と1行ずつ順番にアクセスしていけば、表示している内容だけ取れるのではと思ったのですが、w0が特別なだけで、w1、w2...なんてものは無いんですよね...うーむ。)

解決方法

ループしつつfoldclosed()foldclosedend()を使って非表示行をスキップします。
foldclosed()は、もし引数の行番号が畳まれていた場合はfoldの開始行番号を返し、foldclosedend()は引数の行番号が畳まれている場合はfoldの終了行番号を返します。

ドキュメントはこちら
https://vim-jp.org/vimdoc-ja/eval.html#foldclosed()

サンプルを書いてみました。
行番号だけ欲しければ、l:current_lineをよしなに使ってください。

サンプル
function! EchoDisplayedAll()
    let l:lines = []
    let l:current_line = line("w0")
    let l:end_line = line("w$")

    while 1
        if foldclosed(l:current_line) > 0
            let l:current_line = foldclosedend(l:current_line) + 1
        endif

        if l:current_line > l:end_line
            break
        endif

        call add(l:lines, getline(l:current_line))

        let l:current_line = l:current_line + 1
    endwhile

    echo l:lines
endfunction

先程のテキストでの結果は以下の通り。

(↓↓↓call EchoDisplayedAll()の結果)

結果
['" vim: foldmethod=marker', '', 'aaaa', '', '', 'cccc']

これでVim scriptで表示されている範囲だけ取得することができました💪