Today — a line long enough to wrap becomes two rows, and no accessor can put it back together, so a needle that spans the wrap is not found. Measured against 0.9.0 on a 20-column terminal:
// the child printed exactly: "the quick brown fox jumps"
screen:
the quick brown fox
jumps
s.contains("brown fox jumps") // false — but a reader sees it on the screen
s.find("brown fox jumps") // None
s.contains("fox\njumps") // true — only if you know where the wrap fell
The information needed to fix this is already in the backend and never asked for. vt100::Screen::row_wrapped(row) -> bool is public and reports whether a row ended in a soft wrap rather than a newline:
$ grep -rn 'row_wrapped' crates/termlens/src/
$ # never called
Why it is worth fixing — the failure looks like the crate is lying. The timeout embeds the screen, the screen plainly shows brown fox jumps, and the predicate that was waiting for it says it never appeared. This crate has met that shape before and treated it as a defect worth real work: when text scrolls off the top, contains cannot see it either, and #203/#204 added a note to every timeout saying how many rows have scrolled and pointing at full_text. A wrap is the same trap one row lower, with no note and no full_text to point at.
It is also load-bearing for a second decision. Terminal::resize's rustdoc (terminal.rs:3573) and docs/DESIGN.md:451 both justify not reflowing scrollback like this:
History is text with no record of which rows were soft-wrapped, so there is nothing to reflow from
The record exists. It is row_wrapped, it is public, and it is available at the moment history is captured — it simply is not captured. The decision not to reflow may well still be right on cost grounds, but its stated reason is not the true one, and a contributor reading it is being told a capability does not exist when it does.
Fix — capture the flag and expose the joined text; leave the search semantics alone.
- Read
row_wrapped for each row when the snapshot is built (emu/vt100.rs, beside the cell conversion) and carry one bit per row on Screen.
Screen::row_wrapped(row) -> bool — the raw fact, matching the backend's name.
Screen::logical_text() -> String — the grid as text with soft-wrapped rows joined by nothing and hard line ends joined by \n, so logical_text().contains("brown fox jumps") is true. This is the accessor the failing assertion above wants.
contains and find do not change. Their rustdocs promise that a needle is found precisely when contains is true and that columns are real grid columns, and a match spanning a wrap has no single row or column to report. Instead their docs gain the wrap trap beside the scrollback trap they already carry, pointing at logical_text the way they point at full_text — the two are the same shape of answer to the same shape of question.
Then correct the two sentences above: whatever resize decides about reflow, the reason is the cost of doing it, not the absence of the record.
Out of scope, deliberately: reflowing on resize, and carrying the flag into scrollback history. Both become possible after this and neither should ride along with it — say so in the PR.
Done when — Screen::row_wrapped reports the backend's flag; logical_text() joins soft-wrapped rows so a needle spanning a wrap is found in it; contains and find are unchanged but document the trap and name logical_text; the resize rustdoc and docs/DESIGN.md no longer say the record does not exist; and a test drives a wrapping line and asserts all three.
Today — a line long enough to wrap becomes two rows, and no accessor can put it back together, so a needle that spans the wrap is not found. Measured against 0.9.0 on a 20-column terminal:
The information needed to fix this is already in the backend and never asked for.
vt100::Screen::row_wrapped(row) -> boolis public and reports whether a row ended in a soft wrap rather than a newline:Why it is worth fixing — the failure looks like the crate is lying. The timeout embeds the screen, the screen plainly shows
brown fox jumps, and the predicate that was waiting for it says it never appeared. This crate has met that shape before and treated it as a defect worth real work: when text scrolls off the top,containscannot see it either, and #203/#204 added a note to every timeout saying how many rows have scrolled and pointing atfull_text. A wrap is the same trap one row lower, with no note and nofull_textto point at.It is also load-bearing for a second decision.
Terminal::resize's rustdoc (terminal.rs:3573) anddocs/DESIGN.md:451both justify not reflowing scrollback like this:The record exists. It is
row_wrapped, it is public, and it is available at the moment history is captured — it simply is not captured. The decision not to reflow may well still be right on cost grounds, but its stated reason is not the true one, and a contributor reading it is being told a capability does not exist when it does.Fix — capture the flag and expose the joined text; leave the search semantics alone.
row_wrappedfor each row when the snapshot is built (emu/vt100.rs, beside the cell conversion) and carry one bit per row onScreen.Screen::row_wrapped(row) -> bool— the raw fact, matching the backend's name.Screen::logical_text() -> String— the grid as text with soft-wrapped rows joined by nothing and hard line ends joined by\n, sological_text().contains("brown fox jumps")is true. This is the accessor the failing assertion above wants.containsandfinddo not change. Their rustdocs promise that a needle is found precisely whencontainsis true and that columns are real grid columns, and a match spanning a wrap has no single row or column to report. Instead their docs gain the wrap trap beside the scrollback trap they already carry, pointing atlogical_textthe way they point atfull_text— the two are the same shape of answer to the same shape of question.Then correct the two sentences above: whatever
resizedecides about reflow, the reason is the cost of doing it, not the absence of the record.Out of scope, deliberately: reflowing on resize, and carrying the flag into scrollback history. Both become possible after this and neither should ride along with it — say so in the PR.
Done when —
Screen::row_wrappedreports the backend's flag;logical_text()joins soft-wrapped rows so a needle spanning a wrap is found in it;containsandfindare unchanged but document the trap and namelogical_text; theresizerustdoc anddocs/DESIGN.mdno longer say the record does not exist; and a test drives a wrapping line and asserts all three.