|
718 | 718 | c.font = monospaceFont; |
719 | 719 | const spaceWidth = c.measureText(' ').width; |
720 | 720 | const spacesPerTab = 2; |
721 | | - const lines = text.split(/\r\n|\r|\n/g); |
| 721 | + const parts = text.split(/(\r\n|\r|\n)/g); |
722 | 722 | const unicodeWidthCache = new Map(); |
| 723 | + const lines = []; |
723 | 724 | let longestLineInColumns = 0; |
| 725 | + let lineStartOffset = 0; |
| 726 | + |
| 727 | + for (let part = 0; part < parts.length; part++) { |
| 728 | + let raw = parts[part]; |
| 729 | + if (part & 1) { |
| 730 | + // Accumulate the length of the newline (CRLF uses two code units) |
| 731 | + lineStartOffset += raw.length; |
| 732 | + continue; |
| 733 | + } |
724 | 734 |
|
725 | | - for (let line = 0; line < lines.length; line++) { |
726 | | - let raw = lines[line]; |
727 | 735 | let runs = []; |
728 | 736 | let i = 0; |
729 | 737 | let n = raw.length + 1; // Add 1 for the extra character at the end |
|
841 | 849 | startIndex, endIndex: i, |
842 | 850 | startColumn, endColumn: column, |
843 | 851 | isSingleChunk, |
844 | | - text: |
845 | | - !whitespace ? raw.slice(startIndex, i) : |
846 | | - whitespace === 0x20 /* space */ ? '·'.repeat(i - startIndex) : |
847 | | - whitespace === 0x0A /* newline */ ? line + 1 === lines.length ? '∅' : '↵' : |
848 | | - '→' /* tab */, |
| 852 | + text: lineStartOffset, // The string for this run will be lazily-generated using this offset |
849 | 853 | }); |
850 | 854 | } |
851 | 855 |
|
852 | | - lines[line] = { raw, runs, endIndex: i, endColumn: column }; |
| 856 | + lines.push({ raw, runs, endIndex: i, endColumn: column }); |
853 | 857 | longestLineInColumns = Math.max(longestLineInColumns, column); |
| 858 | + lineStartOffset += raw.length; |
854 | 859 | } |
855 | 860 |
|
856 | 861 | return { lines, longestLineInColumns }; |
|
1275 | 1280 |
|
1276 | 1281 | // Draw the runs |
1277 | 1282 | let currentColumn = firstColumn; |
1278 | | - for (let run = firstRun; run <= lastRun; run++) { |
1279 | | - let { whitespace, text, startColumn, endColumn, isSingleChunk } = runs[run]; |
| 1283 | + for (let i = firstRun; i <= lastRun; i++) { |
| 1284 | + const run = runs[i]; |
| 1285 | + let { whitespace, text: runText, startColumn, endColumn } = run; |
| 1286 | + |
| 1287 | + // Lazily-generate text for runs to improve performance. When |
| 1288 | + // this happens, the run text is the code unit offset of the |
| 1289 | + // start of the line containing this run. |
| 1290 | + if (typeof runText === 'number') { |
| 1291 | + runText = run.text = |
| 1292 | + !whitespace ? text.slice(runText + run.startIndex, runText + run.endIndex) : |
| 1293 | + whitespace === 0x20 /* space */ ? '·'.repeat(run.endIndex - run.startIndex) : |
| 1294 | + whitespace === 0x0A /* newline */ ? runText + run.startIndex === text.length ? '∅' : '↵' : |
| 1295 | + '→' /* tab */; |
| 1296 | + } |
1280 | 1297 |
|
1281 | 1298 | // Limit the run to the visible columns (but only for ASCII runs) |
1282 | | - if (!isSingleChunk) { |
| 1299 | + if (!run.isSingleChunk) { |
1283 | 1300 | if (startColumn < currentColumn) { |
1284 | | - text = text.slice(currentColumn - startColumn); |
| 1301 | + runText = runText.slice(currentColumn - startColumn); |
1285 | 1302 | startColumn = currentColumn; |
1286 | 1303 | } |
1287 | 1304 | if (endColumn > lastColumn) { |
1288 | | - text = text.slice(0, lastColumn - startColumn); |
| 1305 | + runText = runText.slice(0, lastColumn - startColumn); |
1289 | 1306 | endColumn = lastColumn; |
1290 | 1307 | } |
1291 | 1308 | } |
1292 | 1309 |
|
1293 | 1310 | // Draw whitespace in a separate batch |
1294 | | - (whitespace ? whitespaceBatch : textBatch).push(text, dx + startColumn * columnWidth, dy); |
| 1311 | + (whitespace ? whitespaceBatch : textBatch).push(runText, dx + startColumn * columnWidth, dy); |
1295 | 1312 | currentColumn = endColumn; |
1296 | 1313 | } |
1297 | 1314 | } |
|
0 commit comments