r3bl_tuify

What does it do?

This crate can be used in two ways: 1. As a library. This is useful if you want to add simple interactivity to your CLI app written in Rust. You can see an example of this in the examples folder in the main_interactive.rs file. You can run it using cargo run --example main_interactive. 1. As a binary. This is useful if you want to use this crate as a command line tool. The binary target is called rt.

How to use it as a library?

Here's a demo of the library target of this crate in action.

https://user-images.githubusercontent.com/2966499/266870250-9af806a6-9d2a-48b3-9c02-22d8a05cbdc3.mp4

The following example illustrates how you can use this as a library. The function that does the work of rendering the UI is called select_from_list. It takes a list of items, and returns the selected item or items (depending on the selection mode). If the user does not select anything, it returns None. The function also takes the maximum height and width of the display, and the selection mode (single select or multiple select).

It works on macOS, Linux, and Windows. And is aware of terminal color output limitations of each. For eg, it uses Windows API on Windows for keyboard input. And on macOS Terminal.app it restricts color output to a 256 color palette.

Currently only single selection is implemented. An issue is open to add this feature: https://github.com/r3bl-org/r3bl_rs_utils/issues if you would like to contribute.

```rust use r3blrsutilscore::*; use r3bltuify::*; use std::io::Result;

fn main() -> Result<()> { // Get display size. let maxwidthcolcount: usize = getsize().map(|it| it.colcount).unwrapor(ch!(80)).into(); let maxheightrow_count: usize = 5;

let user_input = select_from_list(
    "Select an item".to_string(),
    [
        "item 1", "item 2", "item 3", "item 4", "item 5", "item 6", "item 7", "item 8",
        "item 9", "item 10",
    ]
    .iter()
    .map(|it| it.to_string())
    .collect(),
    max_height_row_count,
    max_width_col_count,
    SelectionMode::Single,
);

match &user_input {
    Some(it) => {
        println!("User selected: {:?}", it);
    }
    None => println!("User did not select anything"),
}

Ok(())

} ```

How to use it as a binary?

Here's a demo of the binary target of this crate in action.

https://github.com/r3bl-org/r3blrsutils/assets/2966499/2b42db72-cd62-4ea2-80ae-ccc01008190c

You can install the binary using cargo install r3bl_tuify (from crates.io). Or cargo install --path . from source. Once installed, you can rt is a command line tool that allows you to select one of the options from the list that is passed into it via stdin. It supports both stdin and stdout piping.

Here are the command line arguments that it accepts: 1. -s or --selection-mode - Allows you to select the selection mode. There are two options: single and multiple. 1. -c or --command-to-run-with-selection - Allows you to specify the command to run with the selected item. For example "echo foo \'%\'" simply prints each selected item. 1. -t or --tui-height - Optionally allows you to set the height of the TUI. The default is 5.

Currently only single selection is implemented. An issue is open to add this feature: https://github.com/r3bl-org/r3bl_rs_utils/issues if you would like to contribute.

Interactive user experience

Typically a CLI app is not interactive. You can pass commands, subcommands, options, and arguments to it, but if you get something wrong, then you get an error, and have to start all over again. This "conversation" style interface might require a lot of trial and error to get the desired result.

The following is an example of using the binary with many subcommands, options, and arguments.

shell cat TODO.todo | cargo run -- select-from-list \ --selection-mode single \ --command-to-run-with-each-selection "echo %"

Here's a video of this in action.

https://github.com/r3bl-org/r3blrsutils/assets/2966499/c9b49bfb-b811-460e-a844-fe260eaa860a

What does this do?

  1. cat TODO.todo - prints the contents of the TODO.todo file to stdout.
  2. | - pipes the output of the previous command to the next command, which is rt (ie, the binary target of this crate).
  3. cargo run -- - runs the rt debug binary in the target folder.
  4. select-from-list - runs the rt binary with the select-from-list subcommand. This subcommand requires 2 arguments: --selection-mode and --command-to-run-with-each-selection. Whew! This is getting long!
  5. --selection-mode single - sets the selection mode to single. This means that the user can only select one item from the list. What list? The list that is piped in from the previous command (ie, cat TODO.todo).
  6. --command-to-run-with-each-selection "echo %" - sets the command to run with each selection. In this case, it is echo %. The % is a placeholder for the selected item. So if the user selects item 1, then the command that will be run is echo item 1. The echo command simply prints the selected item to stdout.

Now that is a lot to remember. It is helpful to use clap to provide nice command line help but that is still quite a few things that you have to get right in order for this command to work.

It doesn't have to be this way. It is entirely possible for the binary to be interactive along with the use of clap to specify some of the subcommands, and arguments. It doesn't have to be an all or nothing approach. We can have the best of both worlds. The following videos illustrate what happens when:

  1. --selection-mode and --command-to-run-with-each-selection are not passed in the command line. shell cat TODO.todo | cargo run -- select-from-list

    Here are the 3 scenarios that can happen:

  2. --selection-mode is not passed in the command line. So it only interactively prompts the user for this piece of information. Similarly, if the user does not provide this information, the app exits and provides a help message. shell cat TODO.todo | cargo run -- select-from-list --command-to-run-with-each-selection "echo %" https://github.com/r3bl-org/r3blrsutils/assets/2966499/be65d9b2-575b-47c0-8291-110340bd2fe7

  3. --command-to-run-with-each-selection is not passed in the command line. So it only interactively prompts the user for this piece of information. Similarly, if the user does not provide this information, the app exits and provides a help message. shell cat TODO.todo | cargo run -- select-from-list --selection-mode single https://github.com/r3bl-org/r3blrsutils/assets/2966499/d8d7d419-c85e-4c10-bea5-345aa31a92a3

Paths

There are a lot of different execution paths that you can take with this relatively simple program. Here is a list.

Due to the way in which unix pipes are implemented, it is not possible to pipe the stdout of this command to anything else. Unix pipes are non blocking. So there is no way to stop the pipe "mid way". This is why rt displays an error when the stdout is piped out. It is not possible to pipe the stdout of rt to another command. Instead, the rt binary simply takes a command that it will run after the user has made their selection. Using the selected item(s) and applying them to this command.

Build, run, test tasks

Prerequisites

🌠 In order for these to work you have to install the Rust toolchain and just and cargo-watch:

  1. Install the Rust toolchain using rustup by following the instructions here.
  2. Install cargo-watch using cargo install cargo-watch.
  3. Install flamegraph using cargo install flamegraph.
  4. Install just just on your system using cargo install just. It is available for Linux, macOS, and Windows.

Just commands

Note to run a just command named all on Windows, you have to use the following: just --shell powershell.exe --shell-arg -c all

The following commands will watch for changes in the source folder and re-run:

References

CLI UX guidelines: - https://rust-cli-recommendations.sunshowers.io/handling-arguments.html - https://rust-cli-recommendations.sunshowers.io/configuration.html - https://rust-cli-recommendations.sunshowers.io/hierarchical-config.html - https://rust-cli-recommendations.sunshowers.io/hierarchical-config.html - https://docs.rs/clap/latest/clap/_derive/#overview - https://clig.dev/#foreword

ANSI escape codes: - https://notes.burke.libbey.me/ansi-escape-codes/ - https://en.wikipedia.org/wiki/ANSIescapecode - https://www.asciitable.com/ - https://commons.wikimedia.org/wiki/File:Xterm256colorchart.svg - https://www.ditig.com/256-colors-cheat-sheet - https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences - https://www.compuphase.com/cmetric.htm