Pasting text whose paragraphs are separated by a lone \r into a textarea draws the words after each CR one row lower, on top of the next wrapped row. The text itself is intact; only the drawing is wrong. U+2028 and vertical tab (0x0B) do the same. LF and CRLF draw correctly.
Seen with CLI 0.10.1 on macOS; the code below is as of 6b05318 on main.
Reproduction. A fixed-width textarea (mine is 484pt), four paragraphs of prose separated by a single \r, each long enough to wrap two or three times. Paste it (native automate widget-key <view> cmd+v reproduces it too). In the host render the first words of each paragraph after the first are drawn over that paragraph's first wrapped row.
What I found in the source
- Layout breaks a line only at LF (
src/primitives/canvas/text_layout.zig:761, :793, :840) and wraps only at space or tab (isTextBreakByte, :1088). So end.\rNext is one unbreakable word on one row.
- The macOS host draws a row through NSLayoutManager (
src/platform/macos/appkit_host.m:2093-2106), which treats \r as a paragraph break, so the part after the CR is drawn a line lower.
- Width measurement on macOS uses
sizeWithAttributes: (appkit_host.m:2539), which returns the width of the widest line for a string containing \r, so measureTextAdvance (src/primitives/canvas/text_metrics.zig:98) gives every character after a CR an advance of 0. The reference renderer (native automate screenshot) accordingly draws those characters collapsed into one cell.
- The paste keeps
\r as pasted.
U+2029, U+0085 and FF should behave the same (NSLayoutManager breaks at them and the layout does not); I have only read that, not pasted it.
Possible fixes. Treat a lone CR (and the other separators) as a hard break wherever LF is one, or normalize them to LF when text enters a textarea. Either way, the text handed to the host for one row should not contain a character the host breaks at.
A related gap. A TEA app cannot tell a textarea where its caret is: Widget has text_selection, but ui.el's ElementOptions has no field for it and widgetFromOptions never sets it. So when an app's model text differs from the editor's copy (here, because the app normalized a paste), the editor takes the app's text, puts the caret at the end, and its undo history for the field restarts. Letting ElementOptions carry a text_selection through to the widget would let an app keep the caret where the edit left it.
In Plaza I work around it by turning those separators into LF before an edit reaches the model, leaving CRLF alone, and moving the model's caret to the end when a paste had to change (zig-nostr/plaza#388).
Pasting text whose paragraphs are separated by a lone
\rinto a textarea draws the words after each CR one row lower, on top of the next wrapped row. The text itself is intact; only the drawing is wrong. U+2028 and vertical tab (0x0B) do the same. LF and CRLF draw correctly.Seen with CLI 0.10.1 on macOS; the code below is as of
6b05318on main.Reproduction. A fixed-width textarea (mine is 484pt), four paragraphs of prose separated by a single
\r, each long enough to wrap two or three times. Paste it (native automate widget-key <view> cmd+vreproduces it too). In the host render the first words of each paragraph after the first are drawn over that paragraph's first wrapped row.What I found in the source
src/primitives/canvas/text_layout.zig:761,:793,:840) and wraps only at space or tab (isTextBreakByte,:1088). Soend.\rNextis one unbreakable word on one row.src/platform/macos/appkit_host.m:2093-2106), which treats\ras a paragraph break, so the part after the CR is drawn a line lower.sizeWithAttributes:(appkit_host.m:2539), which returns the width of the widest line for a string containing\r, someasureTextAdvance(src/primitives/canvas/text_metrics.zig:98) gives every character after a CR an advance of 0. The reference renderer (native automate screenshot) accordingly draws those characters collapsed into one cell.\ras pasted.U+2029, U+0085 and FF should behave the same (NSLayoutManager breaks at them and the layout does not); I have only read that, not pasted it.
Possible fixes. Treat a lone CR (and the other separators) as a hard break wherever LF is one, or normalize them to LF when text enters a textarea. Either way, the text handed to the host for one row should not contain a character the host breaks at.
A related gap. A TEA app cannot tell a textarea where its caret is:
Widgethastext_selection, butui.el'sElementOptionshas no field for it andwidgetFromOptionsnever sets it. So when an app's model text differs from the editor's copy (here, because the app normalized a paste), the editor takes the app's text, puts the caret at the end, and its undo history for the field restarts. LettingElementOptionscarry atext_selectionthrough to the widget would let an app keep the caret where the edit left it.In Plaza I work around it by turning those separators into LF before an edit reaches the model, leaving CRLF alone, and moving the model's caret to the end when a paste had to change (zig-nostr/plaza#388).