prompt_toolkit Bug Report: Cursor drift after auto-wrap with leading space
Summary
When typing in a TextArea (or BufferControl) with wrap_lines=True and
multiline=False, the cursor visually drifts to the right when a space
character becomes the first character of an auto-wrapped visual line. The
drift equals approximately the number of wraps (e.g., 4 wraps → 4 characters
of drift). Navigating back to the line with arrow keys corrects the cursor
position.
Environment
- prompt_toolkit version: 3.0.x (shipped with Hermes Agent, May 2026)
- Python version: 3.11
- OS: Linux (Ubuntu 24.04)
- Terminal: SSH from Windows (PowerShell, Termius)
- TERM: xterm-256color
- Application: Hermes Agent (
hermes --tui)
Steps to Reproduce
- Open a prompt_toolkit application with a
TextArea that has:
multiline=False (single line input)
wrap_lines=True
- A prompt (e.g.,
BeforeInput)
- Type a long line of text without spaces until it auto-wraps to the next
visual line
- Continue typing — the first character typed on the new visual line, if it
is a space (because the text logically has a space at that point), causes
the cursor to visually jump ~1 character to the right per wrap
- The cursor now appears 4+ characters away from where text is actually
inserted
Additional Observations
- Adding spaces BEFORE the auto-wrap point (pushing the wrap point further
right) prevents the drift
- With
full_screen=False, the drift is ~1 character per wrap (4 wraps = 4
chars)
- With
full_screen=True, the drift becomes a full line offset
- The drift is corrected when the user navigates to the line with arrow keys
(cursor position is recalculated)
Suspected Root Cause
In BufferControl._create_get_processed_line_func, the translate_rowcol
function computes cursor position as:
Point(x=get_processed_line(row).source_to_display(col), y=row)
The y coordinate is always the buffer row (row), which is 0 for a single
line. For wrapped content, the cursor may be on a different visual row, but
y doesn't account for wrapping. The Window._copy_body renders wrapped
content correctly, but the UIContent.cursor_position uses the unadjusted Y.
Additionally, BeforeInput.apply_transformation uses fragment_list_len
for the source_to_display shift, which counts characters rather than
display width. Multi-width characters (emoji, CJK) are miscounted.
Related Code
prompt_toolkit/layout/containers.py — Window._copy_body wraps text at
line 2016+ but doesn't feed wrapping info back to cursor position
prompt_toolkit/layout/controls.py — BufferControl._create_get_processed_line_func
line 773: translate_rowcol returns y=row
prompt_toolkit/layout/processors.py — BeforeInput line 507:
shift_position = fragment_list_len(fragments_before) should be
fragment_list_width(fragments_before)
prompt_toolkit Bug Report: Cursor drift after auto-wrap with leading space
Summary
When typing in a
TextArea(orBufferControl) withwrap_lines=Trueandmultiline=False, the cursor visually drifts to the right when a spacecharacter becomes the first character of an auto-wrapped visual line. The
drift equals approximately the number of wraps (e.g., 4 wraps → 4 characters
of drift). Navigating back to the line with arrow keys corrects the cursor
position.
Environment
hermes --tui)Steps to Reproduce
TextAreathat has:multiline=False(single line input)wrap_lines=TrueBeforeInput)visual line
is a space (because the text logically has a space at that point), causes
the cursor to visually jump ~1 character to the right per wrap
inserted
Additional Observations
right) prevents the drift
full_screen=False, the drift is ~1 character per wrap (4 wraps = 4chars)
full_screen=True, the drift becomes a full line offset(cursor position is recalculated)
Suspected Root Cause
In
BufferControl._create_get_processed_line_func, thetranslate_rowcolfunction computes cursor position as:
The
ycoordinate is always the buffer row (row), which is 0 for a singleline. For wrapped content, the cursor may be on a different visual row, but
ydoesn't account for wrapping. TheWindow._copy_bodyrenders wrappedcontent correctly, but the
UIContent.cursor_positionuses the unadjusted Y.Additionally,
BeforeInput.apply_transformationusesfragment_list_lenfor the
source_to_displayshift, which counts characters rather thandisplay width. Multi-width characters (emoji, CJK) are miscounted.
Related Code
prompt_toolkit/layout/containers.py—Window._copy_bodywraps text atline 2016+ but doesn't feed wrapping info back to cursor position
prompt_toolkit/layout/controls.py—BufferControl._create_get_processed_line_funcline 773:
translate_rowcolreturnsy=rowprompt_toolkit/layout/processors.py—BeforeInputline 507:shift_position = fragment_list_len(fragments_before)should befragment_list_width(fragments_before)