Cursor drift after auto-wrap when first char of wrapped line is a space
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
TextAreathat 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_bodywraps text at line 2016+ but doesn't feed wrapping info back to cursor positionprompt_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)
Source: prompt-toolkit/python-prompt-toolkit