Skip to content

Text wrapping produces hard line breaks, breaking clipboard copy and terminal reflow #883

@isaac-levine

Description

@isaac-levine

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:

  1. 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.
  2. 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:

  1. wrap-ansi splits text at the terminal width, inserting \n characters
  2. Each resulting line is placed at (x, y) coordinates in the Output grid
  3. Output.get() joins all rows with \n
  4. 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>);
  1. Run the above with a terminal width of ~80 columns
  2. Select the output text and copy it (Cmd+C / Ctrl+Shift+C)
  3. 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:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions