Me original idea was building a ratatui backend for picotron which is a fanatsay workstation that happens to feature a terminal, with quite feature rich p8scii (this documentation is for pico-8 but it should be identical) commands analogous to ANSI sequences.
I started writing a wasm interpreter fennel (because I figured it's pretty hard to be worse than lua). I actually got somewhat far, getting a WASI hello world fully working including parsing, but fighting through integration hell it quickly dawned on me I'm not interpreting full rust programs (maybe starting it sooner than a week before the deadline would've helped). I might make a blog about it, it actually presets quite an interesting set of problems, and it's one of those rare situations where 1-based indexing is actually a problem, not just an annoyance.
I've briefly explored using the recently-added tcp socket support in picotron to instead stream, which sounds pretty straight forward but it was really not fun to work on, it felt like a mixture of cheating (you can "run" anything on anything connected to the internet) and giving up on the much cooler and shinier idea.
By this point I had about a day left and considered leaving the jam, but then randomly nerdsnipped myself with an idea: A TUI that runs in a FUSE and gets input from editing inputs over the file.
This entirely removes the notion of focus, instead making the events themselves positional (akin to a touchscreen compared to a mouse).
Eventually I dropped FUSE in favor of regular files, the property of saving the state of an application to a file in and of itself is really cool and removes the friction of running daemon and makes platform support much better. With hot-reloading of files and custom formmater support in editors it's also surprisingly practical.
I started out trying to write a custom backend, which I've was already pretty familiar with from my previous two attempts. I eventually realized Backend isn't the right abstraction to do the actual reading from and writing to the file, leaving it as just a buffer, so I ended up switching to (ab)using TestBackend.
After that it was surprisingly easy to get the read state -> read -> diff -> update -> render -> write -> write state logic and events list widget up and running, and from there it's a mostly normal ratatui app.
Future work:
- Robustness: It's currently very trigger happy to panic, and panics corrupts the state and create a panic loop. And it's currently vulnerable to TOCTOU.
- Character Escapes (e.g. enter, backspace, modifiers)
- Grapheme Clusters
- Colors (although editor support seems fairly bleak)
- Using the rendered view as the source-of-truth: this is not generally possible in all cases and might require inserting characters instead of replacing them, but is a lot more elegent and seems genuinely useful for some standard TUIs, e.g. copying a frame from asciinema into that exact state.
This is actually my first time using ratatui, and I've encountered a couple of things I found quite awkward:
- Rendering of widgets requiring mutable state, it feels rarely used and has signifact implications. iced (and the elm architecture in geneal) show how powerful it can be, for example it recently added a time traveling inspector.
- Lack of views into buffers, combined with the lack of
impl Add for Position, makes working with positions very painful. E.g. with the scrollable canvas, I take the screen position, convert it into a viewport position, then into a canvas position. And it's easy to get off-by-one bugs with borders. Once again, I feel like [iced] is a good example, it actually has separatePointandVectortypes withimpl Add<Vector> for Pointbut notimpl Add<Point> for Pointto avoid accidentally adding together positions, that feels a bit overkill here. - Widgets being responsible for applying borders around them feels really odd and limiting, and adds boilerplate to widgets. Scrollbars are actually the other way around, being standalone instead of part of a scrollable widget is actually really cool and makes sense for a terminal, I don't see a reason borders can't be the same.
- Scrollbar widget seems bugged and uses more width than allowed? I ended up dropping it because of that, I'll open a bug report if I'll investigate further.
I would love to know if I've missed something or there're design reasons behind some of these, I'm genuinely curious.