⚠️
As of September 29, 2020 I’ve decided to put this project on hold for a bit, until the Revery team addresses an issue with their ScrollView
component. While it’s not a showstopper, the UI wouldn’t really work without it. I’ll be back on this as soon as the issue is addressed!
I’ve been thinking a lot about text editors lately, going so far as to playing with writing my own. In the process of exploring that idea I came across a tutorial written by Pailey Quilts on how to build a text editor in C. It’s a great write-up that thoroughly explains the code behind kilo, a simple terminal editor.
I’ve been writing code in Reason in different projects lately and I’m quickly falling in love with the language. I find the developer experience very pleasing, and I absolutely love the type system. I’ve also been keeping my eyes on Onivim 2, a new text editor that aims to have the usability of VSCode with the utility of Vim—an ambitious project to say the least!
It just so happens that Onivim 2 is built with Reason! More specifically, it’s built with Revery, a GUI framework that came to be because of Onivim 2. And all of this got me thinking; what would a kilo-like editor look like when made with Reason and Revery?
So, here we are. My take on Pailey’s tutorial, except instead of writing a simple terminal editor in C, you’re going to write a simple GUI editor in Reason!
💡
All the code and incremental changes will be published in ~reykjalin/kilo-gui.
Setup
Just like in Pailey’s post, the first thing you’ll want to do is to set up your environment. To build GUIs using Reason and Revery, you’ll need to install Esy. Esy is a package manager and build tool, similar to Node’s npm
, but built for Reason and OCaml.
How to install Esy
The first thing you’ll need is Node. The LTS version will do just fine. Node should come with npm
, and it’s really npm
you need. You’ll use npm
to install esy
.
npm install -g esy
You may need to run this command as an administrator, in which case you’ll need to add the --unsafe-perm=true
flag.
sudo npm install -g esy --unsafe-perm=true
Code language: JavaScript (javascript)
I’d recommend you try running the test project they’ve set up to make sure esy
is installed correctly. You can find instructions on how to do this in Esy’s getting started guide.
Creating a Revery project
The simplest way to get started with a new Revery project is to clone the Revery quick start project, and go from there.
git clone https://github.com/revery-ui/revery-quick-start.git
Code language: PHP (php)
I’ve found that the dependencies tend to be out of date here, and there might be some key differences in the Revery API between the version installed in the quick start vs. the latest version of Revery, so the first thing you’re going to do is update all the project dependencies.
Updating the project dependencies
The current dependencies can be found in package.json
in the dependencies
and devDependencies
objects. The dependencies should look something like this
/* package.json */
"dependencies": {
"revery": "revery-ui/revery#8fd380c",
"@opam/dune": "2.5.0",
"@glennsl/timber": "^1.2.0",
"esy-macdylibbundler": "*"
},
"devDependencies": {
"ocaml": "~4.9.0",
"@opam/ocaml-lsp-server": "ocaml/ocaml-lsp:ocaml-lsp-server.opam#04733ed"
}
Code language: JavaScript (javascript)
Finding the newest versions for your dependencies can be a bit tricky, so here is how I found the newest versions:
- Revery‘s latest commit hash is
1531d5f
. - dune‘s latest version is
2.6.1
. - OCaml‘s latest version is
4.10.0
. - ocaml-lsp‘s latest commit hash is
b017b14
. - Timber is already using the latest version.
- The
*
in esy-macdylibbundler means the latest version will be installed.
So after we update package.json
the dependencies should look something like this.
/* package.json */
"dependencies": {
"revery": "revery-ui/revery#1531d5f",
"@opam/dune": "2.6.1",
"@glennsl/timber": "^1.2.0",
"esy-macdylibbundler": "*"
},
"devDependencies": {
"ocaml": "~4.10.0",
"@opam/ocaml-lsp-server": "ocaml/ocaml-lsp:ocaml-lsp-server.opam#b017b14"
}
Code language: JavaScript (javascript)
Updating the project metadata
Updating the project metadata to fit your needs is usually a good idea. You should use this chance to do exactly that! You can find this project metadata in package.json
.
/* package.json */
{
"name": "kilo-gui",
"version": "0.1.0",
"description": "A simple GUI editor built with Revery",
"license": "MIT",
"scripts": {
"format": "bash -c \"refmt --in-place **/*.re\"",
"run": "esy x Kilo"
},
"esy": {
"build": "dune build -p Kilo",
"buildDev": "refmterr dune build -p Kilo",
"buildsInSource": "_build"
},
"revery-packager": {
"bundleName": "Kilo",
"bundleId": "com.thorlaksson.kilo",
"displayName": "Kilo",
"mainExecutable": "Kilo",
// ...
}
Code language: JSON / JSON with Comments (json)
The changes I’ve suggested here mean you need to rename src/App.re
to src/Kilo.re
, and change some of the dune
files to accommodate these project changes. It’s a bit tedious to list all of those here, so I’d recommend you look at the commit where I make these changes instead.
Building the project
The first build after updating your dependencies needs to be completely clean, so before doing anything you should delete the esy.lock
directory, which is used to lock the dependency versions in place. If you don’t delete this directory now, esy
will not update the dependencies, despite the changes in package.json
.
rm -rf esy.lock
Code language: CSS (css)
Now you can build the project by running esy
.
esy
Running esy
will install your dependencies and build your project. This first build will take a while since esy
will actually build all the dependencies for you. esy
caches your built dependencies so subsequent builds will be close to instantaneous. You just need to suffer through this long build time the first time you build your project.
And now you can run the project!
esy run
💡
This command will both build and run the project for you! No need to run 2 commands, yay! 🎉
And you should be greeted with the default quick start app!

Make room for your own work
The last thing for you to do is remove all the unnecessary code, and start with a clean slate. This is relatively simple; delete src/AnimatedText.re
, src/SimpleButton.re
, and src/Theme.re
, and then modify src/Kilo.re
. to remove the unnecessary Style
, and reduce the main app component to an empty component.
/* src/Kilo.re */
let main = () => React.empty;
Code language: JavaScript (javascript)
Finally, you need to remove the dependency on Theme.re
by changing the win
variable to
/* src/Kilo.re */
let win =
App.createWindow(
app,
"Kilo",
~createOptions=
WindowCreateOptions.create(
~backgroundColor=Colors.white,
~width=512,
~height=384,
(),
),
);
Code language: JavaScript (javascript)
Now when you run the program you should see an empty window—a great place to start a new project!

Going forward you’ll want to build and run the app to see the changes in action and make sure you didn’t accidentally break something. Like Pailey says:
It is easy to forget to recompile, and just run
./kilo
, and wonder why your changes tokilo.c
don’t seem to have any effect. You must recompile in order for changes inkilo.c
to be reflected inkilo
.
The same can be said here! Make sure to run the app after making changes!
The next post will be up soon!
Leave a Reply