Path: blob/main/extensions/copilot/test/simulation/fixtures/tests/for-method-issue-3699/foldingRanges.ts
13405 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*--------------------------------------------------------------------------------------------*/45export interface ILineRange {6startLineNumber: number;7endLineNumber: number;8}910export const enum FoldSource {11provider = 0,12userDefined = 1,13recovered = 214}1516export const foldSourceAbbr = {17[FoldSource.provider]: ' ',18[FoldSource.userDefined]: 'u',19[FoldSource.recovered]: 'r',20};2122export interface FoldRange {23startLineNumber: number;24endLineNumber: number;25type: string | undefined;26isCollapsed: boolean;27source: FoldSource;28}2930export const MAX_FOLDING_REGIONS = 0xFFFF;31export const MAX_LINE_NUMBER = 0xFFFFFF;3233const MASK_INDENT = 0xFF000000;3435class BitField {36private readonly _states: Uint32Array;37constructor(size: number) {38const numWords = Math.ceil(size / 32);39this._states = new Uint32Array(numWords);40}4142public get(index: number): boolean {43const arrayIndex = (index / 32) | 0;44const bit = index % 32;45return (this._states[arrayIndex] & (1 << bit)) !== 0;46}4748public set(index: number, newState: boolean) {49const arrayIndex = (index / 32) | 0;50const bit = index % 32;51const value = this._states[arrayIndex];52if (newState) {53this._states[arrayIndex] = value | (1 << bit);54} else {55this._states[arrayIndex] = value & ~(1 << bit);56}57}58}5960export class FoldingRegions {61private readonly _startIndexes: Uint32Array;62private readonly _endIndexes: Uint32Array;63private readonly _collapseStates: BitField;64private readonly _userDefinedStates: BitField;65private readonly _recoveredStates: BitField;6667private _parentsComputed: boolean;68private readonly _types: Array<string | undefined> | undefined;6970constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array<string | undefined>) {71if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {72throw new Error('invalid startIndexes or endIndexes size');73}74this._startIndexes = startIndexes;75this._endIndexes = endIndexes;76this._collapseStates = new BitField(startIndexes.length);77this._userDefinedStates = new BitField(startIndexes.length);78this._recoveredStates = new BitField(startIndexes.length);79this._types = types;80this._parentsComputed = false;81}8283private ensureParentIndices() {84if (!this._parentsComputed) {85this._parentsComputed = true;86const parentIndexes: number[] = [];87const isInsideLast = (startLineNumber: number, endLineNumber: number) => {88const index = parentIndexes[parentIndexes.length - 1];89return this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber;90};91for (let i = 0, len = this._startIndexes.length; i < len; i++) {92const startLineNumber = this._startIndexes[i];93const endLineNumber = this._endIndexes[i];94if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {95throw new Error('startLineNumber or endLineNumber must not exceed ' + MAX_LINE_NUMBER);96}97while (parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber)) {98parentIndexes.pop();99}100const parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1;101parentIndexes.push(i);102this._startIndexes[i] = startLineNumber + ((parentIndex & 0xFF) << 24);103this._endIndexes[i] = endLineNumber + ((parentIndex & 0xFF00) << 16);104}105}106}107108public get length(): number {109return this._startIndexes.length;110}111112public getStartLineNumber(index: number): number {113return this._startIndexes[index] & MAX_LINE_NUMBER;114}115116public getEndLineNumber(index: number): number {117return this._endIndexes[index] & MAX_LINE_NUMBER;118}119120public getType(index: number): string | undefined {121return this._types ? this._types[index] : undefined;122}123124public hasTypes() {125return !!this._types;126}127128public isCollapsed(index: number): boolean {129return this._collapseStates.get(index);130}131132public setCollapsed(index: number, newState: boolean) {133this._collapseStates.set(index, newState);134}135136private isUserDefined(index: number): boolean {137return this._userDefinedStates.get(index);138}139140private setUserDefined(index: number, newState: boolean) {141return this._userDefinedStates.set(index, newState);142}143144private isRecovered(index: number): boolean {145return this._recoveredStates.get(index);146}147148private setRecovered(index: number, newState: boolean) {149return this._recoveredStates.set(index, newState);150}151152public getSource(index: number): FoldSource {153if (this.isUserDefined(index)) {154return FoldSource.userDefined;155} else if (this.isRecovered(index)) {156return FoldSource.recovered;157}158return FoldSource.provider;159}160161public setSource(index: number, source: FoldSource): void {162if (source === FoldSource.userDefined) {163this.setUserDefined(index, true);164this.setRecovered(index, false);165} else if (source === FoldSource.recovered) {166this.setUserDefined(index, false);167this.setRecovered(index, true);168} else {169this.setUserDefined(index, false);170this.setRecovered(index, false);171}172}173174public setCollapsedAllOfType(type: string, newState: boolean) {175let hasChanged = false;176if (this._types) {177for (let i = 0; i < this._types.length; i++) {178if (this._types[i] === type) {179this.setCollapsed(i, newState);180hasChanged = true;181}182}183}184return hasChanged;185}186187public toRegion(index: number): FoldingRegion {188return new FoldingRegion(this, index);189}190191public getParentIndex(index: number) {192this.ensureParentIndices();193const parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16);194if (parent === MAX_FOLDING_REGIONS) {195return -1;196}197return parent;198}199200public contains(index: number, line: number) {201return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line;202}203204private findIndex(line: number) {205let low = 0, high = this._startIndexes.length;206if (high === 0) {207return -1; // no children208}209while (low < high) {210const mid = Math.floor((low + high) / 2);211if (line < this.getStartLineNumber(mid)) {212high = mid;213} else {214low = mid + 1;215}216}217return low - 1;218}219220public findRange(line: number): number {221let index = this.findIndex(line);222if (index >= 0) {223const endLineNumber = this.getEndLineNumber(index);224if (endLineNumber >= line) {225return index;226}227index = this.getParentIndex(index);228while (index !== -1) {229if (this.contains(index, line)) {230return index;231}232index = this.getParentIndex(index);233}234}235return -1;236}237238239public toString() {240const res: string[] = [];241for (let i = 0; i < this.length; i++) {242res[i] = `[${foldSourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;243}244return res.join(', ');245}246247public toFoldRange(index: number): FoldRange {248return <FoldRange>{249startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER,250endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,251type: this._types ? this._types[index] : undefined,252isCollapsed: this.isCollapsed(index),253source: this.getSource(index)254};255}256257public static fromFoldRanges(ranges: FoldRange[]): FoldingRegions {258const rangesLength = ranges.length;259const startIndexes = new Uint32Array(rangesLength);260const endIndexes = new Uint32Array(rangesLength);261let types: Array<string | undefined> | undefined = [];262let gotTypes = false;263for (let i = 0; i < rangesLength; i++) {264const range = ranges[i];265startIndexes[i] = range.startLineNumber;266endIndexes[i] = range.endLineNumber;267types.push(range.type);268if (range.type) {269gotTypes = true;270}271}272if (!gotTypes) {273types = undefined;274}275const regions = new FoldingRegions(startIndexes, endIndexes, types);276for (let i = 0; i < rangesLength; i++) {277if (ranges[i].isCollapsed) {278regions.setCollapsed(i, true);279}280regions.setSource(i, ranges[i].source);281}282return regions;283}284285/**286* Two inputs, each a FoldingRegions or a FoldRange[], are merged.287* Each input must be pre-sorted on startLineNumber.288* The first list is assumed to always include all regions currently defined by range providers.289* The second list only contains the previously collapsed and all manual ranges.290* If the line position matches, the range of the new range is taken, and the range is no longer manual291* When an entry in one list overlaps an entry in the other, the second list's entry "wins" and292* overlapping entries in the first list are discarded.293* Invalid entries are discarded. An entry is invalid if:294* the start and end line numbers aren't a valid range of line numbers,295* it is out of sequence or has the same start line as a preceding entry,296* it overlaps a preceding entry and is not fully contained by that entry.297*/298public static sanitizeAndMerge(299rangesA: FoldingRegions | FoldRange[],300rangesB: FoldingRegions | FoldRange[],301maxLineNumber: number | undefined): FoldRange[] {302maxLineNumber = maxLineNumber ?? Number.MAX_VALUE;303304const getIndexedFunction = (r: FoldingRegions | FoldRange[], limit: number) => {305return Array.isArray(r)306? ((i: number) => { return (i < limit) ? r[i] : undefined; })307: ((i: number) => { return (i < limit) ? r.toFoldRange(i) : undefined; });308};309const getA = getIndexedFunction(rangesA, rangesA.length);310const getB = getIndexedFunction(rangesB, rangesB.length);311let indexA = 0;312let indexB = 0;313let nextA = getA(0);314let nextB = getB(0);315316const stackedRanges: FoldRange[] = [];317let topStackedRange: FoldRange | undefined;318let prevLineNumber = 0;319const resultRanges: FoldRange[] = [];320321while (nextA || nextB) {322323let useRange: FoldRange | undefined = undefined;324if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) {325if (nextA && nextA.startLineNumber === nextB.startLineNumber) {326if (nextB.source === FoldSource.userDefined) {327// a user defined range (possibly unfolded)328useRange = nextB;329} else {330// a previously folded range or a (possibly unfolded) recovered range331useRange = nextA;332useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber;333useRange.source = FoldSource.provider;334}335nextA = getA(++indexA); // not necessary, just for speed336} else {337useRange = nextB;338if (nextB.isCollapsed && nextB.source === FoldSource.provider) {339// a previously collapsed range340useRange.source = FoldSource.recovered;341}342}343nextB = getB(++indexB);344} else {345// nextA is next. The user folded B set takes precedence and we sometimes need to look346// ahead in it to check for an upcoming conflict.347let scanIndex = indexB;348let prescanB = nextB;349while (true) {350if (!prescanB || prescanB.startLineNumber > nextA!.endLineNumber) {351useRange = nextA;352break; // no conflict, use this nextA353}354if (prescanB.source === FoldSource.userDefined && prescanB.endLineNumber > nextA!.endLineNumber) {355// we found a user folded range, it wins356break; // without setting nextResult, so this nextA gets skipped357}358prescanB = getB(++scanIndex);359}360nextA = getA(++indexA);361}362363if (useRange) {364while (topStackedRange365&& topStackedRange.endLineNumber < useRange.startLineNumber) {366topStackedRange = stackedRanges.pop();367}368if (useRange.endLineNumber > useRange.startLineNumber369&& useRange.startLineNumber > prevLineNumber370&& useRange.endLineNumber <= maxLineNumber371&& (!topStackedRange372|| topStackedRange.endLineNumber >= useRange.endLineNumber)) {373resultRanges.push(useRange);374prevLineNumber = useRange.startLineNumber;375if (topStackedRange) {376stackedRanges.push(topStackedRange);377}378topStackedRange = useRange;379}380}381382}383return resultRanges;384}385386}387388export class FoldingRegion {389390constructor(private readonly ranges: FoldingRegions, private index: number) {391}392393public get startLineNumber() {394return this.ranges.getStartLineNumber(this.index);395}396397public get endLineNumber() {398return this.ranges.getEndLineNumber(this.index);399}400401public get regionIndex() {402return this.index;403}404405public get parentIndex() {406return this.ranges.getParentIndex(this.index);407}408409public get isCollapsed() {410return this.ranges.isCollapsed(this.index);411}412413containedBy(range: ILineRange): boolean {414return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber;415}416containsLine(lineNumber: number) {417return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber;418}419hidesLine(lineNumber: number) {420return this.startLineNumber < lineNumber && lineNumber <= this.endLineNumber;421}422}423424425