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
5283 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
160
const languageIdAtPosition = model.getLanguageIdAtPosition(validRange.startLineNumber, validRange.startColumn);
161
const tokenLanguageId = tokens.getLanguageId(idx);
162
return (tokenLanguageId === languageIdAtPosition && tokenType === StandardTokenType.Comment);
163
});
164
}
165
166
const oldDecorations = Object.values(this.currentOccurrences).map(occurrence => occurrence.decorationId);
167
const newDecorations = sectionHeaders.map(sectionHeader => decoration(sectionHeader));
168
169
this.editor.changeDecorations((changeAccessor) => {
170
const decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations);
171
172
this.currentOccurrences = {};
173
for (let i = 0, len = decorations.length; i < len; i++) {
174
const occurrence = { sectionHeader: sectionHeaders[i], decorationId: decorations[i] };
175
this.currentOccurrences[occurrence.decorationId] = occurrence;
176
}
177
});
178
}
179
180
private stop(): void {
181
this.computeSectionHeaders.cancel();
182
if (this.computePromise) {
183
this.computePromise.cancel();
184
this.computePromise = null;
185
}
186
}
187
188
public override dispose(): void {
189
super.dispose();
190
this.stop();
191
this.decorations.clear();
192
}
193
194
}
195
196
interface SectionHeaderOccurrence {
197
readonly sectionHeader: SectionHeader;
198
readonly decorationId: string;
199
}
200
201
function decoration(sectionHeader: SectionHeader): IModelDeltaDecoration {
202
return {
203
range: sectionHeader.range,
204
options: ModelDecorationOptions.createDynamic({
205
description: 'section-header',
206
stickiness: TrackedRangeStickiness.GrowsOnlyWhenTypingAfter,
207
collapseOnReplaceEdit: true,
208
minimap: {
209
color: undefined,
210
position: MinimapPosition.Inline,
211
sectionHeaderStyle: sectionHeader.hasSeparatorLine ? MinimapSectionHeaderStyle.Underlined : MinimapSectionHeaderStyle.Normal,
212
sectionHeaderText: sectionHeader.text,
213
},
214
})
215
};
216
}
217
218
registerEditorContribution(SectionHeaderDetector.ID, SectionHeaderDetector, EditorContributionInstantiation.AfterFirstRender);
219
220