Path: blob/main/src/vs/editor/contrib/folding/browser/hiddenRangeModel.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 { findFirstIdxMonotonousOrArrLen } from '../../../../base/common/arraysFind.js';67import { Emitter, Event } from '../../../../base/common/event.js';8import { IDisposable } from '../../../../base/common/lifecycle.js';9import { IRange, Range } from '../../../common/core/range.js';10import { Selection } from '../../../common/core/selection.js';11import { IModelContentChangedEvent } from '../../../common/textModelEvents.js';12import { countEOL } from '../../../common/core/misc/eolCounter.js';13import { FoldingModel } from './foldingModel.js';1415export class HiddenRangeModel {1617private readonly _foldingModel: FoldingModel;18private _hiddenRanges: IRange[];19private _foldingModelListener: IDisposable | null;20private readonly _updateEventEmitter = new Emitter<IRange[]>();21private _hasLineChanges: boolean = false;2223public get onDidChange(): Event<IRange[]> { return this._updateEventEmitter.event; }24public get hiddenRanges() { return this._hiddenRanges; }2526public constructor(model: FoldingModel) {27this._foldingModel = model;28this._foldingModelListener = model.onDidChange(_ => this.updateHiddenRanges());29this._hiddenRanges = [];30if (model.regions.length) {31this.updateHiddenRanges();32}33}3435public notifyChangeModelContent(e: IModelContentChangedEvent) {36if (this._hiddenRanges.length && !this._hasLineChanges) {37this._hasLineChanges = e.changes.some(change => {38return change.range.endLineNumber !== change.range.startLineNumber || countEOL(change.text)[0] !== 0;39});40}41}4243private updateHiddenRanges(): void {44let updateHiddenAreas = false;45const newHiddenAreas: IRange[] = [];46let i = 0; // index into hidden47let k = 0;4849let lastCollapsedStart = Number.MAX_VALUE;50let lastCollapsedEnd = -1;5152const ranges = this._foldingModel.regions;53for (; i < ranges.length; i++) {54if (!ranges.isCollapsed(i)) {55continue;56}5758const startLineNumber = ranges.getStartLineNumber(i) + 1; // the first line is not hidden59const endLineNumber = ranges.getEndLineNumber(i);60if (lastCollapsedStart <= startLineNumber && endLineNumber <= lastCollapsedEnd) {61// ignore ranges contained in collapsed regions62continue;63}6465if (!updateHiddenAreas && k < this._hiddenRanges.length && this._hiddenRanges[k].startLineNumber === startLineNumber && this._hiddenRanges[k].endLineNumber === endLineNumber) {66// reuse the old ranges67newHiddenAreas.push(this._hiddenRanges[k]);68k++;69} else {70updateHiddenAreas = true;71newHiddenAreas.push(new Range(startLineNumber, 1, endLineNumber, 1));72}73lastCollapsedStart = startLineNumber;74lastCollapsedEnd = endLineNumber;75}76if (this._hasLineChanges || updateHiddenAreas || k < this._hiddenRanges.length) {77this.applyHiddenRanges(newHiddenAreas);78}79}8081private applyHiddenRanges(newHiddenAreas: IRange[]) {82this._hiddenRanges = newHiddenAreas;83this._hasLineChanges = false;84this._updateEventEmitter.fire(newHiddenAreas);85}8687public hasRanges() {88return this._hiddenRanges.length > 0;89}9091public isHidden(line: number): boolean {92return findRange(this._hiddenRanges, line) !== null;93}9495public adjustSelections(selections: Selection[]): boolean {96let hasChanges = false;97const editorModel = this._foldingModel.textModel;98let lastRange: IRange | null = null;99100const adjustLine = (line: number) => {101if (!lastRange || !isInside(line, lastRange)) {102lastRange = findRange(this._hiddenRanges, line);103}104if (lastRange) {105return lastRange.startLineNumber - 1;106}107return null;108};109for (let i = 0, len = selections.length; i < len; i++) {110let selection = selections[i];111const adjustedStartLine = adjustLine(selection.startLineNumber);112if (adjustedStartLine) {113selection = selection.setStartPosition(adjustedStartLine, editorModel.getLineMaxColumn(adjustedStartLine));114hasChanges = true;115}116const adjustedEndLine = adjustLine(selection.endLineNumber);117if (adjustedEndLine) {118selection = selection.setEndPosition(adjustedEndLine, editorModel.getLineMaxColumn(adjustedEndLine));119hasChanges = true;120}121selections[i] = selection;122}123return hasChanges;124}125126127public dispose() {128if (this.hiddenRanges.length > 0) {129this._hiddenRanges = [];130this._updateEventEmitter.fire(this._hiddenRanges);131}132if (this._foldingModelListener) {133this._foldingModelListener.dispose();134this._foldingModelListener = null;135}136}137}138139function isInside(line: number, range: IRange) {140return line >= range.startLineNumber && line <= range.endLineNumber;141}142function findRange(ranges: IRange[], line: number): IRange | null {143const i = findFirstIdxMonotonousOrArrLen(ranges, r => line < r.startLineNumber) - 1;144if (i >= 0 && ranges[i].endLineNumber >= line) {145return ranges[i];146}147return null;148}149150151