UNPKG

monaco-editor-core

Version:

A browser based code editor

353 lines (352 loc) • 14.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export const foldSourceAbbr = { [0 /* FoldSource.provider */]: ' ', [1 /* FoldSource.userDefined */]: 'u', [2 /* FoldSource.recovered */]: 'r', }; export const MAX_FOLDING_REGIONS = 0xFFFF; export const MAX_LINE_NUMBER = 0xFFFFFF; const MASK_INDENT = 0xFF000000; class BitField { constructor(size) { const numWords = Math.ceil(size / 32); this._states = new Uint32Array(numWords); } get(index) { const arrayIndex = (index / 32) | 0; const bit = index % 32; return (this._states[arrayIndex] & (1 << bit)) !== 0; } set(index, newState) { const arrayIndex = (index / 32) | 0; const bit = index % 32; const value = this._states[arrayIndex]; if (newState) { this._states[arrayIndex] = value | (1 << bit); } else { this._states[arrayIndex] = value & ~(1 << bit); } } } export class FoldingRegions { constructor(startIndexes, endIndexes, types) { 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 BitField(startIndexes.length); this._userDefinedStates = new BitField(startIndexes.length); this._recoveredStates = new BitField(startIndexes.length); this._types = types; this._parentsComputed = false; } ensureParentIndices() { if (!this._parentsComputed) { this._parentsComputed = true; const parentIndexes = []; const isInsideLast = (startLineNumber, endLineNumber) => { const 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++) { const startLineNumber = this._startIndexes[i]; const 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(); } const 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); } } } get length() { return this._startIndexes.length; } getStartLineNumber(index) { return this._startIndexes[index] & MAX_LINE_NUMBER; } getEndLineNumber(index) { return this._endIndexes[index] & MAX_LINE_NUMBER; } getType(index) { return this._types ? this._types[index] : undefined; } hasTypes() { return !!this._types; } isCollapsed(index) { return this._collapseStates.get(index); } setCollapsed(index, newState) { this._collapseStates.set(index, newState); } isUserDefined(index) { return this._userDefinedStates.get(index); } setUserDefined(index, newState) { return this._userDefinedStates.set(index, newState); } isRecovered(index) { return this._recoveredStates.get(index); } setRecovered(index, newState) { return this._recoveredStates.set(index, newState); } getSource(index) { if (this.isUserDefined(index)) { return 1 /* FoldSource.userDefined */; } else if (this.isRecovered(index)) { return 2 /* FoldSource.recovered */; } return 0 /* FoldSource.provider */; } setSource(index, source) { if (source === 1 /* FoldSource.userDefined */) { this.setUserDefined(index, true); this.setRecovered(index, false); } else if (source === 2 /* FoldSource.recovered */) { this.setUserDefined(index, false); this.setRecovered(index, true); } else { this.setUserDefined(index, false); this.setRecovered(index, false); } } setCollapsedAllOfType(type, newState) { let hasChanged = false; if (this._types) { for (let i = 0; i < this._types.length; i++) { if (this._types[i] === type) { this.setCollapsed(i, newState); hasChanged = true; } } } return hasChanged; } toRegion(index) { return new FoldingRegion(this, index); } getParentIndex(index) { this.ensureParentIndices(); const parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16); if (parent === MAX_FOLDING_REGIONS) { return -1; } return parent; } contains(index, line) { return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line; } findIndex(line) { let low = 0, high = this._startIndexes.length; if (high === 0) { return -1; // no children } while (low < high) { const mid = Math.floor((low + high) / 2); if (line < this.getStartLineNumber(mid)) { high = mid; } else { low = mid + 1; } } return low - 1; } findRange(line) { let index = this.findIndex(line); if (index >= 0) { const 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; } toString() { const res = []; for (let i = 0; i < this.length; i++) { res[i] = `[${foldSourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`; } return res.join(', '); } toFoldRange(index) { return { startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER, endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER, type: this._types ? this._types[index] : undefined, isCollapsed: this.isCollapsed(index), source: this.getSource(index) }; } static fromFoldRanges(ranges) { const rangesLength = ranges.length; const startIndexes = new Uint32Array(rangesLength); const endIndexes = new Uint32Array(rangesLength); let types = []; let gotTypes = false; for (let i = 0; i < rangesLength; i++) { const range = ranges[i]; startIndexes[i] = range.startLineNumber; endIndexes[i] = range.endLineNumber; types.push(range.type); if (range.type) { gotTypes = true; } } if (!gotTypes) { types = undefined; } const regions = new FoldingRegions(startIndexes, endIndexes, types); for (let i = 0; i < rangesLength; i++) { if (ranges[i].isCollapsed) { regions.setCollapsed(i, true); } regions.setSource(i, ranges[i].source); } return regions; } /** * Two inputs, each a FoldingRegions or a FoldRange[], are merged. * Each input must be pre-sorted on startLineNumber. * The first list is assumed to always include all regions currently defined by range providers. * The second list only contains the previously collapsed and all manual ranges. * If the line position matches, the range of the new range is taken, and the range is no longer manual * When an entry in one list overlaps an entry in the other, the second list's entry "wins" and * overlapping entries in the first list are discarded. * Invalid entries are discarded. An entry is invalid if: * the start and end line numbers aren't a valid range of line numbers, * it is out of sequence or has the same start line as a preceding entry, * it overlaps a preceding entry and is not fully contained by that entry. */ static sanitizeAndMerge(rangesA, rangesB, maxLineNumber, selection) { maxLineNumber = maxLineNumber ?? Number.MAX_VALUE; const getIndexedFunction = (r, limit) => { return Array.isArray(r) ? ((i) => { return (i < limit) ? r[i] : undefined; }) : ((i) => { return (i < limit) ? r.toFoldRange(i) : undefined; }); }; const getA = getIndexedFunction(rangesA, rangesA.length); const getB = getIndexedFunction(rangesB, rangesB.length); let indexA = 0; let indexB = 0; let nextA = getA(0); let nextB = getB(0); const stackedRanges = []; let topStackedRange; let prevLineNumber = 0; const resultRanges = []; while (nextA || nextB) { let useRange = undefined; if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) { if (nextA && nextA.startLineNumber === nextB.startLineNumber) { if (nextB.source === 1 /* FoldSource.userDefined */) { // a user defined range (possibly unfolded) useRange = nextB; } else { // a previously folded range or a (possibly unfolded) recovered range useRange = nextA; // stays collapsed if the range still has the same number of lines or the selection is not in the range or after it useRange.isCollapsed = nextB.isCollapsed && (nextA.endLineNumber === nextB.endLineNumber || !selection?.startsInside(nextA.startLineNumber + 1, nextA.endLineNumber + 1)); useRange.source = 0 /* FoldSource.provider */; } nextA = getA(++indexA); // not necessary, just for speed } else { useRange = nextB; if (nextB.isCollapsed && nextB.source === 0 /* FoldSource.provider */) { // a previously collapsed range useRange.source = 2 /* FoldSource.recovered */; } } nextB = getB(++indexB); } else { // nextA is next. The user folded B set takes precedence and we sometimes need to look // ahead in it to check for an upcoming conflict. let scanIndex = indexB; let prescanB = nextB; while (true) { if (!prescanB || prescanB.startLineNumber > nextA.endLineNumber) { useRange = nextA; break; // no conflict, use this nextA } if (prescanB.source === 1 /* FoldSource.userDefined */ && prescanB.endLineNumber > nextA.endLineNumber) { // we found a user folded range, it wins break; // without setting nextResult, so this nextA gets skipped } prescanB = getB(++scanIndex); } nextA = getA(++indexA); } if (useRange) { while (topStackedRange && topStackedRange.endLineNumber < useRange.startLineNumber) { topStackedRange = stackedRanges.pop(); } if (useRange.endLineNumber > useRange.startLineNumber && useRange.startLineNumber > prevLineNumber && useRange.endLineNumber <= maxLineNumber && (!topStackedRange || topStackedRange.endLineNumber >= useRange.endLineNumber)) { resultRanges.push(useRange); prevLineNumber = useRange.startLineNumber; if (topStackedRange) { stackedRanges.push(topStackedRange); } topStackedRange = useRange; } } } return resultRanges; } } export class FoldingRegion { constructor(ranges, index) { this.ranges = ranges; this.index = index; } get startLineNumber() { return this.ranges.getStartLineNumber(this.index); } get endLineNumber() { return this.ranges.getEndLineNumber(this.index); } get regionIndex() { return this.index; } get parentIndex() { return this.ranges.getParentIndex(this.index); } get isCollapsed() { return this.ranges.isCollapsed(this.index); } containedBy(range) { return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber; } containsLine(lineNumber) { return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber; } }