Bug Description
With the zsh plugin loaded and a multi-line PROMPT, every paste redraws the prompt one line too high, overwriting the line directly above it (usually the last line of the previous command's output).
The cause is forge-bracketed-paste in shell-plugin/lib/bindings.zsh, which calls zle redisplay and then zle reset-prompt. In zsh, reset-prompt is reexpandprompt() followed by redisplay() (Src/Zle/zle_main.c), and redisplay() (Src/Zle/zle_refresh.c, zsh 5.9) moves the cursor up lprompth - 1 lines immediately while only scheduling the redraw:
redisplay(UNUSED(char **args))
{
moveto(0, 0);
zputc(&zr_cr); /* extra care */
tc_upcurs(lprompth - 1);
resetneeded = 1;
clearflag = 0;
return 0;
}
Calling it twice in one widget moves the cursor up 2 × (lprompth - 1) lines before the single redraw runs. With a one-line prompt that is 0, so nothing is visible. With a two-line prompt the redraw starts one line too high, and the ESC[J it emits erases the line above.
The explicit zle redisplay was added in #2121 to address paste content not rendering. Neither widget draws synchronously, though: both only set resetneeded, and the refresh happens once the widget returns. The extra call does not change what is drawn; it only adds the extra cursor-up.
Proposed fix
Drop the zle redisplay; zle reset-prompt alone performs the full redraw (and still refreshes syntax highlighting):
fi
fi
- # Explicitly redisplay the buffer to ensure paste content is visible
- # This is critical for large or multiline pastes
- zle redisplay
-
# Reset the prompt to trigger syntax highlighting refresh
- # The redisplay before reset-prompt ensures the buffer is fully rendered
zle reset-prompt
}
Verified with the full plugin and theme loaded, with a two-line prompt: single-line pastes, multi-line pastes and :-prefixed pastes each emit exactly one cursor-up, and multi-line pastes render every line.
Workaround until a fix ships (in .zshrc, after the plugin is loaded):
functions[forge-bracketed-paste]=${functions[forge-bracketed-paste]/zle redisplay/}
Steps to Reproduce
Minimal, without forge:
zsh -f
PROMPT=$'%~\n> '
function forge-bracketed-paste() { zle .$WIDGET "$@"; zle redisplay; zle reset-prompt }
zle -N bracketed-paste forge-bracketed-paste
echo previous output
# now paste any text at the prompt: "previous output" is overwritten
With forge:
- Load the plugin and theme via
forge zsh setup.
- Use a two-line prompt, e.g.
PROMPT=$'%~\n❯ '.
- Run any command that prints output, e.g.
echo previous output.
- Paste any text at the next prompt.
Bytes zsh writes after pasting PASTED (captured in a pty, TERM=xterm-256color, 80×24, prompt %~\n> ):
- current widget:
\r\r\e[A\r\e[A\e[0m\e[27m\e[24m\e[J~\r\n> PASTED\e[K (two cursor-ups)
- with
zle redisplay removed: \r\r\e[A\e[0m\e[27m\e[24m\e[J~\r\n> PASTED\e[K (one)
Expected Behavior
Pasting redraws the prompt in place and leaves the line above it untouched.
Actual Behavior
Every paste redraws the prompt one line higher, overwriting the line above it, e.g. the previous command's output. Pressing Enter a few times before pasting works around it by giving the extra cursor-up blank lines to land on.
Forge Version
2.13.21 (the widget is unchanged on main)
Operating System & Version
macOS 27.0, zsh 5.9, Terminal.app. Not terminal-specific: the extra cursor-up is in zsh's own output.
Installation Method
curl -fsSL https://forgecode.dev/cli | sh
Bug Description
With the zsh plugin loaded and a multi-line
PROMPT, every paste redraws the prompt one line too high, overwriting the line directly above it (usually the last line of the previous command's output).The cause is
forge-bracketed-pasteinshell-plugin/lib/bindings.zsh, which callszle redisplayand thenzle reset-prompt. In zsh,reset-promptisreexpandprompt()followed byredisplay()(Src/Zle/zle_main.c), andredisplay()(Src/Zle/zle_refresh.c, zsh 5.9) moves the cursor uplprompth - 1lines immediately while only scheduling the redraw:Calling it twice in one widget moves the cursor up
2 × (lprompth - 1)lines before the single redraw runs. With a one-line prompt that is 0, so nothing is visible. With a two-line prompt the redraw starts one line too high, and theESC[Jit emits erases the line above.The explicit
zle redisplaywas added in #2121 to address paste content not rendering. Neither widget draws synchronously, though: both only setresetneeded, and the refresh happens once the widget returns. The extra call does not change what is drawn; it only adds the extra cursor-up.Proposed fix
Drop the
zle redisplay;zle reset-promptalone performs the full redraw (and still refreshes syntax highlighting):fi fi - # Explicitly redisplay the buffer to ensure paste content is visible - # This is critical for large or multiline pastes - zle redisplay - # Reset the prompt to trigger syntax highlighting refresh - # The redisplay before reset-prompt ensures the buffer is fully rendered zle reset-prompt }Verified with the full plugin and theme loaded, with a two-line prompt: single-line pastes, multi-line pastes and
:-prefixed pastes each emit exactly one cursor-up, and multi-line pastes render every line.Workaround until a fix ships (in
.zshrc, after the plugin is loaded):functions[forge-bracketed-paste]=${functions[forge-bracketed-paste]/zle redisplay/}Steps to Reproduce
Minimal, without forge:
With forge:
forge zsh setup.PROMPT=$'%~\n❯ '.echo previous output.Bytes zsh writes after pasting
PASTED(captured in a pty,TERM=xterm-256color, 80×24, prompt%~\n>):\r\r\e[A\r\e[A\e[0m\e[27m\e[24m\e[J~\r\n> PASTED\e[K(two cursor-ups)zle redisplayremoved:\r\r\e[A\e[0m\e[27m\e[24m\e[J~\r\n> PASTED\e[K(one)Expected Behavior
Pasting redraws the prompt in place and leaves the line above it untouched.
Actual Behavior
Every paste redraws the prompt one line higher, overwriting the line above it, e.g. the previous command's output. Pressing Enter a few times before pasting works around it by giving the extra cursor-up blank lines to land on.
Forge Version
2.13.21 (the widget is unchanged on
main)Operating System & Version
macOS 27.0, zsh 5.9, Terminal.app. Not terminal-specific: the extra cursor-up is in zsh's own output.
Installation Method
curl -fsSL https://forgecode.dev/cli | sh