Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/browser/hiddenRangeModel.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { findFirstIdxMonotonousOrArrLen } from '../../../../base/common/arraysFind.js';
7
8
import { Emitter, Event } from '../../../../base/common/event.js';
9
import { IDisposable } from '../../../../base/common/lifecycle.js';
10
import { IRange, Range } from '../../../common/core/range.js';
11
import { Selection } from '../../../common/core/selection.js';
12
import { IModelContentChangedEvent } from '../../../common/textModelEvents.js';
13
import { countEOL } from '../../../common/core/misc/eolCounter.js';
14
import { FoldingModel } from './foldingModel.js';
15
16
export class HiddenRangeModel {
17
18
private readonly _foldingModel: FoldingModel;
19
private _hiddenRanges: IRange[];
20
private _foldingModelListener: IDisposable | null;
21
private readonly _updateEventEmitter = new Emitter<IRange[]>();
22
private _hasLineChanges: boolean = false;
23
24
public get onDidChange(): Event<IRange[]> { return this._updateEventEmitter.event; }
25
public get hiddenRanges() { return this._hiddenRanges; }
26
27
public constructor(model: FoldingModel) {
28
this._foldingModel = model;
29
this._foldingModelListener = model.onDidChange(_ => this.updateHiddenRanges());
30
this._hiddenRanges = [];
31
if (model.regions.length) {
32
this.updateHiddenRanges();
33
}
34
}
35
36
public notifyChangeModelContent(e: IModelContentChangedEvent) {
37
if (this._hiddenRanges.length && !this._hasLineChanges) {
38
this._hasLineChanges = e.changes.some(change => {
39
return change.range.endLineNumber !== change.range.startLineNumber || countEOL(change.text)[0] !== 0;
40
});
41
}
42
}
43
44
private updateHiddenRanges(): void {
45
let updateHiddenAreas = false;
46
const newHiddenAreas: IRange[] = [];
47
let i = 0; // index into hidden
48
let k = 0;
49
50
let lastCollapsedStart = Number.MAX_VALUE;
51
let lastCollapsedEnd = -1;
52
53
const ranges = this._foldingModel.regions;
54
for (; i < ranges.length; i++) {
55
if (!ranges.isCollapsed(i)) {
56
continue;
57
}
58
59
const startLineNumber = ranges.getStartLineNumber(i) + 1; // the first line is not hidden
60
const endLineNumber = ranges.getEndLineNumber(i);
61
if (lastCollapsedStart <= startLineNumber && endLineNumber <= lastCollapsedEnd) {
62
// ignore ranges contained in collapsed regions
63
continue;
64
}
65
66
if (!updateHiddenAreas && k < this._hiddenRanges.length && this._hiddenRanges[k].startLineNumber === startLineNumber && this._hiddenRanges[k].endLineNumber === endLineNumber) {
67
// reuse the old ranges
68
newHiddenAreas.push(this._hiddenRanges[k]);
69
k++;
70
} else {
71
updateHiddenAreas = true;
72
newHiddenAreas.push(new Range(startLineNumber, 1, endLineNumber, 1));
73
}
74
lastCollapsedStart = startLineNumber;
75
lastCollapsedEnd = endLineNumber;
76
}
77
if (this._hasLineChanges || updateHiddenAreas || k < this._hiddenRanges.length) {
78
this.applyHiddenRanges(newHiddenAreas);
79
}
80
}
81
82
private applyHiddenRanges(newHiddenAreas: IRange[]) {
83
this._hiddenRanges = newHiddenAreas;
84
this._hasLineChanges = false;
85
this._updateEventEmitter.fire(newHiddenAreas);
86
}
87
88
public hasRanges() {
89
return this._hiddenRanges.length > 0;
90
}
91
92
public isHidden(line: number): boolean {
93
return findRange(this._hiddenRanges, line) !== null;
94
}
95
96
public adjustSelections(selections: Selection[]): boolean {
97
let hasChanges = false;
98
const editorModel = this._foldingModel.textModel;
99
let lastRange: IRange | null = null;
100
101
const adjustLine = (line: number) => {
102
if (!lastRange || !isInside(line, lastRange)) {
103
lastRange = findRange(this._hiddenRanges, line);
104
}
105
if (lastRange) {
106
return lastRange.startLineNumber - 1;
107
}
108
return null;
109
};
110
for (let i = 0, len = selections.length; i < len; i++) {
111
let selection = selections[i];
112
const adjustedStartLine = adjustLine(selection.startLineNumber);
113
if (adjustedStartLine) {
114
selection = selection.setStartPosition(adjustedStartLine, editorModel.getLineMaxColumn(adjustedStartLine));
115
hasChanges = true;
116
}
117
const adjustedEndLine = adjustLine(selection.endLineNumber);
118
if (adjustedEndLine) {
119
selection = selection.setEndPosition(adjustedEndLine, editorModel.getLineMaxColumn(adjustedEndLine));
120
hasChanges = true;
121
}
122
selections[i] = selection;
123
}
124
return hasChanges;
125
}
126
127
128
public dispose() {
129
if (this.hiddenRanges.length > 0) {
130
this._hiddenRanges = [];
131
this._updateEventEmitter.fire(this._hiddenRanges);
132
}
133
if (this._foldingModelListener) {
134
this._foldingModelListener.dispose();
135
this._foldingModelListener = null;
136
}
137
}
138
}
139
140
function isInside(line: number, range: IRange) {
141
return line >= range.startLineNumber && line <= range.endLineNumber;
142
}
143
function findRange(ranges: IRange[], line: number): IRange | null {
144
const i = findFirstIdxMonotonousOrArrLen(ranges, r => line < r.startLineNumber) - 1;
145
if (i >= 0 && ranges[i].endLineNumber >= line) {
146
return ranges[i];
147
}
148
return null;
149
}
150
151