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 中加载库和自己的模块,它非常适合玩你的代码。你很快就会发现用户体验并不理想,因为它没有编辑支持:你无法方便地更改你输入的内容,也无法回退到以前输入的短语。
可以使用诸如 ledit 或 rlwrap 之类的工具来改进这一点,这些工具为任何程序添加了行编辑支持: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 在以下方面更好
- 它提供了上下文敏感的完成
- 它表现得像一个真正的 shell,即你无法删除提示符
有几个 Emacs 库用于编写类似 shell 的模式,但我自己写了一个,因为在我找到的所有库中,都可以从提示符中插入或删除字符,我发现这很令人沮丧。即使使用 Emacs Shell 模式使用的模式,也可以做到这一点。据我所知,在我编写它时,UTop 模式是唯一一个真的无法编辑缓冲区中冻结部分中的内容的模式。
其他功能
这是一个随着时间的推移而添加的功能的非详尽列表,旨在增强用户体验。其中一些可能存在争议,因此我试图选择最常被请求的功能。
- 在使用 lwt 或 async 库时,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 平台邮件列表 与我联系。