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