Cursor drift after auto-wrap when first char of wrapped line is a space

Author: herrschmidtCreated May 12, 2026Updated May 12, 2026

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

  1. Open a prompt_toolkit application with a TextArea that has:
    • multiline=False (single line input)
    • wrap_lines=True
    • A prompt (e.g., BeforeInput)
  2. Type a long line of text without spaces until it auto-wraps to the next visual line
  3. 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
  4. 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:

python
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.pyWindow._copy_body wraps text at line 2016+ but doesn't feed wrapping info back to cursor position
  • prompt_toolkit/layout/controls.pyBufferControl._create_get_processed_line_func line 773: translate_rowcol returns y=row
  • prompt_toolkit/layout/processors.pyBeforeInput line 507: shift_position = fragment_list_len(fragments_before) should be fragment_list_width(fragments_before)

Source: prompt-toolkit/python-prompt-toolkit