+ {lines.map((tokens, ln) => {
+ if (isHidden(ln)) return null;
+ const end = startToEnd.get(ln);
+ const folded = end != null && collapsed.has(ln);
+ let pos = 0;
+ const lineMatches = matches.map((m, idx) => ({ ...m, idx })).filter((m) => m.line === ln);
+ const content: React.ReactNode[] = [];
+ tokens.forEach((tok, ti) => {
+ const textSeg = tok.text;
+ const tokenStart = pos;
+ const tokenEnd = pos + textSeg.length;
+
+ if (!query || lineMatches.length === 0) {
+ const cls = tok.type === "plain" ? undefined : `tok-${tok.type}`;
+ content.push(
+
+ {textSeg}
+ ,
+ );
+ pos = tokenEnd;
+ return;
+ }
+
+ // Collect matches that intersect this token
+ const matchesInToken = lineMatches
+ .filter((m) => m.start < tokenEnd && m.end > tokenStart)
+ .sort((a, b) => a.start - b.start);
+
+ if (matchesInToken.length === 0) {
+ const cls = tok.type === "plain" ? undefined : `tok-${tok.type}`;
+ content.push(
+
+ {textSeg}
+ ,
+ );
+ pos = tokenEnd;
+ return;
+ }
+
+ let cursor = 0;
+ const tokenCls = tok.type === "plain" ? "" : `tok-${tok.type}`;
+
+ matchesInToken.forEach((m, mi) => {
+ const localStart = Math.max(0, m.start - tokenStart);
+ const localEnd = Math.min(textSeg.length, m.end - tokenStart);
+
+ // before match
+ if (localStart > cursor) {
+ const beforeText = textSeg.slice(cursor, localStart);
+ const cls = tokenCls || undefined;
+ content.push(
+
+ {beforeText}
+ ,
+ );
+ }
+ // matched piece
+ const hitText = textSeg.slice(localStart, localEnd);
+ const hitCls =
+ ["search-hit", m.idx === active ? "search-hit-active" : "", tokenCls].filter(Boolean).join(" ") ||
+ undefined;
+ content.push(
+
+ {hitText}
+ ,
+ );
+ cursor = localEnd;
+ });
+
+ // tail after last match
+ if (cursor < textSeg.length) {
+ const tailText = textSeg.slice(cursor);
+ const cls = tokenCls || undefined;
+ content.push(
+
+ {tailText}
+ ,
+ );
+ }
+
+ pos = tokenEnd;
+ });
+ return (
+
+
+ {end != null ? (
+
+ ) : (
+
+ )}
+ {ln + 1}
+
+
+ {content}
+ {folded && {"{...}"}}
+
+
+ );
+ })}
+