wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
18.17k stars 813 forks source link

Bad prompt on resize behavior #2987

Open BetaRavener opened 1 year ago

BetaRavener commented 1 year ago

What Operating System(s) are you seeing this problem on?

Windows, macOS

Which Wayland compositor or X11 Window manager(s) are you using?

On Windows and MacOS, running WezTerm natively. On Windows, showing behavior in WSL (Windows Subsytem for Linux). Windows also runs X410 server that was used to show Terminator ran from WSL that was recorded for comparison.

WezTerm version

20230119-205418-9a76a540

Did you try the latest nightly build to see if the issue is better (or worse!) than your current version?

Yes, and I updated the version box above to show the version of the nightly that I tried

Describe the bug

The prompt redraw behavior in WezTerm produces lot of output, spamming scrollback buffer. I have recorded videos as this is most easily explained visually. Expected Behavior section then shows behavior in iTerm2 and Terminator.

Using ZSH: https://user-images.githubusercontent.com/1514221/213687735-33221eb5-5c79-4a43-8ac9-ba9022c3727b.mp4

Using simple Bash: https://user-images.githubusercontent.com/1514221/213688043-9ce953f0-d659-4534-bb60-187a6b00f6ec.mp4

To Reproduce

The issue appears when terminal's pane width changes, by resizing pane or when TogglePaneZoomState is triggered.

Configuration

no config - the issue is reproducible running wezterm -n.

Expected Behavior

iTerm2: https://user-images.githubusercontent.com/1514221/213687607-68285ce9-4a73-43dd-ba9b-bb5c3f176d16.mp4

Terminator: https://user-images.githubusercontent.com/1514221/213687426-251bcf2a-ad3a-4b78-84f8-19730677825e.mp4

Notice that when pane is expanded in iTerm2, the prompt always simply expands. image Compare that with WezTerm above where in the beginning this seems to work, but later when there's some output it reprints prompt on new line even when expanded. Other terminals also never reprint the Transient Input line (starting with ), while this can be seen in WezTerm scrollback buffer. image

Also, toggling pane-zoom in iTerm2 produces at most one extra dotted line that's not very disturbing: image Compare that with WezTerm that may end up looking like this: image

Research

I've tried to find any reports of this before, at most I found this isssue 1491 that reported rather the native window jumping around, but you can see similarly gobbled scrollback in the user's videos where he seems to be using similar setup to mine.

I've also found this issue in Powerline10k repo - it seems WezTerm is not the only Terminal suffering from this. But knowing iTerm2 and even Terminator can handle the resizes better, there is likely something that can be done to solve it or minimize the impact. There is an interesting bit towards end of discussion, that may be taken out of context though:

The proper solution is to use a terminal that reflows everything except prompt. Kitty did this first, and then I implemented the same thing in zsh4humans.

Does this give you any pointers? Later I also found this section in the long P10K REAMDE.

Logs

No response

Anything else?

Additional information regarding setup: ZSH version: 5.8 (x86_64-ubuntu-linux-gnu) Oh-My-Zsh: f1a80006 (Jan 19th 2023) Powerline10k Theme: 21e89cb (Jan 19th 2023) configured to use Transient Prompt and two-line prompt.

Bash version: 5.0.16(1) Bash prompt: PS1="\[\e]0;@\h: \w\a\]\[\033[01;32m\]@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]$"

I'm only occasionally resizing the panes (though with current behavior even that is quite detrimental), but I'm very often toggling pane-zoom, for example executing command in normal size and toggling zoom to inspect large volumes of output afterwards. Having lot of empty space in scrollback means I always have to scroll-up a lot.

wez commented 1 year ago

FWIW, those videos all look "the same" to me: fundamentally, the problem is that the shell isn't cooperating with the terminal when it comes to drawing the prompt. They all make a mess out of the long lines that are being rendered and reflowed in the terminal, it's just the other terminals allow that mess to manipulate the scrollback outside of the viewport, while wezterm tries to prevent it from corrupting the previous output in scrollback when the cursor position moves around, which is much worse IMO.

I think it's an interesting idea to try to use the OSC 133 semantic zones to prevent re-flowing Prompt zones and instead truncate them; I'm open to the idea of doing something like that, but otherwise, I don't consider this a terminal bug.

wez commented 1 year ago

What would be really helpful is if you could distill the long/wrapping prompt stuff down to one or two lines of (ideally zsh) shell script that I could use to recreate this issue: I am never going to install zsh plugins, and I don't have a lot of spare time available to figure out a repro.

BetaRavener commented 1 year ago

I think it's an interesting idea to try to use the OSC 133 semantic zones to prevent re-flowing Prompt zones and instead truncate them; I'm open to the idea of doing something like that, but otherwise, I don't consider this a terminal bug.

Sounds intriguing. At least with the transient prompt option, there wouldn't be much Prompt zones in scrollback (should only be if I understand correctly also based on this feature), but it could help a lot on the input line(s).

those videos all look "the same" to me

I understand that from technical standpoint they appear the same, but from user experience I never thought about this problem much with iTerm2, while it made me search for fix and eventually open an issue here. WezTerm handling of resize sometimes creates a lot of extra empty or gobbled space.

it's just the other terminals allow that mess to manipulate the scrollback outside of the viewport

I don't think I ever seen iTerm2 mess up any program output in scrollback - at most it messes up the most recent prompt line which usually requires just hitting enter to repair it.

What would be really helpful is if you could distill the long/wrapping prompt stuff down to one or two lines of (ideally zsh) shell script that I could use to recreate this issue: I am never going to install zsh plugins

Will gladly help as much as I can though I don't know if there are perhaps multiple issues going on they're affecting each other. I think I have a simple to reproduce case to start with, will post in a bit.

BetaRavener commented 1 year ago

Ok I have removed .zshrc for now, getting plain ZSH shell.

Steps:

  1. Open two panes (left and right), resizing the left to accommodate 100 characters.
  2. Run for i in {1..200}; do print "${(pl:100::*:)}"; done in the left one to get some stuff in scrollback. The lines shouldn't be wrapped at this point.
  3. Shrink left pane so that lines wrap.
  4. Extend left pane so that lines unwrap.

Alternative Steps:

  1. to 3. are same.
  2. Trigger TogglePaneZoomState (I'm doing it with Ctrl+Shift+X keybinding)

Wezterm

The prompt should jump down considerably, leaving the previous prompt in scrollback followed by many empty lines. The viewport will also jump such that previous prompt is the top-most line.

Video: https://user-images.githubusercontent.com/1514221/213750409-f1c04ada-cd32-44cb-8bc2-25053e976f5e.mp4

Terminator

The lines in scrollback will simply wrap and unwrap, prompt never changes position.

Video: https://user-images.githubusercontent.com/1514221/213750531-e34b24d5-a7c7-43a0-92c3-fef8db2f6cc8.mp4

wez commented 1 year ago

Thanks... but I think that's a different issue from the prompt wrapping issue! That's a cursor positioning issue after re-flowing the scrollback. It will certainly influence the prompt position, and I will look into fixing that as well, but it doesn't help me to recreate and test the suggestion of using shell integration to skip reflowing prompt lines.

What I was hoping to see for the prompt wrapping issue was something that configures PS1 in a similar way to that shown in your original video. It specifically needs to be the prompt that the shell uses so that we can try applying the semantic escapes to it so that we can tell what we should truncate on resize.

wez commented 1 year ago

FWIW, I can't reproduce with your steps from https://github.com/wez/wezterm/issues/2987#issuecomment-1398625311 Are you running on windows? scrollback behaves different on windows to workaround some conpty issues.

BetaRavener commented 1 year ago

I see, this was the first issue I could easily reproduce, and what I meant by:

there are perhaps multiple issues going on they're affecting each other

I think this issue is what leaves terminal looking like the already shared picture after toggling the pane zoom few times. It could further exacerbate the other issues(s) - here all the lines are uniform and wrap at the same time, but the leftover prompts in scrollback will each have different lengths and wrap at different terminal widths, creating more mess at every step.

Currently trying to reproduce the prompt and compare the behavior across terminals.

And yes, running on Windows, can try OSX in a bit, maybe even Linux (Wayland I believe) running from WSL.

BetaRavener commented 1 year ago

Confirming that https://github.com/wez/wezterm/issues/2987#issuecomment-1398625311 reproduces only on Windows, WezTerm in OSX and one launched from WSL don't exhibit this specific behavior.

BetaRavener commented 1 year ago

For the prompt, I have been playing with example provided in P10K README:

setopt prompt_subst
PROMPT=$'left>${(pl.$((COLUMNS-12))..-.)}<right\n> '

Again using plain ZSH - this means whatever terminal integration P10K theme implements through POWERLEVEL9K_TERM_SHELL_INTEGRATION wouldn't apply here, potentially leading to worse results in terminals that support it.

Interestingly, the behavior is again different based on platform and speed.

Slowly resizing pane

Rapidly resizing the pane

When shrinking the pane, all 3 (Win, OSX, Linux) produce similar result: image

Note that this is actually single line, though on Windows the most recent prompt will be separated: Windows:

left>--------------------------------------------------------------------------------------------------------------------<rihtleft>-------------------------------------------------------------------------------------------------------------------<rleft>-----------------------------------------------------------------------------------------------------------------<rileft>----------------------------------------------------------------------------------------------------------------<rleft>-----------------------------------------------------------------------------------------------------------left>------------------------------------------------------------------------------------------------------------------<left>------------------------------------------------------------------------------------------------------------left>----------------------------------------------------------------------------------------------------------left>-----------------------------------------------------------------------------------------------left>----------------------------------------------------------------------------------------------------------<righ----------left>-----------------------------------------------------------------------------------------------------
left>-------------------------------------------------------------------------------------------------------<right
>

Linux:

left>------------------------------------------------------------------------------------------------------------------------<righleft>----------------------------------------------------------------------------------------------------------------------<righleft>---------------------------------------------------------------------------------------------------------------------<rigleft>-------------------------------------------------------------------------------------------------------------------left>---------------------------------------------------------------------------------------------------------------left>-----------------------------------------------------------------------------------------------------------left>-------------------------------------------------------------------------------------------------------left>-----------------------------------------------------------------------------------------------------------left>---------------------------------------------------------------------------------------------------------left>-------------------------------------------------------------------------------------------------------left>-----------------------------------------------------------------------------------------------------left>---------------------------------------------------------------------------------------------------left>-------------------------------------------------------------------------------------------------left>-----------------------------------------------------------------------------------------------left>---------------------------------------------------------------------------------------------left>-------------------------------------------------------------------------------------------left>---------------------------------------------------------------------------------------------------<right
>

When expanding, the speed doesn't seem to matter, can be rapid without artifacts if done carefully. When using mouse though, the pane may be shrunk accidentally by a tiny amount during the motion which will trigger some prompt prints.

iTerm2

Comparing this to iTerm2 on OSX, with same simplified prompt, the behavior during shrinking seems to be actually similar to the slow resizing on Windows, but expanding after shrinking doesn't produce these extra prompts. On the other hand, the previous prompts appear incomplete:

left>-----------------------------------------<righ
left>---------------------------------------<righ
left>-------------------------------------<righ
left>-----------------------------------<righ
left>---------------------------------<rig
left>------------------------------<rig
left>---------------------------<righ
left>-------------------------<rig
left>----------------------------------------------------------------<right

Installing the iTerm2 shell integration enabled jumping to prompt but didn't change resizing behavior. Setting POWERLEVEL9K_TERM_SHELL_INTEGRATION=true in ~/.p10k.zsh didn't seem to have an effect, probably only works with mentioned Kitty.

BetaRavener commented 1 year ago

Hi @wez , I was wondering if you had any time to look into this in the meantime. After some more usage I think more disrupting is the issue described in this post, not the one I described later in my post previous to this one.

I have recorded another video for a case I didn't notice before, that may help to trace down the cause. In zoomed mode, the prompt positioning is badly affected even when there's no apparent line-wrapping happening. It's enough that the lines would wrap if that pane wasn't zoomed:

https://user-images.githubusercontent.com/1514221/226419771-8592ab63-bd97-4d3d-950f-60950ba566b2.mp4

I have even pulled wezterm and tried debugging through the behavior but as it's linked to user interaction it's hard to hit the breakpoints at right time, plus I haven't really worked with Rust before.. The furthest I got was observing somewhat strange behavior in TerminalState::set_cursor_pos. During single resize step first it would get called with correct position, like X: 19, Y: 5 and a bit later it would get called with X: 0, Y:4 that would put cursor incorrectly on previous line (though not at X;0, but correctly after the prompt - maybe the prompt got reprinted?). The second call to set_cursor_pos came from Cursor::CharacterAndLinePosition { line, col } | Cursor::Position { line, col } handler. The pane would then look like (P is prompt):

zzz$ AAAAAAAAAAAAAAAAAAAAAAAAAA
zzz$
zzz$
zzz$ P
zzz$

No idea if this is right place to be looking though.

Btw, tested running WezTerm from within WSL via X11 server (instead of running in Windows and then logging into WSL), can confirm that there it doesn't happen. But it's not a good workaround.

BetaRavener commented 1 year ago

After more debugging I found the code that causes prompt to jump, but as I fully don't understand the implications I wanted to discuss it. Here's an image of the state after resizing terminal from 98 to 115 columns when scrollback stores 200 lines, each consisting of 100 * characters followed by single prompt (so when unwrapped self.lines.len() = 201). At size 98 the lines are wrapped and resizing to 115 causes unwrap. image

I've put relevant variables into watches. Notice the cursor_phys = 400 which comes from here. This value is calculated before lines are unwrapped here. This later goes into the calculation of new_cursor_y which you can already see on the screenshot. Result of max(23 + 200 - 400, 0) = 0, which means required_num_rows_after_cursor = physical_rows = 24. At the same time actual_num_rows_after_cursor = 201 - 200 = 1. That results in the loop pushing 24 - 1 = 23 empty lines on the buffer. That exactly matches observed behavior when the prompt jumps as if the view was basically cleared.

Now, I'm not sure if this is intended behavior even for ConPTY so I wanted to document it and maybe you'll be able to explain it with your experience.

What I believe is an actual root-cause is that WezTerm assumes it's using ConPTY, which I don't believe is the case anymore after switching to WSL (Windows Subsystem for Linux) and getting Linux prompt. So ideally we wouldn't be even running this branch and go with the mutable scrollback for "generally speaking a nicer experience".

I'm currently trying to recompile with hard-coding resize_preserves_scrollback = false and will come back with results. If it indeed resolves the issue, it might be worth looking into detecting WSL, but as hotfix is there any way to set this is_conpty flag from Lua maybe?

BetaRavener commented 1 year ago

Unfortunately it didn't exactly work. The prompt doesn't get reprinted after bunch of empty lines anymore, but instead jumps up. With terminal having 24 rows, there are always 11 empty lines following the prompt:

****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
****************************************************************************************************
Citadel%

Compared with WezTerm on X11, the prompt doesn't move anywhere and stays at the bottom. This wouldn't be too bad on it's own but it messes up scrollback buffer. I've cases with 189 and 178 star-lines remaining after resizing to unwrapped size, so it looks to be multiples of these 11 empty lines that go missing from scrollback. Should be possible to trace where they disappear but this means just setting resize_preserves_scrollback = false doesn't help.

BetaRavener commented 1 year ago

So it seems to be related to what I mentioned here. After handling the resize and positioning cursor correctly, terminal receives CSI that goes to Cursor::Position handler (I've split them to know which one). This causes TerminalState::set_cursor_pos to get called, which resolves parameters to X:0, Y:12. At this point screen.lines.len() = 201 so the buffer is still intact. Then comes another Cursor::Position with X:0, Y:13, and continues to come increasing Y until X:0, Y:23, followed finally by Cursor::Position with X:9, Y:12. After all the events the scrollback seems to still have 201 lines but not sure of content. I don't see how it would get modified though as TerminalState::set_cursor_pos is quite simple. Unfortunately data breakpoint for screen.lines didn't fire so have to set breakpoint everywhere it's modified...

Edit: Turns out after each Cursor::Position the terminal also receives also Edit::EraseInLine so that explains the empty lines. At some point WezTerm probably prunes them, though I didn't find exact location where.

BetaRavener commented 1 year ago

I've spent more time trying to understand the behavior and comparing also with different terminals, I've arrived at conclusion that without some wrapper around ConPTY the console will always behave in awkward ways on resize. There are multiple "quirks" (as as it's named in code) involved. The one where console tends to leave empty space at bottom, rather than pulling the cursor on bottom line ("gravity") is one of them. Another is that lines don't really unwrap when contents of line (N characters) fit the window, but only at N+1 - and it is at this point that console sends all the Position and EraseLine instructions. As this doesn't match behavior of other operating systems, and WezTerm treats line wrapping the same for all of them, there's certain disconnect.

Observe for example here Windows Terminal and then WezTerm:

https://user-images.githubusercontent.com/1514221/227795006-9afbbb2d-fbc1-4f0a-9a39-5e8a912fade9.mp4

https://user-images.githubusercontent.com/1514221/227795004-3e7bfd40-3b3c-409e-9b28-913f60a48b73.mp4

You can see win-term wraps the line sooner, creating at specific size a pattern where every second row is empty. In WezTerm video you can see the step when WezTerm thinks it should add bunch of new lines to keep things correct, and upon further resize moment when actually console thinks the lines unwrapped (the cursor jumps up to the middle of the screen). In other words, what I described here happens all the time, it just doesn't mess things up too much because the cursor jumps up into the blank lines that were inserted to scrollback, rather than actual output history.

This investigation made me understand that by removing ConPTY from equation would most likely solve my troubles. One of the workaround I mentioned earlier was running WezTerm inside WSL and having X11 server on Windows display the window, but this is jerky and has poor performance. If only could frontend run natively while the console be managed under Linux - and here I realized WezTerm can connect to sessions. So I basically created SSH domain in config pointing to WSL2, connected to it and finally those annoying blocks of empty space after resizes are gone!

I'm keeping the issue open as this is not proper solution, but for my purposes I finally got something I can work with.

wez commented 1 year ago

@BetaRavener I don't know if you saw dc36fe18f42ad5af8559ad5b4f04d9212142465d which changed the internal line wrapping logic for conpty.

I agree that wrapping with conpty involved is a bit of a mess :-(

BetaRavener commented 1 year ago

Oh, nice. No I didn't see that commit, thanks for looking into the topic. I've pulled and recompiled to include it and while it doesn't make difference in described scenario, I still appreciate your effort. Thanks!

zechfox commented 1 year ago

same problem here

Cryt1c commented 8 hours ago

I am experiencing the same issue using Powershell 7 in wezterm on Windows 11