Path: blob/main/src/vs/editor/test/common/model/textModelWithTokens.test.ts
3296 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 { DisposableStore } from '../../../../base/common/lifecycle.js';7import { Position } from '../../../common/core/position.js';8import { Range } from '../../../common/core/range.js';9import { IFoundBracket } from '../../../common/textModelBracketPairs.js';10import { TextModel } from '../../../common/model/textModel.js';11import { ITokenizationSupport, TokenizationRegistry, EncodedTokenizationResult } from '../../../common/languages.js';12import { StandardTokenType, MetadataConsts } from '../../../common/encodedTokenAttributes.js';13import { CharacterPair } from '../../../common/languages/languageConfiguration.js';14import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';15import { NullState } from '../../../common/languages/nullTokenize.js';16import { ILanguageService } from '../../../common/languages/language.js';17import { TestLineToken } from '../core/testLineToken.js';18import { createModelServices, createTextModel, instantiateTextModel } from '../testTextModel.js';19import { TestInstantiationService } from '../../../../platform/instantiation/test/common/instantiationServiceMock.js';20import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';2122function createTextModelWithBrackets(disposables: DisposableStore, text: string, brackets: CharacterPair[]): TextModel {23const languageId = 'bracketMode2';24const instantiationService = createModelServices(disposables);25const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);26const languageService = instantiationService.get(ILanguageService);2728disposables.add(languageService.registerLanguage({ id: languageId }));29disposables.add(languageConfigurationService.register(languageId, { brackets }));3031return disposables.add(instantiateTextModel(instantiationService, text, languageId));32}3334suite('TextModelWithTokens', () => {3536ensureNoDisposablesAreLeakedInTestSuite();3738function testBrackets(contents: string[], brackets: CharacterPair[]): void {39const languageId = 'testMode';40const disposables = new DisposableStore();41const instantiationService = createModelServices(disposables);42const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);43const languageService = instantiationService.get(ILanguageService);44disposables.add(languageService.registerLanguage({ id: languageId }));45disposables.add(languageConfigurationService.register(languageId, {46brackets: brackets47}));484950function toRelaxedFoundBracket(a: IFoundBracket | null) {51if (!a) {52return null;53}54return {55range: a.range.toString(),56info: a.bracketInfo,57};58}5960const charIsBracket: { [char: string]: boolean } = {};61const charIsOpenBracket: { [char: string]: boolean } = {};62const openForChar: { [char: string]: string } = {};63const closeForChar: { [char: string]: string } = {};64brackets.forEach((b) => {65charIsBracket[b[0]] = true;66charIsBracket[b[1]] = true;6768charIsOpenBracket[b[0]] = true;69charIsOpenBracket[b[1]] = false;7071openForChar[b[0]] = b[0];72closeForChar[b[0]] = b[1];7374openForChar[b[1]] = b[0];75closeForChar[b[1]] = b[1];76});7778const expectedBrackets: IFoundBracket[] = [];79for (let lineIndex = 0; lineIndex < contents.length; lineIndex++) {80const lineText = contents[lineIndex];8182for (let charIndex = 0; charIndex < lineText.length; charIndex++) {83const ch = lineText.charAt(charIndex);84if (charIsBracket[ch]) {85expectedBrackets.push({86bracketInfo: languageConfigurationService.getLanguageConfiguration(languageId).bracketsNew.getBracketInfo(ch)!,87range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2)88});89}90}91}9293const model = disposables.add(instantiateTextModel(instantiationService, contents.join('\n'), languageId));9495// findPrevBracket96{97let expectedBracketIndex = expectedBrackets.length - 1;98let currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null;99for (let lineNumber = contents.length; lineNumber >= 1; lineNumber--) {100const lineText = contents[lineNumber - 1];101102for (let column = lineText.length + 1; column >= 1; column--) {103104if (currentExpectedBracket) {105if (lineNumber === currentExpectedBracket.range.startLineNumber && column < currentExpectedBracket.range.endColumn) {106expectedBracketIndex--;107currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null;108}109}110111const actual = model.bracketPairs.findPrevBracket({112lineNumber: lineNumber,113column: column114});115116assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column);117}118}119}120121// findNextBracket122{123let expectedBracketIndex = 0;124let currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null;125for (let lineNumber = 1; lineNumber <= contents.length; lineNumber++) {126const lineText = contents[lineNumber - 1];127128for (let column = 1; column <= lineText.length + 1; column++) {129130if (currentExpectedBracket) {131if (lineNumber === currentExpectedBracket.range.startLineNumber && column > currentExpectedBracket.range.startColumn) {132expectedBracketIndex++;133currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null;134}135}136137const actual = model.bracketPairs.findNextBracket({138lineNumber: lineNumber,139column: column140});141142assert.deepStrictEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column);143}144}145}146147disposables.dispose();148}149150test('brackets1', () => {151testBrackets([152'if (a == 3) { return (7 * (a + 5)); }'153], [154['{', '}'],155['[', ']'],156['(', ')']157]);158});159});160161function assertIsNotBracket(model: TextModel, lineNumber: number, column: number) {162const match = model.bracketPairs.matchBracket(new Position(lineNumber, column));163assert.strictEqual(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column);164}165166function assertIsBracket(model: TextModel, testPosition: Position, expected: [Range, Range]): void {167expected.sort(Range.compareRangesUsingStarts);168const actual = model.bracketPairs.matchBracket(testPosition);169actual?.sort(Range.compareRangesUsingStarts);170assert.deepStrictEqual(actual, expected, 'matches brackets at ' + testPosition);171}172173suite('TextModelWithTokens - bracket matching', () => {174175const languageId = 'bracketMode1';176let disposables: DisposableStore;177let instantiationService: TestInstantiationService;178let languageConfigurationService: ILanguageConfigurationService;179let languageService: ILanguageService;180181setup(() => {182disposables = new DisposableStore();183instantiationService = createModelServices(disposables);184languageConfigurationService = instantiationService.get(ILanguageConfigurationService);185languageService = instantiationService.get(ILanguageService);186disposables.add(languageService.registerLanguage({ id: languageId }));187disposables.add(languageConfigurationService.register(languageId, {188brackets: [189['{', '}'],190['[', ']'],191['(', ')'],192]193}));194});195196teardown(() => {197disposables.dispose();198});199200ensureNoDisposablesAreLeakedInTestSuite();201202test('bracket matching 1', () => {203const text =204')]}{[(' + '\n' +205')]}{[(';206const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));207208assertIsNotBracket(model, 1, 1);209assertIsNotBracket(model, 1, 2);210assertIsNotBracket(model, 1, 3);211assertIsBracket(model, new Position(1, 4), [new Range(1, 4, 1, 5), new Range(2, 3, 2, 4)]);212assertIsBracket(model, new Position(1, 5), [new Range(1, 5, 1, 6), new Range(2, 2, 2, 3)]);213assertIsBracket(model, new Position(1, 6), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]);214assertIsBracket(model, new Position(1, 7), [new Range(1, 6, 1, 7), new Range(2, 1, 2, 2)]);215216assertIsBracket(model, new Position(2, 1), [new Range(2, 1, 2, 2), new Range(1, 6, 1, 7)]);217assertIsBracket(model, new Position(2, 2), [new Range(2, 2, 2, 3), new Range(1, 5, 1, 6)]);218assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]);219assertIsBracket(model, new Position(2, 4), [new Range(2, 3, 2, 4), new Range(1, 4, 1, 5)]);220assertIsNotBracket(model, 2, 5);221assertIsNotBracket(model, 2, 6);222assertIsNotBracket(model, 2, 7);223});224225test('bracket matching 2', () => {226const text =227'var bar = {' + '\n' +228'foo: {' + '\n' +229'}, bar: {hallo: [{' + '\n' +230'}, {' + '\n' +231'}]}}';232const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));233234const brackets: [Position, Range, Range][] = [235[new Position(1, 11), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)],236[new Position(1, 12), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)],237238[new Position(2, 6), new Range(2, 6, 2, 7), new Range(3, 1, 3, 2)],239[new Position(2, 7), new Range(2, 6, 2, 7), new Range(3, 1, 3, 2)],240241[new Position(3, 1), new Range(3, 1, 3, 2), new Range(2, 6, 2, 7)],242[new Position(3, 2), new Range(3, 1, 3, 2), new Range(2, 6, 2, 7)],243[new Position(3, 9), new Range(3, 9, 3, 10), new Range(5, 3, 5, 4)],244[new Position(3, 10), new Range(3, 9, 3, 10), new Range(5, 3, 5, 4)],245[new Position(3, 17), new Range(3, 17, 3, 18), new Range(5, 2, 5, 3)],246[new Position(3, 18), new Range(3, 18, 3, 19), new Range(4, 1, 4, 2)],247[new Position(3, 19), new Range(3, 18, 3, 19), new Range(4, 1, 4, 2)],248249[new Position(4, 1), new Range(4, 1, 4, 2), new Range(3, 18, 3, 19)],250[new Position(4, 2), new Range(4, 1, 4, 2), new Range(3, 18, 3, 19)],251[new Position(4, 4), new Range(4, 4, 4, 5), new Range(5, 1, 5, 2)],252[new Position(4, 5), new Range(4, 4, 4, 5), new Range(5, 1, 5, 2)],253254[new Position(5, 1), new Range(5, 1, 5, 2), new Range(4, 4, 4, 5)],255[new Position(5, 2), new Range(5, 2, 5, 3), new Range(3, 17, 3, 18)],256[new Position(5, 3), new Range(5, 3, 5, 4), new Range(3, 9, 3, 10)],257[new Position(5, 4), new Range(5, 4, 5, 5), new Range(1, 11, 1, 12)],258[new Position(5, 5), new Range(5, 4, 5, 5), new Range(1, 11, 1, 12)],259];260261const isABracket: { [lineNumber: number]: { [col: number]: boolean } } = { 1: {}, 2: {}, 3: {}, 4: {}, 5: {} };262for (let i = 0, len = brackets.length; i < len; i++) {263const [testPos, b1, b2] = brackets[i];264assertIsBracket(model, testPos, [b1, b2]);265isABracket[testPos.lineNumber][testPos.column] = true;266}267268for (let i = 1, len = model.getLineCount(); i <= len; i++) {269const line = model.getLineContent(i);270for (let j = 1, lenJ = line.length + 1; j <= lenJ; j++) {271if (!isABracket[i].hasOwnProperty(<any>j)) {272assertIsNotBracket(model, i, j);273}274}275}276});277});278279suite('TextModelWithTokens 2', () => {280281ensureNoDisposablesAreLeakedInTestSuite();282283test('bracket matching 3', () => {284const text = [285'begin',286' loop',287' if then',288' end if;',289' end loop;',290'end;',291'',292'begin',293' loop',294' if then',295' end ifa;',296' end loop;',297'end;',298].join('\n');299300const disposables = new DisposableStore();301const model = createTextModelWithBrackets(disposables, text, [302['if', 'end if'],303['loop', 'end loop'],304['begin', 'end']305]);306307// <if> ... <end ifa> is not matched308assertIsNotBracket(model, 10, 9);309310// <if> ... <end if> is matched311assertIsBracket(model, new Position(3, 9), [new Range(3, 9, 3, 11), new Range(4, 9, 4, 15)]);312assertIsBracket(model, new Position(4, 9), [new Range(4, 9, 4, 15), new Range(3, 9, 3, 11)]);313314// <loop> ... <end loop> is matched315assertIsBracket(model, new Position(2, 5), [new Range(2, 5, 2, 9), new Range(5, 5, 5, 13)]);316assertIsBracket(model, new Position(5, 5), [new Range(5, 5, 5, 13), new Range(2, 5, 2, 9)]);317318// <begin> ... <end> is matched319assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 6), new Range(6, 1, 6, 4)]);320assertIsBracket(model, new Position(6, 1), [new Range(6, 1, 6, 4), new Range(1, 1, 1, 6)]);321322disposables.dispose();323});324325test('bracket matching 4', () => {326const text = [327'recordbegin',328' simplerecordbegin',329' endrecord',330'endrecord',331].join('\n');332333const disposables = new DisposableStore();334const model = createTextModelWithBrackets(disposables, text, [335['recordbegin', 'endrecord'],336['simplerecordbegin', 'endrecord'],337]);338339// <recordbegin> ... <endrecord> is matched340assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]);341assertIsBracket(model, new Position(4, 1), [new Range(4, 1, 4, 10), new Range(1, 1, 1, 12)]);342343// <simplerecordbegin> ... <endrecord> is matched344assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 20), new Range(3, 3, 3, 12)]);345assertIsBracket(model, new Position(3, 3), [new Range(3, 3, 3, 12), new Range(2, 3, 2, 20)]);346347disposables.dispose();348});349350test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => {351const disposables = new DisposableStore();352const instantiationService = createModelServices(disposables);353const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);354const languageService = instantiationService.get(ILanguageService);355const mode1 = 'testMode1';356const mode2 = 'testMode2';357358const languageIdCodec = languageService.languageIdCodec;359360disposables.add(languageService.registerLanguage({ id: mode1 }));361disposables.add(languageService.registerLanguage({ id: mode2 }));362const encodedMode1 = languageIdCodec.encodeLanguageId(mode1);363const encodedMode2 = languageIdCodec.encodeLanguageId(mode2);364365const otherMetadata1 = (366(encodedMode1 << MetadataConsts.LANGUAGEID_OFFSET)367| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)368| (MetadataConsts.BALANCED_BRACKETS_MASK)369) >>> 0;370const otherMetadata2 = (371(encodedMode2 << MetadataConsts.LANGUAGEID_OFFSET)372| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)373| (MetadataConsts.BALANCED_BRACKETS_MASK)374) >>> 0;375376const tokenizationSupport: ITokenizationSupport = {377getInitialState: () => NullState,378tokenize: undefined!,379tokenizeEncoded: (line, hasEOL, state) => {380switch (line) {381case 'function f() {': {382const tokens = new Uint32Array([3830, otherMetadata1,3848, otherMetadata1,3859, otherMetadata1,38610, otherMetadata1,38711, otherMetadata1,38812, otherMetadata1,38913, otherMetadata1,390]);391return new EncodedTokenizationResult(tokens, state);392}393case ' return <p>{true}</p>;': {394const tokens = new Uint32Array([3950, otherMetadata1,3962, otherMetadata1,3978, otherMetadata1,3989, otherMetadata2,39910, otherMetadata2,40011, otherMetadata2,40112, otherMetadata2,40213, otherMetadata1,40317, otherMetadata2,40418, otherMetadata2,40520, otherMetadata2,40621, otherMetadata2,40722, otherMetadata2,408]);409return new EncodedTokenizationResult(tokens, state);410}411case '}': {412const tokens = new Uint32Array([4130, otherMetadata1414]);415return new EncodedTokenizationResult(tokens, state);416}417}418throw new Error(`Unexpected`);419}420};421422disposables.add(TokenizationRegistry.register(mode1, tokenizationSupport));423disposables.add(languageConfigurationService.register(mode1, {424brackets: [425['{', '}'],426['[', ']'],427['(', ')']428],429}));430disposables.add(languageConfigurationService.register(mode2, {431brackets: [432['{', '}'],433['[', ']'],434['(', ')']435],436}));437438const model = disposables.add(instantiateTextModel(439instantiationService,440[441'function f() {',442' return <p>{true}</p>;',443'}',444].join('\n'),445mode1446));447448model.tokenization.forceTokenization(1);449model.tokenization.forceTokenization(2);450model.tokenization.forceTokenization(3);451452assert.deepStrictEqual(453model.bracketPairs.matchBracket(new Position(2, 14)),454[new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]455);456457disposables.dispose();458});459460test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => {461const disposables = new DisposableStore();462const instantiationService = createModelServices(disposables);463const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);464const mode = 'testMode';465466const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;467468const encodedMode = languageIdCodec.encodeLanguageId(mode);469470const otherMetadata = (471(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)472| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)473) >>> 0;474const stringMetadata = (475(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)476| (StandardTokenType.String << MetadataConsts.TOKEN_TYPE_OFFSET)477) >>> 0;478479const tokenizationSupport: ITokenizationSupport = {480getInitialState: () => NullState,481tokenize: undefined!,482tokenizeEncoded: (line, hasEOL, state) => {483switch (line) {484case 'function hello() {': {485const tokens = new Uint32Array([4860, otherMetadata487]);488return new EncodedTokenizationResult(tokens, state);489}490case ' console.log(`${100}`);': {491const tokens = new Uint32Array([4920, otherMetadata,49316, stringMetadata,49419, otherMetadata,49522, stringMetadata,49624, otherMetadata,497]);498return new EncodedTokenizationResult(tokens, state);499}500case '}': {501const tokens = new Uint32Array([5020, otherMetadata503]);504return new EncodedTokenizationResult(tokens, state);505}506}507throw new Error(`Unexpected`);508}509};510511disposables.add(TokenizationRegistry.register(mode, tokenizationSupport));512disposables.add(languageConfigurationService.register(mode, {513brackets: [514['{', '}'],515['[', ']'],516['(', ')']517],518}));519520const model = disposables.add(instantiateTextModel(521instantiationService,522[523'function hello() {',524' console.log(`${100}`);',525'}'526].join('\n'),527mode528));529530model.tokenization.forceTokenization(1);531model.tokenization.forceTokenization(2);532model.tokenization.forceTokenization(3);533534assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 23)), null);535assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 20)), null);536537disposables.dispose();538});539});540541542suite('TextModelWithTokens regression tests', () => {543544ensureNoDisposablesAreLeakedInTestSuite();545546test('microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => {547function assertViewLineTokens(model: TextModel, lineNumber: number, forceTokenization: boolean, expected: TestLineToken[]): void {548if (forceTokenization) {549model.tokenization.forceTokenization(lineNumber);550}551const _actual = model.tokenization.getLineTokens(lineNumber).inflate();552interface ISimpleViewToken {553endIndex: number;554foreground: number;555}556const actual: ISimpleViewToken[] = [];557for (let i = 0, len = _actual.getCount(); i < len; i++) {558actual[i] = {559endIndex: _actual.getEndOffset(i),560foreground: _actual.getForeground(i)561};562}563const decode = (token: TestLineToken) => {564return {565endIndex: token.endIndex,566foreground: token.getForeground()567};568};569assert.deepStrictEqual(actual, expected.map(decode));570}571572let _tokenId = 10;573const LANG_ID1 = 'indicisiveMode1';574const LANG_ID2 = 'indicisiveMode2';575576const tokenizationSupport: ITokenizationSupport = {577getInitialState: () => NullState,578tokenize: undefined!,579tokenizeEncoded: (line, hasEOL, state) => {580const myId = ++_tokenId;581const tokens = new Uint32Array(2);582tokens[0] = 0;583tokens[1] = (584myId << MetadataConsts.FOREGROUND_OFFSET585) >>> 0;586return new EncodedTokenizationResult(tokens, state);587}588};589590const registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport);591const registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport);592593const model = createTextModel('A model with\ntwo lines');594595assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);596assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);597598model.setLanguage(LANG_ID1);599600assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);601assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);602603model.setLanguage(LANG_ID2);604605assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);606assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);607608model.dispose();609registration1.dispose();610registration2.dispose();611612function createViewLineToken(endIndex: number, foreground: number): TestLineToken {613const metadata = (614(foreground << MetadataConsts.FOREGROUND_OFFSET)615) >>> 0;616return new TestLineToken(endIndex, metadata);617}618});619620621test('microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => {622623const disposables = new DisposableStore();624const model = createTextModelWithBrackets(625disposables,626[627'Imports System',628'Imports System.Collections.Generic',629'',630'Module m1',631'',632'\tSub Main()',633'\tEnd Sub',634'',635'End Module',636].join('\n'),637[638['module', 'end module'],639['sub', 'end sub']640]641);642643const actual = model.bracketPairs.matchBracket(new Position(4, 1));644assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]);645646disposables.dispose();647});648649test('issue #11856: Bracket matching does not work as expected if the opening brace symbol is contained in the closing brace symbol', () => {650651const disposables = new DisposableStore();652const model = createTextModelWithBrackets(653disposables,654[655'sequence "outer"',656' sequence "inner"',657' endsequence',658'endsequence',659].join('\n'),660[661['sequence', 'endsequence'],662['feature', 'endfeature']663]664);665666const actual = model.bracketPairs.matchBracket(new Position(3, 9));667assert.deepStrictEqual(actual, [new Range(2, 6, 2, 14), new Range(3, 6, 3, 17)]);668669disposables.dispose();670});671672test('issue #63822: Wrong embedded language detected for empty lines', () => {673const disposables = new DisposableStore();674const instantiationService = createModelServices(disposables);675const languageService = instantiationService.get(ILanguageService);676677const outerMode = 'outerMode';678const innerMode = 'innerMode';679680disposables.add(languageService.registerLanguage({ id: outerMode }));681disposables.add(languageService.registerLanguage({ id: innerMode }));682683const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;684const encodedInnerMode = languageIdCodec.encodeLanguageId(innerMode);685686const tokenizationSupport: ITokenizationSupport = {687getInitialState: () => NullState,688tokenize: undefined!,689tokenizeEncoded: (line, hasEOL, state) => {690const tokens = new Uint32Array(2);691tokens[0] = 0;692tokens[1] = (693encodedInnerMode << MetadataConsts.LANGUAGEID_OFFSET694) >>> 0;695return new EncodedTokenizationResult(tokens, state);696}697};698699disposables.add(TokenizationRegistry.register(outerMode, tokenizationSupport));700701const model = disposables.add(instantiateTextModel(instantiationService, 'A model with one line', outerMode));702703model.tokenization.forceTokenization(1);704assert.strictEqual(model.getLanguageIdAtPosition(1, 1), innerMode);705706disposables.dispose();707});708});709710suite('TextModel.getLineIndentGuide', () => {711712ensureNoDisposablesAreLeakedInTestSuite();713714function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void {715const languageId = 'testLang';716const disposables = new DisposableStore();717const instantiationService = createModelServices(disposables);718const languageService = instantiationService.get(ILanguageService);719disposables.add(languageService.registerLanguage({ id: languageId }));720721const text = lines.map(l => l[4]).join('\n');722const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));723model.updateOptions({ indentSize: indentSize });724725const actualIndents = model.guides.getLinesIndentGuides(1, model.getLineCount());726727const actual: [number, number, number, number, string][] = [];728for (let line = 1; line <= model.getLineCount(); line++) {729const activeIndentGuide = model.guides.getActiveIndentGuide(line, 1, model.getLineCount());730actual[line - 1] = [actualIndents[line - 1], activeIndentGuide.startLineNumber, activeIndentGuide.endLineNumber, activeIndentGuide.indent, model.getLineContent(line)];731}732733assert.deepStrictEqual(actual, lines);734735disposables.dispose();736}737738test('getLineIndentGuide one level 2', () => {739assertIndentGuides([740[0, 2, 4, 1, 'A'],741[1, 2, 4, 1, ' A'],742[1, 2, 4, 1, ' A'],743[1, 2, 4, 1, ' A'],744], 2);745});746747test('getLineIndentGuide two levels', () => {748assertIndentGuides([749[0, 2, 5, 1, 'A'],750[1, 2, 5, 1, ' A'],751[1, 4, 5, 2, ' A'],752[2, 4, 5, 2, ' A'],753[2, 4, 5, 2, ' A'],754], 2);755});756757test('getLineIndentGuide three levels', () => {758assertIndentGuides([759[0, 2, 4, 1, 'A'],760[1, 3, 4, 2, ' A'],761[2, 4, 4, 3, ' A'],762[3, 4, 4, 3, ' A'],763[0, 5, 5, 0, 'A'],764], 2);765});766767test('getLineIndentGuide decreasing indent', () => {768assertIndentGuides([769[2, 1, 1, 2, ' A'],770[1, 1, 1, 2, ' A'],771[0, 1, 2, 1, 'A'],772], 2);773});774775test('getLineIndentGuide Java', () => {776assertIndentGuides([777/* 1*/[0, 2, 9, 1, 'class A {'],778/* 2*/[1, 3, 4, 2, ' void foo() {'],779/* 3*/[2, 3, 4, 2, ' console.log(1);'],780/* 4*/[2, 3, 4, 2, ' console.log(2);'],781/* 5*/[1, 3, 4, 2, ' }'],782/* 6*/[1, 2, 9, 1, ''],783/* 7*/[1, 8, 8, 2, ' void bar() {'],784/* 8*/[2, 8, 8, 2, ' console.log(3);'],785/* 9*/[1, 8, 8, 2, ' }'],786/*10*/[0, 2, 9, 1, '}'],787/*11*/[0, 12, 12, 1, 'interface B {'],788/*12*/[1, 12, 12, 1, ' void bar();'],789/*13*/[0, 12, 12, 1, '}'],790], 2);791});792793test('getLineIndentGuide Javadoc', () => {794assertIndentGuides([795[0, 2, 3, 1, '/**'],796[1, 2, 3, 1, ' * Comment'],797[1, 2, 3, 1, ' */'],798[0, 5, 6, 1, 'class A {'],799[1, 5, 6, 1, ' void foo() {'],800[1, 5, 6, 1, ' }'],801[0, 5, 6, 1, '}'],802], 2);803});804805test('getLineIndentGuide Whitespace', () => {806assertIndentGuides([807[0, 2, 7, 1, 'class A {'],808[1, 2, 7, 1, ''],809[1, 4, 5, 2, ' void foo() {'],810[2, 4, 5, 2, ' '],811[2, 4, 5, 2, ' return 1;'],812[1, 4, 5, 2, ' }'],813[1, 2, 7, 1, ' '],814[0, 2, 7, 1, '}']815], 2);816});817818test('getLineIndentGuide Tabs', () => {819assertIndentGuides([820[0, 2, 7, 1, 'class A {'],821[1, 2, 7, 1, '\t\t'],822[1, 4, 5, 2, '\tvoid foo() {'],823[2, 4, 5, 2, '\t \t//hello'],824[2, 4, 5, 2, '\t return 2;'],825[1, 4, 5, 2, ' \t}'],826[1, 2, 7, 1, ' '],827[0, 2, 7, 1, '}']828], 4);829});830831test('getLineIndentGuide checker.ts', () => {832assertIndentGuides([833/* 1*/[0, 1, 1, 0, '/// <reference path="binder.ts"/>'],834/* 2*/[0, 2, 2, 0, ''],835/* 3*/[0, 3, 3, 0, '/* @internal */'],836/* 4*/[0, 5, 16, 1, 'namespace ts {'],837/* 5*/[1, 5, 16, 1, ' let nextSymbolId = 1;'],838/* 6*/[1, 5, 16, 1, ' let nextNodeId = 1;'],839/* 7*/[1, 5, 16, 1, ' let nextMergeId = 1;'],840/* 8*/[1, 5, 16, 1, ' let nextFlowId = 1;'],841/* 9*/[1, 5, 16, 1, ''],842/*10*/[1, 11, 15, 2, ' export function getNodeId(node: Node): number {'],843/*11*/[2, 12, 13, 3, ' if (!node.id) {'],844/*12*/[3, 12, 13, 3, ' node.id = nextNodeId;'],845/*13*/[3, 12, 13, 3, ' nextNodeId++;'],846/*14*/[2, 12, 13, 3, ' }'],847/*15*/[2, 11, 15, 2, ' return node.id;'],848/*16*/[1, 11, 15, 2, ' }'],849/*17*/[0, 5, 16, 1, '}']850], 4);851});852853test('issue #8425 - Missing indentation lines for first level indentation', () => {854assertIndentGuides([855[1, 2, 3, 2, '\tindent1'],856[2, 2, 3, 2, '\t\tindent2'],857[2, 2, 3, 2, '\t\tindent2'],858[1, 2, 3, 2, '\tindent1']859], 4);860});861862test('issue #8952 - Indentation guide lines going through text on .yml file', () => {863assertIndentGuides([864[0, 2, 5, 1, 'properties:'],865[1, 3, 5, 2, ' emailAddress:'],866[2, 3, 5, 2, ' - bla'],867[2, 5, 5, 3, ' - length:'],868[3, 5, 5, 3, ' max: 255'],869[0, 6, 6, 0, 'getters:']870], 4);871});872873test('issue #11892 - Indent guides look funny', () => {874assertIndentGuides([875[0, 2, 7, 1, 'function test(base) {'],876[1, 3, 6, 2, '\tswitch (base) {'],877[2, 4, 4, 3, '\t\tcase 1:'],878[3, 4, 4, 3, '\t\t\treturn 1;'],879[2, 6, 6, 3, '\t\tcase 2:'],880[3, 6, 6, 3, '\t\t\treturn 2;'],881[1, 2, 7, 1, '\t}'],882[0, 2, 7, 1, '}']883], 4);884});885886test('issue #12398 - Problem in indent guidelines', () => {887assertIndentGuides([888[2, 2, 2, 3, '\t\t.bla'],889[3, 2, 2, 3, '\t\t\tlabel(for)'],890[0, 3, 3, 0, 'include script']891], 4);892});893894test('issue #49173', () => {895const model = createTextModel([896'class A {',897' public m1(): void {',898' }',899' public m2(): void {',900' }',901' public m3(): void {',902' }',903' public m4(): void {',904' }',905' public m5(): void {',906' }',907'}',908].join('\n'));909910const actual = model.guides.getActiveIndentGuide(2, 4, 9);911assert.deepStrictEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 });912model.dispose();913});914915test('tweaks - no active', () => {916assertIndentGuides([917[0, 1, 1, 0, 'A'],918[0, 2, 2, 0, 'A']919], 2);920});921922test('tweaks - inside scope', () => {923assertIndentGuides([924[0, 2, 2, 1, 'A'],925[1, 2, 2, 1, ' A']926], 2);927});928929test('tweaks - scope start', () => {930assertIndentGuides([931[0, 2, 2, 1, 'A'],932[1, 2, 2, 1, ' A'],933[0, 2, 2, 1, 'A']934], 2);935});936937test('tweaks - empty line', () => {938assertIndentGuides([939[0, 2, 4, 1, 'A'],940[1, 2, 4, 1, ' A'],941[1, 2, 4, 1, ''],942[1, 2, 4, 1, ' A'],943[0, 2, 4, 1, 'A']944], 2);945});946});947948949