lambda-termでmulti byteを一文字ずつ表示する
気づいたら8月になっていました。今年の梅雨はかなり長かったですね・・・。過ごしやすいのは結構なんですが、野菜が高くなるのでこれも困ります。 今回は、OCamlでTUI(Terminal-based User Interface)を作る際の鉄板ライブラリである lambda-term を使ったときに、multi byteを表示出来なかったのを解消したので、備忘録として書いておきます。 <!–more–> やりたかったことと起こっていたこと やりたかったこととしては、 一文字ずつ表示したい 各文字にstyleを当てたい lambda-termでは、 LTerm_draw.draw_string と LTerm_draw.draw_char という2つの関数があります。こいつらは字面の通り、stringやcharをレンダリングします。 使い方はこんな感じです。実際にはcontextが必要なので、widgetの中とかで行う感じです。 let str = Zed_string.of_utf8 "foo" in let style = LTerm_style.none in LTerm_draw.draw_string ~style ctx 0 0 str; LTerm_draw.draw_char ~style ctx 0 1 @@ Zed_string.get 0 str これで表示自体は出来るんですが、 LTerm_draw.draw_char を使っていった時に、色々と気になる問題がありました。それは、multi byte(ここでは日本語)を表示しようとした時に、なぜか表示されない、ということでした。 解決 備忘録なのでさっさと行きますが、原因は LTerm_draw.draw_char のcolumn指定の誤りでした。 LTerm_draw.draw_char のシグネチャは、以下のようになっています。 val draw_char: ?style:LTerm_style.style -> LTerm_draw.context -> int -> int -> Zed_char.t -> unit さて、Zed_charですが、こいつは zed というライブラリが提供しているmoduleです。こいつはunicodeを保持していて、保持している文字の幅も持っています。 Zed_char.width で取得できます。 LTerm_draw.draw_char の挙動ですが、基本的にはterminalのascii 1文字を1columnとして描画します。ただ、 Zed_char.width が1より大きい場合は、1より大きい分だけSizeHolderというダミー文字で埋めるようになっています。 この挙動がわかっていなかったので、multi byteを1columnずつずらして表示しようとすると、一つ前に表示したmulti byteを消したのと同じ状態になってしまっていました。 実際、multi byteを考慮した上で LTerm_draw.draw_char を使う場合、以下のようにする必要があります。 let str = Zed_string.of_utf8 "テストfoo" in let style = LTerm_style.none in Zed_string.fold (fun ch index -> LTerm_draw.draw_char ~style ctx 0 index ch; index + Zed_char.width ch ) 0 |> ignore わかってしまえば納得ですが、中々ドキュメントだけでは分かりづらいことなので、誰か(主に自分)の役に立てばと思います。 ...