UNPKG

monaco-editor-core

Version:

A browser based code editor

172 lines (171 loc) 6.98 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { onUnexpectedExternalError } from '../../../../base/common/errors.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js'; const foldingContext = {}; const ID_SYNTAX_PROVIDER = 'syntax'; export class SyntaxRangeProvider { constructor(editorModel, providers, handleFoldingRangesChange, foldingRangesLimit, fallbackRangeProvider // used when all providers return null ) { this.editorModel = editorModel; this.providers = providers; this.handleFoldingRangesChange = handleFoldingRangesChange; this.foldingRangesLimit = foldingRangesLimit; this.fallbackRangeProvider = fallbackRangeProvider; this.id = ID_SYNTAX_PROVIDER; this.disposables = new DisposableStore(); if (fallbackRangeProvider) { this.disposables.add(fallbackRangeProvider); } for (const provider of providers) { if (typeof provider.onDidChange === 'function') { this.disposables.add(provider.onDidChange(handleFoldingRangesChange)); } } } compute(cancellationToken) { return collectSyntaxRanges(this.providers, this.editorModel, cancellationToken).then(ranges => { if (ranges) { const res = sanitizeRanges(ranges, this.foldingRangesLimit); return res; } return this.fallbackRangeProvider?.compute(cancellationToken) ?? null; }); } dispose() { this.disposables.dispose(); } } function collectSyntaxRanges(providers, model, cancellationToken) { let rangeData = null; const promises = providers.map((provider, i) => { return Promise.resolve(provider.provideFoldingRanges(model, foldingContext, cancellationToken)).then(ranges => { if (cancellationToken.isCancellationRequested) { return; } if (Array.isArray(ranges)) { if (!Array.isArray(rangeData)) { rangeData = []; } const nLines = model.getLineCount(); for (const r of ranges) { if (r.start > 0 && r.end > r.start && r.end <= nLines) { rangeData.push({ start: r.start, end: r.end, rank: i, kind: r.kind }); } } } }, onUnexpectedExternalError); }); return Promise.all(promises).then(_ => { return rangeData; }); } class RangesCollector { constructor(foldingRangesLimit) { this._startIndexes = []; this._endIndexes = []; this._nestingLevels = []; this._nestingLevelCounts = []; this._types = []; this._length = 0; this._foldingRangesLimit = foldingRangesLimit; } add(startLineNumber, endLineNumber, type, nestingLevel) { if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) { return; } const index = this._length; this._startIndexes[index] = startLineNumber; this._endIndexes[index] = endLineNumber; this._nestingLevels[index] = nestingLevel; this._types[index] = type; this._length++; if (nestingLevel < 30) { this._nestingLevelCounts[nestingLevel] = (this._nestingLevelCounts[nestingLevel] || 0) + 1; } } toIndentRanges() { const limit = this._foldingRangesLimit.limit; if (this._length <= limit) { this._foldingRangesLimit.update(this._length, false); const startIndexes = new Uint32Array(this._length); const endIndexes = new Uint32Array(this._length); for (let i = 0; i < this._length; i++) { startIndexes[i] = this._startIndexes[i]; endIndexes[i] = this._endIndexes[i]; } return new FoldingRegions(startIndexes, endIndexes, this._types); } else { this._foldingRangesLimit.update(this._length, limit); let entries = 0; let maxLevel = this._nestingLevelCounts.length; for (let i = 0; i < this._nestingLevelCounts.length; i++) { const n = this._nestingLevelCounts[i]; if (n) { if (n + entries > limit) { maxLevel = i; break; } entries += n; } } const startIndexes = new Uint32Array(limit); const endIndexes = new Uint32Array(limit); const types = []; for (let i = 0, k = 0; i < this._length; i++) { const level = this._nestingLevels[i]; if (level < maxLevel || (level === maxLevel && entries++ < limit)) { startIndexes[k] = this._startIndexes[i]; endIndexes[k] = this._endIndexes[i]; types[k] = this._types[i]; k++; } } return new FoldingRegions(startIndexes, endIndexes, types); } } } export function sanitizeRanges(rangeData, foldingRangesLimit) { const sorted = rangeData.sort((d1, d2) => { let diff = d1.start - d2.start; if (diff === 0) { diff = d1.rank - d2.rank; } return diff; }); const collector = new RangesCollector(foldingRangesLimit); let top = undefined; const previous = []; for (const entry of sorted) { if (!top) { top = entry; collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length); } else { if (entry.start > top.start) { if (entry.end <= top.end) { previous.push(top); top = entry; collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length); } else { if (entry.start > top.end) { do { top = previous.pop(); } while (top && entry.start > top.end); if (top) { previous.push(top); } top = entry; } collector.add(entry.start, entry.end, entry.kind && entry.kind.value, previous.length); } } } } return collector.toIndentRanges(); }