Skip to content

[Bug]: Pasting with a multi-line prompt overwrites the line above it (double cursor-up in forge-bracketed-paste) #3937

Description

@scalinity

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:

  1. Load the plugin and theme via forge zsh setup.
  2. Use a two-line prompt, e.g. PROMPT=$'%~\n❯ '.
  3. Run any command that prints output, e.g. echo previous output.
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions