r3bl-org/r3bl-open-core

Create a new lightweight experiment `tuify`

Closed this issue · 0 comments

Context:

Why?

Currently the mental model to create a TUI app is very all encompassing. It requires total commitment to the idea of building an application like a React app. The main function that launches the TUI app is very much like a web app or desktop app container. Then the apps are loaded inside the container, etc.

What

However, if you have an existing CLI app that doesn't want to opt into this mental model, then we need to provide something lighter weight. An example is giving a version of r3bl_tui (called tuify) that just allows the user to select from a list of strings. And then report back to the caller what the user just chose.

This entire flow of:

  1. display the list of strings in raw mode
  2. allow the user to select something
  3. report this selection result back to the main app
    is called tuify.

Here is a concrete example of "components" that we might want to build in Rust: https://github.com/charmbracelet/bubbles. Things like text input fields, list selection components, etc.

Preliminary design

Reedline is a crate that uses crossterm and gets into raw mode partially (meaning that it doesn't use the alt screen and take over the entire terminal viewport).

Here's a video showing how it handles terminal resize events. It doesn't do it perfectly but it does an adequate job.

tuify-thoughts-2023-09-03_09.14.27.mp4

So the following is plausible. Create a higher-order function that:

  1. Takes a Command that will be executed and a Vec<String> will be generated from reading its stdout.
  2. Takes a function that filters a Vec<String> and produces another Vec<String>. This will get returned by the function.

Image
Source image

Example walkthrough

With this approach, we can take a command like ls -la and then convert it into a bunch of strings and then filter out the word root. Now, this string vector can then be passed into another function which displays a tui that:

  1. puts the terminal in raw mode,
  2. without switching to alternate screen,
  3. does not use the full terminal viewport, but just a portion of it (specify the height & use the full width)
  4. handles Resize events correctly by repainting itself

This other function takes a Vec<String> as input argument and produces a Result<String> as return value. In other words, we can just select a single item from a list.

IIRC ink does a similar thing w/ most of its components. I'm not sure how charm.sh bubbles does it. Might be useful to take a look at both of them.