|
600 | 600 | let raw = lines[line]; |
601 | 601 | let runs = []; |
602 | 602 | let i = 0; |
603 | | - let n = raw.length; |
| 603 | + let n = raw.length + 1; // Add 1 for the extra character at the end |
604 | 604 | let column = 0; |
605 | 605 |
|
606 | 606 | while (i < n) { |
|
624 | 624 | break; |
625 | 625 | } |
626 | 626 |
|
| 627 | + // Draw each newline into its own run |
| 628 | + if (c1 !== c1 /* end of line */) { |
| 629 | + if (i > startIndex) break; |
| 630 | + isSingleChunk = true; |
| 631 | + column++; |
| 632 | + i++; |
| 633 | + whitespace = 0x0A /* newline */; |
| 634 | + break; |
| 635 | + } |
| 636 | + |
627 | 637 | // Draw each non-ASCII character into its own run (e.g. emoji) |
628 | 638 | if (c1 < 0x20 || c1 > 0x7E) { |
629 | 639 | if (i > startIndex) break; |
|
701 | 711 | } |
702 | 712 |
|
703 | 713 | runs.push({ |
704 | | - isWhitespace: !!whitespace, |
| 714 | + whitespace, |
705 | 715 | startIndex, endIndex: i, |
706 | 716 | startColumn, endColumn: column, |
707 | 717 | isSingleChunk, |
708 | 718 | text: |
709 | 719 | !whitespace ? raw.slice(startIndex, i) : |
710 | 720 | whitespace === 0x20 /* space */ ? '·'.repeat(i - startIndex) : |
711 | | - '→' /* tab */, |
| 721 | + whitespace === 0x0A /* newline */ ? line + 1 === lines.length ? '∅' : '↵' : |
| 722 | + '→' /* tab */, |
712 | 723 | }); |
713 | 724 | } |
714 | 725 |
|
|
789 | 800 | let runCount = runs.length; |
790 | 801 | let endOfLineIndex = 0; |
791 | 802 | let endOfLineColumn = 0; |
| 803 | + let beforeNewlineIndex = 0; |
| 804 | + let hasTrailingNewline = false; |
792 | 805 |
|
793 | 806 | if (runCount > 0) { |
794 | | - endOfLineIndex = runs[runCount - 1].endIndex; |
795 | | - endOfLineColumn = runs[runCount - 1].endColumn; |
| 807 | + let lastRun = runs[runCount - 1]; |
| 808 | + endOfLineIndex = lastRun.endIndex; |
| 809 | + endOfLineColumn = lastRun.endColumn; |
| 810 | + beforeNewlineIndex = lastRun.startIndex; |
| 811 | + hasTrailingNewline = lastRun.whitespace === 0x0A /* newline */; |
796 | 812 |
|
797 | 813 | // Binary search to find the first run |
798 | 814 | firstRun = 0; |
|
889 | 905 | function rangeOfMapping(map) { |
890 | 906 | if (mappings[map + mappingsOffset] !== row) return null; |
891 | 907 | let startIndex = mappings[map + mappingsOffset + 1]; |
892 | | - let endIndex = startIndex > endOfLineIndex ? startIndex : endOfLineIndex; |
| 908 | + let endIndex = |
| 909 | + startIndex > endOfLineIndex ? startIndex : |
| 910 | + hasTrailingNewline && startIndex < beforeNewlineIndex ? beforeNewlineIndex : |
| 911 | + endOfLineIndex; |
893 | 912 | let isLastMappingInLine = false; |
894 | 913 |
|
895 | 914 | // Ignore subsequent duplicate mappings |
|
1130 | 1149 | // Draw the runs |
1131 | 1150 | let currentColumn = firstColumn; |
1132 | 1151 | for (let run = firstRun; run <= lastRun; run++) { |
1133 | | - let { isWhitespace, text, startColumn, endColumn, isSingleChunk } = runs[run]; |
| 1152 | + let { whitespace, text, startColumn, endColumn, isSingleChunk } = runs[run]; |
1134 | 1153 |
|
1135 | 1154 | // Limit the run to the visible columns (but only for ASCII runs) |
1136 | 1155 | if (!isSingleChunk) { |
|
1145 | 1164 | } |
1146 | 1165 |
|
1147 | 1166 | // Draw whitespace in a separate batch |
1148 | | - (isWhitespace ? whitespaceBatch : textBatch).push(text, dx + startColumn * columnWidth, dy); |
| 1167 | + (whitespace ? whitespaceBatch : textBatch).push(text, dx + startColumn * columnWidth, dy); |
1149 | 1168 | currentColumn = endColumn; |
1150 | 1169 | } |
1151 | 1170 | } |
|
0 commit comments