wcwidth(), mbstrlen()使用の際の注意点

wcwidth(), mbstrlen()っていうのは自分が追加したオレオレ関数です。詳細はこちらをどうぞ。

wcwidth()追加してみました - Humanity
Real Vim Hacks Project - Humanity


mbstrlen()だけじゃなくwcwidth()でも通じることだったので色々修正


文字コードのことはややこしいので冗長な感じに書いてみた。(いつもと同じとか言わない)
helpにも後で書く。


あとこれらの内容はバッドノウハウであり避けられるものなら避けるべきものなので、
つまり「wcwidth(), mbstrlen()の実装をこういう風にすればこれらの問題は解消する」というアイデアを持っている方はコメント欄ででもブコメでも教えてくださると助かります。

まず

wcwidth(), mbstrlen()の使用の際には2つの注意点がある。

  1. スクリプトのソースの文字コードが&encodingと同じであること
  2. 実行される時にhas_mbyteがtrueになっていること
    1. これは&encodingの値がutf8か、cp932、euc-jp、iso-2022-jpなど、いわゆるCJKV系の文字コード*1である場合にtrueになる
スクリプトのソースの文字コードが&encodingと同じであること

ようするにwcwidth(), mbstrlen()は引数の文字列の文字コードは&encodingと見なすため、
スクリプトのソースの文字コードが&encodingでないと正しい結果が得られない、ということ。

実行される時にhas_mbyteがtrueになっていること

has_mbyteがfalseの時は、単純にmbstrlen(), wcwidth()両方ともstrlen()と同じ結果になるため。

これらの条件を保証するための注意点

まず第1の条件は:scriptencodingでスクリプト文字コード
明示的に変換することで保証することができる。

これは何をするExコマンドかというと、例えば

:set encoding=cp932

の場合に

:scriptencoding utf8

とすると、「ソースがutf8であること」を宣言するため、
ソース中の文字列を現在の&encoding、つまりcp932に変換するという作業を、実行したと同時に行う。
ようするにこの場合はutf8 => cp932という変換が行われるということになる。


これを使えばこの第1の問題である「スクリプトのソースの文字コードが&encodingと同じであること」は解消する。
具体的には、Vimスクリプト文字コードがutf8の場合は

:scriptencoding utf8

と行頭に書けば自動的にソース中の文字列を&encodingにしてくれる。


ただし、

:scriptencoding utf8

だけではその時点での&encodingの値は環境によって様々なので、
その&encodingの値が次の「実行される時にhas_mbyteがtrueになっていること」を満たすとは限らない。
なのでソースファイルを読み込んでいる最中と、wcwidth(), mbstrlen()を実行する時に、
&encodingの値が、has_mbyteがtrueになるような値でなくてはならない。*2

「ソースファイルを読み込んでいる最中」は以下のようにして変えられる。

let s:save_encoding = &encoding
set encoding=utf8

(Vimスクリプトのコード)

let &encoding = s:save_encoding

ただ、これだけだと実行時の&encodingの値が保証されない。
実行時に&encodingの値を保証する方法は今のところ実装していないがこんな形を予想している。

echo mbstrlen('あ', 'utf8')

これはようするに

let s:save_encoding = &encoding
set encoding=utf8

echo mbstrlen('あ')
let &encoding = s:save_encoding

と同じこと。
ようするに上で「ソースファイルを読み込んでいる最中」で示したコードと
だいたい同じ意味を持つようにするということ。


ようするに&encodingを一時的に変えるだけなのだけど、
必ず実行時に&encodingの値が第2引数の値になるように保証する。
実行する時に&encodingが必ず第1引数の文字コード
同じになっているか分からない場合は常に指定した方がいいかも。

参考: 検証の際に使ったテストファイ


*1:wcwidth()追加してみました - Humanityのenc_dbcsがnon-zeroである場合は、globals.hのDBCS_*というそれぞれの文字コードを表す定数が入っている

*2:もちろん両方一緒であるべき