diff --git a/src/vs/editor/common/model/bracketPairColorizer/ast.ts b/src/vs/editor/common/model/bracketPairColorizer/ast.ts index 3bd7ad539e8f2..18f6114504aa7 100644 --- a/src/vs/editor/common/model/bracketPairColorizer/ast.ts +++ b/src/vs/editor/common/model/bracketPairColorizer/ast.ts @@ -35,7 +35,7 @@ abstract class BaseAstNode { /** * Flattenes all lists in this AST. Only for debugging. */ - abstract normalizeLists(): AstNode; + abstract flattenLists(): AstNode; /** * Creates a deep clone. @@ -103,12 +103,12 @@ export class PairAstNode extends BaseAstNode { return true; } - normalizeLists(): PairAstNode { + flattenLists(): PairAstNode { return PairAstNode.create( this.category, - this.openingBracket.normalizeLists(), - this.child && this.child.normalizeLists(), - this.closingBracket && this.closingBracket.normalizeLists() + this.openingBracket.flattenLists(), + this.child && this.child.flattenLists(), + this.closingBracket && this.closingBracket.flattenLists() ); } @@ -224,10 +224,10 @@ export class ListAstNode extends BaseAstNode { ); } - normalizeLists(): ListAstNode { + flattenLists(): ListAstNode { const items = new Array(); for (const c of this.children) { - const normalized = c.normalizeLists(); + const normalized = c.flattenLists(); if (normalized.kind === AstNodeKind.List) { items.push(...normalized._items); } else { @@ -385,7 +385,7 @@ export class TextAstNode extends BaseAstNode { return !endLineDidChange; } - normalizeLists(): TextAstNode { + flattenLists(): TextAstNode { return this; } clone(): TextAstNode { @@ -436,7 +436,7 @@ export class BracketAstNode extends BaseAstNode { return false; } - normalizeLists(): BracketAstNode { + flattenLists(): BracketAstNode { return this; } @@ -470,7 +470,7 @@ export class InvalidBracketAstNode extends BaseAstNode { return !expectedClosingCategories.intersects(this.unopenedBrackets); } - normalizeLists(): InvalidBracketAstNode { + flattenLists(): InvalidBracketAstNode { return this; } diff --git a/src/vs/editor/common/model/bracketPairColorizer/mergeItems.ts b/src/vs/editor/common/model/bracketPairColorizer/mergeItems.ts index 0b96e30d2b271..57ec74f0b19d1 100644 --- a/src/vs/editor/common/model/bracketPairColorizer/mergeItems.ts +++ b/src/vs/editor/common/model/bracketPairColorizer/mergeItems.ts @@ -7,6 +7,7 @@ import { AstNode, ListAstNode } from './ast'; /** * Merges a list of (2,3) AstNode's into a single (2,3) AstNode. + * This mutates the items of the input array! */ export function merge23Trees(items: AstNode[]): AstNode | null { if (items.length === 0) { diff --git a/src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts b/src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts index 3b2dae4f0bcb7..692da947a1ec1 100644 --- a/src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts +++ b/src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts @@ -39,14 +39,13 @@ export class NodeReader { if (lengthLessThan(offset, curNodeOffset)) { // The next best node is not here yet. - // The reader must advance before a cached node is hite. + // The reader must advance before a cached node is hit. return undefined; } if (lengthLessThan(curNodeOffset, offset)) { // The reader is ahead of the current node. - if (lengthAdd(curNodeOffset, curNode.length) <= - offset) { + if (lengthAdd(curNodeOffset, curNode.length) <= offset) { // The reader is after the end of the current node. this.nextNodeAfterCurrent(); } else { @@ -66,19 +65,19 @@ export class NodeReader { if (predicate(curNode)) { this.nextNodeAfterCurrent(); return curNode; + } else { + // look for shorter node + if (curNode.children.length === 0) { + // There is no shorter node. + this.nextNodeAfterCurrent(); + return undefined; + } else { + // Descend into first child & repeat. + this.nextNodes.push(curNode.children[0]); + this.offsets.push(curNodeOffset); + this.idxs.push(0); + } } - - // look for smaller node - if (curNode.children.length === 0) { - // There is no smaller node. - this.nextNodeAfterCurrent(); - return undefined; - } - - // Go to next node & repeat. - this.nextNodes.push(curNode.children[0]); - this.offsets.push(curNodeOffset); - this.idxs.push(0); } } } diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/positionMapping.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts similarity index 99% rename from src/vs/editor/test/common/model/bracketPairColorizer/positionMapping.test.ts rename to src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts index 41db54c586c90..e2cf155dd975b 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/positionMapping.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts @@ -10,7 +10,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairColorizer/beforeEditPositionMapper'; import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairColorizer/length'; -suite('Bracket Pair Colorizer - Position Mapping', () => { +suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { test('Single-Line 1', () => { assert.deepStrictEqual( compute( diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts new file mode 100644 index 0000000000000..6d3982af495e7 --- /dev/null +++ b/src/vs/editor/test/common/model/bracketPairColorizer/length.test.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { Length, lengthAdd, lengthDiffNonNeg, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairColorizer/length'; + +suite('Bracket Pair Colorizer - Length', () => { + function toStr(length: Length): string { + return lengthToObj(length).toString(); + } + + test('Basic', () => { + const l1 = toLength(100, 10); + assert.strictEqual(lengthToObj(l1).lineCount, 100); + assert.strictEqual(lengthToObj(l1).columnCount, 10); + + assert.deepStrictEqual(toStr(lengthAdd(l1, toLength(100, 10))), '200,10'); + assert.deepStrictEqual(toStr(lengthAdd(l1, toLength(0, 10))), '100,20'); + }); + + test('lengthDiffNonNeg', () => { + assert.deepStrictEqual( + toStr( + lengthDiffNonNeg( + toLength(100, 10), + toLength(100, 20)) + ), + '0,10' + ); + + assert.deepStrictEqual( + toStr( + lengthDiffNonNeg( + toLength(100, 10), + toLength(101, 20)) + ), + '1,20' + ); + + assert.deepStrictEqual( + toStr( + lengthDiffNonNeg( + toLength(101, 30), + toLength(101, 20)) + ), + '0,0' + ); + + assert.deepStrictEqual( + toStr( + lengthDiffNonNeg( + toLength(102, 10), + toLength(101, 20)) + ), + '0,0' + ); + }); +}); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/mergeItems.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/mergeItems.test.ts new file mode 100644 index 0000000000000..7f561b383c92e --- /dev/null +++ b/src/vs/editor/test/common/model/bracketPairColorizer/mergeItems.test.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairColorizer/ast'; +import { toLength } from 'vs/editor/common/model/bracketPairColorizer/length'; +import { merge23Trees } from 'vs/editor/common/model/bracketPairColorizer/mergeItems'; + +suite('Bracket Pair Colorizer - mergeItems', () => { + test('Clone', () => { + const tree = ListAstNode.create([ + new TextAstNode(toLength(1, 1)), + new TextAstNode(toLength(1, 1)), + ]); + + assert.ok(equals(tree, tree.clone())); + }); + + function equals(node1: AstNode, node2: AstNode): boolean { + if (node1.length !== node2.length) { + return false; + } + + if (node1.children.length !== node2.children.length) { + return false; + } + + for (let i = 0; i < node1.children.length; i++) { + if (!equals(node1.children[i], node2.children[i])) { + return false; + } + } + + if (!node1.unopenedBrackets.equals(node2.unopenedBrackets)) { + return false; + } + + if (node1.kind === AstNodeKind.Pair && node2.kind === AstNodeKind.Pair) { + return node1.category === node2.category; + } else if (node1.kind === node2.kind) { + return true; + } + + return false; + } + + function testMerge(lists: AstNode[]) { + const node = (merge23Trees(lists.map(l => l.clone())) || ListAstNode.create([])).flattenLists(); + // This trivial merge does not maintain the (2,3) tree invariant. + const referenceNode = ListAstNode.create(lists).flattenLists(); + + assert.ok(equals(node, referenceNode), 'merge23Trees failed'); + } + + test('Empty List', () => { + testMerge([]); + }); + + test('Same Height Lists', () => { + const textNode = new TextAstNode(toLength(1, 1)); + const tree = ListAstNode.create([textNode.clone(), textNode.clone()]); + testMerge([tree.clone(), tree.clone(), tree.clone(), tree.clone(), tree.clone()]); + }); + + test('Different Height Lists 1', () => { + const textNode = new TextAstNode(toLength(1, 1)); + const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]); + const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]); + + testMerge([tree1, tree2]); + }); + + test('Different Height Lists 2', () => { + const textNode = new TextAstNode(toLength(1, 1)); + const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]); + const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]); + + testMerge([tree2, tree1]); + }); + + test('Different Height Lists 3', () => { + const textNode = new TextAstNode(toLength(1, 1)); + const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]); + const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]); + + testMerge([tree2, tree1, tree1, tree2, tree2]); + }); +});