My friend and I are huge Neovim nerds. We like to rice our terminals and our editors. We just wanted a harness that we can do the same with, rice it till our hearts' desire. So, we started looking out for harnesses. Pi was great, Pi was good. But in the end, it was JavaScript. I don't have anything against JavaScript, but Lua is more fitting, and I can just as easily base it in Rust to make it even faster. So, we started cooking, and we ended up with a design that's minimal at its core and is built using Lua. Every built-in abstraction, the way you define providers, the way you rice it up, everything, literally everything, you can do in Lua.
But how? How does it all work? I proposed we just do it the Ratatui way, but my friend was like, nah! We shall make it possible to build TUIs in Lua. (Weirdo!) But it paid off, and we got ourselves a shiny new Lua library, ito!!!
He didn't get the gravity of the situation. We had an awesome terminal UI library in Lua! I wanted everyone to know how cool that is, so I got cooking, wrote myself a custom canvas implementation of this library instead of the usual terminal adaptor, exported it as wasm, and made this super cool website where you can experiment building awesome UIs in Lua. For vibe-coders, we got an llms.txt too. Have at it, and see if you can build something really cool with it. The library's at github.com/uji-labs/ito, and the awesome sauce harness is at github.com/uji-labs/uji.
Great job! The declarative API looks sick! I'll try it. Sometimes I love to think that if our universe is really infinite, then somewhere there's a universe where the browsers supports native luajit scripting instead of javascript(and in another universe all of this plus array indexes starting from 0 ;)
While I generally like Lua this code just looks .. not so Lua - like, it definitely is Lua, but it could look much better by utilizing language features like "Parenthesis-free calls":
I had a similar impression. It feels "not idiomatic". I disagree on the diagostic though - I think parenthesis-free calls are not what define what idiomatic lua is.
To me it is more about expressiveness and usage of basic language features. For example here's how a text field is set up currently.
ito.TextField(draft)
:label("Name")
:focused()
:on_submit(function(value)
if value:match("%S") then
phase.value = "review"
end
end)
:on_cancel(function()
phase.value = "choose"
end),
The field is created on the first line, and then altered further with subsequent method calls that alter it. This reads like someone very used to Rust (and its Builder pattern) and then translating it verbatim to Lua. A more native Lua interface for me would look like this:
ito.TextField(draft, {
label = "Name",
focused = true,
on_submit = function(value)
if value:match("%S") then
phase.value = "review"
end
end,
on_cancel = function()
phase.value = "choose"
end),
})
As in: just pass a table to the builder. And if they ned to modify it later, they can just do:
some_textfield.on_cancel = <some other function>
No need to "guard" the internal table with those extra methods.
Since you mentioned you're Neovim nerds: you may enjoy this: I build a declarative rendering engine on top of Neovim itself: morph.nvim [1]. Every Neovim install already has a grid, extmarks, floating windows, and a Lua runtime. On top of morph, I've dog-fooded 14+ interactive TUI applets -- docker, k8s, systemd, AWS, GitHub, HN reader [2].
Aside from declarative rendering, morph does a lot of other stuff: region-specific keymaps, read-only regions, extmark creation/managment/etc.
I do find it funny how TUIs are trying to become increasingly ergonomic with more friendly UX.. to the point where they're basically just becoming GUIs.
Lua is the ultimate ricer tool. I've put it in all sorts of places and it has pretty much always been a productive boost to the timetables of the projects.
Its also very flexible and allows a great deal of coding styles. I like the declarative API!
15 comments
[ 0.23 ms ] story [ 6.7 ms ] threadBut how? How does it all work? I proposed we just do it the Ratatui way, but my friend was like, nah! We shall make it possible to build TUIs in Lua. (Weirdo!) But it paid off, and we got ourselves a shiny new Lua library, ito!!!
He didn't get the gravity of the situation. We had an awesome terminal UI library in Lua! I wanted everyone to know how cool that is, so I got cooking, wrote myself a custom canvas implementation of this library instead of the usual terminal adaptor, exported it as wasm, and made this super cool website where you can experiment building awesome UIs in Lua. For vibe-coders, we got an llms.txt too. Have at it, and see if you can build something really cool with it. The library's at github.com/uji-labs/ito, and the awesome sauce harness is at github.com/uji-labs/uji.
I hope you like it!
To me it is more about expressiveness and usage of basic language features. For example here's how a text field is set up currently.
The field is created on the first line, and then altered further with subsequent method calls that alter it. This reads like someone very used to Rust (and its Builder pattern) and then translating it verbatim to Lua. A more native Lua interface for me would look like this: As in: just pass a table to the builder. And if they ned to modify it later, they can just do: No need to "guard" the internal table with those extra methods.maybe it’s a bad idea :X
Since you mentioned you're Neovim nerds: you may enjoy this: I build a declarative rendering engine on top of Neovim itself: morph.nvim [1]. Every Neovim install already has a grid, extmarks, floating windows, and a Lua runtime. On top of morph, I've dog-fooded 14+ interactive TUI applets -- docker, k8s, systemd, AWS, GitHub, HN reader [2].
Aside from declarative rendering, morph does a lot of other stuff: region-specific keymaps, read-only regions, extmark creation/managment/etc.
[1] https://github.com/jrop/morph.nvim
[2] https://github.com/jrop/tuis.nvim
Its also very flexible and allows a great deal of coding styles. I like the declarative API!