Skip to content

Add selection semantics: flows, boundaries, and semantic Text props - #985

Draft
chiga0 wants to merge 2 commits into
vadimdemedes:masterfrom
chiga0:feat/text-selection-semantics
Draft

Add selection semantics: flows, boundaries, and semantic Text props#985
chiga0 wants to merge 2 commits into
vadimdemedes:masterfrom
chiga0:feat/text-selection-semantics

Conversation

@chiga0

@chiga0 chiga0 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Second part of the split suggested in #980, stacked on #984 (the diff against master includes #984 until it merges — review that one first). Adds the selection semantics needed to extract copied text: flows, boundaries, and semantic <Text> props.

What's included

  • <Text> gains selectable, selectionFlow, selectionBreakAfter, and selectionJoiner props (documented in the readme).
  • Frame cells expose selectable and flowId; frames carry a boundaries grid describing how adjacent text joins when copied (wrap points with the consumed whitespace, source newlines, explicit selectionBreakAfter boundaries).
  • The selection highlight skips non-selectable cells, matching what a copy would include.

Concerns from the #980 review addressed here

  1. Metadata lost when nested Text nodes are squashed — squashing now tracks per-node selection attributes (squashTextNodesWithMetadata), so an inner selectable={false} (or selectionFlow override) is preserved per cell instead of being overwritten by the outer node. Arbitrary <Transform> functions may rewrite visible text, so subtrees transformed by them fall back to one uniform segment (verified byte-identical output); color/style transforms keep full granularity.
  2. Missing test coverage — 14 new tests: nested selectable (both directions), nesting through color transforms, highlight skipping non-selectable cells (ASCII, wide characters, box backgrounds), shared/distinct/nested flows, and boundary placement (hard, soft with custom joiner, source newlines, wrapping).

Performance

Semantics are only resolved when they can be observed (cells captured or a selection applied), extending the opt-in from #984. With neither, the render path is unchanged. The semantic path produces byte-identical output to the plain path (verified for nested/colored text, wrapping, padding, wide characters, multi-line text).

Kept as a draft until #984 lands.

秦奇 added 2 commits August 3, 2026 13:51
Expose the composited frame as a read-only grid of cells that apps
owning their input (e.g. alternate-screen apps handling mouse events)
can subscribe to, and let them push a selection region that Ink
highlights before serialization.

Frames are only generated while subscribers are registered, listeners
are notified outside the render pass with coalesced notifications, and
selections are normalized to reading order so reverse drags work.
Build on the frame controller with the selection semantics needed to
extract copied text:

- `<Text>` gains `selectable`, `selectionFlow`, `selectionBreakAfter`,
  and `selectionJoiner` props.
- Squashing text nodes now tracks per-node selection attributes, so
  nested `<Text>` props (e.g. `selectable={false}`) survive instead of
  being overwritten by the outer node's attributes.
- Frame cells expose `selectable` and `flowId`; frames carry a
  `boundaries` grid describing how adjacent text joins when copied
  (wrap points, source newlines, explicit `selectionBreakAfter`).
- The selection highlight skips non-selectable cells, matching what a
  copy would include.

Semantics are only resolved when observable (cells captured or a
selection applied); the rendered output stays byte-identical to the
plain path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant