Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/sectionHeaders/browser/sectionHeaders.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 { CancelablePromise, RunOnceScheduler } from '../../../../base/common/async.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { ICodeEditor } from '../../../browser/editorBrowser.js';
9
import { EditorContributionInstantiation, registerEditorContribution } from '../../../browser/editorExtensions.js';
10
import { EditorOption, IEditorMinimapOptions } from '../../../common/config/editorOptions.js';
11
import { IEditorContribution, IEditorDecorationsCollection } from '../../../common/editorCommon.js';
12
import { StandardTokenType } from '../../../common/encodedTokenAttributes.js';
13
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
14
import { IModelDeltaDecoration, MinimapPosition, MinimapSectionHeaderStyle, TrackedRangeStickiness } from '../../../common/model.js';
15
import { ModelDecorationOptions } from '../../../common/model/textModel.js';
16
import { IEditorWorkerService } from '../../../common/services/editorWorker.js';
17
import { FindSectionHeaderOptions, SectionHeader } from '../../../common/services/findSectionHeaders.js';
18
19
export class SectionHeaderDetector extends Disposable implements IEditorContribution {
20
21
public static readonly ID: string = 'editor.sectionHeaderDetector';
22
23
private options: FindSectionHeaderOptions | undefined;
24
private decorations: IEditorDecorationsCollection;
25
private computeSectionHeaders: RunOnceScheduler;
26
private computePromise: CancelablePromise<SectionHeader[]> | null;
27
private currentOccurrences: { [decorationId: string]: SectionHeaderOccurrence };
28
29
constructor(
30
private readonly editor: ICodeEditor,
31
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
32
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService,
33
) {
34
super();
35
this.decorations = this.editor.createDecorationsCollection();
36
37
this.options = this.createOptions(editor.getOption(EditorOption.minimap));
38
this.computePromise = null;
39
this.currentOccurrences = {};
40
41
this._register(editor.onDidChangeModel((e) => {
42
this.currentOccurrences = {};
43
this.options = this.createOptions(editor.getOption(EditorOption.minimap));
44
this.stop();
45
this.computeSectionHeaders.schedule(0);
46
}));
47
48
this._register(editor.onDidChangeModelLanguage((e) => {
49
this.currentOccurrences = {};
50
this.options = this.createOptions(editor.getOption(EditorOption.minimap));
51
this.stop();
52
this.computeSectionHeaders.schedule(0);
53
}));
54
55
this._register(languageConfigurationService.onDidChange((e) => {
56
const editorLanguageId = this.editor.getModel()?.getLanguageId();
57
if (editorLanguageId && e.affects(editorLanguageId)) {
58
this.currentOccurrences = {};
59
this.options = this.createOptions(editor.getOption(EditorOption.minimap));
60
this.stop();
61
this.computeSectionHeaders.schedule(0);
62
}
63
}));
64
65
this._register(editor.onDidChangeConfiguration(e => {
66
if (this.options && !e.hasChanged(EditorOption.minimap)) {
67
return;
68
}
69
70
this.options = this.createOptions(editor.getOption(EditorOption.minimap));
71
72
// Remove any links (for the getting disabled case)
73
this.updateDecorations([]);
74
75
// Stop any computation (for the getting disabled case)
76
this.stop();
77
78
// Start computing (for the getting enabled case)
79
this.computeSectionHeaders.schedule(0);
80
}));
81
82
this._register(this.editor.onDidChangeModelContent(e => {
83
this.computeSectionHeaders.schedule();
84
}));
85
86
this._register(editor.onDidChangeModelTokens((e) => {
87
if (!this.computeSectionHeaders.isScheduled()) {
88
this.computeSectionHeaders.schedule(1000);
89
}
90
}));
91
92
this.computeSectionHeaders = this._register(new RunOnceScheduler(() => {
93
this.findSectionHeaders();
94
}, 250));
95
96
this.computeSectionHeaders.schedule(0);
97
}
98
99
private createOptions(minimap: Readonly<Required<IEditorMinimapOptions>>): FindSectionHeaderOptions | undefined {
100
if (!minimap || !this.editor.hasModel()) {
101
return undefined;
102
}
103
104
const languageId = this.editor.getModel().getLanguageId();
105
if (!languageId) {
106
return undefined;
107
}
108
109
const commentsConfiguration = this.languageConfigurationService.getLanguageConfiguration(languageId).comments;
110
const foldingRules = this.languageConfigurationService.getLanguageConfiguration(languageId).foldingRules;
111
112
if (!commentsConfiguration && !foldingRules?.markers) {
113
return undefined;
114
}
115
116
return {
117
foldingRules,
118
markSectionHeaderRegex: minimap.markSectionHeaderRegex,
119
findMarkSectionHeaders: minimap.showMarkSectionHeaders,
120
findRegionSectionHeaders: minimap.showRegionSectionHeaders,
121
};
122
}
123
124
private findSectionHeaders() {
125
if (!this.editor.hasModel()
126
|| (!this.options?.findMarkSectionHeaders && !this.options?.findRegionSectionHeaders)) {
127
return;
128
}
129
130
const model = this.editor.getModel();
131
if (model.isDisposed() || model.isTooLargeForSyncing()) {
132
return;
133
}
134
135
const modelVersionId = model.getVersionId();
136
this.editorWorkerService.findSectionHeaders(model.uri, this.options)
137
.then((sectionHeaders) => {
138
if (model.isDisposed() || model.getVersionId() !== modelVersionId) {
139
// model changed in the meantime
140
return;
141
}
142
this.updateDecorations(sectionHeaders);
143
});
144
}
145
146
private updateDecorations(sectionHeaders: SectionHeader[]): void {
147
148
const model = this.editor.getModel();
149
if (model) {
150
// Remove all section headers that should be in comments and are not in comments
151
sectionHeaders = sectionHeaders.filter((sectionHeader) => {
152
if (!sectionHeader.shouldBeInComments) {
153
return true;
154
}
155
const validRange = model.validateRange(sectionHeader.range);
156
const tokens = model.tokenization.getLineTokens(validRange.startLineNumber);
157
const idx = tokens.findTokenIndexAtOffset(validRange.startColumn - 1);
158
const tokenType = tokens.getStandardTokenType(idx);
159
const languageId = tokens.getLanguageId(idx);
160
return (languageId === model.getLanguageId() && tokenType === StandardTokenType.Comment);
161
});
162
}
163
164
const oldDecorations = Object.values(this.currentOccurrences).map(occurrence => occurrence.decorationId);
165
const newDecorations = sectionHeaders.map(sectionHeader => decoration(sectionHeader));
166
167
this.editor.changeDecorations((changeAccessor) => {
168
const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations);
169
170
this.currentOccurrences = {};
171
for (let i = 0, len = decorations.length; i < len; i++) {
172
const occurrence = { sectionHeader: sectionHeaders[i], decorationId: decorations[i] };
173
this.currentOccurrences[occurrence.decorationId] = occurrence;
174
}
175
});
176
}
177
178
private stop(): void {
179
this.computeSectionHeaders.cancel();
180
if (this.computePromise) {
181
this.computePromise.cancel();
182
this.computePromise = null;
183
}
184
}
185
186
public override dispose(): void {
187
super.dispose();
188
this.stop();
189
this.decorations.clear();
190
}
191
192
}
193
194
interface SectionHeaderOccurrence {
195
readonly sectionHeader: SectionHeader;
196
readonly decorationId: string;
197
}
198
199
function decoration(sectionHeader: SectionHeader): IModelDeltaDecoration {
200
return {
201
range: sectionHeader.range,
202
options: ModelDecorationOptions.createDynamic({
203
description: 'section-header',
204
stickiness: TrackedRangeStickiness.GrowsOnlyWhenTypingAfter,
205
collapseOnReplaceEdit: true,
206
minimap: {
207
color: undefined,
208
position: MinimapPosition.Inline,
209
sectionHeaderStyle: sectionHeader.hasSeparatorLine ? MinimapSectionHeaderStyle.Underlined : MinimapSectionHeaderStyle.Normal,
210
sectionHeaderText: sectionHeader.text,
211
},
212
})
213
};
214
}
215
216
registerEditorContribution(SectionHeaderDetector.ID, SectionHeaderDetector, EditorContributionInstantiation.AfterFirstRender);
217
218