gyscos / cursive

A Text User Interface library for the Rust programming language
MIT License
4.3k stars 245 forks source link

Pause Cursive program and pass stdin and stdout to subprocess #199

Open thallada opened 6 years ago

thallada commented 6 years ago

I am new to Rust and this library so I'm not sure if this is currently possible.

Problem description

I'm trying to make a Cursive program that will launch another interactive program and then resume after the program exits. I'm using the Termion backend. I have tried this on the callback of a submit:

Command::new("w3m")
    .args(&["http://google.com"])
    .stdin(Stdio::inherit())
    .stdout(Stdio::inherit())
    .output().unwrap();

But it seems like Cursive in the background is still reacting to the stdin and the spawned program isn't working correctly (I have to hit tab multiple times before w3m responds and goes to the next link, there's no cursor, etc.). If I run that code above in a fresh main.rs without Cursive, the spawned program behaves fine.

blessed, a terminal UI library in Node.js has a function for this called spawn.

The cursor would have to be shown again and Cursive would need to stop processing stdin until the spawned program closes.

Environment

Thanks for creating this library. I've been able to get a lot done with it so far!

gyscos commented 6 years ago

Hi!

Cursive currently sets up the terminal at creation (Cursive::new()) and cleans up on drop. Though this rigid structure is already not ideal, as seen in #180 .

Being able to "pause" the cursive app while executing something else sounds like a good use-case, and shouldn't be too hard. Maybe something like Cursive::pause<F: FnOnce()>(&mut self, command: F), where command would be run in a cursive-free context (and the app would resume when the closure exits).

matthiasbeyer commented 6 years ago

I am interested in this functionality, too: for spawning vim from a cursive app.

IGBC commented 6 years ago

@matthiasbeyer I'm building a terminal widget. It can't handle control codes yet but I am working on it.

This can give you a window you can attach an external command to.

It's currently buried in https://github.com/igbc/cue but I can break it into a crate as soon as someone want's it for something.

thallada commented 6 years ago

@IGBC I tried running your program but I get a whole bunch of errors if I try to run anything more complex than ls. Looks like you are just piping the output of commands back into Cursive?

I tried messing with this some more but still got nowhere. I added this function to Cursive:

pub fn pause(&mut self) {
    self.running = false;  // what quit() calls
    self.backend.finish();  // what drop() calls
}

But, even if I called that before the Command I mentioned above, nothing changed. The cursor is still not shown and I still have the weird stdin behavior where I have to hit keys multiple times before the child process registers them.

I thought it might have something to do with the separate thread termion.rs spawns that processes stdin events, so I added a way to pause that as well:

fn init() -> Self {

    ...

    let process_input = true;

    thread::spawn(move || {
        for key in ::std::io::stdin().events() {
            if process_input {
                if let Ok(key) = key {
                    sender.send(key)
                }
            }
        }
    });

    ...

}

...

fn pause(&mut self) {
    self.process_input = false;
    print!("{}", ToMainScreen);  // what AlternateScreen's drop() calls
}

But calling self.backend.pause() to trigger that didn't seem to fix the issue either.

It seems like Cursive doesn't really clean up after itself and return the terminal to normal until after the whole Rust program exits, and I can't get that same process to happen while the program is still running.

IGBC commented 6 years ago

yes the output is passed back into cursive, the idea is to create an embedded terminal emulator inside of cursive. The errors you are encountering are because this is still a work in progress and not remotely shipable yet.

winding-lines commented 6 years ago

I am also interested by getting access to stdout, my use case is much simpler. I want to be able to get the output of cursive and use as part of some command, for example

vim `cursive-pick-file`

This doesn't work today because Cursive uses initscr. I think that if Cursive were to use newterm instead this use case would work. https://www.linuxquestions.org/questions/linux-newbie-8/can-a-process-redirect-stdout-and-still-use-ncurses-in-a-terminal-4175411385/

Let me know what you think :)

gyscos commented 6 years ago

Ah indeed, I didn't know about newterm.

Latest master should now use this instead of initscr; if this works for you, I'll use the same for pancurses. We'll see then how to bring this to the termion backend.

winding-lines commented 6 years ago

@gyscos the newterm() approach seems to be working fine 👍 Many thanks!

gyscos commented 6 years ago

We still print a few things to stdout, I still need to replace these with writing to the tty.

winding-lines commented 6 years ago

Ah yes, I was able to reproduce the issue with extra characters after going in circles for a while. The extra characters are not visible, gotta love it.

gyscos commented 6 years ago

2729e77838b2312054251d95fe162e1ee7d092d1 should remove the last bits we were writing to stdout. For instance, cargo run --example colors | hexdump should show no output. In addition, stdin is also untouched, making it easier to use cursive applications to pipe data in/out (could be used as a more interactive pv for instance).

For the pancurses backend, we're waiting on a new release to make newterm usable. For the termion backend, we'll need to replace the calls to print! with direct writes to /dev/tty.

winding-lines commented 6 years ago

I confirm that back-tick use case works for me with the latest commit 👍 Looking forward to the next release - and maybe we can get also cursive_table_view to upgrade.

gyscos commented 6 years ago

Just added the vpv example - a visual pipe viewer - to showcase stdin/stdout usage. It uses moves data from stdin (or directly a file) to stdout and shows the progress visually. Ex:

$ # Follows any pipe
$ cat /dev/urandom | cargo run --example vpv > /dev/null
$ # Can also open files directly - in this case we know the size and can show a progress bar
$ # `pv` is used here to limit the transfer speed
$ cargo run --example vpv -- target/debug/examples/vpv | pv -qL1M > /dev/null
ghost commented 6 years ago

Maybe just me, but much minimal - even artificial - example showing this feature would be also welcome. This example is very practical with logic shadowing the essence of feature?

gyscos commented 6 years ago

Ah, sorry, the example only shows how stdin/stdout are now free for other usages, like piping data. This "feature" is more abstract and really simple, so I hope it won't be too confusing. The core issue (pausing cursive to let something else handle the terminal, like running vim) is not implemented yet.

dzhang-b commented 4 years ago

Maybe a multiplexer-mode can be added? To make a terminal emulator inside cursive to allow other interactive terminal apps will be a killing feature. I think it can be implemented similar to this https://github.com/deadpixi/mtm. (It is about 1000 lines of C code for simple terminal emulation). Or could the project borrow some codes from alacrity for its terminal processing?

matthiasbeyer commented 4 years ago

There is already a multiplexer for cursive!

dzhang-b commented 4 years ago

@matthiasbeyer They are different. I want to open other TUI app inside, not just another internal cursive view.

matthiasbeyer commented 4 years ago

I understood that! You think of a terminal emulator that can be used to start, for example, vim inside a cursive app. There's no need for a tiling functionality, because there's the cursive-multiplex crate that can handle that part.

gyscos commented 4 years ago

So there's a bunch of different needs here:

The last point seems to be what cursive-multiplex solves, and the first point is not too difficult (it will probably come in the next month or two). The middle point is the tricky one: it would need to create a pt device (pseudoterminal) and handle a bunch of event translations. This is where a third-party library may make things a bit easier, but it would still be a fair amount of work.

dzhang-b commented 4 years ago

@gyscos Indeed writing a terminal emulator properly is fair amount of work. Maybe alacrity_terminal rust module from alacritty can help and be used as a standalone terminal emulation layer.

matthiasbeyer commented 4 years ago

I'd rather use a VTE binding library to implement one, tbh.

dzhang-b commented 4 years ago

additional event loop implementation will be needed to terminal emulation if just using rust VTE crate.

gyscos commented 4 years ago

The latest commit starts experimenting with the idea of pausable/resumable backends. More specifically:

As a result you should be able to run an event loop; when it exits, the terminal will be reset, and you can run another process (bash, vim, ...), and when that process completes, you can re-use the same Cursive instance for another event loop.

It's quite a big change, and will likely take a major version bump. In particular, it moves the error handling from cursive creation to running the event loop.

A CursiveRunnable wrapper is added to embed a Cursive instance with a backend-initializing function, to bring back the same behaviour as previously (backend selected at creation time).

It's still experimental though: I'm not 100% sure yet if backends can be initialized again multiple times without issue. There may very well be memory leaks or possibly lost input - will need to test some more.

gyscos commented 4 years ago

Added the pause example to show how to run multiple event loops, potentially running other commands in between.

schneiderfelipe commented 3 years ago

Added the pause example to show how to run multiple event loops, potentially running other commands in between.

The example seems to be located here now 😃.