| 661 | } |
| 662 | |
| 663 | function wrapText(h: Highlight, width: number, theme: Theme): void { |
| 664 | const newLines: Block[][] = [] |
| 665 | for (const line of h.lines) { |
| 666 | const queue: Block[] = line.slice() |
| 667 | let cur: Block[] = [] |
| 668 | let curW = 0 |
| 669 | while (queue.length > 0) { |
| 670 | const [style, text] = queue.shift()! |
| 671 | const tw = stringWidth(text) |
| 672 | if (curW + tw <= width) { |
| 673 | cur.push([style, text]) |
| 674 | curW += tw |
| 675 | } else { |
| 676 | const remaining = width - curW |
| 677 | let bytePos = 0 |
| 678 | let accW = 0 |
| 679 | // iterate by codepoint |
| 680 | for (const ch of text) { |
| 681 | const cw = charWidth(ch) |
| 682 | if (accW + cw > remaining) break |
| 683 | accW += cw |
| 684 | bytePos += ch.length |
| 685 | } |
| 686 | if (bytePos === 0) { |
| 687 | if (curW === 0) { |
| 688 | // Fresh line and first char still doesn't fit — force one codepoint |
| 689 | // to guarantee forward progress (overflows, but prevents infinite loop) |
| 690 | const firstCp = text.codePointAt(0)! |
| 691 | bytePos = firstCp > 0xffff ? 2 : 1 |
| 692 | } else { |
| 693 | // Line has content and next char doesn't fit — finish this line, |
| 694 | // re-queue the whole block for a fresh line |
| 695 | newLines.push(cur) |
| 696 | queue.unshift([style, text]) |
| 697 | cur = [] |
| 698 | curW = 0 |
| 699 | continue |
| 700 | } |
| 701 | } |
| 702 | cur.push([style, text.slice(0, bytePos)]) |
| 703 | newLines.push(cur) |
| 704 | queue.unshift([style, text.slice(bytePos)]) |
| 705 | cur = [] |
| 706 | curW = 0 |
| 707 | } |
| 708 | } |
| 709 | newLines.push(cur) |
| 710 | } |
| 711 | h.lines = newLines |
| 712 | |
| 713 | // Pad changed lines so background extends to edge |
| 714 | if (h.marker && h.marker !== ' ') { |
| 715 | const bg = lineBackground(h.marker, theme) |
| 716 | const padStyle: Style = { foreground: theme.foreground, background: bg } |
| 717 | for (const line of h.lines) { |
| 718 | const curW = line.reduce((s, [, t]) => s + stringWidth(t), 0) |
| 719 | if (curW < width) { |
| 720 | line.push([padStyle, ' '.repeat(width - curW)]) |