/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export interface ILineRange { startLineNumber: number; endLineNumber: number; } export const MAX_FOLDING_REGIONS = 0xffff; export const MAX_LINE_NUMBER = 0xffffff; const MASK_INDENT = 0xff000000; export class FoldingRegions { private readonly _startIndexes: Uint32Array; private readonly _endIndexes: Uint32Array; private readonly _collapseStates: Uint32Array; private _parentsComputed: boolean; private readonly _types: Array | undefined; constructor( startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array ) { if ( startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS ) { throw new Error('invalid startIndexes or endIndexes size'); } this._startIndexes = startIndexes; this._endIndexes = endIndexes; this._collapseStates = new Uint32Array(Math.ceil(startIndexes.length / 32)); this._types = types; this._parentsComputed = false; } private ensureParentIndices() { if (!this._parentsComputed) { this._parentsComputed = true; let parentIndexes: number[] = []; let isInsideLast = (startLineNumber: number, endLineNumber: number) => { let index = parentIndexes[parentIndexes.length - 1]; return ( this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber ); }; for (let i = 0, len = this._startIndexes.length; i < len; i++) { let startLineNumber = this._startIndexes[i]; let endLineNumber = this._endIndexes[i]; if ( startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER ) { throw new Error( 'startLineNumber or endLineNumber must not exceed ' + MAX_LINE_NUMBER ); } while ( parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber) ) { parentIndexes.pop(); } let parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1; parentIndexes.push(i); this._startIndexes[i] = startLineNumber + ((parentIndex & 0xff) << 24); this._endIndexes[i] = endLineNumber + ((parentIndex & 0xff00) << 16); } } } public get length(): number { return this._startIndexes.length; } public getStartLineNumber(index: number): number { return this._startIndexes[index] & MAX_LINE_NUMBER; } public getEndLineNumber(index: number): number { return this._endIndexes[index] & MAX_LINE_NUMBER; } public getType(index: number): string | undefined { return this._types ? this._types[index] : undefined; } public hasTypes() { return !!this._types; } public isCollapsed(index: number): boolean { let arrayIndex = (index / 32) | 0; let bit = index % 32; return (this._collapseStates[arrayIndex] & (1 << bit)) !== 0; } public setCollapsed(index: number, newState: boolean) { let arrayIndex = (index / 32) | 0; let bit = index % 32; let value = this._collapseStates[arrayIndex]; if (newState) { this._collapseStates[arrayIndex] = value | (1 << bit); } else { this._collapseStates[arrayIndex] = value & ~(1 << bit); } } public toRegion(index: number): FoldingRegion { return new FoldingRegion(this, index); } public getParentIndex(index: number) { this.ensureParentIndices(); let parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16); if (parent === MAX_FOLDING_REGIONS) { return -1; } return parent; } public contains(index: number, line: number) { return ( this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line ); } private findIndex(line: number) { let low = 0, high = this._startIndexes.length; if (high === 0) { return -1; // no children } while (low < high) { let mid = Math.floor((low + high) / 2); if (line < this.getStartLineNumber(mid)) { high = mid; } else { low = mid + 1; } } return low - 1; } public findRange(line: number): number { let index = this.findIndex(line); if (index >= 0) { let endLineNumber = this.getEndLineNumber(index); if (endLineNumber >= line) { return index; } index = this.getParentIndex(index); while (index !== -1) { if (this.contains(index, line)) { return index; } index = this.getParentIndex(index); } } return -1; } public toString() { let res: string[] = []; for (let i = 0; i < this.length; i++) { res[i] = `[${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber( i )}/${this.getEndLineNumber(i)}`; } return res.join(', '); } } export class FoldingRegion { constructor(private readonly ranges: FoldingRegions, private index: number) {} public get startLineNumber() { return this.ranges.getStartLineNumber(this.index); } public get endLineNumber() { return this.ranges.getEndLineNumber(this.index); } public get regionIndex() { return this.index; } public get parentIndex() { return this.ranges.getParentIndex(this.index); } public get isCollapsed() { return this.ranges.isCollapsed(this.index); } containedBy(range: ILineRange): boolean { return ( range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber ); } containsLine(lineNumber: number) { return ( this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber ); } }