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 dynamic prompt support via ExternalPrinter #772

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

dmlary
Copy link

@dmlary dmlary commented Apr 6, 2024

rustyline doesn't currently support changing the prompt while in the core readline loop. There are a number of open PRs and issues for this functionality, but all of them appear to be stalled for more than a year.

Looking at #696 and 4ec26e8, the traditional appoach to this is to provide a reference to a trait object (Prompt or ToString), but with that appoach there's no way to cause the prompt to be redrawn for a change without user input. This means for these appoaches the prompt could change without being displayed to the user.

There's an existing mechanism to allow another async task/thread to push input into the core readline loop, the ExternalPrinter.

In this commit, I expand ExternalPrinter to add set_prompt(). With various plumbing, this function results in wait_for_input to return Cmd::SetPrompt(String).

One of key change here is State.prompt changes from &str to String. There is a performance hit here from the copy, but rustyline would need to prompt and receive input hundreds of times per second for the copy to have a noticable performance inpact.

Added examples/dynamic_prompt.rs to demonstrate the functionality.

Closes #417

Related #208, #372, #369, #417, #598, #696

rustyline doesn't currently support changing the prompt while in the
core readline loop.  There are a number of open PRs and issues for this
functionality, but all of them appear to be stalled for more than a
year.

Looking at kkawakam#696 and 4ec26e8, the traditional appoach to this is to
provide a reference to a trait object (`Prompt` or `ToString`), but with
that appoach there's no way to cause the prompt to be redrawn for a
change without user input.  This means for these appoaches the prompt
could change without being displayed to the user.

There's an existing mechanism to allow another async task/thread to push
input into the core readline loop, the `ExternalPrinter`.

In this commit, I expand `ExternalPrinter` to add `set_prompt()`.  With
various plumbing, this function results in `wait_for_input` to return
`Cmd::SetPrompt(String)`.

One of key change here is `State.prompt` changes from `&str`
to `String`.  There is a performance hit here from the copy, but
rustyline would need to prompt and receive input hundreds of times per
second for the copy to have a noticable performance inpact.

Added examples/dynamic_prompt.rs to demonstrate the functionality.

Closes kkawakam#417

Related kkawakam#208, kkawakam#372, kkawakam#369, kkawakam#417, kkawakam#598, kkawakam#696
@dmlary
Copy link
Author

dmlary commented Apr 6, 2024

Note, this is only the unix implementation. I do not have access to a windows development environment to add the implementation for that side. It's not a complicated change, but for now it's marked as unimplemented!().

@gwenn
Copy link
Collaborator

gwenn commented Apr 7, 2024

See #126 (comment)

removing / replacing ExternalPrinter by the clever / simple linenoise solution is appealing.

And another issue related to the prompt: #348
And a more fundamental issue: https://github.com/kkawakam/rustyline/blob/master/Incremental.md

Ideally, we would like to redraw only impacted row(s) / cell(s).

So, for me, when only the prompt (Cmd::SetPrompt) is changed, we should not have to redraw the whole line ( s.refresh_line()) or at least we should not have to invoke Hinter::hint() / Highlighter::highlight_char / Highlighter::highlight (only Highlighter::highlight_prompt).

I apologize for the stalled situation induced by some bad design choices.

from PR comments:
@gwenn> So, for me, when only the prompt (Cmd::SetPrompt) is changed, we
should not have to redraw the whole line (s.refresh_line()) or at least
we should not have to invoke Hinter::hint() /
Highlighter::highlight_char / Highlighter::highlight (only
Highlighter::highlight_prompt).

Implemented this as `Refresher::refresh_prompt()` that is a cut-down
version of `Refresher::refresh_line()`.
@dmlary
Copy link
Author

dmlary commented Apr 7, 2024

TL;DR: Pushing a change to reduce processing when prompt changes. Linenoise approach is out-of-scope for this PR, but I am looking at how to do something similar.

Reduce computation for Cmd::SetPrompt

removing / replacing ExternalPrinter by the clever / simple linenoise solution is appealing.

I dug into the linenoise code to look at this note. Linenoise does not directly support changing the prompt. Although the prompt is stored in the struct linenoiseState, the only way to get the prompt to be redrawn is by calling linenoiseShow() at minimum. This causes a full redraw of the prompt. Which leads to...

[...] when only the prompt (Cmd::SetPrompt) is changed, we should not have to redraw the whole line [...]

I don't think this is advisable when you consider that the length of the prompt may change. In that case you'd have to redraw the rest of the line as all of the characters would need to be shifted. You can absolutely implement this, but it's added complexity for sub-millisecond performance gain on user keyboard input.

when only the prompt (Cmd::SetPrompt) is changed, [...] or at least we should not have to invoke Hinter::hint() / Highlighter::highlight_char / Highlighter::highlight (only Highlighter::highlight_prompt)

This makes sense as the hint() function may do blocking things like checking networked filesystems. I made this change by adding Refresher::refresh_prompt() as a cut down version of refresh_line(). I could instead make the call to State::refresh() in State::set_prompt(), but it looked like you wanted to maintain a clear separation.

Remove ExternalPrinter

I agree with you that the linenoise approach where the caller only calls into rustyline when there is input to be processed is a much better approach. I've already hit some additional limitations with ExternalPrinter in my project, so I'll see if there's a simple way to implement that for rustyline. No guarantees on my timeline here.

That said, I think adding that interface is out of scope for this PR. It's an architectural shift in rustyline, and not directly related to adding dynamic prompt support.

@xeruf
Copy link

xeruf commented Aug 23, 2024

Hey, would love to see progress here!

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

Successfully merging this pull request may close these issues.

Changing the prompt string dynamically
3 participants