Path: blob/main/src/vs/editor/contrib/folding/browser/syntaxRangeProvider.ts
3296 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 { CancellationToken } from '../../../../base/common/cancellation.js';6import { onUnexpectedExternalError } from '../../../../base/common/errors.js';7import { DisposableStore } from '../../../../base/common/lifecycle.js';8import { ITextModel } from '../../../common/model.js';9import { FoldingContext, FoldingRange, FoldingRangeProvider } from '../../../common/languages.js';10import { FoldingLimitReporter, RangeProvider } from './folding.js';11import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';1213export interface IFoldingRangeData extends FoldingRange {14rank: number;15}1617const foldingContext: FoldingContext = {18};1920const ID_SYNTAX_PROVIDER = 'syntax';2122export class SyntaxRangeProvider implements RangeProvider {2324readonly id = ID_SYNTAX_PROVIDER;2526readonly disposables: DisposableStore;2728constructor(29private readonly editorModel: ITextModel,30private readonly providers: FoldingRangeProvider[],31readonly handleFoldingRangesChange: () => void,32private readonly foldingRangesLimit: FoldingLimitReporter,33private readonly fallbackRangeProvider: RangeProvider | undefined // used when all providers return null34) {35this.disposables = new DisposableStore();36if (fallbackRangeProvider) {37this.disposables.add(fallbackRangeProvider);38}3940for (const provider of providers) {41if (typeof provider.onDidChange === 'function') {42this.disposables.add(provider.onDidChange(handleFoldingRangesChange));43}44}45}4647compute(cancellationToken: CancellationToken): Promise<FoldingRegions | null> {48return collectSyntaxRanges(this.providers, this.editorModel, cancellationToken).then(ranges => {49if (this.editorModel.isDisposed()) {50return null;51}52if (ranges) {53const res = sanitizeRanges(ranges, this.foldingRangesLimit);54return res;55}56return this.fallbackRangeProvider?.compute(cancellationToken) ?? null;57});58}5960dispose() {61this.disposables.dispose();62}63}6465function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextModel, cancellationToken: CancellationToken): Promise<IFoldingRangeData[] | null> {66let rangeData: IFoldingRangeData[] | null = null;67const promises = providers.map((provider, i) => {68return Promise.resolve(provider.provideFoldingRanges(model, foldingContext, cancellationToken)).then(ranges => {69if (cancellationToken.isCancellationRequested) {70return;71}72if (Array.isArray(ranges)) {73if (!Array.isArray(rangeData)) {74rangeData = [];75}76const nLines = model.getLineCount();77for (const r of ranges) {78if (r.start > 0 && r.end > r.start && r.end <= nLines) {79rangeData.push({ start: r.start, end: r.end, rank: i, kind: r.kind });80}81}82}83}, onUnexpectedExternalError);84});85return Promise.all(promises).then(_ => {86return rangeData;87});88}8990class RangesCollector {91private readonly _startIndexes: number[];92private readonly _endIndexes: number[];93private readonly _nestingLevels: number[];94private readonly _nestingLevelCounts: number[];95private readonly _types: Array<string | undefined>;96private _length: number;97private readonly _foldingRangesLimit: FoldingLimitReporter;9899constructor(foldingRangesLimit: FoldingLimitReporter) {100this._startIndexes = [];101this._endIndexes = [];102this._nestingLevels = [];103this._nestingLevelCounts = [];104this._types = [];105this._length = 0;106this._foldingRangesLimit = foldingRangesLimit;107}108109public add(startLineNumber: number, endLineNumber: number, type: string | undefined, nestingLevel: number) {110if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {111return;112}113const index = this._length;114this._startIndexes[index] = startLineNumber;115this._endIndexes[index] = endLineNumber;116this._nestingLevels[index] = nestingLevel;117this._types[index] = type;118this._length++;119if (nestingLevel < 30) {120this._nestingLevelCounts[nestingLevel] = (this._nestingLevelCounts[nestingLevel] || 0) + 1;121}122}123124public toIndentRanges() {125const limit = this._foldingRangesLimit.limit;126if (this._length <= limit) {127this._foldingRangesLimit.update(this._length, false);128129const startIndexes = new Uint32Array(this._length);130const endIndexes = new Uint32Array(this._length);131for (let i = 0; i < this._length; i++) {132startIndexes[i] = this._startIndexes[i];133endIndexes[i] = this._endIndexes[i];134}135return new FoldingRegions(startIndexes, endIndexes, this._types);136} else {137this._foldingRangesLimit.update(this._length, limit);138139let entries = 0;140let maxLevel = this._nestingLevelCounts.length;141for (let i = 0; i < this._nestingLevelCounts.length; i++) {142const n = this._nestingLevelCounts[i];143if (n) {144if (n + entries > limit) {145maxLevel = i;146break;147}148entries += n;149}150}151152const startIndexes = new Uint32Array(limit);153const endIndexes = new Uint32Array(limit);154const types: Array<string | undefined> = [];155for (let i = 0, k = 0; i < this._length; i++) {156const level = this._nestingLevels[i];157if (level < maxLevel || (level === maxLevel && entries++ < limit)) {158startIndexes[k] = this._startIndexes[i];159endIndexes[k] = this._endIndexes[i];160types[k] = this._types[i];161k++;162}163}164return new FoldingRegions(startIndexes, endIndexes, types);165}166167}168169}170171export function sanitizeRanges(rangeData: IFoldingRangeData[], foldingRangesLimit: FoldingLimitReporter): FoldingRegions {172const sorted = rangeData.sort((d1, d2) => {173let diff = d1.start - d2.start;174if (diff === 0) {175diff = d1.rank - d2.rank;176}177return diff;178});179const collector = new RangesCollector(foldingRangesLimit);180181let top: IFoldingRangeData | undefined = undefined;182const previous: IFoldingRangeData[] = [];183for (const entry of sorted) {184if (!top) {185top = entry;186collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);187} else {188if (entry.start > top.start) {189if (entry.end <= top.end) {190previous.push(top);191top = entry;192collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);193} else {194if (entry.start > top.end) {195do {196top = previous.pop();197} while (top && entry.start > top.end);198if (top) {199previous.push(top);200}201top = entry;202}203collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length);204}205}206}207}208return collector.toIndentRanges();209}210211212