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.