Hori Blog

フリーランスでバックエンドエンジニアとして活動している Ryota Hori のブログです。
最近はテック系記事より雑記ブログ気味。

Neovim で日本語入力の確定時に文字化けする症状の直し方

Neovim を使用していて日本語入力時の文字化けに長いこと苦しんでいたのですが、解決の目処が立ったのでブログに残しておきます。

日本語が普通に打てるって幸せ…!

そもそものバグ

Neovim で IME ( Google 日本語入力を使用しています)からの入力時を確定したタイミングでバーンっと文字化けするという症状がありました。

直し方

詳細は後述しますが、 ttmeoutttimeoutlen が適切に設定されていないと生じる症状でした。

init.nvim に以下を追記すれば直ります。

init.nvim
set ttimeout
set ttimeoutlen=50

原因

Neovim に issue があります。

Arbitrarily invoke insert mysterious words when Japanese(or not english lauguege) input · Issue #3094 · neovim/neovim

2015 年の古い closed issue ですが、実はまだ直っていなかったという。

下の方に私の(拙い)英語コメントが新しく追加されていますが、概要をブログにも記しておきます。

Neovim の input の挙動ですが、 Neovim ではキー入力( input )を受け取った際、一定の処理量ごとにバッファリングして処理します。

neovim/input.c at 685ca180f7c96f77a79c78d3b26bd003f8cd834c · neovim/neovim

一回のループでは

input.c
size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len));

で input 用のバッファから input->tk のサイズ分だけ抽出して処理を行います。

抽出した input->tk のバッファを

input.c
tk_getkeys(input, false);

で処理するのですが、先ほどの termkey_push_bytes で取得したバッファはバイト数で切り取っただけなのでマルチバイト文字を考慮できていません。

tk_getkeys はタイムアウトした場合、もしくはタイムアウトが設定されていない場合に、第二引数を true で呼び直し、すぐに解釈できない(確定できない)バイト列でも確定する、という挙動をします。

neovim/input.c at 685ca180f7c96f77a79c78d3b26bd003f8cd834c · neovim/neovim

よってマルチバイト文字を入力した際、分割されてしまったマルチバイトが上手く解釈できず悪さをし、文字化けする場合があるというのが主旨です。

tk_getkeys のタイムアウトの設定が先述のオプション ttimeoutttimeoutlen なので、これらを適切に設定すると直りますが、デフォルト値が無効とする設定だったため文字化けに遭遇した日本ユーザーが続出したという経緯でした。

なお、 2017-07-06 にデフォルト値を設定するプルリクがマージされたため、現在の master ブランチでは直っています。

options: Default to ‘ttimeout’ and ‘ttimeoutlen=50’ by jamessan · Pull Request #6969 · neovim/neovim

日本語以外でもマルチバイトな入力を行うと症状が出るため、カーソルキーの連打などで変な文字が入ってしまう症状もおなじ原因と考えています。今のところ私の環境では併せて改善しました。

ちなみに、 ttimeouttimeout の違いが混乱しがちなのですが、以下の区別です。

  • ttimeout : キーコードのタイムアウト
    • 入力文字の確定に対するタイムアウト
    • 流れ込んでくるバイト列を文字として判定するときに使われるタイムアウトなので、本来はとくに体感するようなタイムアウトではない
    • Esc キーのような、後ろにバイト列が付くと別のキーになるキーの入力時に、確定するまでの待ちとして体感する
  • timeout : マッピングのタイムアウト
    • たとえばキーマッピングで ggg を設定したとき、 g だけ入力してもマッピングは確定しない。マッピングを確定するまでのタイムアウト

以上

Neovim のコードは C なので、初めて gdb を使ってがっつりデバッグしてみました。

デバッガは IDE でしかやったことなかったのですが、意外となんとかなるもんですね。

文字化けを言い訳にブログを滞らせていたので、更新再開します。なおってよかった。