Expand description
Turn selected text into something worth hearing.
Order matters: code fences are dropped before anything inspects their contents, and whitespace is collapsed last so earlier removals do not leave gaps.
URLs get special handling. After code fences are dropped, the remaining
text is segmented into alternating runs of non-URL text and URL matches.
Transforms that must never touch URL text — hyphenation rejoin, markdown
stripping, acronym spelling — run only on the non-URL segments; the
UrlPolicy replacement (the literal word “link”, a bare host, or the URL
verbatim) is computed only for the URL segments. The results are
concatenated back together in their original order.
This replaces an earlier placeholder-based scheme (hide URLs behind
\u{E000}<index>\u{E001} markers, restore after the markdown/acronym
passes) that assumed those private-use codepoints never occur in real
input. That assumption doesn’t hold: Nerd Font and Powerline glyphs live
in exactly that codepoint range, and this daemon’s primary input is
terminal selections, so users routinely paste text containing them.
Segmentation makes no assumption about which codepoints appear anywhere
in the input — URL text and non-URL text are simply never in the same
string at the same time while the URL-unsafe transforms run.
The two remaining passes — the control-character strip and whitespace collapse — run once, globally, on the concatenated result, and that is safe:
- The control-character strip is unconditional and must stay that way:
it is the only thing standing between an embedded NUL and a downstream
FFI
CString::newcall, and a test pins that guarantee. Running it globally cannot corrupt a URL span because it only ever removes characters, and a legitimate URL cannot contain a control character in the first place — there is nothing there to protect. - Whitespace collapse is safe to run globally because the
URLregex’s exclusion set already excludes whitespace from a URL match, so a URL span can never contain, start with, or end with whitespace. Collapsing whitespace runs elsewhere in the string can therefore never reach into a URL span or merge two URL spans together.
Two of the transforms that do run per-segment inside clean_non_url
are anchor-based, and an anchor evaluated on an isolated segment does not
necessarily correspond to a real boundary in the original, unsegmented
input. Both are handled the same way: clean_non_url is given the real
character that precedes (and, for ACRONYM, follows) the segment in the
original input, and a sentinel is temporarily glued onto the segment edge
so the anchor sees what it would have seen unsegmented, then stripped
back off before the result is used.
LIST_OR_HEADINGis anchored on^(line start, via(?m)). Handing it an isolated segment is wrong: position 0 of a segment that begins right after a URL is not a line start in the original text, but^would match there anyway (position 0 of any string it is handed is a line-start match, per(?m)semantics), misreading ordinary punctuation that follows a URL (-,#,1.) as a bullet/heading/list marker. Position 0 of a segment is a genuine line start only whenprev— the character immediately preceding the segment in the original input — is absent (segment starts at input position 0) or is a newline. Whenprevis anything else, a sentinel character is prepended beforeLIST_OR_HEADINGruns, then stripped back off;(?m)^still correctly matches after any newline inside the segment, since the sentinel only occupies the position before the segment, not any position within it. The sentinel used is'\u{1}'(SOH, a C0 control character): it is not\n, so it never itself becomes a line start for^to match after; it is not whitespace, soLIST_OR_HEADING’s\s*cannot absorb it and then continue matching into the segment’s real leading whitespace; and it is none of#,-,*,+, or a digit, so it can never itself begin a marker match. No other transform runs while it is present — it is pushed and popped within a single tightly-scoped step — so there is no window for it to be matched or mangled byEMPHASIS,HYPHEN_BREAK, or anything else. It also cannot leak into output even if some future bug skipped the strip-back-off step: the global, unconditional control-character filter at the end ofcleanremoves every control character except\n/\t, and SOH is neither. A test (bullet_immediately_followed_by_url_is_still_stripped) confirms a marker is still stripped when a URL immediately follows it, and another (marker_after_interior_newline_is_still_stripped) confirms the sentinel does not suppress a legitimate match after a newline inside the segment.ACRONYMis anchored on\b(word boundary) at both ends. Evaluated on an isolated segment,\bat position 0 or at the end of the string is computed against “nothing” on the outside — even when the original input actually had an alphanumeric character right there (typically the edge of an adjacent URL), which would have suppressed the boundary. To reproduce full-string semantics,clean_non_urlis given the real character that precedes and follows the segment in the original input; if that neighbor is alphanumeric, a one-character lowercase-letter sentinel is temporarily glued onto that side of the segment beforeACRONYMruns (lowercase so it can never itself match[A-Z]{3,}), reproducing the same “word character on the other side”\bwould have seen, and is stripped back off afterward.
LIST_OR_HEADING and HYPHEN_BREAK both run inside clean_non_url, and
their relative order is load-bearing in the other direction: hyphenation
rejoin must run first. A hyphen-wrapped word can wrap onto a line that
is itself a marker line ("machine-\n- learning"), and HYPHEN_BREAK
needs to see the marker’s leading - still in place to know there is a
non-word character between the two word halves and decline to touch
them; if the marker were stripped first, HYPHEN_BREAK would see
"machine-\nlearning" and rejoin it, silently fusing what was — genuinely
ambiguously, see the test below — either a wrapped hyphenated word or a
new list item.