123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import { TextModel } from '../../common/model/textModel.js';
- import { LanguageConfigurationRegistry } from '../../common/modes/languageConfigurationRegistry.js';
- import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';
- const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000;
- export const ID_INDENT_PROVIDER = 'indent';
- export class IndentRangeProvider {
- constructor(editorModel) {
- this.editorModel = editorModel;
- this.id = ID_INDENT_PROVIDER;
- }
- dispose() {
- }
- compute(cancelationToken) {
- let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this.editorModel.getLanguageId());
- let offSide = foldingRules && !!foldingRules.offSide;
- let markers = foldingRules && foldingRules.markers;
- return Promise.resolve(computeRanges(this.editorModel, offSide, markers));
- }
- }
- // public only for testing
- export class RangesCollector {
- constructor(foldingRangesLimit) {
- this._startIndexes = [];
- this._endIndexes = [];
- this._indentOccurrences = [];
- this._length = 0;
- this._foldingRangesLimit = foldingRangesLimit;
- }
- insertFirst(startLineNumber, endLineNumber, indent) {
- if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
- return;
- }
- let index = this._length;
- this._startIndexes[index] = startLineNumber;
- this._endIndexes[index] = endLineNumber;
- this._length++;
- if (indent < 1000) {
- this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;
- }
- }
- toIndentRanges(model) {
- if (this._length <= this._foldingRangesLimit) {
- // reverse and create arrays of the exact length
- let startIndexes = new Uint32Array(this._length);
- let endIndexes = new Uint32Array(this._length);
- for (let i = this._length - 1, k = 0; i >= 0; i--, k++) {
- startIndexes[k] = this._startIndexes[i];
- endIndexes[k] = this._endIndexes[i];
- }
- return new FoldingRegions(startIndexes, endIndexes);
- }
- else {
- let entries = 0;
- let maxIndent = this._indentOccurrences.length;
- for (let i = 0; i < this._indentOccurrences.length; i++) {
- let n = this._indentOccurrences[i];
- if (n) {
- if (n + entries > this._foldingRangesLimit) {
- maxIndent = i;
- break;
- }
- entries += n;
- }
- }
- const tabSize = model.getOptions().tabSize;
- // reverse and create arrays of the exact length
- let startIndexes = new Uint32Array(this._foldingRangesLimit);
- let endIndexes = new Uint32Array(this._foldingRangesLimit);
- for (let i = this._length - 1, k = 0; i >= 0; i--) {
- let startIndex = this._startIndexes[i];
- let lineContent = model.getLineContent(startIndex);
- let indent = TextModel.computeIndentLevel(lineContent, tabSize);
- if (indent < maxIndent || (indent === maxIndent && entries++ < this._foldingRangesLimit)) {
- startIndexes[k] = startIndex;
- endIndexes[k] = this._endIndexes[i];
- k++;
- }
- }
- return new FoldingRegions(startIndexes, endIndexes);
- }
- }
- }
- export function computeRanges(model, offSide, markers, foldingRangesLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT) {
- const tabSize = model.getOptions().tabSize;
- let result = new RangesCollector(foldingRangesLimit);
- let pattern = undefined;
- if (markers) {
- pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
- }
- let previousRegions = [];
- let line = model.getLineCount() + 1;
- previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry
- for (let line = model.getLineCount(); line > 0; line--) {
- let lineContent = model.getLineContent(line);
- let indent = TextModel.computeIndentLevel(lineContent, tabSize);
- let previous = previousRegions[previousRegions.length - 1];
- if (indent === -1) {
- if (offSide) {
- // for offSide languages, empty lines are associated to the previous block
- // note: the next block is already written to the results, so this only
- // impacts the end position of the block before
- previous.endAbove = line;
- }
- continue; // only whitespace
- }
- let m;
- if (pattern && (m = lineContent.match(pattern))) {
- // folding pattern match
- if (m[1]) { // start pattern match
- // discard all regions until the folding pattern
- let i = previousRegions.length - 1;
- while (i > 0 && previousRegions[i].indent !== -2) {
- i--;
- }
- if (i > 0) {
- previousRegions.length = i + 1;
- previous = previousRegions[i];
- // new folding range from pattern, includes the end line
- result.insertFirst(line, previous.line, indent);
- previous.line = line;
- previous.indent = indent;
- previous.endAbove = line;
- continue;
- }
- else {
- // no end marker found, treat line as a regular line
- }
- }
- else { // end pattern match
- previousRegions.push({ indent: -2, endAbove: line, line });
- continue;
- }
- }
- if (previous.indent > indent) {
- // discard all regions with larger indent
- do {
- previousRegions.pop();
- previous = previousRegions[previousRegions.length - 1];
- } while (previous.indent > indent);
- // new folding range
- let endLineNumber = previous.endAbove - 1;
- if (endLineNumber - line >= 1) { // needs at east size 1
- result.insertFirst(line, endLineNumber, indent);
- }
- }
- if (previous.indent === indent) {
- previous.endAbove = line;
- }
- else { // previous.indent < indent
- // new region with a bigger indent
- previousRegions.push({ indent, endAbove: line, line });
- }
- }
- return result.toIndentRanges(model);
- }
|