Vimでスペルチェックする(set spell)日本語を赤くしない設定
英語のREADMEやコミットメッセージで:set spellを使うと、tehのような打ち間違いに下線が付きます。ところが日本語が混ざった文書では、日本語の行がすべて誤り扱いになり、肝心の英単語の誤りが埋もれます。
原因はspelllangにcjkが無いことで、set spelllang=en,cjkの1行で日本語は判定から外れます。その確かめ方から、修正・単語の登録・Neovim 0.12の変更・配布設定での止め方までを扱います。
検証環境:Vim 9.1.1752とNVIM v0.12.6(macOS)。空の一時ディレクトリをHOMEにして起動し、spellbadword()の戻り値、tmuxで打ったキーの結果、作られたファイルを見ています。LazyVimは入れておらず、ソースを読んで同じautocmdを最小のinit.luaで再現しました。辞書のダウンロードは確認画面までで、実際には落としていません。
目次(9項目)
| 操作 | これだけ覚える |
|---|---|
:set spell spelllang=en,cjk | 英語だけ判定し、日本語は外す |
| ]s / [s | 次 / 前の誤りへ移動 |
| z= | カーソル下の単語の候補を出す |
| zg / zw | 正しい単語として登録 / 誤りとして登録 |
| zug / zuw | 登録を取り消す |
:set nospell | オフにする |
スペルチェックをオンにする(:set spell と spelllang)
スペルチェックはspellとspelllangの2つのオプションで動きます。spellはオンオフのスイッチで、どの言語の辞書で判定するかを決めるのがspelllangです。既定値はVimもNeovimもenなので、英語を見るだけなら:set spellだけで下線が出ます。
:set spell " オン
:set nospell " オフ
:set spell! " 切り替え
:set spelllang? " spelllang=en
辞書はランタイムのspellディレクトリにあり、Vim 9.1はen.utf-8.spl en.latin1.spl en.ascii.spl、Neovim 0.12.6はen.utf-8.splだけでした。名前の真ん中はencodingの値で、合うものが読まれます。encodingを変えているvimrcでは、spelllangをそのあとに書きます(Vimで文字コードを確認・変換する方法)。
判定は4種類あり、辞書に無い単語がSpellBad、文頭の小文字がSpellCap、別の地域の綴りがSpellLocal、まれな単語がSpellRareです。spelllang=en_usでspellbadword("colour")は['colour', 'local']、spellbadword("It works. then fails")は['then', 'caps']でした。日本語の句点のあとのthenはcapsにならず、spellcapcheckの既定値は半角の.?!しか文末と見ません。
日本語が真っ赤になるのは spelllang に cjk が無いから
spelllang=enのままだと、ひらがなも漢字も英語の辞書に無いので全部がSpellBadになります。cjkは辞書ではなく、東アジアの文字を判定から外すという特別な指定です。
If the name "cjk" is included East Asian characters are excluded from spell checking. This is useful when editing text that also has Asian words.
vimhelp.org options.txt 'spelllang'
違いはspellbadword()の戻り値ではっきり出ます。VimとNeovimで同じスクリプトを流し、結果も同じでした。
" spelllang=en
spellbadword("これは日本語") " ['これは日本語', 'bad']
spellbadword("teh") " ['teh', 'bad']
" spelllang=en,cjk
spellbadword("これは日本語") " ['', '']
spellbadword("teh") " ['teh', 'bad']
spellbadword("これはtehです") " ['teh', 'bad']
大事なのは最後の行で、日本語に挟まれたtehはcjkのあとも拾われます。外れるのは日本語の部分だけです。日本語の辞書はどちらにも同梱されていないので日本語の誤字は見つけられず、英単語の打ち間違いを拾う道具として使います。
誤りへ移動して直す(]s / [s / z= / Ctrl-x s)
ノーマルモードの]sで次の誤りへ、[sで前の誤りへ飛びます。teh quick brwn foxの行頭で]sを押すと、今いるtehを飛ばしてbrwnへ移り、もう一度押すとwrapscanで先頭のtehへ戻りました。]sはSpellCapのthenにも止まりますが、]Sは飛ばしてSpellBadだけを巡ります。
移った先でz=を押すと候補の一覧が出て、番号を打ってEnterで置き換えます。brwnの上で押したときの画面は次のとおりで、brownは3番目でした。
Change "brwn" to:
1 "bran"
2 "brawn"
3 "brown"
4 "Bryn"
5 "Bran"
6 "Brown"
7 "been"
8 "own"
9 "run"
spellsuggest("brwn", 3)も['bran', 'brawn', 'brown']で、正解が先頭に来るとは限りません。
挿入モードではCtrl-x sです。recieveの直後に押すとreceiveに置き換わり、relieve reserveなどの候補が並びました(Vimの標準入力補完(Ctrl-n / Ctrl-p / Ctrl-x)の使い方)。
正しい単語を登録する(zg / zw / zug と spellfile)
技術文書では、正しいのに辞書に無い単語がたくさん出てきます。手元で試すとvimrcもNeovimもbadでした。こうした単語はカーソルを合わせてzgを押すと自分の単語リストに入り、以後は下線が付きません。逆に辞書では正しいが自分は誤りとして扱いたい単語はzwで登録します。取り消しはzugとzuwです。
単語リストの場所はspellfileで決まります。既定値はVimもNeovimも空で、空のままzgを押すと置き場所を自動で決めてspellfileに書き込みます。決め方はVimとNeovim 0.12で違い、実際に作られたファイルは次のとおりでした。
| 環境 | zg で作られたファイル |
|---|---|
| Vim 9.1 | ~/.vim/spell/en.utf-8.add |
Vim 9.1(~/.vimが無い) | 作られず E764: Option 'spellfile' is not set |
| Neovim 0.12.6 | ~/.local/share/nvim/site/spell/en.utf-8.add |
Vimはruntimepathで最初に書ける場所を使い、~/.vimがあればspellディレクトリを作って置きました。~/.vim自体が無いとE764で失敗するので、mkdir ~/.vimするかspellfileを明示します。Neovimは~/.local/share/nvimが無くてもsite/spellまで作りました。
隣のen.utf-8.add.splは.addを判定用に変換したものです。.addにはzgでteh、zwでquick/!が書かれました。zugは行を消さず先頭の1文字を#で上書きし、tehが#ehとして残ります。VimでもNeovimでも同じでした。
Markdownとコミットメッセージだけでオンにする
vimrcにset spellを書くと、コードを開いたときも変数名に下線が付いて邪魔になります。文章を書くファイルタイプだけでオンにするならFileTypeのautocmdを使います。
autocmd FileType markdown,gitcommit setlocal spell spelllang=en,cjk
setだとほかのバッファにも効くのでsetlocalにします。a.md、COMMIT_EDITMSG、x.txtの順に開くと、spellは1、1、0でした(Vim・Neovimとも)。オンにならないときはVimでファイルタイプを手動指定する方法を見て:set filetype?を確かめます。init.luaなら次のとおりです。
vim.api.nvim_create_autocmd("FileType", {
pattern = { "markdown", "gitcommit" },
callback = function()
vim.opt_local.spell = true
vim.opt_local.spelllang = { "en", "cjk" }
end,
})
コードのコメントまで見たい場合に効くのがspelloptions=camelです。exists('+spelloptions')はVim 9.1でもNeovim 0.12.6でも1でした。キャメルケースの識別子を大文字の位置で区切って、部分ごとに判定します。
" spelloptions=(空)
spellbadword("myVariable") " ['myVariable', 'bad']
spellbadword("getUserName") " ['getUserName', 'bad']
" spelloptions=camel
spellbadword("myVariable") " ['', '']
spellbadword("getUserName") " ['', '']
spellbadword("myVariabel") " ['Variabel', 'bad']
camelにするとmyVariabelの誤りだけがVariabelとして拾われます。構文ハイライトが有効ならコード部分は判定から外れ、int tehvar = 0; /* teh comment */で]sは:syntax onだとコメント内のtehへ、:syntax offだとtehvarへ飛びました。
Neovim 0.12で変わったこと
:help newsにある変更の1つは単語リストの置き場所で、「'spellfile' location defaults to stdpath("data").."/site/spell/" instead of the first writable directory in 'runtimepath'.」です。0.11は試していませんが、この記述どおりなら以前は~/.config/nvim/spell/に書かれていたはずで、そこをdotfilesで管理していた人はspellfileを明示します。
vim.opt.spellfile = vim.fn.stdpath("config") .. "/spell/en.utf-8.add"
もう1つは、辞書が無い言語を指定したときにダウンロードを持ちかける仕組みです。Vim script製のspellfile.vimがLua製のpackage-spellfileに置き換わりました。-u NORC(プラグインは読み、設定は読まない)で起動して:set spelllang=deとすると、Neovim 0.12.6は次のように聞いてきました。
No spell file found for de (utf-8). Download? [y/N]
Vim 9.1では~/.vimが無いと、There is no writable spell directory.に続けて~/.vim/spellを作るかを先に聞かれました。-u NONEではどちらもWarning: Cannot find word listと出るだけです。
Neovim側の設定はrequire('nvim.spellfile').config()で変えます。引数なしで呼ぶと今の値が返り、手元では次のとおりでした。
{
confirm = true,
timeout_ms = 15000,
url = "https://ftp.nluug.nl/pub/vim/runtime/spell"
}
confirm = falseで確認なしに落とし、urlで取得元を変えます。止めるならg:loaded_spellfile_plugin = 1です(Neovim 0.12の新機能)。
配布設定でオンになるスペルチェックを止める
LazyVimのソースでは、options.luaがopt.spelllang = { "en" }を設定し、autocmds.luaのlazyvim_wrap_spellグループがtext gitcommit markdownなどでvim.opt_local.spell = trueにします。cjkが無いので日本語は全部誤り扱いです。
vim.opt.spell = falseで止まらないのは、ファイルを開いたときにautocmdがバッファの値を上書きするからです。再現したinit.luaでもspellは1のままでした。止めるにはグループを消します(スターターのコメントにある例です)。
-- lua/config/autocmds.lua
vim.api.nvim_del_augroup_by_name("lazyvim_wrap_spell")
これでspellは0になりましたが、同じグループのwrapも戻ります。日本語だけ外したいなら言語を上書きします。
-- lua/config/options.lua
vim.opt.spelllang = { "en", "cjk" }
LazyVimは自分の設定を後から読むので、こちらが勝ちます。この2つはLazyVimを入れて確かめたものではありません。
検証で最初につまずいたのはzgでした。HOMEを空にしたVimで押すとE764が出て、spellfileは自分で設定するものだと思い込みました。Neovimでは通ったので版の違いを疑い、ヘルプを読み直して~/.vimが無いせいだと気づいています。zugのあと.addに#ehという行が残っていたのも意外で、ファイルが壊れたのかと身構えました。
まとめ
日本語の文書でスペルチェックを使うなら、set spell spelllang=en,cjkが出発点です。cjkで外れるのは日本語の文字だけで、日本語に挟まれた英単語の打ち間違いは引き続き拾われます。誤りは]sで飛んでz=で直し、正しい用語はzgで単語リストに入れます。
単語リストはVimが~/.vim/spell/、Neovim 0.12が~/.local/share/nvim/site/spell/です。コードが中心ならFileTypeのautocmdでMarkdownとコミットメッセージだけに絞ります。