Vimの検索ハイライトを消す(:nohlsearch)といちいち消さない設定
検索を終えたのに画面のあちこちがハイライトされたままで、地味に目障りに感じます。hlsearchを有効にしている環境ではよくあることで、これを一発で消すコマンドがあります。
ただ、消し方は1種類ではありません。ハイライトが出るかどうかはオプション1つで決まっているように見えて、Vimは内部にもう1つ状態を持っています。それを知らないまま設定を書くと、「マッピングにしたら効かない」「関数にまとめたら効かない」という形でつまずきます。この記事の確認はすべて macOS 26.4.1 上の Vim 9.1(patches 1-1752)と Neovim 0.12.4 で、設定ファイルを置かない一時ディレクトリを$HOMEにして行いました。
目次(9項目)
| コマンド | これだけ覚える |
|---|---|
:nohlsearch / :noh | 今のハイライトだけを一時的に消す |
nnoremap <Esc> :nohlsearch<CR> | Escを押すたびに自動で消す |
set nohlsearch | ハイライトそのものを無効化する |
let v:hlsearch = 0 | :nohlsearchと同じ。式の中から消したいとき |
:packadd nohlsearch | 放置すると自動で消える。同梱パッケージ |
ハイライトを消す
:nohlsearch
省略形の:nohでも同じように動きます。「No Highlight」の頭文字だと覚えておくと分かりやすいです。実行してもハイライトが消えるだけで、検索履歴やカーソル位置には影響しません。
このコマンドが触っているのはオプションではありません。Vimはハイライトの状態を、hlsearchというオプションと「いまは止めている」という内部の目印の2つで持っていて、:nohが倒すのは後者だけです。掛け合わせた結果がv:hlsearchという変数に現れるので、手元で値を追うと動きがそのまま読めます。
実際にset hlsearchを入れたVim 9.1で、操作ごとに&hlsearchとv:hlsearchを:echoで読んだ結果です。
| 操作 | &hlsearch | v:hlsearch |
|---|---|---|
/fooで検索 | 1 | 1 |
:nohlsearch | 1 | 0 |
| nで次の一致へ | 1 | 1 |
:let v:hlsearch = 0 | 1 | 0 |
:set nohlsearch | 0 | 0 |
:set hlsearch | 1 | 1 |
左の列が0に落ちるのは最後から2行目だけです。:nohlsearchを打ってもオプションは1のままなので、:set hlsearch?で確かめても「有効」としか返ってきません。設定を見ても消えている理由が分からない、という混乱はここから来ています。逆にv:hlsearchは普通の変数として代入できるので、コマンドラインから:let v:hlsearch = 0と打てば:nohとまったく同じ効果になります。
紛らわしいことに、短縮形は途中まで共通です。コマンドのほうは:nohまで縮められますが、オプション名の短縮形はhlsです。そのため:setのうしろにnohだけを置くとVim 9.1はE518: Unknown optionを返し、:set nohlsと書いたときだけ通って機能ごと切れます。設定ファイルに書くときは、setを付けるかどうかで意味が反転すると覚えておくと間違えません。
毎回コマンドを打つのが面倒なら
nnoremap <Esc> :nohlsearch<CR>
Escキーにマッピングしておくと、検索後に何か他の操作をしようとしてEscを押した瞬間にハイライトも一緒に消えます。Escは元々ノーマルモードでは大きな副作用を持たないキーなので、上書きしても困る場面はほとんどありません。
末尾の<CR>は飾りではありません。落としてnnoremap <Esc> :nohlsearchと書くと、Escを押したときにコマンドラインへ:nohlsearchが入力された状態で止まります。手元で試したところ、続けて押したjが行末にそのまま文字として足され、コマンドラインが伸びていくだけでした。実行されていないのでハイライトも残ったままです。
<silent>を足す理由も並べて測ると分かります。付けずにEscを押すと、画面最下行の表示が/fooから:nohlsearchに置き換わり、そのまま残りました。<silent>を付けたほうは/fooのままで、直前の検索メッセージが押し流されません。実用上は次のように書きます。
" 検索メッセージを消さずにハイライトだけ落とす
nnoremap <silent> <Esc> :nohlsearch<CR>
端末で使うときはもう1つ落とし穴があります。矢印キーやファンクションキーもエスケープ文字で始まるため、Esc単独なのか長いキー列の先頭なのかをVimは待って判断します。macOS付属のVim 9.1はttimeoutが無効でttimeoutlenが-1なので、この待ち時間はtimeoutlenの1000ミリ秒がそのまま乗ります。実測するとEscを押してからマッピングが動くまで1.12秒かかりました。set ttimeout ttimeoutlen=10を足して測り直すと0.19秒(計測側のポーリング分を含む)まで縮みます。「Escを押しても消えない」と感じたら、効いていないのではなく待たされているだけ、という可能性を先に潰してください。
Escを避けたいなら、画面を再描画するCtrl-lに相乗りさせる形が定番です。末尾でもう一度Ctrl-lを送るので、本来の再描画を残したままハイライトも落ちます。
" 再描画のついでにハイライトも落とす
nnoremap <silent> <C-l> :nohlsearch<CR><C-l>
手元ではCtrl-lを1回押すだけでv:hlsearchが0になりました。Escと違って端末のキー列と紛れないので、待ち時間の問題も起きません。ほかに空いているキーを探すなら独自キーマッピングに使える未割り当てキー一覧から選べます。
ハイライトは次の検索で自動的に復活する
:nohで消したハイライトは、恒久的に無効化されるわけではありません。次に/や*で検索すると、また自動的にハイライトが表示されます。あくまで「今のハイライトだけを一時的に消す」コマンドだと理解しておくと、動作の予測がしやすくなります。
戻るきっかけは検索コマンドだけではありません。最後の検索パターンを書き換えるExコマンドは、どれもハイライトを連れ戻します。:nohlsearchで消したあとに:%s/bar/BAR/gを実行したところ、パターンを保持するレジスタ@/がbarに変わり、v:hlsearchも0から1に戻りました。:g/alpha/pでも同じで、こちらは@/がalphaになります。置換を1回かけただけで画面が急に色づくのはこれが理由です。
逆に、カーソルを動かしただけでは戻りません。Gやkで移動してもv:hlsearchは0のままでした。「消したはずのハイライトが勝手に戻る」と感じたときは、直前に検索パターンを触るコマンドを打っていないかを疑うと早く切り分けられます。カーソル下の単語をそのまま検索する* と #は、無意識に押しやすいキーの筆頭です。
関数やautocommandの中では :nohlsearch が効かない
ここが:nohlsearchのいちばん厄介な性質です。ヘルプにも書かれているとおり、autocommandの中とユーザー関数の中では効きません。ハイライトの状態は、それらを実行する前に保存されて実行後に復元されるので、途中で消しても抜けた時点で元に戻ります。
This command doesn't work in an autocommand, because the highlighting state is saved and restored when executing autocommands. Same thing for when invoking a user function.
実際に確かめました。fooを検索してハイライトを出した状態で、nohlsearchの1行だけを書いた関数を:callで呼んでもv:hlsearchは1のままです。同じ内容をコマンドラインへ直接打つと0になります。逃げ道になりそうなlet v:hlsearch = 0も、関数の中に書いた場合は同じく無効でした。ヘルプのv:hlsearchの項にも「値は関数から戻るときに復元される」と明記されています。
| 呼び出し方 | 結果 |
|---|---|
| コマンドラインで直接 | 消える |
| Vim scriptの関数から | 消えない |
| autocommandから | 消えない |
関数の中でlet v:hlsearch = 0 | 消えない |
関数の中でfeedkeys()経由 | 消える |
NeovimのLua関数からvim.cmd | 消える |
回避策は、関数の外で実行させることです。Vim 9.1に同梱されているnohlsearchパッケージがそのお手本になっていて、中身はcall feedkeys("\<cmd>nohlsearch\<cr>", 'm')で、要はこの1行です。コマンドとして実行するのではなくキー入力として流し込むので、関数を抜けたあとに通常のコマンドとして処理されます。手元でも同じ書き方の関数を呼ぶとv:hlsearchが0になりました。
そのパッケージは:packadd nohlsearchの1行で有効になります。CursorHoldとInsertEnterにフックしていて、updatetimeのあいだ何も操作しなかったときと、挿入モードに入ったときにハイライトを落とす作りです。set updatetime=1000にして試すと、検索直後はv:hlsearchが1、そのまま3秒放置すると0になりました。マッピングを覚えなくても消し忘れが無くなるので、キーを1つ潰したくない人にはこちらが向きます。Neovim 0.12.4にも同じパッケージが同梱されています。
Neovimでは事情が少し違います。同じ:nohlsearchでも、Vim scriptの関数から呼ぶと効かないのに、Luaの関数の中でvim.cmd("nohlsearch")として呼ぶと効きました。Luaの関数はVim scriptのユーザー関数として呼ばれておらず、状態の保存と復元の対象に入らないためです。設定をLuaで書いていても、踏まずに済むのは関数から呼ぶ場合だけです。nvim_create_autocmd()のコールバックにvim.cmd("nohlsearch")を置いてCursorHoldで試したところ、v:hlsearchは1のままでした。autocommandから消したいときは、Luaでもvim.opt.hlsearch = falseのようにオプション側を触ります。
そもそもハイライト自体が不要な場合
set nohlsearch
検索結果のハイライト自体を最初から不要と感じるなら、この設定でそもそも有効にしないという選択もできます。ただし複数箇所ある同じ単語を目視で確認したい場面ではハイライトが便利なことも多いので、無効にするか、こまめに消す運用にするかは好みが分かれるところです。
全部切ってしまう前に、折衷案があります。検索のコマンドラインにいる間だけ光らせて、確定した瞬間に消す書き方です。
" 検索のコマンドラインにいる間だけハイライトする
autocmd CmdlineEnter /,\? set hlsearch
autocmd CmdlineLeave /,\? set nohlsearch
ここで:nohlsearchではなくset nohlsearchを使っているのは、前の節の制限があるからです。両方を登録して比べたところ、autocommandにnohlsearchと書いた場合はv:hlsearchが1のまま素通りし、set nohlsearchと書いた場合は0になりました。オプションを切る書き方なら保存と復元の対象外なので確実に効きます。
この形にすると、パターンを打っている最中だけ候補が全部光り、Enterで確定した瞬間に画面が静かになります。「どこにいくつあるか」を知りたいだけなら、ハイライトが要るのは打っている数秒だけです。カーソル位置を先行表示するincsearchと組み合わせると、打ちながら位置を確認して確定と同時に消える流れになります。確定後のカーソル位置まで指定したいときは、検索オフセットが別の道具として使えます。
VimとNeovimで既定が違う
そもそもハイライトが出るかどうかは、VimとNeovimで逆です。設定ファイルを置かない状態で測ると、Vim 9.1はhlsearchもincsearchも無効、Neovim 0.12.4はどちらも有効でした。「Neovimでは何もしていないのに検索が光る」「Vimに戻したら光らない」という食い違いは、ほぼここが原因です。
Vim側にはもう一段ややこしい事情があります。ユーザーの設定ファイルが1つも無いとき、Vimはdefaults.vimという無難な設定を読みますが、macOS付属のVimは/usr/share/vim/vimrcにlet skip_defaults_vim=1を書いていて、これが読み込みを止めています。しかもdefaults.vim自体が有効にしているのはincsearchだけで、hlsearchには触れていません。どちらの経路でもVimは光らないので、欲しければ自分でset hlsearchと書く必要があります。
待ち時間の既定も違います。Neovim 0.12.4はttimeoutlenが50なので、Escにマッピングしても遅れはほとんど体感できません。先ほど測った1秒の待ちはVim側の話で、Neovimでそのまま起きるわけではありません。両方を行き来している場合、同じ設定を書いても反応が違って見えるのはこのためです。
hlsearchと内部の目印の掛け算で決まり、:nohlsearchが倒すのは目印だけです。オプションを見ても消えている理由が分からないのも、次の検索で戻るのも、関数の中で効かないのも、すべてここから説明が付きます。
:nohをコマンドラインで打つのが面倒で、しばらくハイライトを有効にしたまま我慢していた時期があります。Escにマッピングしてからは、検索を終えて次の操作に移る自然な流れの中でハイライトが消えるようになり、意識する必要すらなくなりました。むしろ手間取ったのは、そのマッピングを「検索したあとの後始末」としてまとめた関数へ移したときです。中身は変えていないのに一切消えなくなり、キーの綴りやマッピングの衝突を延々と疑いました。関数の中では効かないという仕様のせいだと分かるまでに30分ほど使っています。いまは関数に入れるときだけfeedkeys()を挟むようにしていて、それ以来この件で悩んでいません。
他のマッピングと衝突しないか確認する
Escへのマッピングを足す前に、そのキーがすでに使われていないかを見ておきます。:verbose map <Esc>と打つと、割り当てがあれば内容と定義元まで出ます。手元で登録したものを問い合わせたときの返答は次の1行でした。
n <Esc> * :nohlsearch<CR>
行頭のnはノーマルモードの割り当てという意味で、プラグインが定義したものなら「Last set from」の行が続けて出ます。何も表示されなければ未使用なので、そのまま割り当てて構いません。
Escを上書きすること自体の危険は小さいものです。ノーマルモードのEscは本来「入力しかけたものを捨てる」役目で、数字を打ちかけた状態や演算子待ちの状態をここで取り消せます。マッピングを入れてもこの取り消しは残っていて、ggで先頭へ移動してから3、Esc、jと押すと、カウントが捨てられて2行目に止まりました。取り消しが壊れるわけではなく、増えるのは前述の待ち時間だけです。それも避けたいならCtrl-l側へ寄せるほうが無難です。
まとめ
検索ハイライトが煩わしいときは:nohで即座に消せます。触っているのはオプションではなく内部の目印なので、次の検索や:sで戻るのは仕様どおりです。二度と出したくないときだけset nohlsearchでオプションごと切ります。
頻度が高いならEscかCtrl-lへのマッピング、キーを潰したくないなら:packadd nohlsearchで放置時に自動で消す形が使えます。詰まりやすいのは、関数やautocommandの中に書いても効かないという一点だけです。そこさえ頭に入れておけば、設定を書いていて原因不明の状態に陥ることはほとんどありません。検索コマンドそのものの使い分けはVimの検索まとめに整理してあります。