🖱

Neovimのマウスジェスチャーpluginで線を描く

2020/12/13に公開

マウスジェスチャーを可能にするプラグイン gesture.nvim を作っている。

以前は実現できなかった楽しい機能を追加した。

それがこちら。

今回はこの機能の実装に使ったテクニックを紹介する。

透明なfloating window上でのhighlight

winblendを使うとfloating windowを(擬似的に)透明にできる。
以下でwinblend=100のfloating windowを表示し、そのウィンドウ全体を動けるようにする。
元のウィンドウ、バッファに影響を与えずに装飾できるのが利点。

local bufnr = vim.api.nvim_create_buf(false, true)
local height = vim.o.lines
local window_id = vim.api.nvim_open_win(bufnr, true, {
  width = vim.o.columns,
  height = height,
  relative = "editor",
  row = 0,
  col = 0,
  external = false,
  style = "minimal",
})
vim.wo[window_id].winblend = 100

local lines = vim.fn["repeat"]({""}, height)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)

vim.o.virtualedit = "all" -- 実際はジェスチャー終了時に元の値に復元する

このウィンドウ上で目に見えるhighlight groupを作るにはblendを100より小さくする必要がある。
例えば、以下で線を不透明な白にできる。

highlight! GestureLine guibg=#ffffff blend=0

extmarkで線を描画する

これはテクニックと言えるか怪しいが、
1行を1個のextmarkで表現すると大量に描画してもパフォーマンスが落ちにくい。

ざっくり1行を描画する部分をコードにするとこうなる。

local line = lines[row] or {}
local ranges = line.ranges or {}
local virt_text = to_virt_text(ranges)
local id = vim.api.nvim_buf_set_extmark(bufnr, ns, row - 1, 0, {virt_text = virt_text, id = line.id})
lines[row] = {id = id, ranges = ranges}

既にセットしてあるextmarkを編集できるので、描画ごとにクリアしないで済む。

描画範囲をvirt_textに渡せる形式にするのは愚直にやるのみ。
{{1, 2, "GestureLine"}, {4, 5, "GestureLine"}}
{{" ", "GestureLine"}, {" "}, {" ", "GestureLine"}}にする。

実際のコードは方向を表示するレイヤーを作るためにもっと苦しい。

スクロール禁止(仮)

ただのウィンドウなので、マウスホイールでスクロールすると描画が上に吹っ飛んでいく。
これを完全に阻止する方法は見つけられていない。

ただ、スクロール後に元に戻す方法はあった。
nvim_set_decoration_provider()を使うと、以下のように描画にフックした処理を書ける。

vim.api.nvim_set_decoration_provider(ns, {
  on_win = function(_, _, buf, topline)
    if topline == 0 or buf ~= bufnr or not vim.api.nvim_win_is_valid(window_id) then
      return false
    end
    vim.fn.winrestview({topline = 0, leftcol = 0})
  end,
})

ヘルプにもあるが、主に描画ごとに処理したい一時的な(ephemeral) extmarkを扱うためのAPIで、
Neovim本体のtree-sitter highlighterの実装に使われている。

ちなみに同じnamespaceで上書きすると無効にできる。

vim.api.nvim_set_decoration_provider(ns, {})

感想

自分は使わなそうだと思っていたAPIにも意外な用途があって楽しい。

Discussion