rustyline icon indicating copy to clipboard operation
rustyline copied to clipboard

Add an async interface

Open Marwes opened this issue 7 years ago • 14 comments

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

Marwes avatar Dec 09 '17 23:12 Marwes

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

gwenn avatar Jan 28 '18 09:01 gwenn

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

rubdos avatar Feb 06 '18 15:02 rubdos

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

The handle implements the AsyncRead trait

gwenn avatar Jan 31 '19 19:01 gwenn

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.

RadicalZephyr avatar Jan 31 '19 20:01 RadicalZephyr

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

gwenn avatar Jan 31 '19 21:01 gwenn

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 avatar Jan 31 '19 21:01 RadicalZephyr

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!

RadicalZephyr avatar Jan 31 '19 22:01 RadicalZephyr

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.

Ralith avatar Feb 16 '19 17:02 Ralith

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.

remexre avatar Jun 22 '19 01:06 remexre

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

zyansheep avatar Jul 23 '22 14:07 zyansheep

https://github.com/antirez/linenoise#asyncrhronous-api

gwenn avatar Mar 30 '23 05:03 gwenn

Are you planning to add this in the future?

azoyan avatar Jan 11 '24 06:01 azoyan

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.

gwenn avatar Jan 11 '24 17:01 gwenn

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.

dmlary avatar Apr 07 '24 19:04 dmlary