← Vim研究所

Vim/Neovimでヤンクした範囲をハイライトする(hlyank / vim.hl.on_yank)

2026-10-25 公開 ・

yi(やyapでヤンクしても、Vimの画面は何も変わりません。カーソルが範囲の先頭に戻るだけなので、どこまで取れたかは貼り付けてみるまで分からないままです。ヤンクした範囲を一瞬だけ色で塗っておけば、その場で確かめられます。Vimは本体同梱のhlyankパッケージ、Neovimはvim.hl.on_yank()がこれを受け持ちます。

設定は数行で済みますが、使っていると「ddでは光らない」「マクロの中では光らない」という場面に出会います。設定の誤りではなく、どちらも実装に書かれた条件どおりの動きです。この記事では、ヤンクのたびに届くv:eventの中身を実機で表示して、その条件を確かめます。Neovimでは名前がvim.highlightからvim.hlへ、さらに0.13でvim.hl.hl_op()へと変わってきたので、古い設定がどう扱われるかも見ておきます。

検証環境:Vim 9.1(patches 1-1752)、NVIM v0.12.4、NVIM v0.13.0-dev-1946+g659164699f(nightlyを一時ディレクトリに展開して使用)。いずれもmacOS。tmuxの中で設定ファイルを読ませずに起動し、ヤンクの直後にVimではgetmatches()、Neovimではextmarkの数を表示して、光っているかどうかを判定しました。v:eventの値と警告文は実行結果をそのまま載せています。

目次(9項目)

ヤンクした範囲が光ると何がうれしいか

テキストオブジェクトは範囲を目で見ないまま使う操作です。yi(はカーソルを囲むいちばん内側の括弧を選びますが、括弧が入れ子になっていると、思っていた外側の括弧ではなく内側が取れていることがあります。yapは段落の後ろの空行まで含み、yipは含みません。この差も、貼り付けて空行が増えたときに初めて気づくことが多いです。

ヤンクの直後に範囲が塗られれば、取り違えはその場で分かります。yyやywのような基本のヤンクそのものはVimでコピーする操作(yy / yw / 矩形選択)にまとめてあるので、ここでは範囲を見えるようにする設定だけを扱います。

Vimで光らせる(:packadd hlyank)

Vimにはhlyankというパッケージが同梱されています。$VIMRUNTIME/pack/dist/opt/hlyank/にあるオプションのパッケージなので、読み込むまでは何もしません。vimrcに次の1行を書きます。

packadd hlyank

pack/dist/optの仕組みはVimにプラグインを入れる方法(標準パッケージとvim-plug)で説明しています。このパッケージはvim/vimのリポジトリに2025年3月19日に入りました。パッチ番号の付かないランタイムの更新で、直前のパッチは9.1.1227です。中身はVim9 scriptで、範囲の計算にgetregionpos()を使っています。

読み込んだ状態でyiwを押し、すぐに:echo getmatches()を打つと、塗られた範囲がそのまま見えます。

[{'group': 'IncSearch', 'id': 1000, 'priority': 10, 'pos1': [1, 1, 5]}]

1行目の1桁目から5文字をIncSearchで塗っています。0.6秒待ってから同じことをすると[]が返り、既定の300ミリ秒で消えていることが分かります。設定できる変数は3つです。

変数既定値意味
g:hlyank_hlgroup'IncSearch'塗る色(ハイライトグループ名)
g:hlyank_duration300消えるまでのミリ秒。3000が上限
g:hlyank_invisualv:trueビジュアル選択からのyも光らせるか

g:hlyank_durationに1000を入れると0.6秒後もまだ残り、g:hlyank_hlgroupを'Visual'にするとgetmatches()のgroupがVisualに変わりました。上限のほうは、10000を入れて試しています。2.5秒後には塗りが1つ残っていて、3.5秒後には0になりました。ソースがmin([get(g:, "hlyank_duration", 300), 3000])で丸めているので、大きな値を入れても3秒で止まります。

g:hlyank_invisualをv:falseにしたときだけ、少し癖があります。ビジュアル選択かどうかをv:event.visualではなくvisualmode()で見ているためです。visualmode()は最後に使ったビジュアルモードを覚えていて、ヤンクに関係なく値が残ります。手元ではvjからEscで抜けただけのあと、次のyiwが光りませんでした。その次のyiwからはまた光ります。光らなかった1回でvisualmode(1)が値を消すからです。

Vim 9.1でpackadd hlyankを読み込み、2行目のprice(の中でyi(を押した直後の画面。括弧の中のx, rate=taxだけがIncSearchの色で塗られている
:packadd hlyankのあとでyi(。外側のsum(ではなく、カーソルのあるprice(の中身だけが光る

Neovim 0.12で光らせる(vim.hl.on_yank)

Neovimにはパッケージではなく関数が用意されています。ヤンクの直後に起きるTextYankPostイベントからvim.hl.on_yank()を呼ぶと、そのヤンクの範囲が塗られます。init.luaなら次のとおりです。

vim.api.nvim_create_autocmd('TextYankPost', {
  callback = function()
    vim.hl.on_yank({ higroup = 'Visual', timeout = 300 })
  end,
})

init.vim派なら、ヘルプに載っているautocmd TextYankPost * silent! lua vim.hl.on_yank {higroup='Visual', timeout=300}の1行でも同じです。引数を省くとIncSearchで150ミリ秒光ります。この150は、塗りが消えるまでをタイマーで測って確かめました。結果は156ミリ秒でした。渡せる値は次の6つです。

キー既定値意味
higroup'IncSearch'塗る色
timeout150消えるまでのミリ秒。上限は無い
on_visualtrueビジュアル選択からのyも光らせるか
on_macrofalseマクロの実行中も光らせるか
priorityvim.hl.priorities.userほかのハイライトと重なったときの優先度
eventvim.v.event判定に使うイベントの中身

古い名前のvim.highlight.on_yank()も、0.12.4ではまだ動きます。画面には何も出ず、ヤンクした範囲もちゃんと光りました。ただし非推奨の記録は残っていて、:checkhealth vim.deprecatedを開くと次のように出ます(行頭の警告の記号は省いています)。

vim.deprecated:
- WARNING vim.highlight is deprecated. Feature will be removed in Nvim 2.0.0
  - ADVICE:
    - use vim.hl instead.

削除の予定は2.0なので急ぐ必要はありませんが、0.11より前の記事からコピーした設定はこの形のままです。

Neovim 0.12.4でvim.hl.on_yankをTextYankPostから呼び、3行目でyapを押した直後の画面。3行目と4行目がVisualの色で塗られ、下に3 lines yankedと出ている
Neovim 0.12.4でyap。3 lines yankedのとおり5行目の空行も範囲に入っているが、空行には塗る文字が無いので色は見えない

色と長さを変える

どちらも既定の色はIncSearchです。このグループは、/で検索語を打っている途中に一致箇所を塗る色でもあります。ヤンクを目立たせたくてIncSearchそのものの色を変えると、検索中の色まで変わってしまいます。ヤンク専用のグループを作って、それを指定するほうが安全です。

" Vim
highlight YankFlash ctermbg=22 guibg=#1f5f3f
let g:hlyank_hlgroup = 'YankFlash'
-- Neovim
vim.api.nvim_set_hl(0, 'YankFlash', { bg = '#1f5f3f' })
-- on_yank に higroup = 'YankFlash' を渡す

どちらも、塗りがYankFlashで付くことをgetmatches()とextmarkの中身で確かめました。

長さを伸ばしたときは、続けてヤンクしたときの振る舞いが分かれます。3秒に設定して1行目と2行目で続けてyiwを押すと、Vimでは['YankFlash[1, 1, 5]', 'YankFlash[2, 1, 6]']と2つの塗りが同時に残りました。Neovimは新しいヤンクで前の塗りを消すので、残ったのは2行目の1つだけでした。on_yankが前回のタイマーを止めてから塗り直す作りになっているためです。長めにするなら、Neovimのほうが画面が散らかりません。

dやcやマクロでは光らない理由

TextYankPostという名前ですが、このイベントは削除でも変更でも起きます。テキストがレジスタに入る操作すべてが対象だからです。Neovim 0.12.4で、イベントのたびにv:eventの3つの値を記録する設定を入れ、5つの操作を順に打ちました。

操作operatorregtypevisualon_yank
yyyVfalse光る
dddVfalse光らない
ciwcvfalse光らない
viw→yyvtrue光る
Ctrl-v j yy^V1true光る

矩形選択のregtypeはCtrl-vの制御文字の後ろに幅が付いた値で、表では^V1と書いています。ddでもciwでもイベントは届いています。光らないのは、on_yankの中に次の判定があるからです(0.12.4のruntime/lua/vim/hl.luaから抜粋)。

if not on_macro and vim.fn.reg_executing() ~= '' then
  return
end
if event.operator ~= 'y' or event.regtype == '' then
  return
end
if not on_visual and event.visual then
  return
end

2つ目の判定で、operatorがy以外のものはすべて落ちます。Vimのhlyankもv:event.operator ==? 'y'のときだけ塗る作りで、手元でもddとciwは光りませんでした。黒穴レジスタに捨てる"_yiwは、そもそもTextYankPostが起きません。記録にも1件も残りませんでした。レジスタの種類はVimのレジスタの使い分けで整理しています。

1つ目の判定がマクロの分です。reg_executing()はマクロを実行している間だけレジスタ名を返すので、qa yiw qで記録して@aで流すと、中のyiwは塗られません。on_macro = trueを渡すと塗られました。ここはVimと違うところで、Vimのhlyankにはこの判定が無く、@aの中のヤンクも光りました。

3つ目はon_visual = falseのときで、表のvisualがtrueの行だけが落ちます。試すと、viw→yは光らず、続くyiwは光りました。Vimのg:hlyank_invisualのような1回ぶんのずれは起きません。Neovimはv:event.visualを見ているからです。

名前が2度変わった経緯

Neovimのこの関数は、2回名前が変わっています。

版名前扱い
0.10までvim.highlight.on_yank()正式な名前
0.11・0.12vim.hl.on_yank()vim.highlightは非推奨(2.0で削除予定)
0.13(開発版)vim.hl.hl_op()on_yankは非推奨(0.14で削除予定)

1回目はモジュール名の短縮で、:help deprecated-0.11に「vim.highlight Renamed to vim.hl」とあります。2回目は0.13で、関数がヤンク専用ではなくなったための改名です。nightlyのnews.txtには次の1文があります。

vim.hl.hl_op() highlights text regions for TextYankPost and TextPutPost events. It replaces the now deprecated vim.hl.on_yank().

0.12系の非推奨とは扱いが違い、nightlyでvim.hl.on_yank()を呼ぶと、ヤンクした瞬間に画面の下へ警告が出ました。

vim.hl.on_yank is deprecated. Run ":checkhealth vim.deprecated" for more information

:checkhealth vim.deprecatedにはFeature will be removed in Nvim 0.14と出ます。中身はhl_op()をそのまま呼ぶだけなので、光り方は変わりません。0.12と0.13の両方で同じ設定を使うなら、ある方を選ぶ1行で済みます。0.12.4ではvim.hl.hl_opがnilなのでon_yankが選ばれ、nightlyではhl_opが選ばれて警告も出ませんでした。

local hl = vim.hl.hl_op or vim.hl.on_yank
vim.api.nvim_create_autocmd('TextYankPost', {
  callback = function() hl({ higroup = 'Visual', timeout = 300 }) end,
})

0.13でほかに何が変わるかはNeovim 0.13の新機能と、意味が変わるキーにまとめています。

Neovim 0.13で貼り付けた範囲も光らせる

改名のきっかけになったのが、0.13で増えたTextPutPreとTextPutPostの2つのイベントです。TextPutPostはpやPで貼り付けた直後に起き、v:eventの形はTextYankPostとほぼ同じです。hl_op()はoperatorがy・p・Pのどれかなら塗るので、2つのイベントに同じ関数をつなぐだけで、貼り付けた範囲も光ります。

vim.api.nvim_create_autocmd({ 'TextYankPost', 'TextPutPost' }, {
  callback = function()
    vim.hl.hl_op({ higroup = 'Visual', timeout = 300 })
  end,
})

nightlyでこの設定を入れ、yy、p、yiw、P、ddの順に打つと、記録は次のようになりました。ddだけが塗られず、残りの4つは塗られています。

TextYankPost operator=y regtype=V visual=false
TextPutPost operator=p regtype=V visual=false
TextYankPost operator=y regtype=v visual=false
TextPutPost operator=P regtype=v visual=false
TextYankPost operator=d regtype=V visual=false

ヤンクの塗りと貼り付けの塗りは別々に管理されていて、yyの直後にpを押すと、ヤンクした行と貼り付けた行の2か所が同時に塗られていました。hl_op()の中でyankとputを別の状態として持っているためです。0.12.4にはTextPutPost自体が無く、exists('##TextPutPost')は0でした。

Vimにも同じ名前のイベントが入りつつあります。vim/vimのリポジトリでは、2026年5月のパッチ9.2.0470でTextPutPostが加わり、hlyankもg:hlput_enableを立てると貼り付けを光らせるように更新されました。手元のVim 9.1.1752には入っておらずexists('##TextPutPost')も0だったので、Vim側の動きは確かめていません。

使ってみて

検証を始める前は、ddで光らないのはTextYankPostが削除では起きないからだと思い込んでいました。v:eventを記録してみるとddでもciwでもイベントは毎回届いていて、落としているのはon_yankの側でした。もう1つ引っかかったのはVimのg:hlyank_invisualで、v:falseにした直後のテストでyiwが1回だけ光らず、しばらく自分のテスト手順を疑っていました。ソースを開いてvisualmode()を見ていると分かるまで、直前にvjから抜けた操作が効いているとは考えもしませんでした。

まとめ

VimとNeovimで、ヤンクの範囲を光らせる仕組みは次のように違います。どれもdやcでは光らず、塗るのはヤンク(と0.13の貼り付け)だけです。

項目Vim 9.1 hlyankNeovim 0.12 on_yankNeovim 0.13 hl_op
有効にする方法packadd hlyankTextYankPostから呼ぶTextYankPostとTextPutPostから呼ぶ
既定の色IncSearchIncSearchIncSearch
既定の長さ300ミリ秒(上限3000)150ミリ秒150ミリ秒
マクロの中光る光らない(on_macroで変更)光らない(on_macroで変更)
続けてヤンク前の塗りも残る前の塗りを消す前の塗りを消す
貼り付け光らない光らない光る

Neovimでは、0.12のうちはvim.hl.on_yank()、0.13へ上げたらvim.hl.hl_op()に替えてTextPutPostも足します。

関連記事

← Vim研究所 トップへ戻る