Path: blob/main/src/vs/editor/test/common/model/textModelWithTokens.test.ts
5238 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++) {271// eslint-disable-next-line local/code-no-any-casts272if (!isABracket[i].hasOwnProperty(<any>j)) {273assertIsNotBracket(model, i, j);274}275}276}277});278});279280suite('TextModelWithTokens 2', () => {281282ensureNoDisposablesAreLeakedInTestSuite();283284test('bracket matching 3', () => {285const text = [286'begin',287' loop',288' if then',289' end if;',290' end loop;',291'end;',292'',293'begin',294' loop',295' if then',296' end ifa;',297' end loop;',298'end;',299].join('\n');300301const disposables = new DisposableStore();302const model = createTextModelWithBrackets(disposables, text, [303['if', 'end if'],304['loop', 'end loop'],305['begin', 'end']306]);307308// <if> ... <end ifa> is not matched309assertIsNotBracket(model, 10, 9);310311// <if> ... <end if> is matched312assertIsBracket(model, new Position(3, 9), [new Range(3, 9, 3, 11), new Range(4, 9, 4, 15)]);313assertIsBracket(model, new Position(4, 9), [new Range(4, 9, 4, 15), new Range(3, 9, 3, 11)]);314315// <loop> ... <end loop> is matched316assertIsBracket(model, new Position(2, 5), [new Range(2, 5, 2, 9), new Range(5, 5, 5, 13)]);317assertIsBracket(model, new Position(5, 5), [new Range(5, 5, 5, 13), new Range(2, 5, 2, 9)]);318319// <begin> ... <end> is matched320assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 6), new Range(6, 1, 6, 4)]);321assertIsBracket(model, new Position(6, 1), [new Range(6, 1, 6, 4), new Range(1, 1, 1, 6)]);322323disposables.dispose();324});325326test('bracket matching 4', () => {327const text = [328'recordbegin',329' simplerecordbegin',330' endrecord',331'endrecord',332].join('\n');333334const disposables = new DisposableStore();335const model = createTextModelWithBrackets(disposables, text, [336['recordbegin', 'endrecord'],337['simplerecordbegin', 'endrecord'],338]);339340// <recordbegin> ... <endrecord> is matched341assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]);342assertIsBracket(model, new Position(4, 1), [new Range(4, 1, 4, 10), new Range(1, 1, 1, 12)]);343344// <simplerecordbegin> ... <endrecord> is matched345assertIsBracket(model, new Position(2, 3), [new Range(2, 3, 2, 20), new Range(3, 3, 3, 12)]);346assertIsBracket(model, new Position(3, 3), [new Range(3, 3, 3, 12), new Range(2, 3, 2, 20)]);347348disposables.dispose();349});350351test('issue #95843: Highlighting of closing braces is indicating wrong brace when cursor is behind opening brace', () => {352const disposables = new DisposableStore();353const instantiationService = createModelServices(disposables);354const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);355const languageService = instantiationService.get(ILanguageService);356const mode1 = 'testMode1';357const mode2 = 'testMode2';358359const languageIdCodec = languageService.languageIdCodec;360361disposables.add(languageService.registerLanguage({ id: mode1 }));362disposables.add(languageService.registerLanguage({ id: mode2 }));363const encodedMode1 = languageIdCodec.encodeLanguageId(mode1);364const encodedMode2 = languageIdCodec.encodeLanguageId(mode2);365366const otherMetadata1 = (367(encodedMode1 << MetadataConsts.LANGUAGEID_OFFSET)368| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)369| (MetadataConsts.BALANCED_BRACKETS_MASK)370) >>> 0;371const otherMetadata2 = (372(encodedMode2 << MetadataConsts.LANGUAGEID_OFFSET)373| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)374| (MetadataConsts.BALANCED_BRACKETS_MASK)375) >>> 0;376377const tokenizationSupport: ITokenizationSupport = {378getInitialState: () => NullState,379tokenize: undefined!,380tokenizeEncoded: (line, hasEOL, state) => {381switch (line) {382case 'function f() {': {383const tokens = new Uint32Array([3840, otherMetadata1,3858, otherMetadata1,3869, otherMetadata1,38710, otherMetadata1,38811, otherMetadata1,38912, otherMetadata1,39013, otherMetadata1,391]);392return new EncodedTokenizationResult(tokens, [], state);393}394case ' return <p>{true}</p>;': {395const tokens = new Uint32Array([3960, otherMetadata1,3972, otherMetadata1,3988, otherMetadata1,3999, otherMetadata2,40010, otherMetadata2,40111, otherMetadata2,40212, otherMetadata2,40313, otherMetadata1,40417, otherMetadata2,40518, otherMetadata2,40620, otherMetadata2,40721, otherMetadata2,40822, otherMetadata2,409]);410return new EncodedTokenizationResult(tokens, [], state);411}412case '}': {413const tokens = new Uint32Array([4140, otherMetadata1415]);416return new EncodedTokenizationResult(tokens, [], state);417}418}419throw new Error(`Unexpected`);420}421};422423disposables.add(TokenizationRegistry.register(mode1, tokenizationSupport));424disposables.add(languageConfigurationService.register(mode1, {425brackets: [426['{', '}'],427['[', ']'],428['(', ')']429],430}));431disposables.add(languageConfigurationService.register(mode2, {432brackets: [433['{', '}'],434['[', ']'],435['(', ')']436],437}));438439const model = disposables.add(instantiateTextModel(440instantiationService,441[442'function f() {',443' return <p>{true}</p>;',444'}',445].join('\n'),446mode1447));448449model.tokenization.forceTokenization(1);450model.tokenization.forceTokenization(2);451model.tokenization.forceTokenization(3);452453assert.deepStrictEqual(454model.bracketPairs.matchBracket(new Position(2, 14)),455[new Range(2, 13, 2, 14), new Range(2, 18, 2, 19)]456);457458disposables.dispose();459});460461test('issue #88075: TypeScript brace matching is incorrect in `${}` strings', () => {462const disposables = new DisposableStore();463const instantiationService = createModelServices(disposables);464const languageConfigurationService = instantiationService.get(ILanguageConfigurationService);465const mode = 'testMode';466467const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;468469const encodedMode = languageIdCodec.encodeLanguageId(mode);470471const otherMetadata = (472(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)473| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)474) >>> 0;475const stringMetadata = (476(encodedMode << MetadataConsts.LANGUAGEID_OFFSET)477| (StandardTokenType.String << MetadataConsts.TOKEN_TYPE_OFFSET)478) >>> 0;479480const tokenizationSupport: ITokenizationSupport = {481getInitialState: () => NullState,482tokenize: undefined!,483tokenizeEncoded: (line, hasEOL, state) => {484switch (line) {485case 'function hello() {': {486const tokens = new Uint32Array([4870, otherMetadata488]);489return new EncodedTokenizationResult(tokens, [], state);490}491case ' console.log(`${100}`);': {492const tokens = new Uint32Array([4930, otherMetadata,49416, stringMetadata,49519, otherMetadata,49622, stringMetadata,49724, otherMetadata,498]);499return new EncodedTokenizationResult(tokens, [], state);500}501case '}': {502const tokens = new Uint32Array([5030, otherMetadata504]);505return new EncodedTokenizationResult(tokens, [], state);506}507}508throw new Error(`Unexpected`);509}510};511512disposables.add(TokenizationRegistry.register(mode, tokenizationSupport));513disposables.add(languageConfigurationService.register(mode, {514brackets: [515['{', '}'],516['[', ']'],517['(', ')']518],519}));520521const model = disposables.add(instantiateTextModel(522instantiationService,523[524'function hello() {',525' console.log(`${100}`);',526'}'527].join('\n'),528mode529));530531model.tokenization.forceTokenization(1);532model.tokenization.forceTokenization(2);533model.tokenization.forceTokenization(3);534535assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 23)), null);536assert.deepStrictEqual(model.bracketPairs.matchBracket(new Position(2, 20)), null);537538disposables.dispose();539});540});541542543suite('TextModelWithTokens regression tests', () => {544545ensureNoDisposablesAreLeakedInTestSuite();546547test('microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => {548function assertViewLineTokens(model: TextModel, lineNumber: number, forceTokenization: boolean, expected: TestLineToken[]): void {549if (forceTokenization) {550model.tokenization.forceTokenization(lineNumber);551}552const _actual = model.tokenization.getLineTokens(lineNumber).inflate();553interface ISimpleViewToken {554endIndex: number;555foreground: number;556}557const actual: ISimpleViewToken[] = [];558for (let i = 0, len = _actual.getCount(); i < len; i++) {559actual[i] = {560endIndex: _actual.getEndOffset(i),561foreground: _actual.getForeground(i)562};563}564const decode = (token: TestLineToken) => {565return {566endIndex: token.endIndex,567foreground: token.getForeground()568};569};570assert.deepStrictEqual(actual, expected.map(decode));571}572573let _tokenId = 10;574const LANG_ID1 = 'indicisiveMode1';575const LANG_ID2 = 'indicisiveMode2';576577const tokenizationSupport: ITokenizationSupport = {578getInitialState: () => NullState,579tokenize: undefined!,580tokenizeEncoded: (line, hasEOL, state) => {581const myId = ++_tokenId;582const tokens = new Uint32Array(2);583tokens[0] = 0;584tokens[1] = (585myId << MetadataConsts.FOREGROUND_OFFSET586) >>> 0;587return new EncodedTokenizationResult(tokens, [], state);588}589};590591const registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport);592const registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport);593594const model = createTextModel('A model with\ntwo lines');595596assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]);597assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]);598599model.setLanguage(LANG_ID1);600601assertViewLineTokens(model, 1, true, [createViewLineToken(12, 11)]);602assertViewLineTokens(model, 2, true, [createViewLineToken(9, 12)]);603604model.setLanguage(LANG_ID2);605606assertViewLineTokens(model, 1, false, [createViewLineToken(12, 1)]);607assertViewLineTokens(model, 2, false, [createViewLineToken(9, 1)]);608609model.dispose();610registration1.dispose();611registration2.dispose();612613function createViewLineToken(endIndex: number, foreground: number): TestLineToken {614const metadata = (615(foreground << MetadataConsts.FOREGROUND_OFFSET)616) >>> 0;617return new TestLineToken(endIndex, metadata);618}619});620621622test('microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => {623624const disposables = new DisposableStore();625const model = createTextModelWithBrackets(626disposables,627[628'Imports System',629'Imports System.Collections.Generic',630'',631'Module m1',632'',633'\tSub Main()',634'\tEnd Sub',635'',636'End Module',637].join('\n'),638[639['module', 'end module'],640['sub', 'end sub']641]642);643644const actual = model.bracketPairs.matchBracket(new Position(4, 1));645assert.deepStrictEqual(actual, [new Range(4, 1, 4, 7), new Range(9, 1, 9, 11)]);646647disposables.dispose();648});649650test('issue #11856: Bracket matching does not work as expected if the opening brace symbol is contained in the closing brace symbol', () => {651652const disposables = new DisposableStore();653const model = createTextModelWithBrackets(654disposables,655[656'sequence "outer"',657' sequence "inner"',658' endsequence',659'endsequence',660].join('\n'),661[662['sequence', 'endsequence'],663['feature', 'endfeature']664]665);666667const actual = model.bracketPairs.matchBracket(new Position(3, 9));668assert.deepStrictEqual(actual, [new Range(2, 6, 2, 14), new Range(3, 6, 3, 17)]);669670disposables.dispose();671});672673test('issue #63822: Wrong embedded language detected for empty lines', () => {674const disposables = new DisposableStore();675const instantiationService = createModelServices(disposables);676const languageService = instantiationService.get(ILanguageService);677678const outerMode = 'outerMode';679const innerMode = 'innerMode';680681disposables.add(languageService.registerLanguage({ id: outerMode }));682disposables.add(languageService.registerLanguage({ id: innerMode }));683684const languageIdCodec = instantiationService.get(ILanguageService).languageIdCodec;685const encodedInnerMode = languageIdCodec.encodeLanguageId(innerMode);686687const tokenizationSupport: ITokenizationSupport = {688getInitialState: () => NullState,689tokenize: undefined!,690tokenizeEncoded: (line, hasEOL, state) => {691const tokens = new Uint32Array(2);692tokens[0] = 0;693tokens[1] = (694encodedInnerMode << MetadataConsts.LANGUAGEID_OFFSET695) >>> 0;696return new EncodedTokenizationResult(tokens, [], state);697}698};699700disposables.add(TokenizationRegistry.register(outerMode, tokenizationSupport));701702const model = disposables.add(instantiateTextModel(instantiationService, 'A model with one line', outerMode));703704model.tokenization.forceTokenization(1);705assert.strictEqual(model.getLanguageIdAtPosition(1, 1), innerMode);706707disposables.dispose();708});709});710711suite('TextModel.getLineIndentGuide', () => {712713ensureNoDisposablesAreLeakedInTestSuite();714715function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void {716const languageId = 'testLang';717const disposables = new DisposableStore();718const instantiationService = createModelServices(disposables);719const languageService = instantiationService.get(ILanguageService);720disposables.add(languageService.registerLanguage({ id: languageId }));721722const text = lines.map(l => l[4]).join('\n');723const model = disposables.add(instantiateTextModel(instantiationService, text, languageId));724model.updateOptions({ indentSize: indentSize });725726const actualIndents = model.guides.getLinesIndentGuides(1, model.getLineCount());727728const actual: [number, number, number, number, string][] = [];729for (let line = 1; line <= model.getLineCount(); line++) {730const activeIndentGuide = model.guides.getActiveIndentGuide(line, 1, model.getLineCount());731actual[line - 1] = [actualIndents[line - 1], activeIndentGuide.startLineNumber, activeIndentGuide.endLineNumber, activeIndentGuide.indent, model.getLineContent(line)];732}733734assert.deepStrictEqual(actual, lines);735736disposables.dispose();737}738739test('getLineIndentGuide one level 2', () => {740assertIndentGuides([741[0, 2, 4, 1, 'A'],742[1, 2, 4, 1, ' A'],743[1, 2, 4, 1, ' A'],744[1, 2, 4, 1, ' A'],745], 2);746});747748test('getLineIndentGuide two levels', () => {749assertIndentGuides([750[0, 2, 5, 1, 'A'],751[1, 2, 5, 1, ' A'],752[1, 4, 5, 2, ' A'],753[2, 4, 5, 2, ' A'],754[2, 4, 5, 2, ' A'],755], 2);756});757758test('getLineIndentGuide three levels', () => {759assertIndentGuides([760[0, 2, 4, 1, 'A'],761[1, 3, 4, 2, ' A'],762[2, 4, 4, 3, ' A'],763[3, 4, 4, 3, ' A'],764[0, 5, 5, 0, 'A'],765], 2);766});767768test('getLineIndentGuide decreasing indent', () => {769assertIndentGuides([770[2, 1, 1, 2, ' A'],771[1, 1, 1, 2, ' A'],772[0, 1, 2, 1, 'A'],773], 2);774});775776test('getLineIndentGuide Java', () => {777assertIndentGuides([778/* 1*/[0, 2, 9, 1, 'class A {'],779/* 2*/[1, 3, 4, 2, ' void foo() {'],780/* 3*/[2, 3, 4, 2, ' console.log(1);'],781/* 4*/[2, 3, 4, 2, ' console.log(2);'],782/* 5*/[1, 3, 4, 2, ' }'],783/* 6*/[1, 2, 9, 1, ''],784/* 7*/[1, 8, 8, 2, ' void bar() {'],785/* 8*/[2, 8, 8, 2, ' console.log(3);'],786/* 9*/[1, 8, 8, 2, ' }'],787/*10*/[0, 2, 9, 1, '}'],788/*11*/[0, 12, 12, 1, 'interface B {'],789/*12*/[1, 12, 12, 1, ' void bar();'],790/*13*/[0, 12, 12, 1, '}'],791], 2);792});793794test('getLineIndentGuide Javadoc', () => {795assertIndentGuides([796[0, 2, 3, 1, '/**'],797[1, 2, 3, 1, ' * Comment'],798[1, 2, 3, 1, ' */'],799[0, 5, 6, 1, 'class A {'],800[1, 5, 6, 1, ' void foo() {'],801[1, 5, 6, 1, ' }'],802[0, 5, 6, 1, '}'],803], 2);804});805806test('getLineIndentGuide Whitespace', () => {807assertIndentGuides([808[0, 2, 7, 1, 'class A {'],809[1, 2, 7, 1, ''],810[1, 4, 5, 2, ' void foo() {'],811[2, 4, 5, 2, ' '],812[2, 4, 5, 2, ' return 1;'],813[1, 4, 5, 2, ' }'],814[1, 2, 7, 1, ' '],815[0, 2, 7, 1, '}']816], 2);817});818819test('getLineIndentGuide Tabs', () => {820assertIndentGuides([821[0, 2, 7, 1, 'class A {'],822[1, 2, 7, 1, '\t\t'],823[1, 4, 5, 2, '\tvoid foo() {'],824[2, 4, 5, 2, '\t \t//hello'],825[2, 4, 5, 2, '\t return 2;'],826[1, 4, 5, 2, ' \t}'],827[1, 2, 7, 1, ' '],828[0, 2, 7, 1, '}']829], 4);830});831832test('getLineIndentGuide checker.ts', () => {833assertIndentGuides([834/* 1*/[0, 1, 1, 0, '/// <reference path="binder.ts"/>'],835/* 2*/[0, 2, 2, 0, ''],836/* 3*/[0, 3, 3, 0, '/* @internal */'],837/* 4*/[0, 5, 16, 1, 'namespace ts {'],838/* 5*/[1, 5, 16, 1, ' let nextSymbolId = 1;'],839/* 6*/[1, 5, 16, 1, ' let nextNodeId = 1;'],840/* 7*/[1, 5, 16, 1, ' let nextMergeId = 1;'],841/* 8*/[1, 5, 16, 1, ' let nextFlowId = 1;'],842/* 9*/[1, 5, 16, 1, ''],843/*10*/[1, 11, 15, 2, ' export function getNodeId(node: Node): number {'],844/*11*/[2, 12, 13, 3, ' if (!node.id) {'],845/*12*/[3, 12, 13, 3, ' node.id = nextNodeId;'],846/*13*/[3, 12, 13, 3, ' nextNodeId++;'],847/*14*/[2, 12, 13, 3, ' }'],848/*15*/[2, 11, 15, 2, ' return node.id;'],849/*16*/[1, 11, 15, 2, ' }'],850/*17*/[0, 5, 16, 1, '}']851], 4);852});853854test('issue #8425 - Missing indentation lines for first level indentation', () => {855assertIndentGuides([856[1, 2, 3, 2, '\tindent1'],857[2, 2, 3, 2, '\t\tindent2'],858[2, 2, 3, 2, '\t\tindent2'],859[1, 2, 3, 2, '\tindent1']860], 4);861});862863test('issue #8952 - Indentation guide lines going through text on .yml file', () => {864assertIndentGuides([865[0, 2, 5, 1, 'properties:'],866[1, 3, 5, 2, ' emailAddress:'],867[2, 3, 5, 2, ' - bla'],868[2, 5, 5, 3, ' - length:'],869[3, 5, 5, 3, ' max: 255'],870[0, 6, 6, 0, 'getters:']871], 4);872});873874test('issue #11892 - Indent guides look funny', () => {875assertIndentGuides([876[0, 2, 7, 1, 'function test(base) {'],877[1, 3, 6, 2, '\tswitch (base) {'],878[2, 4, 4, 3, '\t\tcase 1:'],879[3, 4, 4, 3, '\t\t\treturn 1;'],880[2, 6, 6, 3, '\t\tcase 2:'],881[3, 6, 6, 3, '\t\t\treturn 2;'],882[1, 2, 7, 1, '\t}'],883[0, 2, 7, 1, '}']884], 4);885});886887test('issue #12398 - Problem in indent guidelines', () => {888assertIndentGuides([889[2, 2, 2, 3, '\t\t.bla'],890[3, 2, 2, 3, '\t\t\tlabel(for)'],891[0, 3, 3, 0, 'include script']892], 4);893});894895test('issue #49173', () => {896const model = createTextModel([897'class A {',898' public m1(): void {',899' }',900' public m2(): void {',901' }',902' public m3(): void {',903' }',904' public m4(): void {',905' }',906' public m5(): void {',907' }',908'}',909].join('\n'));910911const actual = model.guides.getActiveIndentGuide(2, 4, 9);912assert.deepStrictEqual(actual, { startLineNumber: 2, endLineNumber: 9, indent: 1 });913model.dispose();914});915916test('tweaks - no active', () => {917assertIndentGuides([918[0, 1, 1, 0, 'A'],919[0, 2, 2, 0, 'A']920], 2);921});922923test('tweaks - inside scope', () => {924assertIndentGuides([925[0, 2, 2, 1, 'A'],926[1, 2, 2, 1, ' A']927], 2);928});929930test('tweaks - scope start', () => {931assertIndentGuides([932[0, 2, 2, 1, 'A'],933[1, 2, 2, 1, ' A'],934[0, 2, 2, 1, 'A']935], 2);936});937938test('tweaks - empty line', () => {939assertIndentGuides([940[0, 2, 4, 1, 'A'],941[1, 2, 4, 1, ' A'],942[1, 2, 4, 1, ''],943[1, 2, 4, 1, ' A'],944[0, 2, 4, 1, 'A']945], 2);946});947});948949950