UTop:OCaml toplevel 的重大改进

这是一篇关于 OPAM 存储库中提供的 utop toplevel 的文章,作为标准 OCaml toplevel 的替代方案。

OCaml 带有一个交互式 toplevel。如果你在 shell 中输入 ocaml,你会得到一个提示符,你可以在其中输入 OCaml 代码,这些代码会立即编译和执行。

$ ocaml
    OCaml version 4.02.0+dev12-2014-07-30

# 1 + 1;;
- : int = 2

你可以在 toplevel 中加载库和自己的模块,它非常适合玩你的代码。你很快就会发现用户体验并不理想,因为它没有编辑支持:你无法方便地更改你输入的内容,也无法回退到以前输入的短语。

可以使用诸如 leditrlwrap 之类的工具来改进这一点,这些工具为任何程序添加了行编辑支持:rlwrap ocaml。这更好,但仍然没有提供诸如上下文敏感完成之类的花哨功能。

这就是 UTop 的起源。UTop 是 OCaml 交互式 toplevel 的一个闪亮前端,它试图专注于用户体验和功能

  • 交互式行编辑
  • 函数和值的实时制表符完成
  • 语法高亮

以及许多其他随着时间的推移而添加的功能,使使用者的生活更轻松。

UTop 代表什么?

UTop 代表 Universal Toplevel。之所以称为通用,是因为它可以在终端或 Emacs 中使用(我最初计划使用 GTK 添加一个窗口版本,但不幸的是从未完成)。

UTop 提示符

utop 提示符看起来比默认 toplevel 的提示符更“闪烁”。使用 OPAM 非常简单地安装它

opam install utop
eval `opam config env`  # may not be needed
utop

这通常是你启动 utop 时看到的内容

─( 16:36:52 )─< command 0 >───────────────────────{ counter: 0 }─
utop #
┌───┬────────────┬─────┬───────────┬──────────────┬───────┬─────┐
│Arg│Arith_status│Array│ArrayLabels│Assert_failure│Big_int│Bigar│
└───┴────────────┴─────┴───────────┴──────────────┴───────┴─────┘

它显示

  • 时间
  • 命令编号
  • 宏计数器(用于 Emacs 样式宏)

底部的框用于完成,将在下一节中介绍。

如果颜色看起来太亮,可以输入 #utop_prompt_fancy_light,这更适合浅色背景。可以通过将该行添加到 ~/.ocamlinit 或将 profile: light 添加到 ~/.utoprc 来永久设置。

用户可以通过设置引用 UTop.prompt 来定制提示符

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

LTerm_text.t 用于格式化文本,而 React.signal 意味着它是一个反应信号,来自 react 库。这使得创建提示符变得非常容易,例如,该提示符中的时间每秒更新一次。

实时完成

这是促使创建 UTop 的主要功能。UTop 利用编译器内部来查找对以下内容的可能完成

  • 函数名
  • 函数参数名
  • 构造函数名
  • 记录字段
  • 方法名

我没有采用经典的方式,即在用户按下 TAB 键时显示单词列表,而是选择在用户输入时动态显示不同的可能性。这个想法来自 dwm 窗口管理器中的 dmenu 工具。

可能的完成显示在提示符下方的完成栏中。可以使用元键(大多数情况下默认情况下为 Alt)和左右箭头在列表中导航。可以通过按下元键和 TAB 键来选择一个词。此外,仅按下 TAB 键将插入所有可能性的最长公共前缀。

语法高亮

UTop 可以进行基本的语法高亮。默认情况下禁用此功能,但可以通过编写 ~/.utoprc 文件来启用。你可以从存储库中复制一个,可以选择 深色背景浅色背景

Emacs 集成

如前所述,UTop 可以在 Emacs 中运行。在 UTop 的自述文件 中可以找到设置此操作的说明。默认 toplevel 也可以以这种方式运行,但 UTop 在以下方面更好

  1. 它提供了上下文敏感的完成
  2. 它表现得像一个真正的 shell,即你无法删除提示符

有几个 Emacs 库用于编写类似 shell 的模式,但我自己写了一个,因为在我找到的所有库中,都可以从提示符中插入或删除字符,我发现这很令人沮丧。即使使用 Emacs Shell 模式使用的模式,也可以做到这一点。据我所知,在我编写它时,UTop 模式是唯一一个真的无法编辑缓冲区中冻结部分中的内容的模式。

其他功能

这是一个随着时间的推移而添加的功能的非详尽列表,旨在增强用户体验。其中一些可能存在争议,因此我试图选择最常被请求的功能。

  • 在使用 lwtasync 库时,UTop 会自动等待 ['a Lwt.t]['a Deferred.t] 值并返回 ['a] 代替
  • -short-paths 设置为默认值。此选项允许在使用打包库(如 core)时显示更短的类型
  • 向用户隐藏以 _ 开头的标识符。这是为了隐藏语法扩展生成的混乱。可以通过 UTop.set_hide_reserved 或命令行参数 -show-reserved 禁用此功能。
  • 当用户请求语法扩展时,自动加载 camlp4。在默认 toplevel 中,必须先输入 #camlp4
  • 隐藏来自 findlib 库管理器的详细消息。
  • 添加 typeof 指令以显示模块和值的类型。
  • 在启动时自动加载来自 $OCAML_TOPLEVEL_PATH/autoload 的文件。
  • 允许在命令行中指定要加载的库。

为支持 UTop 而开发的库

为了满足 UTop 的需求,我编写了 lambda-term,它有点类似于 ncurses+readline,但用 OCaml 编写。之所以编写它,是因为我对 ncurses API 不满意,并且希望得到比 readline 更花哨的东西,尤其是为了完成。最后,我相信使用 lambda-term 在 OCaml 中编写终端应用程序要有趣得多。

纯粹的编辑部分由 zed 库管理,该库独立于用户界面。

UTop 开发

UTop 的功能相当完善,因此我最近没有花太多时间在这上面。它成为 Real World OCaml 书中推荐使用的 toplevel,大多数用户对交互式界面比使用传统 toplevel 更满意。

非常感谢 Peter Zotov 最近加入了该项目,以使其保持最新并添加新的功能,例如扩展点支持。来自他人的贡献(尤其是围绕编辑器集成)非常受欢迎,因此如果你有兴趣参与其中,请通过 GitHub 问题跟踪器OCaml 平台邮件列表 与我联系。