Path: blob/main/src/vs/editor/contrib/folding/browser/indentRangeProvider.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 { ITextModel } from '../../../common/model.js';7import { computeIndentLevel } from '../../../common/model/utils.js';8import { FoldingMarkers } from '../../../common/languages/languageConfiguration.js';9import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';10import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';11import { FoldingLimitReporter, RangeProvider } from './folding.js';1213const MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT = 5000;1415const ID_INDENT_PROVIDER = 'indent';1617export class IndentRangeProvider implements RangeProvider {18readonly id = ID_INDENT_PROVIDER;1920constructor(21private readonly editorModel: ITextModel,22private readonly languageConfigurationService: ILanguageConfigurationService,23private readonly foldingRangesLimit: FoldingLimitReporter24) { }2526dispose() { }2728compute(cancelationToken: CancellationToken,): Promise<FoldingRegions> {29const foldingRules = this.languageConfigurationService.getLanguageConfiguration(this.editorModel.getLanguageId()).foldingRules;30const offSide = foldingRules && !!foldingRules.offSide;31const markers = foldingRules && foldingRules.markers;32return Promise.resolve(computeRanges(this.editorModel, offSide, markers, this.foldingRangesLimit));33}34}3536// public only for testing37export class RangesCollector {38private readonly _startIndexes: number[];39private readonly _endIndexes: number[];40private readonly _indentOccurrences: number[];41private _length: number;42private readonly _foldingRangesLimit: FoldingLimitReporter;4344constructor(foldingRangesLimit: FoldingLimitReporter) {45this._startIndexes = [];46this._endIndexes = [];47this._indentOccurrences = [];48this._length = 0;49this._foldingRangesLimit = foldingRangesLimit;50}5152public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) {53if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {54return;55}56const index = this._length;57this._startIndexes[index] = startLineNumber;58this._endIndexes[index] = endLineNumber;59this._length++;60if (indent < 1000) {61this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;62}63}6465public toIndentRanges(model: ITextModel) {66const limit = this._foldingRangesLimit.limit;67if (this._length <= limit) {68this._foldingRangesLimit.update(this._length, false);6970// reverse and create arrays of the exact length71const startIndexes = new Uint32Array(this._length);72const endIndexes = new Uint32Array(this._length);73for (let i = this._length - 1, k = 0; i >= 0; i--, k++) {74startIndexes[k] = this._startIndexes[i];75endIndexes[k] = this._endIndexes[i];76}77return new FoldingRegions(startIndexes, endIndexes);78} else {79this._foldingRangesLimit.update(this._length, limit);8081let entries = 0;82let maxIndent = this._indentOccurrences.length;83for (let i = 0; i < this._indentOccurrences.length; i++) {84const n = this._indentOccurrences[i];85if (n) {86if (n + entries > limit) {87maxIndent = i;88break;89}90entries += n;91}92}93const tabSize = model.getOptions().tabSize;94// reverse and create arrays of the exact length95const startIndexes = new Uint32Array(limit);96const endIndexes = new Uint32Array(limit);97for (let i = this._length - 1, k = 0; i >= 0; i--) {98const startIndex = this._startIndexes[i];99const lineContent = model.getLineContent(startIndex);100const indent = computeIndentLevel(lineContent, tabSize);101if (indent < maxIndent || (indent === maxIndent && entries++ < limit)) {102startIndexes[k] = startIndex;103endIndexes[k] = this._endIndexes[i];104k++;105}106}107return new FoldingRegions(startIndexes, endIndexes);108}109110}111}112113114interface PreviousRegion {115indent: number; // indent or -2 if a marker116endAbove: number; // end line number for the region above117line: number; // start line of the region. Only used for marker regions.118}119120const foldingRangesLimitDefault: FoldingLimitReporter = {121limit: MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT,122update: () => { }123};124125export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRangesLimit: FoldingLimitReporter = foldingRangesLimitDefault): FoldingRegions {126const tabSize = model.getOptions().tabSize;127const result = new RangesCollector(foldingRangesLimit);128129let pattern: RegExp | undefined = undefined;130if (markers) {131pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);132}133134const previousRegions: PreviousRegion[] = [];135const line = model.getLineCount() + 1;136previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry137138for (let line = model.getLineCount(); line > 0; line--) {139const lineContent = model.getLineContent(line);140const indent = computeIndentLevel(lineContent, tabSize);141let previous = previousRegions[previousRegions.length - 1];142if (indent === -1) {143if (offSide) {144// for offSide languages, empty lines are associated to the previous block145// note: the next block is already written to the results, so this only146// impacts the end position of the block before147previous.endAbove = line;148}149continue; // only whitespace150}151let m;152if (pattern && (m = lineContent.match(pattern))) {153// folding pattern match154if (m[1]) { // start pattern match155// discard all regions until the folding pattern156let i = previousRegions.length - 1;157while (i > 0 && previousRegions[i].indent !== -2) {158i--;159}160if (i > 0) {161previousRegions.length = i + 1;162previous = previousRegions[i];163164// new folding range from pattern, includes the end line165result.insertFirst(line, previous.line, indent);166previous.line = line;167previous.indent = indent;168previous.endAbove = line;169continue;170} else {171// no end marker found, treat line as a regular line172}173} else { // end pattern match174previousRegions.push({ indent: -2, endAbove: line, line });175continue;176}177}178if (previous.indent > indent) {179// discard all regions with larger indent180do {181previousRegions.pop();182previous = previousRegions[previousRegions.length - 1];183} while (previous.indent > indent);184185// new folding range186const endLineNumber = previous.endAbove - 1;187if (endLineNumber - line >= 1) { // needs at east size 1188result.insertFirst(line, endLineNumber, indent);189}190}191if (previous.indent === indent) {192previous.endAbove = line;193} else { // previous.indent < indent194// new region with a bigger indent195previousRegions.push({ indent, endAbove: line, line });196}197}198return result.toIndentRanges(model);199}200201202