Text-mode (terminal) application with asynchronous input/output

I have been using HTML+JavaScript before. I agree it's easy to quickly get something running, but doing things right (session management, CSS, asynchronous requests in the background, etc.) can get things quite complicated also. Maybe anything involving UI will get complex quickly when you do it right.

In my current case, I only have the need for text (but want it to work asynchronous, so I thought I could end up with something more simple.

The use-case is actually a tiny chat program for amateur-radio that I was considering to write. But being able to write small and lightweight text apps might come in handy for other things as well.

I feel like I should perhaps go that way, even if – for now – I only need text. If I understand it right, then using something like GTK will "trap" me in the main event loop? So this wouldn't work with tokio directly? I guess for my I/O I can start a different thread which then can use async I/O using tokio and will send events through GIO, DBus, etc.? Hmmm… I just found this, so maybe this will help me: GUI development with Rust and GTK 4.

I think GTK is the most famous choice for GUI programming with Rust? At least that was my impression when searching for resources on it.

Thanks for the example. It looks nice (and not too complex yet). I feel like things will get more complex when making a real application though, because of:

  • vertical horizontal scrolling when input lengh exceeds screen width
  • displaying the cursor and allowing editing the line
  • avoiding to grow the outbut buffer indefinitely
  • edit/added: vertical scrolling when the output exceeds display size
  • possibly other things I have forgotten yet

Btw, what I found interesting is that enable_raw_mode will keep me even from SIGSTOPping the process with CTRL+Z. It's unusual to have programs exhibiting such behavior (a lot of programs will ignore CTRL+C but still allow CTRL+Z to pause the program so you can kill it for example).

When I went through crossterm's examples, I came across a call of FutureExt::fuse that I did not understand: examples/event-stream-tokio.rs

let mut delay = Delay::new(Duration::from_millis(1_000)).fuse();
let mut event = reader.next().fuse();

select! {
    _ = delay => { println!(".\r"); },
    maybe_event = event => {
        /* … */
    }
};

I did not understand why fuse is needed here. Do these futures behave odd if they are polled after they have been Ready? Does tokio::select! try to poll futures that have been previously Ready? I'm not familiar with async Rust enough to really understand what's happening here.

Whichever way I go with my small program, I feel like it's not going to be a simple task, even if all I want is text input and output.

If I didn't have events while waiting for input, I simply could use rustyline. But I think reading a line is always blocking with rustyline as I have to call rustyline::Editor::readline. I would like if I could just interrupt text input when there is a new message to be displayed, output the text (using normal terminal scrolling capabilities), and then restore the current input line buffer on the last line in the terminal. But I don't think I can do that with rustyline.


Sorry to mix up all sort of different topics in this thread and post, let me try to summarize some of the open questions I have (which perhaps someone knows answers to):

  1. What's the best choice right now to make a GUI? (I know this question could likely trigger another thread, and perhaps that's what I should do. Or look through one of the previous threads:

    But maybe there is a quick answer to questions like gtk4 has better support for Rust than QT or vice versa. (I have worked with none of these yet.)

    I'd like my application to run on several platforms (Linux, Windows, macOS, FreeBSD, …).

  2. Are there some simple terminal line-editors that can work async and which support being interrupted by other events? I.e. something less powerful than tui regarding output, but comparably powerful to rustyline regarding input (and working async)?

  3. Does someone know while that example above needs .fuse()?


If I understand right, you simply redraw the whole screen on each event (including each keystroke). That seems to also ensure that when I resize the window, the boxes get all redrawn.

I would assume tui has some sort of internal buffer, so the content isn't really re-printed on every keystroke? (As this might be bad on low-bandwidth remote connections.)