nushell / reedline

A feature-rich line editor - powering Nushell
https://docs.rs/reedline/
MIT License
529 stars 149 forks source link

Redraw prompt on resize #684

Open fdncred opened 9 months ago

fdncred commented 9 months ago

Platform WSLg Terminal software ghostty and iterm2

The prompt is disappearing when terminal window is resized in Ghostty disappearing-prompt

This is the explanation I received from the author of the Ghostty terminal @mitchellh (thank you for the answer).


When Ghostty sees a "prompt start" semantic prompt message from shell integration, it also assumes that the shell integration is capable of redrawing the prompt on resize events. On future resize events, Ghostty will clear the currently active prompt line if the cursor is at a prompt and assume the shell will redraw it. This should be the same behavior as Kitty.

The reason for this is that it enables complex and multi-line prompts to cleanly redraw when being resized. For example, look at the message with my prompt and iTerm2 (which doesn't have this feature). My shell is fish. The code to do this is in the src/shell-integration folder in Ghostty.

Notice iTerm2 the prompt gets completely screwed up, but on Ghostty the prompt and text remains clean throughout multiple text reflows. This is due to this redraw feature.

iterm2

https://github.com/nushell/reedline/assets/343840/ee6443aa-e6ca-4f39-881c-c0352a928e33

Ghostty

https://github.com/nushell/reedline/assets/343840/82e75336-9734-48e7-ae26-2b4eac23a5ba

My guess is nushell is sending the "prompt start" semantic prompt OSC sequence, but is NOT doing the prompt redraw behavior. I'm not sure how Kitty handles this with nushell... (if at all)


We've been seeing weird prompt-drawing/redrawing issues for a while. I, for one, had no idea that terminals would erase the prompt while waiting for a resize. We need to look into this because that seems to be the case. We need to detect the resize and repaint the prompt. I'm not sure if it's a good idea to try and detect the terminal (kitty, ghostty, iterm2, etc) and only redraw for a few terminals or if we should just keep-it-simple and always redraw the prompt on resize. I'm also not sure if we should wait for all the resize messages to stop coming in before we repaint the prompt, or what heuristic we should use exactly.

Also not sure if this is better or worse since https://github.com/nushell/reedline/pull/675. I haven't tried an earlier version. /cc @danielsomerfield

One last note, there is a shell_integration script for Ghostty + fish/bash/zsh. The one above is fish. The script does some ansi escape sequence calls like set the cursor, set prompt start, stop, pre_exec, post_exec (OSC133), and set title (OSC7) but it seems the important thing in the script is set --global fish_handle_reflow 1 to tell fish to redraw the prompt. At least I think that is what that line means.

danielsomerfield commented 9 months ago

Thanks for the detailed submission. I can take a look at this one. I can imagine how this might happen but I'll have to confirm. I feel like I also need to be a little more disciplined about keeping a test plan against all the various terminals. There are a lot of opportunity for a fix in one shell that breaks another and automated testing would be very challenging. I'll put together a matrix of sorts so it's easier to remember all the edge cases to check.

fdncred commented 9 months ago

Sounds good. I'm not sure which terminals support this "prompt erasure" other than Ghostty and Kitty. I commonly test with WezTerm, Alacritty, Windows Terminal, Ghostty, iTerm2, Tabby, gnome-terminal, and maybe a few others.

danielsomerfield commented 9 months ago

A couple of things:

fdncred commented 9 months ago

Mitchell's prompt is from fish. My prompt is from oh-my.nu in the nu_scripts/modules/prompt folder and this is in the config.nu.

# prompt
use "modules/prompt/oh-my.nu" git_prompt
$env.PROMPT_COMMAND = {|| (git_prompt).left_prompt }
$env.PROMPT_COMMAND_RIGHT = {|| (git_prompt).right_prompt }
$env.PROMPT_INDICATOR = {|| if ($env.LAST_EXIT_CODE == 0) {$"(ansi green_bold)\n❯ (ansi reset)"} else {$"(ansi red_bold)\n❯ (ansi reset)"}}
$env.TRANSIENT_PROMPT_COMMAND = { "" }
$env.TRANSIENT_PROMPT_COMMAND_RIGHT = { || (git_prompt).right_prompt }
$env.TRANSIENT_PROMPT_INDICATOR = {|| if ($env.LAST_EXIT_CODE == 0) {$"(ansi light_yellow_bold)❯ (ansi reset)"} else {$"(ansi light_red_bold)❯ (ansi reset)"}}
mitchellh commented 9 months ago

Do you happen to know what is rendering the prompt from @mitchellh?

I use fish, and I believe it is this: set --global fish_handle_reflow 1 that tells fish to handle reflow on resize for the prompt area.

Note that this isn't just the prompt itself. If you create a multiline input, it redraws the input as well. This isn't the terminal doing this, it's the shell. Example:

https://github.com/nushell/reedline/assets/1299/a831a5a3-a2fd-4576-9638-a26c8754737b

The best bet to learn more about this would be studying the Kitty shell integration scripts: https://github.com/kovidgoyal/kitty/tree/master/shell-integration Kitty was the first terminal I found that did this behavior and their shell integration enables it for all of the shells they support I think...

fdncred commented 9 months ago

Further research from fish-shell's fish_handle_reflow environment variable.

The reflow issue: https://github.com/fish-shell/fish-shell/issues/7491 The reflow PR: https://github.com/fish-shell/fish-shell/pull/7623

And the latest version of these changes. https://github.com/fish-shell/fish-shell/blob/b1a1a3b0a72211310e96d5f14631519df8bd3438/share/functions/__fish_config_interactive.fish#L240-L267

Interesting enough, this is all handled in fish-script and with their internal commandline built-in command. Which is kind of written in rust https://github.com/fish-shell/fish-shell/blob/b1a1a3b0a72211310e96d5f14631519df8bd3438/fish-rust/src/builtins/commandline.rs#L3 but not really.

pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
    run_builtin_ffi(crate::ffi::builtin_commandline, parser, streams, args)
}

https://github.com/fish-shell/fish-shell/blob/b1a1a3b0a72211310e96d5f14631519df8bd3438/fish-rust/src/builtins/shared.rs#L1027-L1055

C code to commandline (i think) https://github.com/fish-shell/fish-shell/blob/b1a1a3b0a72211310e96d5f14631519df8bd3438/src/builtins/commandline.cpp#L133

or maybe it is in rust? it's kind of hard to follow. https://github.com/fish-shell/fish-shell/blob/b1a1a3b0a72211310e96d5f14631519df8bd3438/fish-rust/src/screen.rs#L251