Path: blob/main/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts
5240 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { CharCode } from '../../../../base/common/charCode.js';7import * as strings from '../../../../base/common/strings.js';8import { assertSnapshot } from '../../../../base/test/common/snapshot.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';10import { OffsetRange } from '../../../common/core/ranges/offsetRange.js';11import { MetadataConsts } from '../../../common/encodedTokenAttributes.js';12import { IViewLineTokens } from '../../../common/tokens/lineTokens.js';13import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';14import { CharacterMapping, DomPosition, IRenderLineInputOptions, RenderLineInput, RenderLineOutput2, renderViewLine2 as renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';15import { InlineDecorationType } from '../../../common/viewModel/inlineDecorations.js';16import { TestLineToken, TestLineTokens } from '../core/testLineToken.js';1718const HTML_EXTENSION = { extension: 'html' };1920function createViewLineTokens(viewLineTokens: TestLineToken[]): IViewLineTokens {21return new TestLineTokens(viewLineTokens);22}2324function createPart(endIndex: number, foreground: number): TestLineToken {25return new TestLineToken(endIndex, (26foreground << MetadataConsts.FOREGROUND_OFFSET27) >>> 0);28}2930function inflateRenderLineOutput(renderLineOutput: RenderLineOutput2) {31// remove encompassing <span> to simplify test writing.32let html = renderLineOutput.html;33if (html.startsWith('<span>')) {34html = html.replace(/^<span>/, '');35}36html = html.replace(/<\/span>$/, '');37const spans: string[] = [];38let lastIndex = 0;39do {40const newIndex = html.indexOf('<span', lastIndex + 1);41if (newIndex === -1) {42break;43}44spans.push(html.substring(lastIndex, newIndex));45lastIndex = newIndex;46} while (true);47spans.push(html.substring(lastIndex));4849return {50html: spans,51mapping: renderLineOutput.characterMapping.inflate(),52};53}5455type IRelaxedRenderLineInputOptions = Partial<IRenderLineInputOptions>;5657const defaultRenderLineInputOptions: IRenderLineInputOptions = {58useMonospaceOptimizations: false,59canUseHalfwidthRightwardsArrow: true,60lineContent: '',61continuesWithWrappedLine: false,62isBasicASCII: true,63containsRTL: false,64fauxIndentLength: 0,65lineTokens: createViewLineTokens([]),66lineDecorations: [],67tabSize: 4,68startVisibleColumn: 0,69spaceWidth: 10,70middotWidth: 10,71wsmiddotWidth: 10,72stopRenderingLineAfter: -1,73renderWhitespace: 'none',74renderControlCharacters: false,75fontLigatures: false,76selectionsOnLine: null,77textDirection: null,78verticalScrollbarSize: 14,79renderNewLineWhenEmpty: false80};8182function createRenderLineInputOptions(opts: IRelaxedRenderLineInputOptions): IRenderLineInputOptions {83return {84...defaultRenderLineInputOptions,85...opts86};87}8889function createRenderLineInput(opts: IRelaxedRenderLineInputOptions): RenderLineInput {90const options = createRenderLineInputOptions(opts);91return new RenderLineInput(92options.useMonospaceOptimizations,93options.canUseHalfwidthRightwardsArrow,94options.lineContent,95options.continuesWithWrappedLine,96options.isBasicASCII,97options.containsRTL,98options.fauxIndentLength,99options.lineTokens,100options.lineDecorations,101options.tabSize,102options.startVisibleColumn,103options.spaceWidth,104options.middotWidth,105options.wsmiddotWidth,106options.stopRenderingLineAfter,107options.renderWhitespace,108options.renderControlCharacters,109options.fontLigatures,110options.selectionsOnLine,111options.textDirection,112options.verticalScrollbarSize,113options.renderNewLineWhenEmpty114);115}116117suite('renderViewLine', () => {118119ensureNoDisposablesAreLeakedInTestSuite();120121function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void {122const _actual = renderViewLine(createRenderLineInput({123lineContent,124isBasicASCII: strings.isBasicASCII(lineContent),125lineTokens: createViewLineTokens([new TestLineToken(lineContent.length, 0)]),126tabSize,127spaceWidth: 0,128middotWidth: 0,129wsmiddotWidth: 0130}));131132assert.strictEqual(_actual.html, '<span><span class="mtk0">' + expected + '</span></span>');133const info = expectedCharOffsetInPart.map<CharacterMappingInfo>((absoluteOffset) => [absoluteOffset, [0, absoluteOffset]]);134assertCharacterMapping3(_actual.characterMapping, info);135}136137test('replaces spaces', () => {138assertCharacterReplacement(' ', 4, '\u00a0', [0, 1]);139assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [0, 1, 2]);140assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [0, 1, 2, 3, 4]);141});142143test('escapes HTML markup', () => {144assertCharacterReplacement('a<b', 4, 'a<b', [0, 1, 2, 3]);145assertCharacterReplacement('a>b', 4, 'a>b', [0, 1, 2, 3]);146assertCharacterReplacement('a&b', 4, 'a&b', [0, 1, 2, 3]);147});148149test('replaces some bad characters', () => {150assertCharacterReplacement('a\0b', 4, 'a�b', [0, 1, 2, 3]);151assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]);152assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]);153});154155test('handles tabs', () => {156assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [0, 4]);157assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [0, 1, 4]);158assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [0, 1, 2, 4]);159assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [0, 1, 2, 3, 4]);160assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [0, 1, 2, 3, 4, 8]);161});162163function assertParts(lineContent: string, tabSize: number, parts: TestLineToken[], expected: string, info: CharacterMappingInfo[]): void {164const _actual = renderViewLine(createRenderLineInput({165lineContent,166lineTokens: createViewLineTokens(parts),167tabSize,168spaceWidth: 0,169middotWidth: 0,170wsmiddotWidth: 0171}));172173assert.strictEqual(_actual.html, '<span>' + expected + '</span>');174assertCharacterMapping3(_actual.characterMapping, info);175}176177test('empty line', () => {178assertParts('', 4, [], '<span></span>', []);179});180181test('uses part type', () => {182assertParts('x', 4, [createPart(1, 10)], '<span class="mtk10">x</span>', [[0, [0, 0]], [1, [0, 1]]]);183assertParts('x', 4, [createPart(1, 20)], '<span class="mtk20">x</span>', [[0, [0, 0]], [1, [0, 1]]]);184assertParts('x', 4, [createPart(1, 30)], '<span class="mtk30">x</span>', [[0, [0, 0]], [1, [0, 1]]]);185});186187test('two parts', () => {188assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], '<span class="mtk1">x</span><span class="mtk2">y</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]]]);189assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], '<span class="mtk1">x</span><span class="mtk2">yz</span>', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]], [3, [1, 2]]]);190assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], '<span class="mtk1">xy</span><span class="mtk2">z</span>', [[0, [0, 0]], [1, [0, 1]], [2, [1, 0]], [3, [1, 1]]]);191});192193// overflow194test('overflow', async () => {195const _actual = renderViewLine(createRenderLineInput({196lineContent: 'Hello world!',197lineTokens: createViewLineTokens([198createPart(1, 0),199createPart(2, 1),200createPart(3, 2),201createPart(4, 3),202createPart(5, 4),203createPart(6, 5),204createPart(7, 6),205createPart(8, 7),206createPart(9, 8),207createPart(10, 9),208createPart(11, 10),209createPart(12, 11),210]),211stopRenderingLineAfter: 6,212renderWhitespace: 'boundary'213}));214215const inflated = inflateRenderLineOutput(_actual);216await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);217await assertSnapshot(inflated.mapping);218});219220// typical line221test('typical', async () => {222const lineContent = '\t export class Game { // http://test.com ';223const lineTokens = createViewLineTokens([224createPart(5, 1),225createPart(11, 2),226createPart(12, 3),227createPart(17, 4),228createPart(18, 5),229createPart(22, 6),230createPart(23, 7),231createPart(24, 8),232createPart(25, 9),233createPart(28, 10),234createPart(43, 11),235createPart(48, 12),236]);237const _actual = renderViewLine(createRenderLineInput({238lineContent,239lineTokens,240renderWhitespace: 'boundary'241}));242243const inflated = inflateRenderLineOutput(_actual);244await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);245await assertSnapshot(inflated.mapping);246});247248// issue #2255: Weird line rendering part 1249test('issue-2255-1', async () => {250const lineContent = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),';251const lineTokens = createViewLineTokens([252createPart(3, 1), // 3 chars253createPart(15, 2), // 12 chars254createPart(21, 3), // 6 chars255createPart(22, 4), // 1 char256createPart(43, 5), // 21 chars257createPart(45, 6), // 2 chars258createPart(46, 7), // 1 char259createPart(66, 8), // 20 chars260createPart(67, 9), // 1 char261createPart(68, 10), // 2 chars262]);263const _actual = renderViewLine(createRenderLineInput({264lineContent,265lineTokens266}));267268const inflated = inflateRenderLineOutput(_actual);269await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);270await assertSnapshot(inflated.mapping);271});272273// issue #2255: Weird line rendering part 2274test('issue-2255-2', async () => {275const lineContent = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),';276277const lineTokens = createViewLineTokens([278createPart(4, 1), // 4 chars279createPart(16, 2), // 12 chars280createPart(22, 3), // 6 chars281createPart(23, 4), // 1 char282createPart(44, 5), // 21 chars283createPart(46, 6), // 2 chars284createPart(47, 7), // 1 char285createPart(67, 8), // 20 chars286createPart(68, 9), // 1 char287createPart(69, 10), // 2 chars288]);289const _actual = renderViewLine(createRenderLineInput({290lineContent,291lineTokens292}));293294const inflated = inflateRenderLineOutput(_actual);295await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);296await assertSnapshot(inflated.mapping);297});298299// issue #91178: after decoration type shown before cursor300test('issue-91178', async () => {301const lineContent = '//just a comment';302const lineTokens = createViewLineTokens([303createPart(16, 1)304]);305const actual = renderViewLine(createRenderLineInput({306useMonospaceOptimizations: true,307canUseHalfwidthRightwardsArrow: false,308lineContent,309lineTokens,310lineDecorations: [311new LineDecoration(13, 13, 'dec1', InlineDecorationType.After),312new LineDecoration(13, 13, 'dec2', InlineDecorationType.Before),313]314}));315316const inflated = inflateRenderLineOutput(actual);317await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);318await assertSnapshot(inflated.mapping);319});320321// issue microsoft/monaco-editor#280: Improved source code rendering for RTL languages322test('monaco-280', async () => {323const lineContent = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";';324const lineTokens = createViewLineTokens([325createPart(3, 6),326createPart(13, 1),327createPart(66, 20),328createPart(67, 1),329]);330const _actual = renderViewLine(createRenderLineInput({331lineContent,332isBasicASCII: false,333containsRTL: true,334lineTokens335}));336337const inflated = inflateRenderLineOutput(_actual);338await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);339await assertSnapshot(inflated.mapping);340});341342// issue #137036: Issue in RTL languages in recent versions343test('issue-137036', async () => {344const lineContent = '<option value=\"العربية\">العربية</option>';345const lineTokens = createViewLineTokens([346createPart(1, 2),347createPart(7, 3),348createPart(8, 4),349createPart(13, 5),350createPart(14, 4),351createPart(23, 6),352createPart(24, 2),353createPart(31, 4),354createPart(33, 2),355createPart(39, 3),356createPart(40, 2),357]);358const _actual = renderViewLine(createRenderLineInput({359lineContent,360isBasicASCII: false,361containsRTL: true,362lineTokens363}));364365const inflated = inflateRenderLineOutput(_actual);366await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);367await assertSnapshot(inflated.mapping);368});369370// issue #99589: Rendering whitespace influences bidi layout371test('issue-99589', async () => {372const lineContent = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]';373const lineTokens = createViewLineTokens([374createPart(5, 2),375createPart(21, 3),376createPart(22, 2),377createPart(34, 3),378createPart(35, 2),379]);380const _actual = renderViewLine(createRenderLineInput({381useMonospaceOptimizations: true,382lineContent,383isBasicASCII: false,384containsRTL: true,385lineTokens,386renderWhitespace: 'all'387}));388389const inflated = inflateRenderLineOutput(_actual);390await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);391await assertSnapshot(inflated.mapping);392});393394// issue #260239: HTML containing bidirectional text is rendered incorrectly395test('issue-260239', async () => {396// Simulating HTML like: <p class="myclass" title="العربي">نشاط التدويل!</p>397// The line contains both LTR (class="myclass") and RTL (title="العربي") attribute values398const lineContent = '<p class="myclass" title="العربي">نشاط التدويل!</p>';399const lineTokens = createViewLineTokens([400createPart(1, 1), // <401createPart(2, 2), // p402createPart(3, 3), // (space)403createPart(8, 4), // class404createPart(9, 5), // =405createPart(10, 6), // "406createPart(17, 7), // myclass407createPart(18, 6), // "408createPart(19, 3), // (space)409createPart(24, 4), // title410createPart(25, 5), // =411createPart(26, 6), // "412createPart(32, 8), // العربي (RTL text) - 6 Arabic characters from position 26-31413createPart(33, 6), // " - closing quote at position 32414createPart(34, 1), // >415createPart(47, 9), // نشاط التدويل! (RTL text) - 13 characters from position 34-46416createPart(48, 1), // <417createPart(49, 2), // /418createPart(50, 2), // p419createPart(51, 1), // >420]);421const _actual = renderViewLine(new RenderLineInput(422false,423true,424lineContent,425false,426false,427true,4280,429lineTokens,430[],4314,4320,43310,43410,43510,436-1,437'none',438false,439false,440null,441null,44214443));444445const inflated = inflateRenderLineOutput(_actual);446await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);447await assertSnapshot(inflated.mapping);448});449450// issue #274604: Mixed LTR and RTL in a single token451test('issue-274604', async () => {452const lineContent = 'test.com##a:-abp-contains(إ)';453const lineTokens = createViewLineTokens([454createPart(lineContent.length, 1)455]);456const actual = renderViewLine(createRenderLineInput({457lineContent,458isBasicASCII: false,459containsRTL: true,460lineTokens461}));462463const inflated = inflateRenderLineOutput(actual);464await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);465await assertSnapshot(inflated.mapping);466});467468// issue #277693: Mixed LTR and RTL in a single token with template literal469test('issue-277693', async () => {470const lineContent = 'نام کاربر: ${user.firstName}';471const lineTokens = createViewLineTokens([472createPart(9, 1), // نام کاربر (RTL string content)473createPart(11, 1), // : (space)474createPart(13, 2), // ${ (template expression punctuation)475createPart(17, 3), // user (variable)476createPart(18, 4), // . (punctuation)477createPart(27, 3), // firstName (property)478createPart(28, 2), // } (template expression punctuation)479]);480const actual = renderViewLine(createRenderLineInput({481lineContent,482isBasicASCII: false,483containsRTL: true,484lineTokens485}));486487const inflated = inflateRenderLineOutput(actual);488await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);489await assertSnapshot(inflated.mapping);490});491492// issue #6885: Splits large tokens493test('issue-6885', async () => {494// 1 1 1495// 1 2 3 4 5 6 7 8 9 0 1 2496// 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234497const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.';498499function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void {500const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);501const actual = renderViewLine(createRenderLineInput({502lineContent,503lineTokens504}));505assert.strictEqual(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);506}507508// A token with 49 chars509{510assertSplitsTokens(511'49 chars',512_lineText.substr(0, 49),513[514'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0inter</span>',515]516);517}518519// A token with 50 chars520{521assertSplitsTokens(522'50 chars',523_lineText.substr(0, 50),524[525'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',526]527);528}529530// A token with 51 chars531{532assertSplitsTokens(533'51 chars',534_lineText.substr(0, 51),535[536'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',537'<span class="mtk1">s</span>',538]539);540}541542// A token with 99 chars543{544assertSplitsTokens(545'99 chars',546_lineText.substr(0, 99),547[548'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',549'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contain</span>',550]551);552}553554// A token with 100 chars555{556assertSplitsTokens(557'100 chars',558_lineText.substr(0, 100),559[560'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',561'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains</span>',562]563);564}565566// A token with 101 chars567{568assertSplitsTokens(569'101 chars',570_lineText.substr(0, 101),571[572'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0intere</span>',573'<span class="mtk1">sting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains</span>',574'<span class="mtk1">\u00a0</span>',575]576);577}578});579580// issue #21476: Does not split large tokens when ligatures are on581test('issue-21476', async () => {582// 1 1 1583// 1 2 3 4 5 6 7 8 9 0 1 2584// 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234585const _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.';586587function assertSplitsTokens(message: string, lineContent: string, expectedOutput: string[]): void {588const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);589const actual = renderViewLine(createRenderLineInput({590lineContent,591lineTokens,592fontLigatures: true593}));594assert.strictEqual(actual.html, '<span>' + expectedOutput.join('') + '</span>', message);595}596597// A token with 101 chars598{599assertSplitsTokens(600'101 chars',601_lineText.substr(0, 101),602[603'<span class="mtk1">This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0contains\u00a0very\u00a0</span>',604'<span class="mtk1">interesting\u00a0text.\u00a0This\u00a0is\u00a0just\u00a0a\u00a0long\u00a0line\u00a0that\u00a0</span>',605'<span class="mtk1">contains\u00a0</span>',606]607);608}609});610611// issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns612test('issue-20624', async () => {613const lineContent = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷';614const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);615const actual = renderViewLine(createRenderLineInput({616lineContent,617isBasicASCII: false,618lineTokens619}));620621await assertSnapshot(inflateRenderLineOutput(actual).html.join(''), HTML_EXTENSION);622});623624// issue #6885: Does not split large tokens in RTL text625test('issue-6885-rtl', async () => {626const lineContent = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.';627const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);628const actual = renderViewLine(createRenderLineInput({629lineContent,630isBasicASCII: false,631containsRTL: true,632lineTokens633}));634635await assertSnapshot(actual.html, HTML_EXTENSION);636});637638// issue #95685: Uses unicode replacement character for Paragraph Separator639test('issue-95685', async () => {640const lineContent = 'var ftext = [\u2029"Und", "dann", "eines"];';641const lineTokens = createViewLineTokens([createPart(lineContent.length, 1)]);642const actual = renderViewLine(createRenderLineInput({643lineContent,644isBasicASCII: false,645lineTokens646}));647const inflated = inflateRenderLineOutput(actual);648await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);649await assertSnapshot(inflated.mapping);650});651652// issue #19673: Monokai Theme bad-highlighting in line wrap653test('issue-19673', async () => {654const lineContent = ' MongoCallback<string>): void {';655const lineTokens = createViewLineTokens([656createPart(17, 1),657createPart(18, 2),658createPart(24, 3),659createPart(26, 4),660createPart(27, 5),661createPart(28, 6),662createPart(32, 7),663createPart(34, 8),664]);665const _actual = renderViewLine(createRenderLineInput({666useMonospaceOptimizations: true,667lineContent,668fauxIndentLength: 4,669lineTokens670}));671672const inflated = inflateRenderLineOutput(_actual);673await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);674await assertSnapshot(inflated.mapping);675});676});677678type CharacterMappingInfo = [number, [number, number]];679680function assertCharacterMapping3(actual: CharacterMapping, expectedInfo: CharacterMappingInfo[]): void {681for (let i = 0; i < expectedInfo.length; i++) {682const [horizontalOffset, [partIndex, charIndex]] = expectedInfo[i];683684const actualDomPosition = actual.getDomPosition(i + 1);685assert.deepStrictEqual(actualDomPosition, new DomPosition(partIndex, charIndex), `getDomPosition(${i + 1})`);686687let partLength = charIndex + 1;688for (let j = i + 1; j < expectedInfo.length; j++) {689const [, [nextPartIndex, nextCharIndex]] = expectedInfo[j];690if (nextPartIndex === partIndex) {691partLength = nextCharIndex + 1;692} else {693break;694}695}696697const actualColumn = actual.getColumn(new DomPosition(partIndex, charIndex), partLength);698assert.strictEqual(actualColumn, i + 1, `actual.getColumn(${partIndex}, ${charIndex})`);699700const actualHorizontalOffset = actual.getHorizontalOffset(i + 1);701assert.strictEqual(actualHorizontalOffset, horizontalOffset, `actual.getHorizontalOffset(${i + 1})`);702}703704assert.strictEqual(actual.length, expectedInfo.length, `length mismatch`);705}706707suite('renderViewLine2', () => {708709ensureNoDisposablesAreLeakedInTestSuite();710711function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: TestLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'selection' | 'trailing' | 'all', selections: OffsetRange[] | null) {712const actual = renderViewLine(createRenderLineInput({713useMonospaceOptimizations: fontIsMonospace,714lineContent,715fauxIndentLength,716lineTokens: createViewLineTokens(tokens),717renderWhitespace,718selectionsOnLine: selections719}));720return inflateRenderLineOutput(actual);721}722723// issue #18616: Inline decorations ending at the text length are no longer rendered724test('issue-18616', async () => {725const lineContent = 'https://microsoft.com';726const actual = renderViewLine(createRenderLineInput({727lineContent,728lineTokens: createViewLineTokens([createPart(21, 3)]),729lineDecorations: [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)]730}));731732const inflated = inflateRenderLineOutput(actual);733await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);734await assertSnapshot(inflated.mapping);735});736737// issue #19207: Link in Monokai is not rendered correctly738test('issue-19207', async () => {739const lineContent = '\'let url = `http://***/_api/web/lists/GetByTitle(\\\'Teambuildingaanvragen\\\')/items`;\'';740const actual = renderViewLine(createRenderLineInput({741useMonospaceOptimizations: true,742lineContent,743lineTokens: createViewLineTokens([744createPart(49, 6),745createPart(51, 4),746createPart(72, 6),747createPart(74, 4),748createPart(84, 6),749]),750lineDecorations: [751new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular)752]753}));754755const inflated = inflateRenderLineOutput(actual);756await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);757await assertSnapshot(inflated.mapping);758});759760// createLineParts simple761test('simple', async () => {762const actual = testCreateLineParts(763false,764'Hello world!',765[766createPart(12, 1)767],7680,769'none',770null771);772await assertSnapshot(actual.html.join(''), HTML_EXTENSION);773await assertSnapshot(actual.mapping);774});775776// createLineParts simple two tokens777test('two-tokens', async () => {778const actual = testCreateLineParts(779false,780'Hello world!',781[782createPart(6, 1),783createPart(12, 2)784],7850,786'none',787null788);789await assertSnapshot(actual.html.join(''), HTML_EXTENSION);790await assertSnapshot(actual.mapping);791});792793// createLineParts render whitespace - 4 leading spaces794test('ws-4-leading', async () => {795const actual = testCreateLineParts(796false,797' Hello world! ',798[799createPart(4, 1),800createPart(6, 2),801createPart(20, 3)802],8030,804'boundary',805null806);807await assertSnapshot(actual.html.join(''), HTML_EXTENSION);808await assertSnapshot(actual.mapping);809});810811// createLineParts render whitespace - 8 leading spaces812test('ws-8-leading', async () => {813const actual = testCreateLineParts(814false,815' Hello world! ',816[817createPart(8, 1),818createPart(10, 2),819createPart(28, 3)820],8210,822'boundary',823null824);825await assertSnapshot(actual.html.join(''), HTML_EXTENSION);826await assertSnapshot(actual.mapping);827});828829// createLineParts render whitespace - 2 leading tabs830test('ws-2-tabs', async () => {831const actual = testCreateLineParts(832false,833'\t\tHello world!\t',834[835createPart(2, 1),836createPart(4, 2),837createPart(15, 3)838],8390,840'boundary',841null842);843await assertSnapshot(actual.html.join(''), HTML_EXTENSION);844await assertSnapshot(actual.mapping);845});846847// createLineParts render whitespace - mixed leading spaces and tabs848test('ws-mixed', async () => {849const actual = testCreateLineParts(850false,851' \t\t Hello world! \t \t \t ',852[853createPart(6, 1),854createPart(8, 2),855createPart(31, 3)856],8570,858'boundary',859null860);861await assertSnapshot(actual.html.join(''), HTML_EXTENSION);862await assertSnapshot(actual.mapping);863});864865// createLineParts render whitespace skips faux indent866test('ws-faux-indent', async () => {867const actual = testCreateLineParts(868false,869'\t\t Hello world! \t \t \t ',870[871createPart(4, 1),872createPart(6, 2),873createPart(29, 3)874],8752,876'boundary',877null878);879await assertSnapshot(actual.html.join(''), HTML_EXTENSION);880await assertSnapshot(actual.mapping);881});882883// createLineParts does not emit width for monospace fonts884test('ws-monospace', async () => {885const actual = testCreateLineParts(886true,887'\t\t Hello world! \t \t \t ',888[889createPart(4, 1),890createPart(6, 2),891createPart(29, 3)892],8932,894'boundary',895null896);897await assertSnapshot(actual.html.join(''), HTML_EXTENSION);898await assertSnapshot(actual.mapping);899});900901// createLineParts render whitespace in middle but not for one space902test('ws-middle', async () => {903const actual = testCreateLineParts(904false,905'it it it it',906[907createPart(6, 1),908createPart(7, 2),909createPart(13, 3)910],9110,912'boundary',913null914);915await assertSnapshot(actual.html.join(''), HTML_EXTENSION);916await assertSnapshot(actual.mapping);917});918919// createLineParts render whitespace for all in middle920test('ws-all-middle', async () => {921const actual = testCreateLineParts(922false,923' Hello world!\t',924[925createPart(4, 0),926createPart(6, 1),927createPart(14, 2)928],9290,930'all',931null932);933await assertSnapshot(actual.html.join(''), HTML_EXTENSION);934await assertSnapshot(actual.mapping);935});936937// createLineParts render whitespace for selection with no selections938test('ws-sel-none', async () => {939const actual = testCreateLineParts(940false,941' Hello world!\t',942[943createPart(4, 0),944createPart(6, 1),945createPart(14, 2)946],9470,948'selection',949null950);951await assertSnapshot(actual.html.join(''), HTML_EXTENSION);952await assertSnapshot(actual.mapping);953});954955// createLineParts render whitespace for selection with whole line selection956test('ws-sel-whole', async () => {957const actual = testCreateLineParts(958false,959' Hello world!\t',960[961createPart(4, 0),962createPart(6, 1),963createPart(14, 2)964],9650,966'selection',967[new OffsetRange(0, 14)]968);969await assertSnapshot(actual.html.join(''), HTML_EXTENSION);970await assertSnapshot(actual.mapping);971});972973// createLineParts render whitespace for selection with selection spanning part of whitespace974test('ws-sel-partial', async () => {975const actual = testCreateLineParts(976false,977' Hello world!\t',978[979createPart(4, 0),980createPart(6, 1),981createPart(14, 2)982],9830,984'selection',985[new OffsetRange(0, 5)]986);987await assertSnapshot(actual.html.join(''), HTML_EXTENSION);988await assertSnapshot(actual.mapping);989});990991// createLineParts render whitespace for selection with multiple selections992test('ws-sel-multiple', async () => {993const actual = testCreateLineParts(994false,995' Hello world!\t',996[997createPart(4, 0),998createPart(6, 1),999createPart(14, 2)1000],10010,1002'selection',1003[new OffsetRange(0, 5), new OffsetRange(9, 14)]1004);1005await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1006await assertSnapshot(actual.mapping);1007});10081009// createLineParts render whitespace for selection with multiple, initially unsorted selections1010test('ws-sel-unsorted', async () => {1011const actual = testCreateLineParts(1012false,1013' Hello world!\t',1014[1015createPart(4, 0),1016createPart(6, 1),1017createPart(14, 2)1018],10190,1020'selection',1021[new OffsetRange(9, 14), new OffsetRange(0, 5)]1022);1023await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1024await assertSnapshot(actual.mapping);1025});10261027// createLineParts render whitespace for selection with selections next to each other1028test('ws-sel-adjacent', async () => {1029const actual = testCreateLineParts(1030false,1031' * S',1032[1033createPart(4, 0)1034],10350,1036'selection',1037[new OffsetRange(0, 1), new OffsetRange(1, 2), new OffsetRange(2, 3)]1038);1039await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1040await assertSnapshot(actual.mapping);1041});10421043// createLineParts render whitespace for trailing with leading, inner, and without trailing whitespace1044test('ws-trail-no-trail', async () => {1045const actual = testCreateLineParts(1046false,1047' Hello world!',1048[1049createPart(4, 0),1050createPart(6, 1),1051createPart(14, 2)1052],10530,1054'trailing',1055null1056);1057await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1058await assertSnapshot(actual.mapping);1059});10601061// createLineParts render whitespace for trailing with leading, inner, and trailing whitespace1062test('ws-trail-with-trail', async () => {1063const actual = testCreateLineParts(1064false,1065' Hello world! \t',1066[1067createPart(4, 0),1068createPart(6, 1),1069createPart(15, 2)1070],10710,1072'trailing',1073null1074);1075await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1076await assertSnapshot(actual.mapping);1077});10781079// createLineParts render whitespace for trailing with 8 leading and 8 trailing whitespaces1080test('ws-trail-8-8', async () => {1081const actual = testCreateLineParts(1082false,1083' Hello world! ',1084[1085createPart(8, 1),1086createPart(10, 2),1087createPart(28, 3)1088],10890,1090'trailing',1091null1092);1093await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1094await assertSnapshot(actual.mapping);1095});10961097// createLineParts render whitespace for trailing with line containing only whitespaces1098test('ws-trail-only', async () => {1099const actual = testCreateLineParts(1100false,1101' \t ',1102[1103createPart(2, 0),1104createPart(3, 1),1105],11060,1107'trailing',1108null1109);1110await assertSnapshot(actual.html.join(''), HTML_EXTENSION);1111await assertSnapshot(actual.mapping);1112});11131114// createLineParts can handle unsorted inline decorations1115test('unsorted-deco', async () => {1116const actual = renderViewLine(createRenderLineInput({1117lineContent: 'Hello world',1118lineTokens: createViewLineTokens([createPart(11, 0)]),1119lineDecorations: [1120new LineDecoration(5, 7, 'a', InlineDecorationType.Regular),1121new LineDecoration(1, 3, 'b', InlineDecorationType.Regular),1122new LineDecoration(2, 8, 'c', InlineDecorationType.Regular),1123]1124}));11251126// 012345678901127// Hello world1128// ----aa-----1129// bb---------1130// -cccccc----11311132const inflated = inflateRenderLineOutput(actual);1133await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1134await assertSnapshot(inflated.mapping);1135});11361137// issue #11485: Visible whitespace conflicts with before decorator attachment1138test('issue-11485', async () => {11391140const lineContent = '\tbla';11411142const actual = renderViewLine(createRenderLineInput({1143lineContent,1144lineTokens: createViewLineTokens([createPart(4, 3)]),1145lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],1146renderWhitespace: 'all',1147fontLigatures: true1148}));11491150const inflated = inflateRenderLineOutput(actual);1151await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1152await assertSnapshot(inflated.mapping);1153});11541155// issue #32436: Non-monospace font + visible whitespace + After decorator causes line to "jump"1156test('issue-32436', async () => {11571158const lineContent = '\tbla';11591160const actual = renderViewLine(createRenderLineInput({1161lineContent,1162lineTokens: createViewLineTokens([createPart(4, 3)]),1163lineDecorations: [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)],1164renderWhitespace: 'all',1165fontLigatures: true1166}));11671168const inflated = inflateRenderLineOutput(actual);1169await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1170await assertSnapshot(inflated.mapping);1171});11721173// issue #30133: Empty lines don't render inline decorations1174test('issue-30133', async () => {11751176const lineContent = '';11771178const actual = renderViewLine(createRenderLineInput({1179lineContent,1180lineTokens: createViewLineTokens([createPart(0, 3)]),1181lineDecorations: [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],1182renderWhitespace: 'all',1183fontLigatures: true1184}));11851186const inflated = inflateRenderLineOutput(actual);1187await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1188await assertSnapshot(inflated.mapping);1189});11901191// issue #37208: Collapsing bullet point containing emoji in Markdown document results in [??] character1192test('issue-37208', async () => {11931194const actual = renderViewLine(createRenderLineInput({1195useMonospaceOptimizations: true,1196lineContent: ' 1. 🙏',1197isBasicASCII: false,1198lineTokens: createViewLineTokens([createPart(7, 3)]),1199lineDecorations: [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)],1200tabSize: 2,1201stopRenderingLineAfter: 100001202}));12031204const inflated = inflateRenderLineOutput(actual);1205await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1206await assertSnapshot(inflated.mapping);1207});12081209// issue #37401 #40127: Allow both before and after decorations on empty line1210test('issue-37401', async () => {12111212const actual = renderViewLine(createRenderLineInput({1213useMonospaceOptimizations: true,1214lineContent: '',1215lineTokens: createViewLineTokens([createPart(0, 3)]),1216lineDecorations: [1217new LineDecoration(1, 1, 'before', InlineDecorationType.Before),1218new LineDecoration(1, 1, 'after', InlineDecorationType.After),1219],1220tabSize: 2,1221stopRenderingLineAfter: 100001222}));12231224const inflated = inflateRenderLineOutput(actual);1225await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1226await assertSnapshot(inflated.mapping);1227});12281229// issue #118759: enable multiple text editor decorations in empty lines1230test('issue-118759', async () => {12311232const actual = renderViewLine(createRenderLineInput({1233useMonospaceOptimizations: true,1234lineContent: '',1235lineTokens: createViewLineTokens([createPart(0, 3)]),1236lineDecorations: [1237new LineDecoration(1, 1, 'after1', InlineDecorationType.After),1238new LineDecoration(1, 1, 'after2', InlineDecorationType.After),1239new LineDecoration(1, 1, 'before1', InlineDecorationType.Before),1240new LineDecoration(1, 1, 'before2', InlineDecorationType.Before),1241],1242tabSize: 2,1243stopRenderingLineAfter: 100001244}));12451246const inflated = inflateRenderLineOutput(actual);1247await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1248await assertSnapshot(inflated.mapping);1249});12501251// issue #38935: GitLens end-of-line blame no longer rendering1252test('issue-38935', async () => {12531254const actual = renderViewLine(createRenderLineInput({1255useMonospaceOptimizations: true,1256lineContent: '\t}',1257lineTokens: createViewLineTokens([createPart(2, 3)]),1258lineDecorations: [1259new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3', InlineDecorationType.Before),1260new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After),1261],1262stopRenderingLineAfter: 100001263}));12641265const inflated = inflateRenderLineOutput(actual);1266await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1267await assertSnapshot(inflated.mapping);1268});12691270// issue #136622: Inline decorations are not rendering on non-ASCII lines when renderControlCharacters is on1271test('issue-136622', async () => {12721273const actual = renderViewLine(createRenderLineInput({1274useMonospaceOptimizations: true,1275lineContent: 'some text £',1276isBasicASCII: false,1277lineTokens: createViewLineTokens([createPart(11, 3)]),1278lineDecorations: [1279new LineDecoration(5, 5, 'inlineDec1', InlineDecorationType.After),1280new LineDecoration(6, 6, 'inlineDec2', InlineDecorationType.Before),1281],1282stopRenderingLineAfter: 10000,1283renderControlCharacters: true1284}));12851286const inflated = inflateRenderLineOutput(actual);1287await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1288await assertSnapshot(inflated.mapping);1289});12901291// issue #22832: Consider fullwidth characters when rendering tabs1292test('issue-22832-1', async () => {12931294const actual = renderViewLine(createRenderLineInput({1295useMonospaceOptimizations: true,1296lineContent: 'asd = "擦"\t\t#asd',1297isBasicASCII: false,1298lineTokens: createViewLineTokens([createPart(15, 3)]),1299stopRenderingLineAfter: 100001300}));13011302const inflated = inflateRenderLineOutput(actual);1303await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1304await assertSnapshot(inflated.mapping);1305});13061307// issue #22832: Consider fullwidth characters when rendering tabs (render whitespace)1308test('issue-22832-2', async () => {13091310const actual = renderViewLine(createRenderLineInput({1311useMonospaceOptimizations: true,1312lineContent: 'asd = "擦"\t\t#asd',1313isBasicASCII: false,1314lineTokens: createViewLineTokens([createPart(15, 3)]),1315stopRenderingLineAfter: 10000,1316renderWhitespace: 'all'1317}));13181319const inflated = inflateRenderLineOutput(actual);1320await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1321await assertSnapshot(inflated.mapping);1322});13231324// issue #22352: COMBINING ACUTE ACCENT (U+0301)1325test('issue-22352-1', async () => {13261327const actual = renderViewLine(createRenderLineInput({1328useMonospaceOptimizations: true,1329lineContent: '12345689012345678901234568901234567890123456890abába',1330isBasicASCII: false,1331lineTokens: createViewLineTokens([createPart(53, 3)]),1332stopRenderingLineAfter: 100001333}));13341335const inflated = inflateRenderLineOutput(actual);1336await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1337await assertSnapshot(inflated.mapping);1338});13391340// issue #22352: Partially Broken Complex Script Rendering of Tamil1341test('issue-22352-2', async () => {13421343const actual = renderViewLine(createRenderLineInput({1344useMonospaceOptimizations: true,1345lineContent: ' JoyShareல் பின்தொடர்ந்து, விடீயோ, ஜோக்குகள், அனிமேசன், நகைச்சுவை படங்கள் மற்றும் செய்திகளை பெறுவீர்',1346isBasicASCII: false,1347lineTokens: createViewLineTokens([createPart(100, 3)]),1348stopRenderingLineAfter: 100001349}));13501351const inflated = inflateRenderLineOutput(actual);1352await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1353await assertSnapshot(inflated.mapping);1354});13551356// issue #42700: Hindi characters are not being rendered properly1357test('issue-42700', async () => {13581359const actual = renderViewLine(createRenderLineInput({1360useMonospaceOptimizations: true,1361lineContent: ' वो ऐसा क्या है जो हमारे अंदर भी है और बाहर भी है। जिसकी वजह से हम सब हैं। जिसने इस सृष्टि की रचना की है।',1362isBasicASCII: false,1363lineTokens: createViewLineTokens([createPart(105, 3)]),1364stopRenderingLineAfter: 100001365}));13661367const inflated = inflateRenderLineOutput(actual);1368await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1369await assertSnapshot(inflated.mapping);1370});13711372// issue #38123: editor.renderWhitespace: "boundary" renders whitespace at line wrap point when line is wrapped1373test('issue-38123', async () => {1374const actual = renderViewLine(createRenderLineInput({1375useMonospaceOptimizations: true,1376lineContent: 'This is a long line which never uses more than two spaces. ',1377continuesWithWrappedLine: true,1378lineTokens: createViewLineTokens([createPart(59, 3)]),1379stopRenderingLineAfter: 10000,1380renderWhitespace: 'boundary'1381}));13821383const inflated = inflateRenderLineOutput(actual);1384await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1385await assertSnapshot(inflated.mapping);1386});13871388// issue #33525: Long line with ligatures takes a long time to paint decorations1389test('issue-33525-1', async () => {1390const actual = renderViewLine(createRenderLineInput({1391canUseHalfwidthRightwardsArrow: false,1392lineContent: 'append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to append data to',1393lineTokens: createViewLineTokens([createPart(194, 3)]),1394stopRenderingLineAfter: 10000,1395fontLigatures: true1396}));13971398const inflated = inflateRenderLineOutput(actual);1399await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1400await assertSnapshot(inflated.mapping);1401});14021403// issue #33525: Long line with ligatures takes a long time to paint decorations - not possible1404test('issue-33525-2', async () => {1405const actual = renderViewLine(createRenderLineInput({1406canUseHalfwidthRightwardsArrow: false,1407lineContent: 'appenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatatoappenddatato',1408lineTokens: createViewLineTokens([createPart(194, 3)]),1409stopRenderingLineAfter: 10000,1410fontLigatures: true1411}));14121413const inflated = inflateRenderLineOutput(actual);1414await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1415await assertSnapshot(inflated.mapping);1416});14171418// issue #91936: Semantic token color highlighting fails on line with selected text1419test('issue-91936', async () => {1420const actual = renderViewLine(createRenderLineInput({1421lineContent: ' else if ($s = 08) then \'\\b\'',1422lineTokens: createViewLineTokens([1423createPart(20, 1),1424createPart(24, 15),1425createPart(25, 1),1426createPart(27, 15),1427createPart(28, 1),1428createPart(29, 1),1429createPart(29, 1),1430createPart(31, 16),1431createPart(32, 1),1432createPart(33, 1),1433createPart(34, 1),1434createPart(36, 6),1435createPart(36, 1),1436createPart(37, 1),1437createPart(38, 1),1438createPart(42, 15),1439createPart(43, 1),1440createPart(47, 11)1441]),1442stopRenderingLineAfter: 10000,1443renderWhitespace: 'selection',1444selectionsOnLine: [new OffsetRange(0, 47)],1445middotWidth: 11,1446wsmiddotWidth: 111447}));14481449const inflated = inflateRenderLineOutput(actual);1450await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1451await assertSnapshot(inflated.mapping);1452});14531454// issue #119416: Delete Control Character (U+007F / ) displayed as space1455test('issue-119416', async () => {1456const actual = renderViewLine(createRenderLineInput({1457canUseHalfwidthRightwardsArrow: false,1458lineContent: '[' + String.fromCharCode(127) + '] [' + String.fromCharCode(0) + ']',1459lineTokens: createViewLineTokens([createPart(7, 3)]),1460stopRenderingLineAfter: 10000,1461renderControlCharacters: true,1462fontLigatures: true1463}));14641465const inflated = inflateRenderLineOutput(actual);1466await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1467await assertSnapshot(inflated.mapping);1468});14691470// issue #116939: Important control characters aren't rendered1471test('issue-116939', async () => {1472const actual = renderViewLine(createRenderLineInput({1473canUseHalfwidthRightwardsArrow: false,1474lineContent: `transferBalance(5678,${String.fromCharCode(0x202E)}6776,4321${String.fromCharCode(0x202C)},"USD");`,1475isBasicASCII: false,1476lineTokens: createViewLineTokens([createPart(42, 3)]),1477stopRenderingLineAfter: 10000,1478renderControlCharacters: true1479}));14801481const inflated = inflateRenderLineOutput(actual);1482await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1483await assertSnapshot(inflated.mapping);1484});14851486// issue #124038: Multiple end-of-line text decorations get merged1487test('issue-124038', async () => {1488const actual = renderViewLine(createRenderLineInput({1489useMonospaceOptimizations: true,1490canUseHalfwidthRightwardsArrow: false,1491lineContent: ' if',1492lineTokens: createViewLineTokens([createPart(4, 1), createPart(6, 2)]),1493lineDecorations: [1494new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-3 ced-1-TextEditorDecorationType2-3', InlineDecorationType.Before),1495new LineDecoration(7, 7, 'ced-1-TextEditorDecorationType2-17c14d98-4 ced-1-TextEditorDecorationType2-4', InlineDecorationType.After),1496new LineDecoration(7, 7, 'ced-ghost-text-1-4', InlineDecorationType.After),1497],1498stopRenderingLineAfter: 10000,1499renderWhitespace: 'all'1500}));15011502const inflated = inflateRenderLineOutput(actual);1503await assertSnapshot(inflated.html.join(''), HTML_EXTENSION);1504await assertSnapshot(inflated.mapping);1505});15061507function createTestGetColumnOfLinePartOffset(lineContent: string, tabSize: number, parts: TestLineToken[], expectedPartLengths: number[]): (partIndex: number, partLength: number, offset: number, expected: number) => void {1508const renderLineOutput = renderViewLine(createRenderLineInput({1509lineContent,1510tabSize,1511lineTokens: createViewLineTokens(parts)1512}));15131514return (partIndex: number, partLength: number, offset: number, expected: number) => {1515const actualColumn = renderLineOutput.characterMapping.getColumn(new DomPosition(partIndex, offset), partLength);1516assert.strictEqual(actualColumn, expected, 'getColumn for ' + partIndex + ', ' + offset);1517};1518}15191520test('getColumnOfLinePartOffset 1 - simple text', () => {1521const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(1522'hello world',15234,1524[1525createPart(11, 1)1526],1527[11]1528);1529testGetColumnOfLinePartOffset(0, 11, 0, 1);1530testGetColumnOfLinePartOffset(0, 11, 1, 2);1531testGetColumnOfLinePartOffset(0, 11, 2, 3);1532testGetColumnOfLinePartOffset(0, 11, 3, 4);1533testGetColumnOfLinePartOffset(0, 11, 4, 5);1534testGetColumnOfLinePartOffset(0, 11, 5, 6);1535testGetColumnOfLinePartOffset(0, 11, 6, 7);1536testGetColumnOfLinePartOffset(0, 11, 7, 8);1537testGetColumnOfLinePartOffset(0, 11, 8, 9);1538testGetColumnOfLinePartOffset(0, 11, 9, 10);1539testGetColumnOfLinePartOffset(0, 11, 10, 11);1540testGetColumnOfLinePartOffset(0, 11, 11, 12);1541});15421543test('getColumnOfLinePartOffset 2 - regular JS', () => {1544const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(1545'var x = 3;',15464,1547[1548createPart(3, 1),1549createPart(4, 2),1550createPart(5, 3),1551createPart(8, 4),1552createPart(9, 5),1553createPart(10, 6),1554],1555[3, 1, 1, 3, 1, 1]1556);1557testGetColumnOfLinePartOffset(0, 3, 0, 1);1558testGetColumnOfLinePartOffset(0, 3, 1, 2);1559testGetColumnOfLinePartOffset(0, 3, 2, 3);1560testGetColumnOfLinePartOffset(0, 3, 3, 4);1561testGetColumnOfLinePartOffset(1, 1, 0, 4);1562testGetColumnOfLinePartOffset(1, 1, 1, 5);1563testGetColumnOfLinePartOffset(2, 1, 0, 5);1564testGetColumnOfLinePartOffset(2, 1, 1, 6);1565testGetColumnOfLinePartOffset(3, 3, 0, 6);1566testGetColumnOfLinePartOffset(3, 3, 1, 7);1567testGetColumnOfLinePartOffset(3, 3, 2, 8);1568testGetColumnOfLinePartOffset(3, 3, 3, 9);1569testGetColumnOfLinePartOffset(4, 1, 0, 9);1570testGetColumnOfLinePartOffset(4, 1, 1, 10);1571testGetColumnOfLinePartOffset(5, 1, 0, 10);1572testGetColumnOfLinePartOffset(5, 1, 1, 11);1573});15741575test('getColumnOfLinePartOffset 3 - tab with tab size 6', () => {1576const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(1577'\t',15786,1579[1580createPart(1, 1)1581],1582[6]1583);1584testGetColumnOfLinePartOffset(0, 6, 0, 1);1585testGetColumnOfLinePartOffset(0, 6, 1, 1);1586testGetColumnOfLinePartOffset(0, 6, 2, 1);1587testGetColumnOfLinePartOffset(0, 6, 3, 1);1588testGetColumnOfLinePartOffset(0, 6, 4, 2);1589testGetColumnOfLinePartOffset(0, 6, 5, 2);1590testGetColumnOfLinePartOffset(0, 6, 6, 2);1591});15921593test('getColumnOfLinePartOffset 4 - once indented line, tab size 4', () => {1594const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(1595'\tfunction',15964,1597[1598createPart(1, 1),1599createPart(9, 2),1600],1601[4, 8]1602);1603testGetColumnOfLinePartOffset(0, 4, 0, 1);1604testGetColumnOfLinePartOffset(0, 4, 1, 1);1605testGetColumnOfLinePartOffset(0, 4, 2, 1);1606testGetColumnOfLinePartOffset(0, 4, 3, 2);1607testGetColumnOfLinePartOffset(0, 4, 4, 2);1608testGetColumnOfLinePartOffset(1, 8, 0, 2);1609testGetColumnOfLinePartOffset(1, 8, 1, 3);1610testGetColumnOfLinePartOffset(1, 8, 2, 4);1611testGetColumnOfLinePartOffset(1, 8, 3, 5);1612testGetColumnOfLinePartOffset(1, 8, 4, 6);1613testGetColumnOfLinePartOffset(1, 8, 5, 7);1614testGetColumnOfLinePartOffset(1, 8, 6, 8);1615testGetColumnOfLinePartOffset(1, 8, 7, 9);1616testGetColumnOfLinePartOffset(1, 8, 8, 10);1617});16181619test('getColumnOfLinePartOffset 5 - twice indented line, tab size 4', () => {1620const testGetColumnOfLinePartOffset = createTestGetColumnOfLinePartOffset(1621'\t\tfunction',16224,1623[1624createPart(2, 1),1625createPart(10, 2),1626],1627[8, 8]1628);1629testGetColumnOfLinePartOffset(0, 8, 0, 1);1630testGetColumnOfLinePartOffset(0, 8, 1, 1);1631testGetColumnOfLinePartOffset(0, 8, 2, 1);1632testGetColumnOfLinePartOffset(0, 8, 3, 2);1633testGetColumnOfLinePartOffset(0, 8, 4, 2);1634testGetColumnOfLinePartOffset(0, 8, 5, 2);1635testGetColumnOfLinePartOffset(0, 8, 6, 2);1636testGetColumnOfLinePartOffset(0, 8, 7, 3);1637testGetColumnOfLinePartOffset(0, 8, 8, 3);1638testGetColumnOfLinePartOffset(1, 8, 0, 3);1639testGetColumnOfLinePartOffset(1, 8, 1, 4);1640testGetColumnOfLinePartOffset(1, 8, 2, 5);1641testGetColumnOfLinePartOffset(1, 8, 3, 6);1642testGetColumnOfLinePartOffset(1, 8, 4, 7);1643testGetColumnOfLinePartOffset(1, 8, 5, 8);1644testGetColumnOfLinePartOffset(1, 8, 6, 9);1645testGetColumnOfLinePartOffset(1, 8, 7, 10);1646testGetColumnOfLinePartOffset(1, 8, 8, 11);1647});1648});164916501651