Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/folding/browser/folding.ts
5262 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, createCancelablePromise, Delayer, RunOnceScheduler } from '../../../../base/common/async.js';
7
import { CancellationToken } from '../../../../base/common/cancellation.js';
8
import { illegalArgument, onUnexpectedError } from '../../../../base/common/errors.js';
9
import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
10
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
11
import { escapeRegExpCharacters } from '../../../../base/common/strings.js';
12
import * as types from '../../../../base/common/types.js';
13
import './folding.css';
14
import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js';
15
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js';
16
import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, registerInstantiatedEditorAction, ServicesAccessor } from '../../../browser/editorExtensions.js';
17
import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js';
18
import { IPosition } from '../../../common/core/position.js';
19
import { IRange } from '../../../common/core/range.js';
20
import { Selection } from '../../../common/core/selection.js';
21
import { IEditorContribution, ScrollType } from '../../../common/editorCommon.js';
22
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
23
import { ITextModel } from '../../../common/model.js';
24
import { IModelContentChangedEvent } from '../../../common/textModelEvents.js';
25
import { FoldingRange, FoldingRangeKind, FoldingRangeProvider } from '../../../common/languages.js';
26
import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';
27
import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from './foldingModel.js';
28
import { HiddenRangeModel } from './hiddenRangeModel.js';
29
import { IndentRangeProvider } from './indentRangeProvider.js';
30
import * as nls from '../../../../nls.js';
31
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
32
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
33
import { FoldingDecorationProvider } from './foldingDecorations.js';
34
import { FoldingRegion, FoldingRegions, FoldRange, FoldSource, ILineRange } from './foldingRanges.js';
35
import { SyntaxRangeProvider } from './syntaxRangeProvider.js';
36
import { INotificationService } from '../../../../platform/notification/common/notification.js';
37
import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';
38
import { StopWatch } from '../../../../base/common/stopwatch.js';
39
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
40
import { Emitter, Event } from '../../../../base/common/event.js';
41
import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';
42
import { URI } from '../../../../base/common/uri.js';
43
import { IModelService } from '../../../common/services/model.js';
44
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
45
46
const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);
47
48
export interface RangeProvider {
49
readonly id: string;
50
compute(cancelationToken: CancellationToken): Promise<FoldingRegions | null>;
51
dispose(): void;
52
}
53
54
interface FoldingStateMemento {
55
collapsedRegions?: CollapseMemento;
56
lineCount?: number;
57
provider?: string;
58
foldedImports?: boolean;
59
}
60
61
export interface FoldingLimitReporter {
62
readonly limit: number;
63
update(computed: number, limited: number | false): void;
64
}
65
66
export type FoldingRangeProviderSelector = (provider: FoldingRangeProvider[], document: ITextModel) => FoldingRangeProvider[] | undefined;
67
68
export class FoldingController extends Disposable implements IEditorContribution {
69
70
public static readonly ID = 'editor.contrib.folding';
71
72
public static get(editor: ICodeEditor): FoldingController | null {
73
return editor.getContribution<FoldingController>(FoldingController.ID);
74
}
75
76
private static _foldingRangeSelector: FoldingRangeProviderSelector | undefined;
77
78
public static getFoldingRangeProviders(languageFeaturesService: ILanguageFeaturesService, model: ITextModel): FoldingRangeProvider[] {
79
const foldingRangeProviders = languageFeaturesService.foldingRangeProvider.ordered(model);
80
return (FoldingController._foldingRangeSelector?.(foldingRangeProviders, model)) ?? foldingRangeProviders;
81
}
82
83
public static setFoldingRangeProviderSelector(foldingRangeSelector: FoldingRangeProviderSelector): IDisposable {
84
FoldingController._foldingRangeSelector = foldingRangeSelector;
85
return { dispose: () => { FoldingController._foldingRangeSelector = undefined; } };
86
}
87
88
private readonly editor: ICodeEditor;
89
private _isEnabled: boolean;
90
private _useFoldingProviders: boolean;
91
private _unfoldOnClickAfterEndOfLine: boolean;
92
private _restoringViewState: boolean;
93
private _foldingImportsByDefault: boolean;
94
private _currentModelHasFoldedImports: boolean;
95
96
private readonly foldingDecorationProvider: FoldingDecorationProvider;
97
98
private foldingModel: FoldingModel | null;
99
private hiddenRangeModel: HiddenRangeModel | null;
100
101
private rangeProvider: RangeProvider | null;
102
private foldingRegionPromise: CancelablePromise<FoldingRegions | null> | null;
103
104
private foldingModelPromise: Promise<FoldingModel | null> | null;
105
private updateScheduler: Delayer<FoldingModel | null> | null;
106
private readonly updateDebounceInfo: IFeatureDebounceInformation;
107
108
private foldingEnabled: IContextKey<boolean>;
109
private cursorChangedScheduler: RunOnceScheduler | null;
110
111
private readonly localToDispose = this._register(new DisposableStore());
112
private mouseDownInfo: { lineNumber: number; iconClicked: boolean } | null;
113
114
public readonly _foldingLimitReporter: RangesLimitReporter;
115
116
constructor(
117
editor: ICodeEditor,
118
@IContextKeyService private readonly contextKeyService: IContextKeyService,
119
@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,
120
@INotificationService notificationService: INotificationService,
121
@ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService,
122
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
123
) {
124
super();
125
this.editor = editor;
126
127
this._foldingLimitReporter = this._register(new RangesLimitReporter(editor));
128
129
const options = this.editor.getOptions();
130
this._isEnabled = options.get(EditorOption.folding);
131
this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';
132
this._unfoldOnClickAfterEndOfLine = options.get(EditorOption.unfoldOnClickAfterEndOfLine);
133
this._restoringViewState = false;
134
this._currentModelHasFoldedImports = false;
135
this._foldingImportsByDefault = options.get(EditorOption.foldingImportsByDefault);
136
this.updateDebounceInfo = languageFeatureDebounceService.for(languageFeaturesService.foldingRangeProvider, 'Folding', { min: 200 });
137
138
this.foldingModel = null;
139
this.hiddenRangeModel = null;
140
this.rangeProvider = null;
141
this.foldingRegionPromise = null;
142
this.foldingModelPromise = null;
143
this.updateScheduler = null;
144
this.cursorChangedScheduler = null;
145
this.mouseDownInfo = null;
146
147
this.foldingDecorationProvider = new FoldingDecorationProvider(editor);
148
this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);
149
this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);
150
this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService);
151
this.foldingEnabled.set(this._isEnabled);
152
153
this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));
154
155
this._register(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {
156
if (e.hasChanged(EditorOption.folding)) {
157
this._isEnabled = this.editor.getOptions().get(EditorOption.folding);
158
this.foldingEnabled.set(this._isEnabled);
159
this.onModelChanged();
160
}
161
if (e.hasChanged(EditorOption.foldingMaximumRegions)) {
162
this.onModelChanged();
163
}
164
if (e.hasChanged(EditorOption.showFoldingControls) || e.hasChanged(EditorOption.foldingHighlight)) {
165
const options = this.editor.getOptions();
166
this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);
167
this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);
168
this.triggerFoldingModelChanged();
169
}
170
if (e.hasChanged(EditorOption.foldingStrategy)) {
171
this._useFoldingProviders = this.editor.getOptions().get(EditorOption.foldingStrategy) !== 'indentation';
172
this.onFoldingStrategyChanged();
173
}
174
if (e.hasChanged(EditorOption.unfoldOnClickAfterEndOfLine)) {
175
this._unfoldOnClickAfterEndOfLine = this.editor.getOptions().get(EditorOption.unfoldOnClickAfterEndOfLine);
176
}
177
if (e.hasChanged(EditorOption.foldingImportsByDefault)) {
178
this._foldingImportsByDefault = this.editor.getOptions().get(EditorOption.foldingImportsByDefault);
179
}
180
}));
181
this.onModelChanged();
182
}
183
184
public get limitReporter() {
185
return this._foldingLimitReporter;
186
}
187
188
/**
189
* Store view state.
190
*/
191
public saveViewState(): FoldingStateMemento | undefined {
192
const model = this.editor.getModel();
193
if (!model || !this._isEnabled || model.isTooLargeForTokenization()) {
194
return {};
195
}
196
if (this.foldingModel) { // disposed ?
197
const collapsedRegions = this.foldingModel.getMemento();
198
const provider = this.rangeProvider ? this.rangeProvider.id : undefined;
199
return { collapsedRegions, lineCount: model.getLineCount(), provider, foldedImports: this._currentModelHasFoldedImports };
200
}
201
return undefined;
202
}
203
204
/**
205
* Restore view state.
206
*/
207
public restoreViewState(state: FoldingStateMemento): void {
208
const model = this.editor.getModel();
209
if (!model || !this._isEnabled || model.isTooLargeForTokenization() || !this.hiddenRangeModel) {
210
return;
211
}
212
if (!state) {
213
return;
214
}
215
216
this._currentModelHasFoldedImports = !!state.foldedImports;
217
if (state.collapsedRegions && state.collapsedRegions.length > 0 && this.foldingModel) {
218
this._restoringViewState = true;
219
try {
220
this.foldingModel.applyMemento(state.collapsedRegions);
221
} finally {
222
this._restoringViewState = false;
223
}
224
}
225
}
226
227
private onModelChanged(): void {
228
this.localToDispose.clear();
229
230
const model = this.editor.getModel();
231
if (!this._isEnabled || !model || model.isTooLargeForTokenization()) {
232
// huge files get no view model, so they cannot support hidden areas
233
return;
234
}
235
236
this._currentModelHasFoldedImports = false;
237
this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);
238
this.localToDispose.add(this.foldingModel);
239
240
this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);
241
this.localToDispose.add(this.hiddenRangeModel);
242
this.localToDispose.add(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr)));
243
244
this.updateScheduler = new Delayer<FoldingModel>(this.updateDebounceInfo.get(model));
245
this.localToDispose.add(this.updateScheduler);
246
247
this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200);
248
this.localToDispose.add(this.cursorChangedScheduler);
249
this.localToDispose.add(this.languageFeaturesService.foldingRangeProvider.onDidChange(() => this.onFoldingStrategyChanged()));
250
this.localToDispose.add(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well
251
this.localToDispose.add(this.editor.onDidChangeModelContent(e => this.onDidChangeModelContent(e)));
252
this.localToDispose.add(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged()));
253
this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
254
this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
255
this.localToDispose.add({
256
dispose: () => {
257
if (this.foldingRegionPromise) {
258
this.foldingRegionPromise.cancel();
259
this.foldingRegionPromise = null;
260
}
261
this.updateScheduler?.cancel();
262
this.updateScheduler = null;
263
this.foldingModel = null;
264
this.foldingModelPromise = null;
265
this.hiddenRangeModel = null;
266
this.cursorChangedScheduler = null;
267
this.rangeProvider?.dispose();
268
this.rangeProvider = null;
269
}
270
});
271
this.triggerFoldingModelChanged();
272
}
273
274
private onFoldingStrategyChanged() {
275
this.rangeProvider?.dispose();
276
this.rangeProvider = null;
277
this.triggerFoldingModelChanged();
278
}
279
280
private getRangeProvider(editorModel: ITextModel): RangeProvider {
281
if (this.rangeProvider) {
282
return this.rangeProvider;
283
}
284
const indentRangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._foldingLimitReporter);
285
this.rangeProvider = indentRangeProvider; // fallback
286
if (this._useFoldingProviders && this.foldingModel) {
287
const selectedProviders = FoldingController.getFoldingRangeProviders(this.languageFeaturesService, editorModel);
288
if (selectedProviders.length > 0) {
289
this.rangeProvider = new SyntaxRangeProvider(editorModel, selectedProviders, () => this.triggerFoldingModelChanged(), this._foldingLimitReporter, indentRangeProvider);
290
}
291
}
292
return this.rangeProvider;
293
}
294
295
public getFoldingModel(): Promise<FoldingModel | null> | null {
296
return this.foldingModelPromise;
297
}
298
299
private onDidChangeModelContent(e: IModelContentChangedEvent) {
300
this.hiddenRangeModel?.notifyChangeModelContent(e);
301
this.triggerFoldingModelChanged();
302
}
303
304
305
public triggerFoldingModelChanged() {
306
if (this.updateScheduler) {
307
if (this.foldingRegionPromise) {
308
this.foldingRegionPromise.cancel();
309
this.foldingRegionPromise = null;
310
}
311
this.foldingModelPromise = this.updateScheduler.trigger(() => {
312
const foldingModel = this.foldingModel;
313
if (!foldingModel) { // null if editor has been disposed, or folding turned off
314
return null;
315
}
316
const sw = new StopWatch();
317
const provider = this.getRangeProvider(foldingModel.textModel);
318
const foldingRegionPromise = this.foldingRegionPromise = createCancelablePromise(token => provider.compute(token));
319
return foldingRegionPromise.then(foldingRanges => {
320
if (foldingRanges && foldingRegionPromise === this.foldingRegionPromise) { // new request or cancelled in the meantime?
321
let scrollState: StableEditorScrollState | undefined;
322
323
if (this._foldingImportsByDefault && !this._currentModelHasFoldedImports) {
324
const hasChanges = foldingRanges.setCollapsedAllOfType(FoldingRangeKind.Imports.value, true);
325
if (hasChanges) {
326
scrollState = StableEditorScrollState.capture(this.editor);
327
this._currentModelHasFoldedImports = hasChanges;
328
}
329
}
330
331
// some cursors might have moved into hidden regions, make sure they are in expanded regions
332
const selections = this.editor.getSelections();
333
foldingModel.update(foldingRanges, toSelectedLines(selections));
334
335
scrollState?.restore(this.editor);
336
337
// update debounce info
338
const newValue = this.updateDebounceInfo.update(foldingModel.textModel, sw.elapsed());
339
if (this.updateScheduler) {
340
this.updateScheduler.defaultDelay = newValue;
341
}
342
}
343
return foldingModel;
344
});
345
}).then(undefined, (err) => {
346
onUnexpectedError(err);
347
return null;
348
});
349
}
350
}
351
352
private onHiddenRangesChanges(hiddenRanges: IRange[]) {
353
if (this.hiddenRangeModel && hiddenRanges.length && !this._restoringViewState) {
354
const selections = this.editor.getSelections();
355
if (selections) {
356
if (this.hiddenRangeModel.adjustSelections(selections)) {
357
this.editor.setSelections(selections);
358
}
359
}
360
}
361
this.editor.setHiddenAreas(hiddenRanges, this);
362
}
363
364
private onCursorPositionChanged() {
365
if (this.hiddenRangeModel && this.hiddenRangeModel.hasRanges()) {
366
this.cursorChangedScheduler!.schedule();
367
}
368
}
369
370
private revealCursor() {
371
const foldingModel = this.getFoldingModel();
372
if (!foldingModel) {
373
return;
374
}
375
foldingModel.then(foldingModel => { // null is returned if folding got disabled in the meantime
376
if (foldingModel) {
377
const selections = this.editor.getSelections();
378
if (selections && selections.length > 0) {
379
const toToggle: FoldingRegion[] = [];
380
for (const selection of selections) {
381
const lineNumber = selection.selectionStartLineNumber;
382
if (this.hiddenRangeModel && this.hiddenRangeModel.isHidden(lineNumber)) {
383
toToggle.push(...foldingModel.getAllRegionsAtLine(lineNumber, r => r.isCollapsed && lineNumber > r.startLineNumber));
384
}
385
}
386
if (toToggle.length) {
387
foldingModel.toggleCollapseState(toToggle);
388
this.reveal(selections[0].getPosition());
389
}
390
}
391
}
392
}).then(undefined, onUnexpectedError);
393
394
}
395
396
private onEditorMouseDown(e: IEditorMouseEvent): void {
397
this.mouseDownInfo = null;
398
399
400
if (!this.hiddenRangeModel || !e.target || !e.target.range) {
401
return;
402
}
403
if (!e.event.leftButton && !e.event.middleButton) {
404
return;
405
}
406
const range = e.target.range;
407
let iconClicked = false;
408
switch (e.target.type) {
409
case MouseTargetType.GUTTER_LINE_DECORATIONS: {
410
const data = e.target.detail;
411
const offsetLeftInGutter = e.target.element!.offsetLeft;
412
const gutterOffsetX = data.offsetX - offsetLeftInGutter;
413
414
// const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft;
415
416
// TODO@joao TODO@alex TODO@martin this is such that we don't collide with dirty diff
417
if (gutterOffsetX < 4) { // the whitespace between the border and the real folding icon border is 4px
418
return;
419
}
420
421
iconClicked = true;
422
break;
423
}
424
case MouseTargetType.CONTENT_EMPTY: {
425
if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) {
426
const data = e.target.detail;
427
if (!data.isAfterLines) {
428
break;
429
}
430
}
431
return;
432
}
433
case MouseTargetType.CONTENT_TEXT: {
434
if (this.hiddenRangeModel.hasRanges()) {
435
const model = this.editor.getModel();
436
if (model && range.startColumn === model.getLineMaxColumn(range.startLineNumber)) {
437
break;
438
}
439
}
440
return;
441
}
442
default:
443
return;
444
}
445
446
this.mouseDownInfo = { lineNumber: range.startLineNumber, iconClicked };
447
}
448
449
private onEditorMouseUp(e: IEditorMouseEvent): void {
450
const foldingModel = this.foldingModel;
451
if (!foldingModel || !this.mouseDownInfo || !e.target) {
452
return;
453
}
454
const lineNumber = this.mouseDownInfo.lineNumber;
455
const iconClicked = this.mouseDownInfo.iconClicked;
456
457
const range = e.target.range;
458
if (!range || range.startLineNumber !== lineNumber) {
459
return;
460
}
461
462
if (iconClicked) {
463
if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
464
return;
465
}
466
} else {
467
const model = this.editor.getModel();
468
if (!model || range.startColumn !== model.getLineMaxColumn(lineNumber)) {
469
return;
470
}
471
}
472
473
const region = foldingModel.getRegionAtLine(lineNumber);
474
if (region && region.startLineNumber === lineNumber) {
475
const isCollapsed = region.isCollapsed;
476
if (iconClicked || isCollapsed) {
477
const surrounding = e.event.altKey;
478
let toToggle = [];
479
if (surrounding) {
480
const filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region) && !region.containedBy(otherRegion);
481
const toMaybeToggle = foldingModel.getRegionsInside(null, filter);
482
for (const r of toMaybeToggle) {
483
if (r.isCollapsed) {
484
toToggle.push(r);
485
}
486
}
487
// if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding
488
if (toToggle.length === 0) {
489
toToggle = toMaybeToggle;
490
}
491
}
492
else {
493
const recursive = e.event.middleButton || e.event.shiftKey;
494
if (recursive) {
495
for (const r of foldingModel.getRegionsInside(region)) {
496
if (r.isCollapsed === isCollapsed) {
497
toToggle.push(r);
498
}
499
}
500
}
501
// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
502
if (isCollapsed || !recursive || toToggle.length === 0) {
503
toToggle.push(region);
504
}
505
}
506
foldingModel.toggleCollapseState(toToggle);
507
this.reveal({ lineNumber, column: 1 });
508
}
509
}
510
}
511
512
public reveal(position: IPosition): void {
513
this.editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth);
514
}
515
}
516
517
export class RangesLimitReporter extends Disposable implements FoldingLimitReporter {
518
constructor(private readonly editor: ICodeEditor) {
519
super();
520
}
521
522
public get limit() {
523
return this.editor.getOptions().get(EditorOption.foldingMaximumRegions);
524
}
525
526
private _onDidChange = this._register(new Emitter<void>());
527
public get onDidChange(): Event<void> { return this._onDidChange.event; }
528
529
private _computed: number = 0;
530
private _limited: number | false = false;
531
public get computed(): number {
532
return this._computed;
533
}
534
public get limited(): number | false {
535
return this._limited;
536
}
537
public update(computed: number, limited: number | false) {
538
if (computed !== this._computed || limited !== this._limited) {
539
this._computed = computed;
540
this._limited = limited;
541
this._onDidChange.fire();
542
}
543
}
544
}
545
546
abstract class FoldingAction<T> extends EditorAction {
547
548
abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T, languageConfigurationService: ILanguageConfigurationService): void;
549
550
public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise<void> {
551
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
552
const foldingController = FoldingController.get(editor);
553
if (!foldingController) {
554
return;
555
}
556
const foldingModelPromise = foldingController.getFoldingModel();
557
if (foldingModelPromise) {
558
this.reportTelemetry(accessor, editor);
559
return foldingModelPromise.then(foldingModel => {
560
if (foldingModel) {
561
this.invoke(foldingController, foldingModel, editor, args, languageConfigurationService);
562
const selection = editor.getSelection();
563
if (selection) {
564
foldingController.reveal(selection.getStartPosition());
565
}
566
}
567
});
568
}
569
}
570
571
protected getSelectedLines(editor: ICodeEditor) {
572
const selections = editor.getSelections();
573
return selections ? selections.map(s => s.startLineNumber) : [];
574
}
575
576
protected getLineNumbers(args: FoldingArguments, editor: ICodeEditor) {
577
if (args && args.selectionLines) {
578
return args.selectionLines.map(l => l + 1); // to 0-bases line numbers
579
}
580
return this.getSelectedLines(editor);
581
}
582
583
public run(_accessor: ServicesAccessor, _editor: ICodeEditor): void {
584
}
585
}
586
587
export interface SelectedLines {
588
startsInside(startLine: number, endLine: number): boolean;
589
}
590
591
export function toSelectedLines(selections: Selection[] | null): SelectedLines {
592
if (!selections || selections.length === 0) {
593
return {
594
startsInside: () => false
595
};
596
}
597
return {
598
startsInside(startLine: number, endLine: number): boolean {
599
for (const s of selections) {
600
const line = s.startLineNumber;
601
if (line >= startLine && line <= endLine) {
602
return true;
603
}
604
}
605
return false;
606
}
607
};
608
}
609
610
interface FoldingArguments {
611
levels?: number;
612
direction?: 'up' | 'down';
613
selectionLines?: number[];
614
}
615
616
function foldingArgumentsConstraint(args: unknown) {
617
if (!types.isUndefined(args)) {
618
if (!types.isObject(args)) {
619
return false;
620
}
621
const foldingArgs: FoldingArguments = args;
622
if (!types.isUndefined(foldingArgs.levels) && !types.isNumber(foldingArgs.levels)) {
623
return false;
624
}
625
if (!types.isUndefined(foldingArgs.direction) && !types.isString(foldingArgs.direction)) {
626
return false;
627
}
628
if (!types.isUndefined(foldingArgs.selectionLines) && (!Array.isArray(foldingArgs.selectionLines) || !foldingArgs.selectionLines.every(types.isNumber))) {
629
return false;
630
}
631
}
632
return true;
633
}
634
635
class UnfoldAction extends FoldingAction<FoldingArguments> {
636
637
constructor() {
638
super({
639
id: 'editor.unfold',
640
label: nls.localize2('unfoldAction.label', "Unfold"),
641
precondition: CONTEXT_FOLDING_ENABLED,
642
kbOpts: {
643
kbExpr: EditorContextKeys.editorTextFocus,
644
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.BracketRight,
645
mac: {
646
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.BracketRight
647
},
648
weight: KeybindingWeight.EditorContrib
649
},
650
metadata: {
651
description: 'Unfold the content in the editor',
652
args: [
653
{
654
name: 'Unfold editor argument',
655
description: `Property-value pairs that can be passed through this argument:
656
* 'levels': Number of levels to unfold. If not set, defaults to 1.
657
* 'direction': If 'up', unfold given number of levels up otherwise unfolds down.
658
* 'selectionLines': Array of the start lines (0-based) of the editor selections to apply the unfold action to. If not set, the active selection(s) will be used.
659
`,
660
constraint: foldingArgumentsConstraint,
661
schema: {
662
'type': 'object',
663
'properties': {
664
'levels': {
665
'type': 'number',
666
'default': 1
667
},
668
'direction': {
669
'type': 'string',
670
'enum': ['up', 'down'],
671
'default': 'down'
672
},
673
'selectionLines': {
674
'type': 'array',
675
'items': {
676
'type': 'number'
677
}
678
}
679
}
680
}
681
}
682
]
683
}
684
});
685
}
686
687
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: FoldingArguments): void {
688
const levels = args && args.levels || 1;
689
const lineNumbers = this.getLineNumbers(args, editor);
690
if (args && args.direction === 'up') {
691
setCollapseStateLevelsUp(foldingModel, false, levels, lineNumbers);
692
} else {
693
setCollapseStateLevelsDown(foldingModel, false, levels, lineNumbers);
694
}
695
}
696
}
697
698
class UnFoldRecursivelyAction extends FoldingAction<void> {
699
700
constructor() {
701
super({
702
id: 'editor.unfoldRecursively',
703
label: nls.localize2('unFoldRecursivelyAction.label', "Unfold Recursively"),
704
precondition: CONTEXT_FOLDING_ENABLED,
705
kbOpts: {
706
kbExpr: EditorContextKeys.editorTextFocus,
707
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.BracketRight),
708
weight: KeybindingWeight.EditorContrib
709
}
710
});
711
}
712
713
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, _args: unknown): void {
714
setCollapseStateLevelsDown(foldingModel, false, Number.MAX_VALUE, this.getSelectedLines(editor));
715
}
716
}
717
718
class FoldAction extends FoldingAction<FoldingArguments> {
719
720
constructor() {
721
super({
722
id: 'editor.fold',
723
label: nls.localize2('foldAction.label', "Fold"),
724
precondition: CONTEXT_FOLDING_ENABLED,
725
kbOpts: {
726
kbExpr: EditorContextKeys.editorTextFocus,
727
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.BracketLeft,
728
mac: {
729
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.BracketLeft
730
},
731
weight: KeybindingWeight.EditorContrib
732
},
733
metadata: {
734
description: 'Fold the content in the editor',
735
args: [
736
{
737
name: 'Fold editor argument',
738
description: `Property-value pairs that can be passed through this argument:
739
* 'levels': Number of levels to fold.
740
* 'direction': If 'up', folds given number of levels up otherwise folds down.
741
* 'selectionLines': Array of the start lines (0-based) of the editor selections to apply the fold action to. If not set, the active selection(s) will be used.
742
If no levels or direction is set, folds the region at the locations or if already collapsed, the first uncollapsed parent instead.
743
`,
744
constraint: foldingArgumentsConstraint,
745
schema: {
746
'type': 'object',
747
'properties': {
748
'levels': {
749
'type': 'number',
750
},
751
'direction': {
752
'type': 'string',
753
'enum': ['up', 'down'],
754
},
755
'selectionLines': {
756
'type': 'array',
757
'items': {
758
'type': 'number'
759
}
760
}
761
}
762
}
763
}
764
]
765
}
766
});
767
}
768
769
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: FoldingArguments): void {
770
const lineNumbers = this.getLineNumbers(args, editor);
771
772
const levels = args && args.levels;
773
const direction = args && args.direction;
774
775
if (typeof levels !== 'number' && typeof direction !== 'string') {
776
// fold the region at the location or if already collapsed, the first uncollapsed parent instead.
777
setCollapseStateUp(foldingModel, true, lineNumbers);
778
} else {
779
if (direction === 'up') {
780
setCollapseStateLevelsUp(foldingModel, true, levels || 1, lineNumbers);
781
} else {
782
setCollapseStateLevelsDown(foldingModel, true, levels || 1, lineNumbers);
783
}
784
}
785
}
786
}
787
788
789
class ToggleFoldAction extends FoldingAction<void> {
790
791
constructor() {
792
super({
793
id: 'editor.toggleFold',
794
label: nls.localize2('toggleFoldAction.label', "Toggle Fold"),
795
precondition: CONTEXT_FOLDING_ENABLED,
796
kbOpts: {
797
kbExpr: EditorContextKeys.editorTextFocus,
798
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyL),
799
weight: KeybindingWeight.EditorContrib
800
}
801
});
802
}
803
804
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
805
const selectedLines = this.getSelectedLines(editor);
806
toggleCollapseState(foldingModel, 1, selectedLines);
807
}
808
}
809
810
811
class FoldRecursivelyAction extends FoldingAction<void> {
812
813
constructor() {
814
super({
815
id: 'editor.foldRecursively',
816
label: nls.localize2('foldRecursivelyAction.label', "Fold Recursively"),
817
precondition: CONTEXT_FOLDING_ENABLED,
818
kbOpts: {
819
kbExpr: EditorContextKeys.editorTextFocus,
820
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.BracketLeft),
821
weight: KeybindingWeight.EditorContrib
822
}
823
});
824
}
825
826
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
827
const selectedLines = this.getSelectedLines(editor);
828
setCollapseStateLevelsDown(foldingModel, true, Number.MAX_VALUE, selectedLines);
829
}
830
}
831
832
833
class ToggleFoldRecursivelyAction extends FoldingAction<void> {
834
835
constructor() {
836
super({
837
id: 'editor.toggleFoldRecursively',
838
label: nls.localize2('toggleFoldRecursivelyAction.label', "Toggle Fold Recursively"),
839
precondition: CONTEXT_FOLDING_ENABLED,
840
kbOpts: {
841
kbExpr: EditorContextKeys.editorTextFocus,
842
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL),
843
weight: KeybindingWeight.EditorContrib
844
}
845
});
846
}
847
848
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
849
const selectedLines = this.getSelectedLines(editor);
850
toggleCollapseState(foldingModel, Number.MAX_VALUE, selectedLines);
851
}
852
}
853
854
855
class FoldAllBlockCommentsAction extends FoldingAction<void> {
856
857
constructor() {
858
super({
859
id: 'editor.foldAllBlockComments',
860
label: nls.localize2('foldAllBlockComments.label', "Fold All Block Comments"),
861
precondition: CONTEXT_FOLDING_ENABLED,
862
kbOpts: {
863
kbExpr: EditorContextKeys.editorTextFocus,
864
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Slash),
865
weight: KeybindingWeight.EditorContrib
866
}
867
});
868
}
869
870
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {
871
if (foldingModel.regions.hasTypes()) {
872
setCollapseStateForType(foldingModel, FoldingRangeKind.Comment.value, true);
873
} else {
874
const editorModel = editor.getModel();
875
if (!editorModel) {
876
return;
877
}
878
const comments = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).comments;
879
if (comments && comments.blockCommentStartToken) {
880
const regExp = new RegExp('^\\s*' + escapeRegExpCharacters(comments.blockCommentStartToken));
881
setCollapseStateForMatchingLines(foldingModel, regExp, true);
882
}
883
}
884
}
885
}
886
887
class FoldAllRegionsAction extends FoldingAction<void> {
888
889
constructor() {
890
super({
891
id: 'editor.foldAllMarkerRegions',
892
label: nls.localize2('foldAllMarkerRegions.label', "Fold All Regions"),
893
precondition: CONTEXT_FOLDING_ENABLED,
894
kbOpts: {
895
kbExpr: EditorContextKeys.editorTextFocus,
896
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit8),
897
weight: KeybindingWeight.EditorContrib
898
}
899
});
900
}
901
902
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {
903
if (foldingModel.regions.hasTypes()) {
904
setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, true);
905
} else {
906
const editorModel = editor.getModel();
907
if (!editorModel) {
908
return;
909
}
910
const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules;
911
if (foldingRules && foldingRules.markers && foldingRules.markers.start) {
912
const regExp = new RegExp(foldingRules.markers.start);
913
setCollapseStateForMatchingLines(foldingModel, regExp, true);
914
}
915
}
916
}
917
}
918
919
class UnfoldAllRegionsAction extends FoldingAction<void> {
920
921
constructor() {
922
super({
923
id: 'editor.unfoldAllMarkerRegions',
924
label: nls.localize2('unfoldAllMarkerRegions.label', "Unfold All Regions"),
925
precondition: CONTEXT_FOLDING_ENABLED,
926
kbOpts: {
927
kbExpr: EditorContextKeys.editorTextFocus,
928
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit9),
929
weight: KeybindingWeight.EditorContrib
930
}
931
});
932
}
933
934
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {
935
if (foldingModel.regions.hasTypes()) {
936
setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, false);
937
} else {
938
const editorModel = editor.getModel();
939
if (!editorModel) {
940
return;
941
}
942
const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules;
943
if (foldingRules && foldingRules.markers && foldingRules.markers.start) {
944
const regExp = new RegExp(foldingRules.markers.start);
945
setCollapseStateForMatchingLines(foldingModel, regExp, false);
946
}
947
}
948
}
949
}
950
951
class FoldAllExceptAction extends FoldingAction<void> {
952
953
constructor() {
954
super({
955
id: 'editor.foldAllExcept',
956
label: nls.localize2('foldAllExcept.label', "Fold All Except Selected"),
957
precondition: CONTEXT_FOLDING_ENABLED,
958
kbOpts: {
959
kbExpr: EditorContextKeys.editorTextFocus,
960
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus),
961
weight: KeybindingWeight.EditorContrib
962
}
963
});
964
}
965
966
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
967
const selectedLines = this.getSelectedLines(editor);
968
setCollapseStateForRest(foldingModel, true, selectedLines);
969
}
970
971
}
972
973
class UnfoldAllExceptAction extends FoldingAction<void> {
974
975
constructor() {
976
super({
977
id: 'editor.unfoldAllExcept',
978
label: nls.localize2('unfoldAllExcept.label', "Unfold All Except Selected"),
979
precondition: CONTEXT_FOLDING_ENABLED,
980
kbOpts: {
981
kbExpr: EditorContextKeys.editorTextFocus,
982
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Equal),
983
weight: KeybindingWeight.EditorContrib
984
}
985
});
986
}
987
988
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
989
const selectedLines = this.getSelectedLines(editor);
990
setCollapseStateForRest(foldingModel, false, selectedLines);
991
}
992
}
993
994
class FoldAllAction extends FoldingAction<void> {
995
996
constructor() {
997
super({
998
id: 'editor.foldAll',
999
label: nls.localize2('foldAllAction.label', "Fold All"),
1000
precondition: CONTEXT_FOLDING_ENABLED,
1001
kbOpts: {
1002
kbExpr: EditorContextKeys.editorTextFocus,
1003
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit0),
1004
weight: KeybindingWeight.EditorContrib
1005
}
1006
});
1007
}
1008
1009
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, _editor: ICodeEditor): void {
1010
setCollapseStateLevelsDown(foldingModel, true);
1011
}
1012
}
1013
1014
class UnfoldAllAction extends FoldingAction<void> {
1015
1016
constructor() {
1017
super({
1018
id: 'editor.unfoldAll',
1019
label: nls.localize2('unfoldAllAction.label', "Unfold All"),
1020
precondition: CONTEXT_FOLDING_ENABLED,
1021
kbOpts: {
1022
kbExpr: EditorContextKeys.editorTextFocus,
1023
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyJ),
1024
weight: KeybindingWeight.EditorContrib
1025
}
1026
});
1027
}
1028
1029
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, _editor: ICodeEditor): void {
1030
setCollapseStateLevelsDown(foldingModel, false);
1031
}
1032
}
1033
1034
class FoldLevelAction extends FoldingAction<void> {
1035
private static readonly ID_PREFIX = 'editor.foldLevel';
1036
public static readonly ID = (level: number) => FoldLevelAction.ID_PREFIX + level;
1037
1038
private getFoldingLevel() {
1039
return parseInt(this.id.substr(FoldLevelAction.ID_PREFIX.length));
1040
}
1041
1042
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1043
setCollapseStateAtLevel(foldingModel, this.getFoldingLevel(), true, this.getSelectedLines(editor));
1044
}
1045
}
1046
1047
/** Action to go to the parent fold of current line */
1048
class GotoParentFoldAction extends FoldingAction<void> {
1049
constructor() {
1050
super({
1051
id: 'editor.gotoParentFold',
1052
label: nls.localize2('gotoParentFold.label', "Go to Parent Fold"),
1053
precondition: CONTEXT_FOLDING_ENABLED,
1054
kbOpts: {
1055
kbExpr: EditorContextKeys.editorTextFocus,
1056
weight: KeybindingWeight.EditorContrib
1057
}
1058
});
1059
}
1060
1061
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1062
const selectedLines = this.getSelectedLines(editor);
1063
if (selectedLines.length > 0) {
1064
const startLineNumber = getParentFoldLine(selectedLines[0], foldingModel);
1065
if (startLineNumber !== null) {
1066
editor.setSelection({
1067
startLineNumber: startLineNumber,
1068
startColumn: 1,
1069
endLineNumber: startLineNumber,
1070
endColumn: 1
1071
});
1072
}
1073
}
1074
}
1075
}
1076
1077
/** Action to go to the previous fold of current line */
1078
class GotoPreviousFoldAction extends FoldingAction<void> {
1079
constructor() {
1080
super({
1081
id: 'editor.gotoPreviousFold',
1082
label: nls.localize2('gotoPreviousFold.label', "Go to Previous Folding Range"),
1083
precondition: CONTEXT_FOLDING_ENABLED,
1084
kbOpts: {
1085
kbExpr: EditorContextKeys.editorTextFocus,
1086
weight: KeybindingWeight.EditorContrib
1087
}
1088
});
1089
}
1090
1091
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1092
const selectedLines = this.getSelectedLines(editor);
1093
if (selectedLines.length > 0) {
1094
const startLineNumber = getPreviousFoldLine(selectedLines[0], foldingModel);
1095
if (startLineNumber !== null) {
1096
editor.setSelection({
1097
startLineNumber: startLineNumber,
1098
startColumn: 1,
1099
endLineNumber: startLineNumber,
1100
endColumn: 1
1101
});
1102
}
1103
}
1104
}
1105
}
1106
1107
/** Action to go to the next fold of current line */
1108
class GotoNextFoldAction extends FoldingAction<void> {
1109
constructor() {
1110
super({
1111
id: 'editor.gotoNextFold',
1112
label: nls.localize2('gotoNextFold.label', "Go to Next Folding Range"),
1113
precondition: CONTEXT_FOLDING_ENABLED,
1114
kbOpts: {
1115
kbExpr: EditorContextKeys.editorTextFocus,
1116
weight: KeybindingWeight.EditorContrib
1117
}
1118
});
1119
}
1120
1121
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1122
const selectedLines = this.getSelectedLines(editor);
1123
if (selectedLines.length > 0) {
1124
const startLineNumber = getNextFoldLine(selectedLines[0], foldingModel);
1125
if (startLineNumber !== null) {
1126
editor.setSelection({
1127
startLineNumber: startLineNumber,
1128
startColumn: 1,
1129
endLineNumber: startLineNumber,
1130
endColumn: 1
1131
});
1132
}
1133
}
1134
}
1135
}
1136
1137
class FoldRangeFromSelectionAction extends FoldingAction<void> {
1138
1139
constructor() {
1140
super({
1141
id: 'editor.createFoldingRangeFromSelection',
1142
label: nls.localize2('createManualFoldRange.label', "Create Folding Range from Selection"),
1143
precondition: CONTEXT_FOLDING_ENABLED,
1144
kbOpts: {
1145
kbExpr: EditorContextKeys.editorTextFocus,
1146
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Comma),
1147
weight: KeybindingWeight.EditorContrib
1148
}
1149
});
1150
}
1151
1152
invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1153
const collapseRanges: FoldRange[] = [];
1154
const selections = editor.getSelections();
1155
if (selections) {
1156
for (const selection of selections) {
1157
let endLineNumber = selection.endLineNumber;
1158
if (selection.endColumn === 1) {
1159
--endLineNumber;
1160
}
1161
if (endLineNumber > selection.startLineNumber) {
1162
collapseRanges.push({
1163
startLineNumber: selection.startLineNumber,
1164
endLineNumber: endLineNumber,
1165
type: undefined,
1166
isCollapsed: true,
1167
source: FoldSource.userDefined
1168
});
1169
editor.setSelection({
1170
startLineNumber: selection.startLineNumber,
1171
startColumn: 1,
1172
endLineNumber: selection.startLineNumber,
1173
endColumn: 1
1174
});
1175
}
1176
}
1177
if (collapseRanges.length > 0) {
1178
collapseRanges.sort((a, b) => {
1179
return a.startLineNumber - b.startLineNumber;
1180
});
1181
const newRanges = FoldingRegions.sanitizeAndMerge(foldingModel.regions, collapseRanges, editor.getModel()?.getLineCount());
1182
foldingModel.updatePost(FoldingRegions.fromFoldRanges(newRanges));
1183
}
1184
}
1185
}
1186
}
1187
1188
class RemoveFoldRangeFromSelectionAction extends FoldingAction<void> {
1189
1190
constructor() {
1191
super({
1192
id: 'editor.removeManualFoldingRanges',
1193
label: nls.localize2('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"),
1194
precondition: CONTEXT_FOLDING_ENABLED,
1195
kbOpts: {
1196
kbExpr: EditorContextKeys.editorTextFocus,
1197
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period),
1198
weight: KeybindingWeight.EditorContrib
1199
}
1200
});
1201
}
1202
1203
invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {
1204
const selections = editor.getSelections();
1205
if (selections) {
1206
const ranges: ILineRange[] = [];
1207
for (const selection of selections) {
1208
const { startLineNumber, endLineNumber } = selection;
1209
ranges.push(endLineNumber >= startLineNumber ? { startLineNumber, endLineNumber } : { endLineNumber, startLineNumber });
1210
}
1211
foldingModel.removeManualRanges(ranges);
1212
foldingController.triggerFoldingModelChanged();
1213
}
1214
}
1215
}
1216
1217
1218
class ToggleImportFoldAction extends FoldingAction<void> {
1219
1220
constructor() {
1221
super({
1222
id: 'editor.toggleImportFold',
1223
label: nls.localize2('toggleImportFold.label', "Toggle Import Fold"),
1224
precondition: CONTEXT_FOLDING_ENABLED,
1225
kbOpts: {
1226
kbExpr: EditorContextKeys.editorTextFocus,
1227
weight: KeybindingWeight.EditorContrib
1228
}
1229
});
1230
}
1231
1232
async invoke(foldingController: FoldingController, foldingModel: FoldingModel): Promise<void> {
1233
const regionsToToggle: FoldingRegion[] = [];
1234
const regions = foldingModel.regions;
1235
for (let i = regions.length - 1; i >= 0; i--) {
1236
if (regions.getType(i) === FoldingRangeKind.Imports.value) {
1237
regionsToToggle.push(regions.toRegion(i));
1238
}
1239
}
1240
foldingModel.toggleCollapseState(regionsToToggle);
1241
foldingController.triggerFoldingModelChanged();
1242
}
1243
}
1244
1245
1246
registerEditorContribution(FoldingController.ID, FoldingController, EditorContributionInstantiation.Eager); // eager because it uses `saveViewState`/`restoreViewState`
1247
registerEditorAction(UnfoldAction);
1248
registerEditorAction(UnFoldRecursivelyAction);
1249
registerEditorAction(FoldAction);
1250
registerEditorAction(FoldRecursivelyAction);
1251
registerEditorAction(ToggleFoldRecursivelyAction);
1252
registerEditorAction(FoldAllAction);
1253
registerEditorAction(UnfoldAllAction);
1254
registerEditorAction(FoldAllBlockCommentsAction);
1255
registerEditorAction(FoldAllRegionsAction);
1256
registerEditorAction(UnfoldAllRegionsAction);
1257
registerEditorAction(FoldAllExceptAction);
1258
registerEditorAction(UnfoldAllExceptAction);
1259
registerEditorAction(ToggleFoldAction);
1260
registerEditorAction(GotoParentFoldAction);
1261
registerEditorAction(GotoPreviousFoldAction);
1262
registerEditorAction(GotoNextFoldAction);
1263
registerEditorAction(FoldRangeFromSelectionAction);
1264
registerEditorAction(RemoveFoldRangeFromSelectionAction);
1265
registerEditorAction(ToggleImportFoldAction);
1266
1267
for (let i = 1; i <= 7; i++) {
1268
registerInstantiatedEditorAction(
1269
new FoldLevelAction({
1270
id: FoldLevelAction.ID(i),
1271
label: nls.localize2('foldLevelAction.label', "Fold Level {0}", i),
1272
precondition: CONTEXT_FOLDING_ENABLED,
1273
kbOpts: {
1274
kbExpr: EditorContextKeys.editorTextFocus,
1275
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | (KeyCode.Digit0 + i)),
1276
weight: KeybindingWeight.EditorContrib
1277
}
1278
})
1279
);
1280
}
1281
1282
CommandsRegistry.registerCommand('_executeFoldingRangeProvider', async function (accessor, ...args) {
1283
const [resource] = args;
1284
if (!(resource instanceof URI)) {
1285
throw illegalArgument();
1286
}
1287
1288
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
1289
1290
const model = accessor.get(IModelService).getModel(resource);
1291
if (!model) {
1292
throw illegalArgument();
1293
}
1294
1295
const configurationService = accessor.get(IConfigurationService);
1296
if (!configurationService.getValue('editor.folding', { resource })) {
1297
return [];
1298
}
1299
1300
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
1301
1302
const strategy = configurationService.getValue('editor.foldingStrategy', { resource });
1303
const foldingLimitReporter = {
1304
get limit() {
1305
return configurationService.getValue<number>('editor.foldingMaximumRegions', { resource });
1306
},
1307
update: (computed: number, limited: number | false) => { }
1308
};
1309
1310
const indentRangeProvider = new IndentRangeProvider(model, languageConfigurationService, foldingLimitReporter);
1311
let rangeProvider: RangeProvider = indentRangeProvider;
1312
if (strategy !== 'indentation') {
1313
const providers = FoldingController.getFoldingRangeProviders(languageFeaturesService, model);
1314
if (providers.length) {
1315
rangeProvider = new SyntaxRangeProvider(model, providers, () => { }, foldingLimitReporter, indentRangeProvider);
1316
}
1317
}
1318
const ranges = await rangeProvider.compute(CancellationToken.None);
1319
const result: FoldingRange[] = [];
1320
try {
1321
if (ranges) {
1322
for (let i = 0; i < ranges.length; i++) {
1323
const type = ranges.getType(i);
1324
result.push({ start: ranges.getStartLineNumber(i), end: ranges.getEndLineNumber(i), kind: type ? FoldingRangeKind.fromValue(type) : undefined });
1325
}
1326
}
1327
return result;
1328
} finally {
1329
rangeProvider.dispose();
1330
}
1331
});
1332
1333