/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import * as assert from 'assert'; import {renderLine, RenderLineInput} from 'vs/editor/common/viewLayout/viewLineRenderer'; import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; suite('viewLineRenderer.renderLine', () => { function createPart(startIndex: number, type:string): ViewLineToken { return new ViewLineToken(startIndex, type); } function assertCharacterReplacement(lineContent:string, tabSize:number, expected:string, expectedCharOffsetInPart: number[]): void { let _actual = renderLine(new RenderLineInput( lineContent, tabSize, 0, -1, 'none', false, [createPart(0, '')] )); assert.equal(_actual.output, '' + expected + ''); assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart); } test('replaces spaces', () => { assertCharacterReplacement(' ', 4, ' ', [0, 1]); assertCharacterReplacement(' ', 4, '  ', [0, 1, 2]); assertCharacterReplacement('a b', 4, 'a  b', [0, 1, 2, 3, 4]); }); test('escapes HTML markup', () => { assertCharacterReplacement('ab', 4, 'a>b', [0, 1, 2, 3]); assertCharacterReplacement('a&b', 4, 'a&b', [0, 1, 2, 3]); }); test('replaces some bad characters', () => { assertCharacterReplacement('a\0b', 4, 'a�b', [0, 1, 2, 3]); assertCharacterReplacement('a' + String.fromCharCode(65279) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]); assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]); assertCharacterReplacement('a\rb', 4, 'a​b', [0, 1, 2, 3]); }); test('handles tabs', () => { assertCharacterReplacement('\t', 4, '    ', [0, 4]); assertCharacterReplacement('x\t', 4, 'x   ', [0, 1, 4]); assertCharacterReplacement('xx\t', 4, 'xx  ', [0, 1, 2, 4]); assertCharacterReplacement('xxx\t', 4, 'xxx ', [0, 1, 2, 3, 4]); assertCharacterReplacement('xxxx\t', 4, 'xxxx    ', [0, 1, 2, 3, 4, 8]); }); function assertParts(lineContent:string, tabSize:number, parts: ViewLineToken[], expected:string, expectedCharOffsetInPart:number[]): void { let _actual = renderLine(new RenderLineInput( lineContent, tabSize, 0, -1, 'none', false, parts )); assert.equal(_actual.output, '' + expected + ''); assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart); } test('empty line', () => { assertParts('', 4, [], ' ', []); }); test('uses part type', () => { assertParts('x', 4, [createPart(0, 'y')], 'x', [0, 1]); assertParts('x', 4, [createPart(0, 'aAbBzZ0123456789-cC')], 'x', [0, 1]); assertParts('x', 4, [createPart(0, '"~!@#$%^&*()\'')], 'x', [0, 1]); }); test('two parts', () => { assertParts('xy', 4, [createPart(0, 'a'), createPart(1, 'b')], 'xy', [0, 0, 1]); assertParts('xyz', 4, [createPart(0, 'a'), createPart(1, 'b')], 'xyz', [0, 0, 1, 2]); assertParts('xyz', 4, [createPart(0, 'a'), createPart(2, 'b')], 'xyz', [0, 1, 0, 1]); }); test('overflow', () => { let _actual = renderLine(new RenderLineInput( 'Hello world!', 4, 10, 6, 'boundary', false, [ createPart( 0, '0'), createPart( 1, '1'), createPart( 2, '2'), createPart( 3, '3'), createPart( 4, '4'), createPart( 5, '5'), createPart( 6, '6'), createPart( 7, '7'), createPart( 8, '8'), createPart( 9, '9'), createPart(10, '10'), createPart(11, '11'), ] )); let expectedOutput = [ 'H', 'e', 'l', 'l', 'o', ' …' ].join(''); assert.equal(_actual.output, '' + expectedOutput + ''); assert.deepEqual(_actual.charOffsetInPart, [ 0, 0, 0, 0, 0, 1 ]); }); test('typical line', () => { let lineText = '\t export class Game { // http://test.com '; let lineParts = [ createPart( 0, 'block meta ts leading whitespace'), createPart( 5, 'block declaration meta modifier object storage ts'), createPart(11, 'block declaration meta object ts'), createPart(12, 'block declaration meta object storage type ts'), createPart(17, 'block declaration meta object ts'), createPart(18, 'block class declaration entity meta name object ts'), createPart(22, 'block declaration meta object ts'), createPart(23, 'delimiter curly typescript'), createPart(24, 'block body declaration meta object ts'), createPart(25, 'block body comment declaration line meta object ts'), createPart(28, 'block body comment declaration line meta object ts detected-link'), createPart(43, 'block body comment declaration line meta object ts trailing whitespace'), ]; let expectedOutput = [ '→   ····', 'export', ' ', 'class', ' ', 'Game', ' ', '{', ' ', '// ', 'http://test.com', '·····' ].join(''); let expectedOffsetsArr = [ [0, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5], [0], [0, 1, 2, 3, 4], [0], [0, 1, 2, 3], [0], [0], [0], [0, 1, 2], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], [0, 1, 2, 3, 4, 5], ]; let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []); let _actual = renderLine(new RenderLineInput( lineText, 4, 10, -1, 'boundary', false, lineParts )); assert.equal(_actual.output, '' + expectedOutput + ''); assert.deepEqual(_actual.charOffsetInPart, expectedOffsets); }); test('issue #2255: Weird line rendering part 1', () => { let lineText = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; let lineParts = [ createPart( 0, 'block body decl declaration meta method object ts'), // 3 chars createPart( 3, 'block body decl declaration member meta method object ts'), // 12 chars createPart(15, 'block body decl declaration member meta method object ts'), // 6 chars createPart(21, 'delimiter paren typescript'), // 1 char createPart(22, 'block body decl declaration member meta method object ts'), // 21 chars createPart(43, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars createPart(45, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char createPart(46, 'block body decl declaration member meta method object ts'), // 20 chars createPart(66, 'delimiter paren typescript'), // 1 char createPart(67, 'block body decl declaration meta method object ts'), // 2 chars ]; let expectedOutput = [ '            ', 'cursorStyle:', '                        ', '(', 'prevOpts.cursorStyle ', '!=', '=', ' newOpts.cursorStyle', ')', ',', ].join(''); let expectedOffsetsArr = [ [0, 4, 8], // 3 chars [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars [0, 4, 8, 12, 16, 20], // 6 chars [0], // 1 char [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars [0, 1], // 2 chars [0], // 1 char [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars [0], // 1 char [0, 1] // 2 chars ]; let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []); let _actual = renderLine(new RenderLineInput( lineText, 4, 10, -1, 'none', false, lineParts )); assert.equal(_actual.output, '' + expectedOutput + ''); assert.deepEqual(_actual.charOffsetInPart, expectedOffsets); }); test('issue #2255: Weird line rendering part 2', () => { let lineText = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; let lineParts = [ createPart( 0, 'block body decl declaration meta method object ts'), // 4 chars createPart( 4, 'block body decl declaration member meta method object ts'), // 12 chars createPart(16, 'block body decl declaration member meta method object ts'), // 6 chars createPart(22, 'delimiter paren typescript'), // 1 char createPart(23, 'block body decl declaration member meta method object ts'), // 21 chars createPart(44, 'block body comparison decl declaration keyword member meta method object operator ts'), // 2 chars createPart(46, 'block body comparison decl declaration keyword member meta method object operator ts'), // 1 char createPart(47, 'block body decl declaration member meta method object ts'), // 20 chars createPart(67, 'delimiter paren typescript'), // 1 char createPart(68, 'block body decl declaration meta method object ts'), // 2 chars ]; let expectedOutput = [ '            ', 'cursorStyle:', '                        ', '(', 'prevOpts.cursorStyle ', '!=', '=', ' newOpts.cursorStyle', ')', ',', ].join(''); let expectedOffsetsArr = [ [0, 1, 4, 8], // 4 chars [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars [0, 4, 8, 12, 16, 20], // 6 chars [0], // 1 char [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars [0, 1], // 2 chars [0], // 1 char [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars [0], // 1 char [0, 1] // 2 chars ]; let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []); let _actual = renderLine(new RenderLineInput( lineText, 4, 10, -1, 'none', false, lineParts )); assert.equal(_actual.output, '' + expectedOutput + ''); assert.deepEqual(_actual.charOffsetInPart, expectedOffsets); }); });