Configuration Information:

  Readline version: 8.3-release
  Machine: x86_64
  OS: linux-gnu (Fedora 44)
  Compilation flags: stock "./configure" from the GNU tarball, no options,
    default CFLAGS (-g -O2), gcc 15
  Also observed via: bash 5.3.9(1)-release (x86_64-redhat-linux-gnu), and with
    the distribution's readline at 8.3 patchlevel 3

Still present with the published 8.3 patches: readline83-002 is the only one
touching display.c, and with it applied the behaviour is unchanged. The
condition discussed below is also still present, unmodified, in the current
development sources.

Description:

If the terminal is widened while readline is reading a line, and the prompt
wrapped at the old width but fits on a single line at the new width, readline
places the cursor too far to the left. Characters typed afterwards overwrite
the tail of the prompt.

The displacement is exactly the byte length of the *last* run of invisible
characters (the last RL_PROMPT_START_IGNORE ... RL_PROMPT_END_IGNORE group).
A prompt with a single invisible run is unaffected. A prompt with no invisible
characters at all is unaffected, at the same visible length.

This is not bash-specific and not multiplexer-specific: it reproduces with
examples/rl from the readline distribution. tmux is used below only as a
convenient way to change the terminal width while a line is being read.

Repeat-By:

  # readline-8.3, built unpatched from the GNU tarball
  $ gcc -g -o rl examples/rl.o libreadline.a libhistory.a -ltinfo

  $ cat > run-rl.sh <<'EOF'
  #!/bin/bash
  P=$(printf 'x%.0s' {1..100})
  exec ./rl -p $'\001\033[32m\002'"$P"$'\001\033[0m\002'" $ "
  EOF
  $ chmod +x run-rl.sh

  $ tmux -L t new-session -d -x 200 -y 12 ./run-rl.sh
  $ tmux -L t split-window -h ./run-rl.sh   # panes are 99 cols; prompt wraps
  $ tmux -L t select-pane -t 0
  $ tmux -L t resize-pane -Z                # pane becomes 200 cols; it fits
  $ tmux -L t send-keys HERE
  $ tmux -L t display-message -p -t 0 '#{cursor_x}'
  $ tmux -L t capture-pane -p -t 0 | head -1

The prompt is 103 visible columns (100 x's plus " $ ").

  Expected: cursor_x 107, line reads  xxx...xxx $ HERE
  Actual:   cursor_x 103, line reads  xxx...xxxHERE

"HERE" starts at column 99 and overwrites " $ ". The error is 4 columns, the
length of "\033[0m".

Changing the trailing escape from "\033[0m" (4 bytes) to "\033[1;35m" (7 bytes)
changes the displacement from 4 to 7 columns. Deleting the trailing invisible
run, keeping the same visible length, makes the problem disappear.

C-l does not repair the display; only accepting the line and getting a fresh
prompt does. A prompt whose last physical line is short (for instance one
ending in "\n$ ") is unaffected, since that takes the redraw_prompt() path.

Analysis:

expand_prompt() computes local_prompt_newlines[] and local_prompt_invis_chars[]
against the screen width in effect when it runs, and local_prompt_invis_chars[]
is indexed by prompt screen line. After SIGWINCH, _rl_redisplay_after_sigwinch()
refreshes them only under (display.c:3528):

    if (_rl_screenwidth < prompt_visible_length)
      _rl_reset_prompt (); /* update local_prompt_newlines array */

When the terminal is widened past prompt_visible_length that condition is false,
so both arrays keep describing the older, narrower layout. WRAP_OFFSET()
(display.c:786) then reads a per-line invisible count for a prompt screen line
that no longer exists, and the invisible characters attributed to it are lost
from the cursor computation. That matches the observed displacement exactly.

The comment at display.c:1015 notes the related assumption:

    /* XXX - There is code that assumes that all the invisible characters occur
       on the first and last prompt lines; change that to use
       local_prompt_invis_chars */

Possible Fix:

The missing case seems to be "the prompt was wrapped before the resize", which
prompt_last_screen_line already records:

--- display.c.orig
+++ display.c
@@ -3525,7 +3525,7 @@
   else
     rl_crlf ();

-  if (_rl_screenwidth < prompt_visible_length)
+  if (_rl_screenwidth < prompt_visible_length || prompt_last_screen_line > 0)
     _rl_reset_prompt (); /* update local_prompt_newlines array */

With this change applied to the readline-8.3 tarball, the scenario above yields
cursor_x 107 and an intact prompt. I compared patched and unpatched builds
across the neighbouring cases; only the failing one changes:

  scenario                              unpatched   patched
  widen, prompt has color (the bug)     103  wrong  107  ok
  widen, no invisible characters        107  ok     107  ok
  widen, prompt short enough to fit      27  ok      27  ok
  widen, prompt still wraps afterwards   47  ok      47  ok
  narrow (wide -> narrow)                37  ok      37  ok

I have not audited whether re-expanding the prompt on every resize has other
side effects, so please treat the patch as a starting point rather than a
finished change.

Reply via email to