Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
thallada opened this issue Jan 28, 2018 · 26 comments
Open

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

thallada opened this issue Jan 28, 2018 · 26 comments

Comments

@thallada
Copy link

thallada commented Jan 28, 2018

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

  • Operating system: linux Ubuntu 16.04
  • Backend used: Termion
  • Current locale (run locale in a terminal): en_US.UTF-8
  • Cursive version (from crates.io, from git, ...): crates.io 0.7.5

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

@gyscos
Copy link
Owner

gyscos commented Jan 28, 2018

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
Copy link
Contributor

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

@IGBC
Copy link
Contributor

IGBC commented Jan 29, 2018

@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
Copy link
Author

thallada commented Feb 3, 2018

@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
Copy link
Contributor

IGBC commented Feb 3, 2018

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.

thallada added a commit to thallada/ferret that referenced this issue Feb 9, 2018
There are still some issues with launching a sub-program from within Cursive
that have not been solved (see: gyscos/cursive#199).
But, it works for the most part.
@winding-lines
Copy link

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
Copy link
Owner

gyscos commented Apr 6, 2018

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
Copy link

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

@gyscos
Copy link
Owner

gyscos commented Apr 7, 2018

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

@winding-lines
Copy link

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
Copy link
Owner

gyscos commented Apr 9, 2018

2729e77 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
Copy link

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
Copy link
Owner

gyscos commented Apr 10, 2018

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
Copy link

ghost commented Apr 11, 2018

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
Copy link
Owner

gyscos commented Apr 11, 2018

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
Copy link

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
Copy link
Contributor

There is already a multiplexer for cursive!

@dzhang-b
Copy link

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

@matthiasbeyer
Copy link
Contributor

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
Copy link
Owner

gyscos commented May 18, 2020

So there's a bunch of different needs here:

  • A simple pause-cursive, the entire screen goes back to normal and it can be used for another app, then bring back cursive and keep everything intact. The current plan for this is to have a "cursive session" handle which handles the terminal lifecycle, instead of just the Cursive root. Until then, the alternative is to tear down the Cursive object, let it clean up the terminal, and re-create it after the "pause" when the other app exits. This seems to be the original intent for this issue.
  • A regular Cursive views which internally runs a terminal emulator, forwards events to the wrapped application, and print the output from the app in the window. This would let you run vim or cursive-in-cursive applications.
  • A cursive split-panel manager to easily organize and layouts different views. Some of these views could be the terminal-emulator-views described earlier.

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
Copy link

@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
Copy link
Contributor

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

@dzhang-b
Copy link

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

@gyscos
Copy link
Owner

gyscos commented Aug 5, 2020

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

  • The backend is now given (and initialized) when running the event loop, not when creating cursive.
  • When the event loop completes the backend is dropped (and cleaned up).
  • You'll need to initialize a backend again to run an event loop again.

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
Copy link
Owner

gyscos commented Aug 6, 2020

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

@schneiderfelipe
Copy link
Contributor

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 😃.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants