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

Let prompts choose to repaint on enter #627

Merged
merged 24 commits into from
Sep 12, 2023

Conversation

ysthakur
Copy link
Member

@ysthakur ysthakur commented Aug 23, 2023

This PR goes towards addressing #348. It adds fn repaint_on_enter(&self) -> bool to the Prompt trait so that a prompt can choose to be repainted on enter. I figured an option could be added to $env.config on the Nushell side to enable a transient prompt. After that, the hooks described in this issue will hopefully start working.

@fdncred
Copy link
Collaborator

fdncred commented Aug 23, 2023

If reedline is going to support a transient prompt, which would be totally rad, I'd love to see an example in this repo that shows folks how to make a basic one.

@ysthakur
Copy link
Member Author

ysthakur commented Aug 23, 2023

So it looks like repaint only repaints the prompt, not the input (at least in this particular case, maybe it's just that the input's already been cleared by the time repaint is called here). I tried making the custom_prompt example use ! as a transient prompt and only the prompt shows up, not previously typed text:

image

@ysthakur
Copy link
Member Author

ysthakur commented Aug 23, 2023

Okay, looks like the line buffer was being cleared by the time the if prompt.repaint_on_enter() line was hit. I changed it to add the last line from history to the line buffer, repaint, and then remove the last line again. I'm not sure how the history is implemented but hopefully there isn't a lot of overhead getting the last line?

image

Looks like syntax highlighting works too. The above screenshot is just the custom_prompt example with the highlighter from the highlighter example pasted in. Let me know if it'd be better to make a completely separate transient_prompt example (and restore the custom_prompt example).

@fdncred
Copy link
Collaborator

fdncred commented Aug 23, 2023

Let me know if it'd be better to make a completely separate transient_prompt example (and restore the custom_prompt example).

Yes, another example would be best. It also has to be confirmed that your change doesn't break anything else or the other examples. Also, since you're pulling the history, you need to make sure it works with text-based history and sqlite-based history, both supported by reedline.

@ysthakur
Copy link
Member Author

ysthakur commented Aug 23, 2023

So I added some more stuff to the transient prompt example (completer, highlighter, hinter, validator) to confirm that they're working. I assume that if this PR's accepted, all that extra stuff will have to be removed right before this PR's merged?

@fdncred
Copy link
Collaborator

fdncred commented Aug 25, 2023

I ran this PR example. I'm not sure what it's supposed to do or how to make it work. I just get this

    Finished dev [unoptimized + debuginfo] target(s) in 3.55s
     Running `target/debug/examples/transient_prompt`
Transient prompt demo:
Abort with Ctrl-C or Ctrl-D
(transient_prompt example)>ls
::: a
::: b
::: d
:::
:::
:::

@ysthakur
Copy link
Member Author

ysthakur commented Aug 25, 2023

My bad, I added a custom validator (line 80) so it only goes to the next line if you end your input with a ? Let me document that later

Copy link
Member

@sholderbach sholderbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for giving this a shot and working up an example and doing due diligence on the testing.

From a long term maintenance perspective I think this API is a bit too bug prone and can cause confusion.

examples/transient_prompt.rs Outdated Show resolved Hide resolved
src/engine.rs Outdated Show resolved Hide resolved
src/prompt/base.rs Outdated Show resolved Hide resolved
@ysthakur
Copy link
Member Author

ysthakur commented Aug 27, 2023

I ended up going with a fn get_transient_prompt since it didn't require changing any method signatures and only required adding a method to Prompt. It does, however, not make the most sense to have a whole Prompt object to represent the post-submit prompt, so let me know if the render_post_submit_* or some other approach would be better

@ysthakur
Copy link
Member Author

Here's a recording of how it currently looks:

asciicast

@fdncred
Copy link
Collaborator

fdncred commented Aug 29, 2023

I was getting ready to object to the unwrap() in the engine.rs, but it appears that we have many unwraps in this code base. Seems like we should fix those eventually. (not in this pr, of course).

@fdncred
Copy link
Collaborator

fdncred commented Aug 29, 2023

Wait a second, now that I look at these unwraps, most of them are in tests, which is fine. I found 10 that are not in tests that we need to fix. @ysthakur, would you mind adding error handling to the one you have in engine.rs? I really don't think we want to continue adding unwraps in our code.

@ysthakur
Copy link
Member Author

Perhaps if the result of lock() is an Err, it could go back to using the normal prompt and simply log an error elsewhere so that the user won't have to see the error?

The whole Arc<Mutex<Box<dyn Prompt>>> thing is a bit unfortunate, though. Any idea how to get around that so even the unwrap isn't needed?

@ysthakur
Copy link
Member Author

By the way, in case you're planning on getting rid of the unwraps, there's also a bunch of .expect("todo: error handling")s, mainly in engine.rs, that may need to be looked at

@fdncred
Copy link
Collaborator

fdncred commented Aug 29, 2023

there's also a bunch of .expect("todo: error handling")

good point. sounds like something for a followup pr, if anyone is interested. hint hint. 😆

@ysthakur ysthakur requested a review from sholderbach August 31, 2023 17:21
@ysthakur
Copy link
Member Author

ysthakur commented Aug 31, 2023

I made another branch to try using a Cell<Option<Box<dyn Prompt>>> to avoid the mutex, but the only way I could think to make it work is a bit hacky: before calling repaint, it removes the Some from the Cell, calls repaint (during which self.transient_prompt contains a None), and then resets self.transient_prompt to a Some containing the original Box<dyn Prompt>. It's unlikely repaint would need the transient prompt, since the only place it seems like it'd be used is submit_buffer, but it might cause bugs down the line anyway.

Edit: I managed to make it not use Cell, but moving the transient_prompt field out while repaint is being called still feels like a hack.

src/engine.rs Outdated
Comment on lines 442 to 447
/// Set a different prompt to be used after submitting each line
#[must_use]
pub fn with_transient_prompt(mut self, transient_prompt: Box<dyn Prompt>) -> Self {
self.transient_prompt = Some(Arc::from(Mutex::from(transient_prompt)));
self
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can get by with this fn (mut self, Box<>) -> Self signature, then I don't think the whole Arc<Mutex<Box<>>> is needed, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not, but self.repaint(self.transient_prompt.as_ref()) requires mutably and immutably borrowing self at the same time, and the only way to get around it that I can think of is by moving the transient_prompt field out of self, replacing it with None, calling repaint, and then moving transient_prompt back into self. That feels like a hack, though. Here's a diff (from a separate branch) showing it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sholderbach Just wanted to nudge you in case you didn't see my previous comment (also, I just pushed a commit to use Option.take() instead of std::mem::replace)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ysthakur sorry for not getting back to you!

Yes I like the variant with .take(). To me that is pretty idiomatic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sholderbach Great, just merged that other branch into this PR's branch

Copy link
Member

@sholderbach sholderbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with us!

Let's land this, so the road is clear to figure out the best way to configure this in nushell.

(with https://github.com/nushell/nushell/blob/ba6d8ad2617c2100021d6e3c949c0b9088f2667b/Cargo.toml#L167 you can just cargo update -p reedline to get the latest unreleased version for testing)

@sholderbach sholderbach merged commit 33828e5 into nushell:main Sep 12, 2023
6 checks passed
@ysthakur
Copy link
Member Author

Thanks for merging this PR, and all the feedback!

@fdncred
Copy link
Collaborator

fdncred commented Sep 12, 2023

Can't wait to see what this looks like in nushell. It should be interesting. Good work @ysthakur!

@ysthakur ysthakur deleted the transient-prompt branch December 27, 2023 22:52
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.

3 participants