-
-
Notifications
You must be signed in to change notification settings - Fork 851
Description
Text wrapping produces hard line breaks, breaking clipboard copy and terminal reflow
Problem
When Ink wraps text to fit the terminal width, it inserts literal \n characters at the wrap points. Terminal emulators interpret these as hard line breaks, which means:
- Clipboard copy is broken — when users select and copy wrapped text, the clipboard contains newlines in the middle of sentences. Pasting into Gmail, Slack, Google Docs, etc. produces mangled text with line breaks mid-paragraph.
- Terminal resize reflow doesn't work — terminals can't reflow soft-wrapped text when the window is resized, because they think every line is intentionally separate.
This affects every application built with Ink. It's particularly noticeable in tools like Claude Code where users frequently copy long-form text output and paste it elsewhere.
Root cause
Ink's rendering pipeline wraps text explicitly rather than letting the terminal's native autowrap mechanism handle it:
wrap-ansisplits text at the terminal width, inserting\ncharacters- Each resulting line is placed at
(x, y)coordinates in theOutputgrid Output.get()joins all rows with\n- The terminal receives a string with explicit newlines between every row
Terminals distinguish between soft wraps (text that overflowed the right edge, triggering autowrap via DECAWM) and hard wraps (explicit \n/\r\n from the application). This distinction controls clipboard behavior and resize reflow. Because Ink always emits explicit \n, all wraps are treated as hard breaks.
Reproduction
import React from 'react';
import { render, Text } from 'ink';
// Resize your terminal to ~80 columns before running
const longText = 'This is a long sentence that will wrap across multiple lines when rendered in the terminal, and when you copy it, you will get newlines in the middle of the sentence which break pasting into other applications like Gmail or Slack.';
render(<Text>{longText}</Text>);- Run the above with a terminal width of ~80 columns
- Select the output text and copy it (Cmd+C / Ctrl+Shift+C)
- Paste into any rich text or plain text editor
Expected: The pasted text is one continuous paragraph with no line breaks mid-sentence.
Actual: The pasted text has \n characters at every point where the terminal visually wrapped the text.
How terminals handle this correctly
When an application writes characters that overflow the right edge of the terminal without sending an explicit newline, the terminal's autowrap (DECAWM) kicks in. The terminal marks this as a soft wrap internally. When the user copies soft-wrapped text, the terminal joins those lines back together — no newline in the clipboard.
This is how cat-ing a long line works correctly: the terminal wraps it visually, but copying gives you the original unwrapped text.
Proposed solution: hybrid rendering for flowing text
A full rearchitecture of Ink's rendering model isn't practical — the 2D grid is what makes <Box>, borders, and multi-column layouts work. But for the most common case (flowing text in a simple vertical container), Ink could emit text without \n at wrap points.
Approach: For rows that originate from a wrapped <Text> node (not box borders, not multi-column layouts), pad the line content to exactly the terminal width instead of emitting \n. When a line is padded to the full terminal width, the next character written triggers the terminal's autowrap, which gets tracked as a soft wrap.
Concretely, in Output.get() or the rendering step, lines that are "continuation" lines of a wrapped text node would:
- Be padded to the full terminal width (or have the last character written at the last column)
- Not be followed by
\n - Let the terminal's autowrap handle the transition to the next line
Lines from box borders, explicit \n in the source text, or multi-column layouts would continue to use \n as they do today.
This is a targeted change that fixes the most impactful case without breaking Ink's layout model.
Impact
This issue affects users of every Ink-based CLI tool. Some notable reports:
- anthropics/claude-code#23014 — copy/paste includes unwanted formatting
- anthropics/claude-code#13378 — hard wrap at 80 chars breaks copy-paste
- ghostty-org/ghostty#7962 — soft-wrap to hard-break conversion on copy
- ghostty-org/ghostty#6815 — copying long lines includes newlines
- prompt-toolkit/python-prompt-toolkit#709 — same architectural issue in another TUI framework