Vimで電源管理

## はじめに
この記事は[Vim Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vim)カレンダー2の18日目の記事です。

設定を面倒くさがって`systemctl poweroff`を毎回手打ちしている中、[14日目](https://www.rasukarusan.com/entry/2021/12/14/000000)の記事を見ていて思いました。
電源ボタンを作ろうと。

## 紹介
電源を切るボタンと言えばこれと作ったものがこちらです。

https://github.com/willelz/pox.nvim

![xp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183505/e1da357d-df84-32ed-7034-099fa80e1d63.png)

機能は見ての通りです。
見なれた(?)ボタンの側に書いてあるキーを押して操作します。クリックはできません。
これでvimからPCの電源が切れて便利ですね。

見た目以上の機能は特に何もないのでここからは使用した機能について紹介します。

## nvim_buf_add_highlight()
floating windowに色を付けるために`nvim_buf_add_highlight()`を使用しています。
通常のハイライトと違い場所を指定して色を付けられるのでお絵書きするのに便利です。

今回だと意味はないですが、`matchaddpos()`と違い編集によるテキスト位置の移動に追従します。

使い方は引数を見るとわかるように簡単です。

`nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, {col_end})`

<dl>
<dt>{buffer}</dt>
<dd>バッファハンドル。0でカレントバッファ。</dd>

<dt>{ns_id}</dt>
<dd>ネームスペースID。<code>nvim_create_namespace()</code>で作成すると一括でハイライトを消したりできます。</dd>

<dt>{hl_group}</dt>
<dd>ハイライトグループの名前。</dd>

<dt>{line}</dt>
<dd>0始まりな行数。</dd>

<dt>{col_start}</dt>
<dd>開始位置(バイト単位)。</dd>

<dt>{col_end}</dt>
<dd>終了位置。-1を指定すると行末まで。</dd>
</dl>

例として下記を実行すると、カレントバッファの3行目の5~10バイト目にある文字の背景が青色になります。

```vim:Vim_script
highlight Blue ctermbg=blue guibg=blue
call nvim_buf_add_highlight(0, 0, 'Blue' 2, 5, 10)
```

## vim.highlight.range()
`nvim_buf_add_highlight()`は行単位でしたが、こちらは行をまたぐ範囲版です。
Luaのvimモジュール内にあるのでLuaから使うか`v:lua`経由で呼び出します。

`vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})`

最初の3つの引数は同じで`{rtype}`と`{inclusive}`は省略できます。

<dl>
<dt>{start}, {finish}</dt>
<dd>それぞれ<code>{line, col}</code>のタプルです。<code>{start}</code>から<code>{finish}</code>までにある文字すべてにハイライトを当てます。</dd>

<dt>{rtype}</dt>
<dd><code>:h setreg()</code>に書かれているように種類を選べます。(しかし、どれを試しても範囲に違いはなくよくわかりません。中身を見ると、blockwiseのときに非ASCII文字を調節しているようです。)デフォルトはcharacterwise。</dd>

<dt>{inclusive}</dt>
<dd><code>{finish}</code>で指定したところを含むか含まないか。デフォルトはfalse。</dd>
</dl>

例を試してみます。

`vim.highlight.range(0,0,'TODO', {1, 2}, {2, 8})`
0000000000
11<strong>11111111</strong>
<strong>2222222</strong>222
3333333333

これを実行すると、2行目の3文字目から3行目の7文字目までがハイライトされます。
`{inclusive}`をtrueにすると、8文字目までになります。

## vim.loop
vimには`timer`機能があり、処理の実行を遅らせたり繰り返したりできます。
もちろんLuaからも使えますがLuaには[luv](https://github.com/luvit/luv)があり似たようなことができます。
[libuv](https://github.com/libuv/libuv)をLuaから使えるようにしたもので、Neovimでもすべての機能が使えるようです。

ネットワークやファイルシステム等のイベントも扱えるようですが今回はtimerだけを紹介します。
luvの関数はNeovimのヘルプには載っていないので[luvのドキュメント](https://github.com/luvit/luv/blob/master/docs.md)を見る必要があります。

timerの使い方はvimと似ています。
`timer_start(timer, timeout, repeat, callback)`
`callback`には関数を渡しますがLuaにはラムダ式がないので少し面倒です。

```lua:lua
local timer = vim.loop.new_timer()
timer:start(1000, 0, function()
  vim.api.nvim_command('echomsg "test"')
end)
```

通常は上の例でいいのですがこの場合はエラーになります。
`vim.api`の関数は遅延されるためそのままではコールバックの中で使えません。
`vim.schedule_wrap()`という関数が用意されているのでこれでラップします。

```lua:lua
local timer = vim.loop.new_timer()
timer:start(1000, 0, vim.schedule_wrap(function()
  vim.api.nvim_command('echomsg "test"')
end))
```

他にも、nミリ秒後に1回だけ実行したい時にはこれという関数が用意されています。
`defer_fn({fn}, {timeout})`
こちらはコールバック内で`vim.api`関数をそのまま使うことができます。

上の例を書き直すとこうなります。

```lua:lua
vim.defer_fn(function ()
    vim.api.nvim_command('echomsg "test"')
  end
  , 1000
)
```

## おわりに
Floating Windowは楽しいですね。
LuaのおかげかNeovimのドキュメントやAPIがずいぶんと更新されていていじりやすくなったと感じます。
これからもネタプラグインが増えることを楽しみにしています。