OCaml
utop

utopのプロンプトのカスタマイズは地味に楽しい

utopのプロンプトはUTop.promptを介してカスタマイズすることができます。

utop # UTop.prompt;;
- : LTerm_text.t React.signal ref = {Pervasives.contents = <abstr>}

 何だこれは・・・
 文字列じゃダメなのか?
 プロンプトごときのためにドキュメント読みたくねーよ
などと思われるのはもっともですが、例を見ていただければそれほど煩雑でないことが分かってもらえるかと思います。それどころか

  • 型がつく!
  • REPLで開発できる!

と思えばむしろ楽しいくらいです。

ちなみにLTerm_text.tReact.signalはそれぞれターミナル操作のためのライブラリであるLambda-TermとFRPライブラリのReactから来ています。

以下では例を挙げて説明しますが、私はそれぞれのライブラリ(特にReact)の使い方をちゃんと理解していないので、下手な書き方になっているかも知れません。

その前に

utopはいくつかプロンプトをディレクティブの形で用意してくれています。ぜひチェックしてみてください。

  • #utop_prompt_dummy
  • #utop_prompt_fancy_dark
  • #utop_prompt_fancy_light
  • #utop_prompt_simple

シンプルなプロンプト

デフォルトのプロンプトは派手なので、シンプルにutop #とだけ表示するようにしてみましょう。色も一色だけにします。

.ocamlinit
#require "lambda-term";;

let open LTerm_text in
UTop.prompt := 
  [B_fg (LTerm_style.rgb 191 255 0); S "utop # "]
  |> eval
  |> React.S.const
;;

各要素の型を確認してみます。

utop # [B_fg (LTerm_style.rgb 191 255 0); S "utop # "];;
- : item list = [B_fg (LTerm_style.RGB (191, 255, 0)); S "utop # "]
utop # LTerm_style.rgb;;
- : int -> int -> int -> LTerm_style.color = <fun>
utop # eval;;
- : markup -> t = <fun>
utop # React.S.const;;
- : 'a -> 'a React.signal = <fun>

詳しく言うとB_fgSitem型のコンストラクタで、前者はLTerm_style.colorを受け取って文字色を指定し、後者は文字列を指定するものです。これ以外のコンストラクタについてはドキュメントをご覧ください。
またmarkup = item listです。

これだけ見れば使い方も大体分かってしまったかと思います。

動的なプロンプト

プロンプトの表示ごとに内容を変更することもできます。個人的にはここでどう書けばいいのか悩みました。動的に内容を変更するならunit -> 'aを用意してやれば良さそうですが、代わりにここではReactの仕組みに頼ることになります。

プロンプトとして<ユーザー名>@<カレントディレクトリ> #を表示してみましょう。

.ocamlinit
let open LTerm_text in
let make_prompt (c : int) =
  eval [S (Printf.sprintf "%s@%s # " (Sys.getenv_exn "LOGNAME") (Sys.getcwd ()))] in
UTop.prompt := React.S.l1 make_prompt UTop.count
;;

React.S.l1UTop.countの型を見てみます。

utop # React.S.l1;;
- : ?eq:('b -> 'b -> bool) -> ('a -> 'b) -> 'a React.signal -> 'b React.signal
= <fun>
utop # UTop.count;;
- : int React.signal = <abstr>

React.S.l1はその名の通り1引数関数を受け取ります。そのような関数がl6まで用意されています。またUTop.countの中身は、プロンプトが表示されるごとに1だけ加増されます。このカウント値をmake_promptの中で使っていないので奇妙に見えますが、こうしないとプロンプトが更新されません。この辺りの理由をうまく説明できないのは私の力不足のせいです。

まとめ

何となくでも使い方が分かっていただけたでしょうか。残念ながらこの記事を書いている時点では情報がほとんどないようです。ライブラリのドキュメント以外ではutopのソースが理解の助けになりました。
utop/uTop.ml at master · diml/utop · GitHub
LTerm_style (lambda-term.LTerm_style)
LTerm_text (lambda-term.LTerm_text)
React.S