* Build a mapping from visual lines to logical positions. * Returns an array where each element represents a visual line with: * - logicalLine: index into this.state.lines * - startCol: starting column in the logical line * - length: length of this visual line segment
(width: number)
| 1796 | * - length: length of this visual line segment |
| 1797 | */ |
| 1798 | private buildVisualLineMap(width: number): Array<{ logicalLine: number; startCol: number; length: number }> { |
| 1799 | const visualLines: Array<{ logicalLine: number; startCol: number; length: number }> = []; |
| 1800 | |
| 1801 | for (let i = 0; i < this.state.lines.length; i++) { |
| 1802 | const line = this.state.lines[i] || ""; |
| 1803 | const lineVisWidth = visibleWidth(line); |
| 1804 | if (line.length === 0) { |
| 1805 | // Empty line still takes one visual line |
| 1806 | visualLines.push({ logicalLine: i, startCol: 0, length: 0 }); |
| 1807 | } else if (lineVisWidth <= width) { |
| 1808 | visualLines.push({ logicalLine: i, startCol: 0, length: line.length }); |
| 1809 | } else { |
| 1810 | // Line needs wrapping - use word-aware wrapping |
| 1811 | const chunks = wordWrapLine(line, width, [...this.segment(line, "grapheme")]); |
| 1812 | for (const chunk of chunks) { |
| 1813 | visualLines.push({ |
| 1814 | logicalLine: i, |
| 1815 | startCol: chunk.startIndex, |
| 1816 | length: chunk.endIndex - chunk.startIndex, |
| 1817 | }); |
| 1818 | } |
| 1819 | } |
| 1820 | } |
| 1821 | |
| 1822 | return visualLines; |
| 1823 | } |
| 1824 | |
| 1825 | /** |
| 1826 | * Find the visual line index that contains the given logical position. |
no test coverage detected