haya14busa

haya14busa’s memo

Vimでマルチバイト文字を座標によって一括置換したときにハマらないようにするメモ

Vimでマルチバイト文字を座標によって一括置換したときにハマらないようにするメモ

Vim-easymotionのマルチバイト文字対応でハマってた

haya14busa/vim-easymotion

Vim-easymotionの動作は基本的に正規表現でマッチする文字列を探して、その座標(cooridinate)としてline番号とcolumn番号をリストに追加し、それをあとからターゲット文字列に一括置換してHighlightさせるという手順を取ります。

Multibyte文字を扱わないときはこれで一切問題ないのですが、日本語の文字列やTab文字など、column番号に使われるbyte数と、実際に表示される文字列の長さが違う場合にバグが起こります。

表示位置がずれる

設定にもよりますが、Tab文字列は4文字分表示させていますが、1byteです。これをターゲットである”f”や”j”といった1byteで置換してしまうと3byte分表示列がずれてしまいます。

解決法

表示がずれる問題は置換した時の文字列の差分の分だけスペースを足せばいいということになります。

strdisplaywidth()関数を使い文字列の長さを取り、repeat(' ', target_char_len -1)を置換する文字に足します

Byte数のズレによって置換、ハイライトができなくなる

日本語はもっと厄介で、表示される1文字の長さは2byte分でさらに実際のbyte数は3byteです。vimで一括置換するといっても、ターゲットにマッチする座標のリストをループしながら置換していくだけなので、行ごとに最初のmultibyte文字を置換した瞬間、その後のbyte数がずれてしまい(日本語の3byte -> 1byte + 日本語の長さ2byte分を埋め合わせるためのスペース1byte)で1byteずつずれてしまいます。

これによって次の3byteの日本語文字列の2byte目がターゲットになってしまい、置換もできなければ、highlightも動かなくなってしまいます。

Example

あ あ あ あ あ あ
1  5  9  13 17 21  <- col('.')の結果かつ、置換するリストのcolumn数
↓ 1文字目を'a 'に置換
a  あ あ あ あ あ
1  4  8  12 16 20  <- 置換後のcol('.')の結果

このように座標が置換するごとにずれてしまい、うまく動かなくなります

解決

Loopごとに置換前と置換後の行全体の文字列のbyteの長さの差を記憶しておき、そのぶん指定するcolumnに足して置換していく

あ あ あ あ あ あ
1  5  9  13 17 21  <- col('.')の結果かつ、置換するリストのcolumn数
↓ 1文字目を'a 'に置換
a  あ あ あ あ あ
1  4  8  12 16 20  <- 置換後のcol('.')の結果。
残りの[5,9,13,17,21]はそれぞれ差分の1を引いて[4,8,12,16,20]になる

結論

すごいわかりづらい感じになったし、わかりやすく書く努力すらできていないけど、何が言いたいかってvim-easymotionにmigemo機能を実装して日本語でもeasymotionするのなかなか捗りを感じる。

ちょっと重いけどおすすめ。

(もとから実装されてた単語移動などによってずれる問題も解決ってことなのでmigemoなしでも便利)

Comments