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

Add an async interface #126

Open
Marwes opened this issue Dec 9, 2017 · 14 comments
Open

Add an async interface #126

Marwes opened this issue Dec 9, 2017 · 14 comments

Comments

@Marwes
Copy link
Contributor

Marwes commented Dec 9, 2017

Probably needs to spawn an OS thread and perform all line reading there with a channel (I don't think there is another way to do it anyway). I could (an likely will) implement this outside rustyline but if it seems useful enough to warrant inclusion I am happy to do a PR.

fn readline(&mut self, prompt: &str) -> Box<Future<Item = String, Error = ReadLineError>>;

https://github.com/alexcrichton/futures-rs

@gwenn
Copy link
Collaborator

gwenn commented Jan 28, 2018

https://github.com/dpc/async-readline/blob/master/src/lib.rs#L165

@rubdos
Copy link

rubdos commented Feb 6, 2018

I was thinking about tokio support, which basically means async support. I'm probably also trying to sketch something up.

@gwenn
Copy link
Collaborator

gwenn commented Jan 31, 2019

https://docs.rs/tokio/0.1/tokio/io/struct.Stdin.html

The handle implements the AsyncRead trait

@RadicalZephyr
Copy link
Contributor

For what it's worth, I think that this could be accomplished with a wrapper type on the current editor. I don't think Rustyline internals need to change to read from stdin asynchronously, I think it just needs to provide an async-compatible interface, which should probably be a futures Stream of String.

As @Marwes mentioned, this would probably be most easily accomplished by spawning a thread and running a standard Rustyline Editor in a loop and send all of the results onto a channel.

@gwenn
Copy link
Collaborator

gwenn commented Jan 31, 2019

@RadicalZephyr I don't know how to make sure rustyline and the other/main thread update the screen correctly ?

@RadicalZephyr
Copy link
Contributor

That's a good question. I'm not super familiar with trying to do this so this might be a naive suggestion, but I think that the most straightforward solution would be to enforce that the thread running the rustyline editor is the only one allowed to write to stdout.

I'll try to put together an example as a proof of concept and at least initial guidance on how to use rustyline in an async context.

@RadicalZephyr
Copy link
Contributor

I've just pushed #200 a basic start on using rustyline from an async context.

The current strategy I'm using would only be able to show lines sent to the display from an async context after the user finishes editing the current line and before the next line becomes available to edit.

I think for async use to be really nice, it would be good to have a new interface that allows output to be sent while the user is editing a line. I'm thinking of being able to create a remote "handle" to an Editor (kind of like the Handle to a Reactor in the previous iteration of tokio. That handle would probably implement AsyncWrite. Though that might be lower level than is necessary... I have to think about this more!

@gwenn gwenn pinned this issue Feb 7, 2019
@Ralith
Copy link

Ralith commented Feb 16, 2019

Very excited to see this receiving attention!

I don't think Rustyline internals need to change to read from stdin asynchronously, I think it just needs to provide an async-compatible interface, which should probably be a futures Stream of String.

One reason to build async support into rustyline would be to allow operation without threads in cases where that is possible (e.g. stdin/out connected to a terminal or pipe on *nix). That's a lot of complexity for dubious benefit, though.

I think for async use to be really nice, it would be good to have a new interface that allows output to be sent while the user is editing a line. I'm thinking of being able to create a remote "handle" to an Editor (kind of like the Handle to a Reactor in the previous iteration of tokio. That handle would probably implement AsyncWrite.

Something like this sounds really great! Producing output without interfering with a prompt is exactly what I want for building e.g. an admin interface to a server. You might be able to get away with a synchronous std::io::Write interface, since nobody should be using this to dump large volumes of data anyway. Support for more fine-grained writes than complete lines will be useful for things like displaying live progress bars concurrent with user input.

@remexre
Copy link

remexre commented Jun 22, 2019

It'd also be nice to be able to use async for output from the log crate; a stretch-goal-feature for this could be a wrapper for Handle that impls Log.

@zyansheep
Copy link

If anyone still wants to add this feature to rustyline, I've made a cleanroom implementation that could be used as a reference.
https://github.com/zyansheep/rustyline-async

@gwenn
Copy link
Collaborator

gwenn commented Mar 30, 2023

@azoyan
Copy link

azoyan commented Jan 11, 2024

Are you planning to add this in the future?

@gwenn
Copy link
Collaborator

gwenn commented Jan 11, 2024

Are you planning to add this in the future?

I am not sure but removing / replacing ExternalPrinter by the clever / simple linenoise solution is appealing.
(see https://github.com/kkawakam/rustyline/blob/master/linenoise.md: we already have the implementation of 2/5 methods related to non-blocking API).
And maybe we should not use the term async.

@dmlary
Copy link

dmlary commented Apr 7, 2024

I am not sure but removing / replacing ExternalPrinter by the clever / simple linenoise solution is appealing.

I spent a few hours looking into this, and implementing an incremental read API similar to linenoise would be a significant architectural shift.

This isn't simply a matter of moving the needed state into the right place (currently the state is spread out across Editor, State, InputState, impl Renderer, and impl RawReader). There's also a significant amount of recursion to handle command completion, reverse history search, paging, etc. Those recursive routines would need to be rewritten to handle single Cmds, and additional state added to determine which cmd handler function should we dispatch the next Cmd to when called.

Basically we'd need to keep a stack of trait CmdDispatcher instances, each holding whatever state they need, then passing it the result of next_cmd() each time readline updates. There's a lot to figure out here, but there's a very high chance of breaking some existing behavior not covered by tests.

I think this approach is the right one as it would make it trivial to use rustyline in async, multi-threaded, and single-threaded multiplex (select()) situations. @gwenn I'm just not sure you're interested in that significant of a change. It would be a large PR to review.

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

No branches or pull requests

9 participants