Path: blob/main/src/vs/editor/contrib/folding/browser/folding.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancelablePromise, createCancelablePromise, Delayer, RunOnceScheduler } from '../../../../base/common/async.js';6import { CancellationToken } from '../../../../base/common/cancellation.js';7import { illegalArgument, onUnexpectedError } from '../../../../base/common/errors.js';8import { KeyChord, KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';9import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';10import { escapeRegExpCharacters } from '../../../../base/common/strings.js';11import * as types from '../../../../base/common/types.js';12import './folding.css';13import { StableEditorScrollState } from '../../../browser/stableEditorScroll.js';14import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from '../../../browser/editorBrowser.js';15import { EditorAction, EditorContributionInstantiation, registerEditorAction, registerEditorContribution, registerInstantiatedEditorAction, ServicesAccessor } from '../../../browser/editorExtensions.js';16import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js';17import { IPosition } from '../../../common/core/position.js';18import { IRange } from '../../../common/core/range.js';19import { Selection } from '../../../common/core/selection.js';20import { IEditorContribution, ScrollType } from '../../../common/editorCommon.js';21import { EditorContextKeys } from '../../../common/editorContextKeys.js';22import { ITextModel } from '../../../common/model.js';23import { IModelContentChangedEvent } from '../../../common/textModelEvents.js';24import { FoldingRange, FoldingRangeKind, FoldingRangeProvider } from '../../../common/languages.js';25import { ILanguageConfigurationService } from '../../../common/languages/languageConfigurationRegistry.js';26import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from './foldingModel.js';27import { HiddenRangeModel } from './hiddenRangeModel.js';28import { IndentRangeProvider } from './indentRangeProvider.js';29import * as nls from '../../../../nls.js';30import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';31import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';32import { FoldingDecorationProvider } from './foldingDecorations.js';33import { FoldingRegion, FoldingRegions, FoldRange, FoldSource, ILineRange } from './foldingRanges.js';34import { SyntaxRangeProvider } from './syntaxRangeProvider.js';35import { INotificationService } from '../../../../platform/notification/common/notification.js';36import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from '../../../common/services/languageFeatureDebounce.js';37import { StopWatch } from '../../../../base/common/stopwatch.js';38import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';39import { Emitter, Event } from '../../../../base/common/event.js';40import { CommandsRegistry } from '../../../../platform/commands/common/commands.js';41import { URI } from '../../../../base/common/uri.js';42import { IModelService } from '../../../common/services/model.js';43import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';4445const CONTEXT_FOLDING_ENABLED = new RawContextKey<boolean>('foldingEnabled', false);4647export interface RangeProvider {48readonly id: string;49compute(cancelationToken: CancellationToken): Promise<FoldingRegions | null>;50dispose(): void;51}5253interface FoldingStateMemento {54collapsedRegions?: CollapseMemento;55lineCount?: number;56provider?: string;57foldedImports?: boolean;58}5960export interface FoldingLimitReporter {61readonly limit: number;62update(computed: number, limited: number | false): void;63}6465export type FoldingRangeProviderSelector = (provider: FoldingRangeProvider[], document: ITextModel) => FoldingRangeProvider[] | undefined;6667export class FoldingController extends Disposable implements IEditorContribution {6869public static readonly ID = 'editor.contrib.folding';7071public static get(editor: ICodeEditor): FoldingController | null {72return editor.getContribution<FoldingController>(FoldingController.ID);73}7475private static _foldingRangeSelector: FoldingRangeProviderSelector | undefined;7677public static getFoldingRangeProviders(languageFeaturesService: ILanguageFeaturesService, model: ITextModel): FoldingRangeProvider[] {78const foldingRangeProviders = languageFeaturesService.foldingRangeProvider.ordered(model);79return (FoldingController._foldingRangeSelector?.(foldingRangeProviders, model)) ?? foldingRangeProviders;80}8182public static setFoldingRangeProviderSelector(foldingRangeSelector: FoldingRangeProviderSelector): IDisposable {83FoldingController._foldingRangeSelector = foldingRangeSelector;84return { dispose: () => { FoldingController._foldingRangeSelector = undefined; } };85}8687private readonly editor: ICodeEditor;88private _isEnabled: boolean;89private _useFoldingProviders: boolean;90private _unfoldOnClickAfterEndOfLine: boolean;91private _restoringViewState: boolean;92private _foldingImportsByDefault: boolean;93private _currentModelHasFoldedImports: boolean;9495private readonly foldingDecorationProvider: FoldingDecorationProvider;9697private foldingModel: FoldingModel | null;98private hiddenRangeModel: HiddenRangeModel | null;99100private rangeProvider: RangeProvider | null;101private foldingRegionPromise: CancelablePromise<FoldingRegions | null> | null;102103private foldingModelPromise: Promise<FoldingModel | null> | null;104private updateScheduler: Delayer<FoldingModel | null> | null;105private readonly updateDebounceInfo: IFeatureDebounceInformation;106107private foldingEnabled: IContextKey<boolean>;108private cursorChangedScheduler: RunOnceScheduler | null;109110private readonly localToDispose = this._register(new DisposableStore());111private mouseDownInfo: { lineNumber: number; iconClicked: boolean } | null;112113public readonly _foldingLimitReporter: RangesLimitReporter;114115constructor(116editor: ICodeEditor,117@IContextKeyService private readonly contextKeyService: IContextKeyService,118@ILanguageConfigurationService private readonly languageConfigurationService: ILanguageConfigurationService,119@INotificationService notificationService: INotificationService,120@ILanguageFeatureDebounceService languageFeatureDebounceService: ILanguageFeatureDebounceService,121@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,122) {123super();124this.editor = editor;125126this._foldingLimitReporter = this._register(new RangesLimitReporter(editor));127128const options = this.editor.getOptions();129this._isEnabled = options.get(EditorOption.folding);130this._useFoldingProviders = options.get(EditorOption.foldingStrategy) !== 'indentation';131this._unfoldOnClickAfterEndOfLine = options.get(EditorOption.unfoldOnClickAfterEndOfLine);132this._restoringViewState = false;133this._currentModelHasFoldedImports = false;134this._foldingImportsByDefault = options.get(EditorOption.foldingImportsByDefault);135this.updateDebounceInfo = languageFeatureDebounceService.for(languageFeaturesService.foldingRangeProvider, 'Folding', { min: 200 });136137this.foldingModel = null;138this.hiddenRangeModel = null;139this.rangeProvider = null;140this.foldingRegionPromise = null;141this.foldingModelPromise = null;142this.updateScheduler = null;143this.cursorChangedScheduler = null;144this.mouseDownInfo = null;145146this.foldingDecorationProvider = new FoldingDecorationProvider(editor);147this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);148this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);149this.foldingEnabled = CONTEXT_FOLDING_ENABLED.bindTo(this.contextKeyService);150this.foldingEnabled.set(this._isEnabled);151152this._register(this.editor.onDidChangeModel(() => this.onModelChanged()));153154this._register(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {155if (e.hasChanged(EditorOption.folding)) {156this._isEnabled = this.editor.getOptions().get(EditorOption.folding);157this.foldingEnabled.set(this._isEnabled);158this.onModelChanged();159}160if (e.hasChanged(EditorOption.foldingMaximumRegions)) {161this.onModelChanged();162}163if (e.hasChanged(EditorOption.showFoldingControls) || e.hasChanged(EditorOption.foldingHighlight)) {164const options = this.editor.getOptions();165this.foldingDecorationProvider.showFoldingControls = options.get(EditorOption.showFoldingControls);166this.foldingDecorationProvider.showFoldingHighlights = options.get(EditorOption.foldingHighlight);167this.triggerFoldingModelChanged();168}169if (e.hasChanged(EditorOption.foldingStrategy)) {170this._useFoldingProviders = this.editor.getOptions().get(EditorOption.foldingStrategy) !== 'indentation';171this.onFoldingStrategyChanged();172}173if (e.hasChanged(EditorOption.unfoldOnClickAfterEndOfLine)) {174this._unfoldOnClickAfterEndOfLine = this.editor.getOptions().get(EditorOption.unfoldOnClickAfterEndOfLine);175}176if (e.hasChanged(EditorOption.foldingImportsByDefault)) {177this._foldingImportsByDefault = this.editor.getOptions().get(EditorOption.foldingImportsByDefault);178}179}));180this.onModelChanged();181}182183public get limitReporter() {184return this._foldingLimitReporter;185}186187/**188* Store view state.189*/190public saveViewState(): FoldingStateMemento | undefined {191const model = this.editor.getModel();192if (!model || !this._isEnabled || model.isTooLargeForTokenization()) {193return {};194}195if (this.foldingModel) { // disposed ?196const collapsedRegions = this.foldingModel.getMemento();197const provider = this.rangeProvider ? this.rangeProvider.id : undefined;198return { collapsedRegions, lineCount: model.getLineCount(), provider, foldedImports: this._currentModelHasFoldedImports };199}200return undefined;201}202203/**204* Restore view state.205*/206public restoreViewState(state: FoldingStateMemento): void {207const model = this.editor.getModel();208if (!model || !this._isEnabled || model.isTooLargeForTokenization() || !this.hiddenRangeModel) {209return;210}211if (!state) {212return;213}214215this._currentModelHasFoldedImports = !!state.foldedImports;216if (state.collapsedRegions && state.collapsedRegions.length > 0 && this.foldingModel) {217this._restoringViewState = true;218try {219this.foldingModel.applyMemento(state.collapsedRegions);220} finally {221this._restoringViewState = false;222}223}224}225226private onModelChanged(): void {227this.localToDispose.clear();228229const model = this.editor.getModel();230if (!this._isEnabled || !model || model.isTooLargeForTokenization()) {231// huge files get no view model, so they cannot support hidden areas232return;233}234235this._currentModelHasFoldedImports = false;236this.foldingModel = new FoldingModel(model, this.foldingDecorationProvider);237this.localToDispose.add(this.foldingModel);238239this.hiddenRangeModel = new HiddenRangeModel(this.foldingModel);240this.localToDispose.add(this.hiddenRangeModel);241this.localToDispose.add(this.hiddenRangeModel.onDidChange(hr => this.onHiddenRangesChanges(hr)));242243this.updateScheduler = new Delayer<FoldingModel>(this.updateDebounceInfo.get(model));244245this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200);246this.localToDispose.add(this.cursorChangedScheduler);247this.localToDispose.add(this.languageFeaturesService.foldingRangeProvider.onDidChange(() => this.onFoldingStrategyChanged()));248this.localToDispose.add(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well249this.localToDispose.add(this.editor.onDidChangeModelContent(e => this.onDidChangeModelContent(e)));250this.localToDispose.add(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged()));251this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));252this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));253this.localToDispose.add({254dispose: () => {255if (this.foldingRegionPromise) {256this.foldingRegionPromise.cancel();257this.foldingRegionPromise = null;258}259this.updateScheduler?.cancel();260this.updateScheduler = null;261this.foldingModel = null;262this.foldingModelPromise = null;263this.hiddenRangeModel = null;264this.cursorChangedScheduler = null;265this.rangeProvider?.dispose();266this.rangeProvider = null;267}268});269this.triggerFoldingModelChanged();270}271272private onFoldingStrategyChanged() {273this.rangeProvider?.dispose();274this.rangeProvider = null;275this.triggerFoldingModelChanged();276}277278private getRangeProvider(editorModel: ITextModel): RangeProvider {279if (this.rangeProvider) {280return this.rangeProvider;281}282const indentRangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._foldingLimitReporter);283this.rangeProvider = indentRangeProvider; // fallback284if (this._useFoldingProviders && this.foldingModel) {285const selectedProviders = FoldingController.getFoldingRangeProviders(this.languageFeaturesService, editorModel);286if (selectedProviders.length > 0) {287this.rangeProvider = new SyntaxRangeProvider(editorModel, selectedProviders, () => this.triggerFoldingModelChanged(), this._foldingLimitReporter, indentRangeProvider);288}289}290return this.rangeProvider;291}292293public getFoldingModel(): Promise<FoldingModel | null> | null {294return this.foldingModelPromise;295}296297private onDidChangeModelContent(e: IModelContentChangedEvent) {298this.hiddenRangeModel?.notifyChangeModelContent(e);299this.triggerFoldingModelChanged();300}301302303public triggerFoldingModelChanged() {304if (this.updateScheduler) {305if (this.foldingRegionPromise) {306this.foldingRegionPromise.cancel();307this.foldingRegionPromise = null;308}309this.foldingModelPromise = this.updateScheduler.trigger(() => {310const foldingModel = this.foldingModel;311if (!foldingModel) { // null if editor has been disposed, or folding turned off312return null;313}314const sw = new StopWatch();315const provider = this.getRangeProvider(foldingModel.textModel);316const foldingRegionPromise = this.foldingRegionPromise = createCancelablePromise(token => provider.compute(token));317return foldingRegionPromise.then(foldingRanges => {318if (foldingRanges && foldingRegionPromise === this.foldingRegionPromise) { // new request or cancelled in the meantime?319let scrollState: StableEditorScrollState | undefined;320321if (this._foldingImportsByDefault && !this._currentModelHasFoldedImports) {322const hasChanges = foldingRanges.setCollapsedAllOfType(FoldingRangeKind.Imports.value, true);323if (hasChanges) {324scrollState = StableEditorScrollState.capture(this.editor);325this._currentModelHasFoldedImports = hasChanges;326}327}328329// some cursors might have moved into hidden regions, make sure they are in expanded regions330const selections = this.editor.getSelections();331foldingModel.update(foldingRanges, toSelectedLines(selections));332333scrollState?.restore(this.editor);334335// update debounce info336const newValue = this.updateDebounceInfo.update(foldingModel.textModel, sw.elapsed());337if (this.updateScheduler) {338this.updateScheduler.defaultDelay = newValue;339}340}341return foldingModel;342});343}).then(undefined, (err) => {344onUnexpectedError(err);345return null;346});347}348}349350private onHiddenRangesChanges(hiddenRanges: IRange[]) {351if (this.hiddenRangeModel && hiddenRanges.length && !this._restoringViewState) {352const selections = this.editor.getSelections();353if (selections) {354if (this.hiddenRangeModel.adjustSelections(selections)) {355this.editor.setSelections(selections);356}357}358}359this.editor.setHiddenAreas(hiddenRanges, this);360}361362private onCursorPositionChanged() {363if (this.hiddenRangeModel && this.hiddenRangeModel.hasRanges()) {364this.cursorChangedScheduler!.schedule();365}366}367368private revealCursor() {369const foldingModel = this.getFoldingModel();370if (!foldingModel) {371return;372}373foldingModel.then(foldingModel => { // null is returned if folding got disabled in the meantime374if (foldingModel) {375const selections = this.editor.getSelections();376if (selections && selections.length > 0) {377const toToggle: FoldingRegion[] = [];378for (const selection of selections) {379const lineNumber = selection.selectionStartLineNumber;380if (this.hiddenRangeModel && this.hiddenRangeModel.isHidden(lineNumber)) {381toToggle.push(...foldingModel.getAllRegionsAtLine(lineNumber, r => r.isCollapsed && lineNumber > r.startLineNumber));382}383}384if (toToggle.length) {385foldingModel.toggleCollapseState(toToggle);386this.reveal(selections[0].getPosition());387}388}389}390}).then(undefined, onUnexpectedError);391392}393394private onEditorMouseDown(e: IEditorMouseEvent): void {395this.mouseDownInfo = null;396397398if (!this.hiddenRangeModel || !e.target || !e.target.range) {399return;400}401if (!e.event.leftButton && !e.event.middleButton) {402return;403}404const range = e.target.range;405let iconClicked = false;406switch (e.target.type) {407case MouseTargetType.GUTTER_LINE_DECORATIONS: {408const data = e.target.detail;409const offsetLeftInGutter = e.target.element!.offsetLeft;410const gutterOffsetX = data.offsetX - offsetLeftInGutter;411412// const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft;413414// TODO@joao TODO@alex TODO@martin this is such that we don't collide with dirty diff415if (gutterOffsetX < 4) { // the whitespace between the border and the real folding icon border is 4px416return;417}418419iconClicked = true;420break;421}422case MouseTargetType.CONTENT_EMPTY: {423if (this._unfoldOnClickAfterEndOfLine && this.hiddenRangeModel.hasRanges()) {424const data = e.target.detail;425if (!data.isAfterLines) {426break;427}428}429return;430}431case MouseTargetType.CONTENT_TEXT: {432if (this.hiddenRangeModel.hasRanges()) {433const model = this.editor.getModel();434if (model && range.startColumn === model.getLineMaxColumn(range.startLineNumber)) {435break;436}437}438return;439}440default:441return;442}443444this.mouseDownInfo = { lineNumber: range.startLineNumber, iconClicked };445}446447private onEditorMouseUp(e: IEditorMouseEvent): void {448const foldingModel = this.foldingModel;449if (!foldingModel || !this.mouseDownInfo || !e.target) {450return;451}452const lineNumber = this.mouseDownInfo.lineNumber;453const iconClicked = this.mouseDownInfo.iconClicked;454455const range = e.target.range;456if (!range || range.startLineNumber !== lineNumber) {457return;458}459460if (iconClicked) {461if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {462return;463}464} else {465const model = this.editor.getModel();466if (!model || range.startColumn !== model.getLineMaxColumn(lineNumber)) {467return;468}469}470471const region = foldingModel.getRegionAtLine(lineNumber);472if (region && region.startLineNumber === lineNumber) {473const isCollapsed = region.isCollapsed;474if (iconClicked || isCollapsed) {475const surrounding = e.event.altKey;476let toToggle = [];477if (surrounding) {478const filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region) && !region.containedBy(otherRegion);479const toMaybeToggle = foldingModel.getRegionsInside(null, filter);480for (const r of toMaybeToggle) {481if (r.isCollapsed) {482toToggle.push(r);483}484}485// if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding486if (toToggle.length === 0) {487toToggle = toMaybeToggle;488}489}490else {491const recursive = e.event.middleButton || e.event.shiftKey;492if (recursive) {493for (const r of foldingModel.getRegionsInside(region)) {494if (r.isCollapsed === isCollapsed) {495toToggle.push(r);496}497}498}499// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.500if (isCollapsed || !recursive || toToggle.length === 0) {501toToggle.push(region);502}503}504foldingModel.toggleCollapseState(toToggle);505this.reveal({ lineNumber, column: 1 });506}507}508}509510public reveal(position: IPosition): void {511this.editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Smooth);512}513}514515export class RangesLimitReporter extends Disposable implements FoldingLimitReporter {516constructor(private readonly editor: ICodeEditor) {517super();518}519520public get limit() {521return this.editor.getOptions().get(EditorOption.foldingMaximumRegions);522}523524private _onDidChange = this._register(new Emitter<void>());525public get onDidChange(): Event<void> { return this._onDidChange.event; }526527private _computed: number = 0;528private _limited: number | false = false;529public get computed(): number {530return this._computed;531}532public get limited(): number | false {533return this._limited;534}535public update(computed: number, limited: number | false) {536if (computed !== this._computed || limited !== this._limited) {537this._computed = computed;538this._limited = limited;539this._onDidChange.fire();540}541}542}543544abstract class FoldingAction<T> extends EditorAction {545546abstract invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: T, languageConfigurationService: ILanguageConfigurationService): void;547548public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: T): void | Promise<void> {549const languageConfigurationService = accessor.get(ILanguageConfigurationService);550const foldingController = FoldingController.get(editor);551if (!foldingController) {552return;553}554const foldingModelPromise = foldingController.getFoldingModel();555if (foldingModelPromise) {556this.reportTelemetry(accessor, editor);557return foldingModelPromise.then(foldingModel => {558if (foldingModel) {559this.invoke(foldingController, foldingModel, editor, args, languageConfigurationService);560const selection = editor.getSelection();561if (selection) {562foldingController.reveal(selection.getStartPosition());563}564}565});566}567}568569protected getSelectedLines(editor: ICodeEditor) {570const selections = editor.getSelections();571return selections ? selections.map(s => s.startLineNumber) : [];572}573574protected getLineNumbers(args: FoldingArguments, editor: ICodeEditor) {575if (args && args.selectionLines) {576return args.selectionLines.map(l => l + 1); // to 0-bases line numbers577}578return this.getSelectedLines(editor);579}580581public run(_accessor: ServicesAccessor, _editor: ICodeEditor): void {582}583}584585export interface SelectedLines {586startsInside(startLine: number, endLine: number): boolean;587}588589export function toSelectedLines(selections: Selection[] | null): SelectedLines {590if (!selections || selections.length === 0) {591return {592startsInside: () => false593};594}595return {596startsInside(startLine: number, endLine: number): boolean {597for (const s of selections) {598const line = s.startLineNumber;599if (line >= startLine && line <= endLine) {600return true;601}602}603return false;604}605};606}607608interface FoldingArguments {609levels?: number;610direction?: 'up' | 'down';611selectionLines?: number[];612}613614function foldingArgumentsConstraint(args: any) {615if (!types.isUndefined(args)) {616if (!types.isObject(args)) {617return false;618}619const foldingArgs: FoldingArguments = args;620if (!types.isUndefined(foldingArgs.levels) && !types.isNumber(foldingArgs.levels)) {621return false;622}623if (!types.isUndefined(foldingArgs.direction) && !types.isString(foldingArgs.direction)) {624return false;625}626if (!types.isUndefined(foldingArgs.selectionLines) && (!Array.isArray(foldingArgs.selectionLines) || !foldingArgs.selectionLines.every(types.isNumber))) {627return false;628}629}630return true;631}632633class UnfoldAction extends FoldingAction<FoldingArguments> {634635constructor() {636super({637id: 'editor.unfold',638label: nls.localize2('unfoldAction.label', "Unfold"),639precondition: CONTEXT_FOLDING_ENABLED,640kbOpts: {641kbExpr: EditorContextKeys.editorTextFocus,642primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.BracketRight,643mac: {644primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.BracketRight645},646weight: KeybindingWeight.EditorContrib647},648metadata: {649description: 'Unfold the content in the editor',650args: [651{652name: 'Unfold editor argument',653description: `Property-value pairs that can be passed through this argument:654* 'levels': Number of levels to unfold. If not set, defaults to 1.655* 'direction': If 'up', unfold given number of levels up otherwise unfolds down.656* '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.657`,658constraint: foldingArgumentsConstraint,659schema: {660'type': 'object',661'properties': {662'levels': {663'type': 'number',664'default': 1665},666'direction': {667'type': 'string',668'enum': ['up', 'down'],669'default': 'down'670},671'selectionLines': {672'type': 'array',673'items': {674'type': 'number'675}676}677}678}679}680]681}682});683}684685invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: FoldingArguments): void {686const levels = args && args.levels || 1;687const lineNumbers = this.getLineNumbers(args, editor);688if (args && args.direction === 'up') {689setCollapseStateLevelsUp(foldingModel, false, levels, lineNumbers);690} else {691setCollapseStateLevelsDown(foldingModel, false, levels, lineNumbers);692}693}694}695696class UnFoldRecursivelyAction extends FoldingAction<void> {697698constructor() {699super({700id: 'editor.unfoldRecursively',701label: nls.localize2('unFoldRecursivelyAction.label', "Unfold Recursively"),702precondition: CONTEXT_FOLDING_ENABLED,703kbOpts: {704kbExpr: EditorContextKeys.editorTextFocus,705primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.BracketRight),706weight: KeybindingWeight.EditorContrib707}708});709}710711invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, _args: any): void {712setCollapseStateLevelsDown(foldingModel, false, Number.MAX_VALUE, this.getSelectedLines(editor));713}714}715716class FoldAction extends FoldingAction<FoldingArguments> {717718constructor() {719super({720id: 'editor.fold',721label: nls.localize2('foldAction.label', "Fold"),722precondition: CONTEXT_FOLDING_ENABLED,723kbOpts: {724kbExpr: EditorContextKeys.editorTextFocus,725primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.BracketLeft,726mac: {727primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.BracketLeft728},729weight: KeybindingWeight.EditorContrib730},731metadata: {732description: 'Fold the content in the editor',733args: [734{735name: 'Fold editor argument',736description: `Property-value pairs that can be passed through this argument:737* 'levels': Number of levels to fold.738* 'direction': If 'up', folds given number of levels up otherwise folds down.739* '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.740If no levels or direction is set, folds the region at the locations or if already collapsed, the first uncollapsed parent instead.741`,742constraint: foldingArgumentsConstraint,743schema: {744'type': 'object',745'properties': {746'levels': {747'type': 'number',748},749'direction': {750'type': 'string',751'enum': ['up', 'down'],752},753'selectionLines': {754'type': 'array',755'items': {756'type': 'number'757}758}759}760}761}762]763}764});765}766767invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: FoldingArguments): void {768const lineNumbers = this.getLineNumbers(args, editor);769770const levels = args && args.levels;771const direction = args && args.direction;772773if (typeof levels !== 'number' && typeof direction !== 'string') {774// fold the region at the location or if already collapsed, the first uncollapsed parent instead.775setCollapseStateUp(foldingModel, true, lineNumbers);776} else {777if (direction === 'up') {778setCollapseStateLevelsUp(foldingModel, true, levels || 1, lineNumbers);779} else {780setCollapseStateLevelsDown(foldingModel, true, levels || 1, lineNumbers);781}782}783}784}785786787class ToggleFoldAction extends FoldingAction<void> {788789constructor() {790super({791id: 'editor.toggleFold',792label: nls.localize2('toggleFoldAction.label', "Toggle Fold"),793precondition: CONTEXT_FOLDING_ENABLED,794kbOpts: {795kbExpr: EditorContextKeys.editorTextFocus,796primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyL),797weight: KeybindingWeight.EditorContrib798}799});800}801802invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {803const selectedLines = this.getSelectedLines(editor);804toggleCollapseState(foldingModel, 1, selectedLines);805}806}807808809class FoldRecursivelyAction extends FoldingAction<void> {810811constructor() {812super({813id: 'editor.foldRecursively',814label: nls.localize2('foldRecursivelyAction.label', "Fold Recursively"),815precondition: CONTEXT_FOLDING_ENABLED,816kbOpts: {817kbExpr: EditorContextKeys.editorTextFocus,818primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.BracketLeft),819weight: KeybindingWeight.EditorContrib820}821});822}823824invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {825const selectedLines = this.getSelectedLines(editor);826setCollapseStateLevelsDown(foldingModel, true, Number.MAX_VALUE, selectedLines);827}828}829830831class ToggleFoldRecursivelyAction extends FoldingAction<void> {832833constructor() {834super({835id: 'editor.toggleFoldRecursively',836label: nls.localize2('toggleFoldRecursivelyAction.label', "Toggle Fold Recursively"),837precondition: CONTEXT_FOLDING_ENABLED,838kbOpts: {839kbExpr: EditorContextKeys.editorTextFocus,840primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyL),841weight: KeybindingWeight.EditorContrib842}843});844}845846invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {847const selectedLines = this.getSelectedLines(editor);848toggleCollapseState(foldingModel, Number.MAX_VALUE, selectedLines);849}850}851852853class FoldAllBlockCommentsAction extends FoldingAction<void> {854855constructor() {856super({857id: 'editor.foldAllBlockComments',858label: nls.localize2('foldAllBlockComments.label', "Fold All Block Comments"),859precondition: CONTEXT_FOLDING_ENABLED,860kbOpts: {861kbExpr: EditorContextKeys.editorTextFocus,862primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Slash),863weight: KeybindingWeight.EditorContrib864}865});866}867868invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {869if (foldingModel.regions.hasTypes()) {870setCollapseStateForType(foldingModel, FoldingRangeKind.Comment.value, true);871} else {872const editorModel = editor.getModel();873if (!editorModel) {874return;875}876const comments = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).comments;877if (comments && comments.blockCommentStartToken) {878const regExp = new RegExp('^\\s*' + escapeRegExpCharacters(comments.blockCommentStartToken));879setCollapseStateForMatchingLines(foldingModel, regExp, true);880}881}882}883}884885class FoldAllRegionsAction extends FoldingAction<void> {886887constructor() {888super({889id: 'editor.foldAllMarkerRegions',890label: nls.localize2('foldAllMarkerRegions.label', "Fold All Regions"),891precondition: CONTEXT_FOLDING_ENABLED,892kbOpts: {893kbExpr: EditorContextKeys.editorTextFocus,894primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit8),895weight: KeybindingWeight.EditorContrib896}897});898}899900invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {901if (foldingModel.regions.hasTypes()) {902setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, true);903} else {904const editorModel = editor.getModel();905if (!editorModel) {906return;907}908const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules;909if (foldingRules && foldingRules.markers && foldingRules.markers.start) {910const regExp = new RegExp(foldingRules.markers.start);911setCollapseStateForMatchingLines(foldingModel, regExp, true);912}913}914}915}916917class UnfoldAllRegionsAction extends FoldingAction<void> {918919constructor() {920super({921id: 'editor.unfoldAllMarkerRegions',922label: nls.localize2('unfoldAllMarkerRegions.label', "Unfold All Regions"),923precondition: CONTEXT_FOLDING_ENABLED,924kbOpts: {925kbExpr: EditorContextKeys.editorTextFocus,926primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit9),927weight: KeybindingWeight.EditorContrib928}929});930}931932invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor, args: void, languageConfigurationService: ILanguageConfigurationService): void {933if (foldingModel.regions.hasTypes()) {934setCollapseStateForType(foldingModel, FoldingRangeKind.Region.value, false);935} else {936const editorModel = editor.getModel();937if (!editorModel) {938return;939}940const foldingRules = languageConfigurationService.getLanguageConfiguration(editorModel.getLanguageId()).foldingRules;941if (foldingRules && foldingRules.markers && foldingRules.markers.start) {942const regExp = new RegExp(foldingRules.markers.start);943setCollapseStateForMatchingLines(foldingModel, regExp, false);944}945}946}947}948949class FoldAllExceptAction extends FoldingAction<void> {950951constructor() {952super({953id: 'editor.foldAllExcept',954label: nls.localize2('foldAllExcept.label', "Fold All Except Selected"),955precondition: CONTEXT_FOLDING_ENABLED,956kbOpts: {957kbExpr: EditorContextKeys.editorTextFocus,958primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Minus),959weight: KeybindingWeight.EditorContrib960}961});962}963964invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {965const selectedLines = this.getSelectedLines(editor);966setCollapseStateForRest(foldingModel, true, selectedLines);967}968969}970971class UnfoldAllExceptAction extends FoldingAction<void> {972973constructor() {974super({975id: 'editor.unfoldAllExcept',976label: nls.localize2('unfoldAllExcept.label', "Unfold All Except Selected"),977precondition: CONTEXT_FOLDING_ENABLED,978kbOpts: {979kbExpr: EditorContextKeys.editorTextFocus,980primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Equal),981weight: KeybindingWeight.EditorContrib982}983});984}985986invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {987const selectedLines = this.getSelectedLines(editor);988setCollapseStateForRest(foldingModel, false, selectedLines);989}990}991992class FoldAllAction extends FoldingAction<void> {993994constructor() {995super({996id: 'editor.foldAll',997label: nls.localize2('foldAllAction.label', "Fold All"),998precondition: CONTEXT_FOLDING_ENABLED,999kbOpts: {1000kbExpr: EditorContextKeys.editorTextFocus,1001primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Digit0),1002weight: KeybindingWeight.EditorContrib1003}1004});1005}10061007invoke(_foldingController: FoldingController, foldingModel: FoldingModel, _editor: ICodeEditor): void {1008setCollapseStateLevelsDown(foldingModel, true);1009}1010}10111012class UnfoldAllAction extends FoldingAction<void> {10131014constructor() {1015super({1016id: 'editor.unfoldAll',1017label: nls.localize2('unfoldAllAction.label', "Unfold All"),1018precondition: CONTEXT_FOLDING_ENABLED,1019kbOpts: {1020kbExpr: EditorContextKeys.editorTextFocus,1021primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyJ),1022weight: KeybindingWeight.EditorContrib1023}1024});1025}10261027invoke(_foldingController: FoldingController, foldingModel: FoldingModel, _editor: ICodeEditor): void {1028setCollapseStateLevelsDown(foldingModel, false);1029}1030}10311032class FoldLevelAction extends FoldingAction<void> {1033private static readonly ID_PREFIX = 'editor.foldLevel';1034public static readonly ID = (level: number) => FoldLevelAction.ID_PREFIX + level;10351036private getFoldingLevel() {1037return parseInt(this.id.substr(FoldLevelAction.ID_PREFIX.length));1038}10391040invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1041setCollapseStateAtLevel(foldingModel, this.getFoldingLevel(), true, this.getSelectedLines(editor));1042}1043}10441045/** Action to go to the parent fold of current line */1046class GotoParentFoldAction extends FoldingAction<void> {1047constructor() {1048super({1049id: 'editor.gotoParentFold',1050label: nls.localize2('gotoParentFold.label', "Go to Parent Fold"),1051precondition: CONTEXT_FOLDING_ENABLED,1052kbOpts: {1053kbExpr: EditorContextKeys.editorTextFocus,1054weight: KeybindingWeight.EditorContrib1055}1056});1057}10581059invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1060const selectedLines = this.getSelectedLines(editor);1061if (selectedLines.length > 0) {1062const startLineNumber = getParentFoldLine(selectedLines[0], foldingModel);1063if (startLineNumber !== null) {1064editor.setSelection({1065startLineNumber: startLineNumber,1066startColumn: 1,1067endLineNumber: startLineNumber,1068endColumn: 11069});1070}1071}1072}1073}10741075/** Action to go to the previous fold of current line */1076class GotoPreviousFoldAction extends FoldingAction<void> {1077constructor() {1078super({1079id: 'editor.gotoPreviousFold',1080label: nls.localize2('gotoPreviousFold.label', "Go to Previous Folding Range"),1081precondition: CONTEXT_FOLDING_ENABLED,1082kbOpts: {1083kbExpr: EditorContextKeys.editorTextFocus,1084weight: KeybindingWeight.EditorContrib1085}1086});1087}10881089invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1090const selectedLines = this.getSelectedLines(editor);1091if (selectedLines.length > 0) {1092const startLineNumber = getPreviousFoldLine(selectedLines[0], foldingModel);1093if (startLineNumber !== null) {1094editor.setSelection({1095startLineNumber: startLineNumber,1096startColumn: 1,1097endLineNumber: startLineNumber,1098endColumn: 11099});1100}1101}1102}1103}11041105/** Action to go to the next fold of current line */1106class GotoNextFoldAction extends FoldingAction<void> {1107constructor() {1108super({1109id: 'editor.gotoNextFold',1110label: nls.localize2('gotoNextFold.label', "Go to Next Folding Range"),1111precondition: CONTEXT_FOLDING_ENABLED,1112kbOpts: {1113kbExpr: EditorContextKeys.editorTextFocus,1114weight: KeybindingWeight.EditorContrib1115}1116});1117}11181119invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1120const selectedLines = this.getSelectedLines(editor);1121if (selectedLines.length > 0) {1122const startLineNumber = getNextFoldLine(selectedLines[0], foldingModel);1123if (startLineNumber !== null) {1124editor.setSelection({1125startLineNumber: startLineNumber,1126startColumn: 1,1127endLineNumber: startLineNumber,1128endColumn: 11129});1130}1131}1132}1133}11341135class FoldRangeFromSelectionAction extends FoldingAction<void> {11361137constructor() {1138super({1139id: 'editor.createFoldingRangeFromSelection',1140label: nls.localize2('createManualFoldRange.label', "Create Folding Range from Selection"),1141precondition: CONTEXT_FOLDING_ENABLED,1142kbOpts: {1143kbExpr: EditorContextKeys.editorTextFocus,1144primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Comma),1145weight: KeybindingWeight.EditorContrib1146}1147});1148}11491150invoke(_foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1151const collapseRanges: FoldRange[] = [];1152const selections = editor.getSelections();1153if (selections) {1154for (const selection of selections) {1155let endLineNumber = selection.endLineNumber;1156if (selection.endColumn === 1) {1157--endLineNumber;1158}1159if (endLineNumber > selection.startLineNumber) {1160collapseRanges.push({1161startLineNumber: selection.startLineNumber,1162endLineNumber: endLineNumber,1163type: undefined,1164isCollapsed: true,1165source: FoldSource.userDefined1166});1167editor.setSelection({1168startLineNumber: selection.startLineNumber,1169startColumn: 1,1170endLineNumber: selection.startLineNumber,1171endColumn: 11172});1173}1174}1175if (collapseRanges.length > 0) {1176collapseRanges.sort((a, b) => {1177return a.startLineNumber - b.startLineNumber;1178});1179const newRanges = FoldingRegions.sanitizeAndMerge(foldingModel.regions, collapseRanges, editor.getModel()?.getLineCount());1180foldingModel.updatePost(FoldingRegions.fromFoldRanges(newRanges));1181}1182}1183}1184}11851186class RemoveFoldRangeFromSelectionAction extends FoldingAction<void> {11871188constructor() {1189super({1190id: 'editor.removeManualFoldingRanges',1191label: nls.localize2('removeManualFoldingRanges.label', "Remove Manual Folding Ranges"),1192precondition: CONTEXT_FOLDING_ENABLED,1193kbOpts: {1194kbExpr: EditorContextKeys.editorTextFocus,1195primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.Period),1196weight: KeybindingWeight.EditorContrib1197}1198});1199}12001201invoke(foldingController: FoldingController, foldingModel: FoldingModel, editor: ICodeEditor): void {1202const selections = editor.getSelections();1203if (selections) {1204const ranges: ILineRange[] = [];1205for (const selection of selections) {1206const { startLineNumber, endLineNumber } = selection;1207ranges.push(endLineNumber >= startLineNumber ? { startLineNumber, endLineNumber } : { endLineNumber, startLineNumber });1208}1209foldingModel.removeManualRanges(ranges);1210foldingController.triggerFoldingModelChanged();1211}1212}1213}121412151216class ToggleImportFoldAction extends FoldingAction<void> {12171218constructor() {1219super({1220id: 'editor.toggleImportFold',1221label: nls.localize2('toggleImportFold.label', "Toggle Import Fold"),1222precondition: CONTEXT_FOLDING_ENABLED,1223kbOpts: {1224kbExpr: EditorContextKeys.editorTextFocus,1225weight: KeybindingWeight.EditorContrib1226}1227});1228}12291230async invoke(foldingController: FoldingController, foldingModel: FoldingModel): Promise<void> {1231const regionsToToggle: FoldingRegion[] = [];1232const regions = foldingModel.regions;1233for (let i = regions.length - 1; i >= 0; i--) {1234if (regions.getType(i) === FoldingRangeKind.Imports.value) {1235regionsToToggle.push(regions.toRegion(i));1236}1237}1238foldingModel.toggleCollapseState(regionsToToggle);1239foldingController.triggerFoldingModelChanged();1240}1241}124212431244registerEditorContribution(FoldingController.ID, FoldingController, EditorContributionInstantiation.Eager); // eager because it uses `saveViewState`/`restoreViewState`1245registerEditorAction(UnfoldAction);1246registerEditorAction(UnFoldRecursivelyAction);1247registerEditorAction(FoldAction);1248registerEditorAction(FoldRecursivelyAction);1249registerEditorAction(ToggleFoldRecursivelyAction);1250registerEditorAction(FoldAllAction);1251registerEditorAction(UnfoldAllAction);1252registerEditorAction(FoldAllBlockCommentsAction);1253registerEditorAction(FoldAllRegionsAction);1254registerEditorAction(UnfoldAllRegionsAction);1255registerEditorAction(FoldAllExceptAction);1256registerEditorAction(UnfoldAllExceptAction);1257registerEditorAction(ToggleFoldAction);1258registerEditorAction(GotoParentFoldAction);1259registerEditorAction(GotoPreviousFoldAction);1260registerEditorAction(GotoNextFoldAction);1261registerEditorAction(FoldRangeFromSelectionAction);1262registerEditorAction(RemoveFoldRangeFromSelectionAction);1263registerEditorAction(ToggleImportFoldAction);12641265for (let i = 1; i <= 7; i++) {1266registerInstantiatedEditorAction(1267new FoldLevelAction({1268id: FoldLevelAction.ID(i),1269label: nls.localize2('foldLevelAction.label', "Fold Level {0}", i),1270precondition: CONTEXT_FOLDING_ENABLED,1271kbOpts: {1272kbExpr: EditorContextKeys.editorTextFocus,1273primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | (KeyCode.Digit0 + i)),1274weight: KeybindingWeight.EditorContrib1275}1276})1277);1278}12791280CommandsRegistry.registerCommand('_executeFoldingRangeProvider', async function (accessor, ...args) {1281const [resource] = args;1282if (!(resource instanceof URI)) {1283throw illegalArgument();1284}12851286const languageFeaturesService = accessor.get(ILanguageFeaturesService);12871288const model = accessor.get(IModelService).getModel(resource);1289if (!model) {1290throw illegalArgument();1291}12921293const configurationService = accessor.get(IConfigurationService);1294if (!configurationService.getValue('editor.folding', { resource })) {1295return [];1296}12971298const languageConfigurationService = accessor.get(ILanguageConfigurationService);12991300const strategy = configurationService.getValue('editor.foldingStrategy', { resource });1301const foldingLimitReporter = {1302get limit() {1303return <number>configurationService.getValue('editor.foldingMaximumRegions', { resource });1304},1305update: (computed: number, limited: number | false) => { }1306};13071308const indentRangeProvider = new IndentRangeProvider(model, languageConfigurationService, foldingLimitReporter);1309let rangeProvider: RangeProvider = indentRangeProvider;1310if (strategy !== 'indentation') {1311const providers = FoldingController.getFoldingRangeProviders(languageFeaturesService, model);1312if (providers.length) {1313rangeProvider = new SyntaxRangeProvider(model, providers, () => { }, foldingLimitReporter, indentRangeProvider);1314}1315}1316const ranges = await rangeProvider.compute(CancellationToken.None);1317const result: FoldingRange[] = [];1318try {1319if (ranges) {1320for (let i = 0; i < ranges.length; i++) {1321const type = ranges.getType(i);1322result.push({ start: ranges.getStartLineNumber(i), end: ranges.getEndLineNumber(i), kind: type ? FoldingRangeKind.fromValue(type) : undefined });1323}1324}1325return result;1326} finally {1327rangeProvider.dispose();1328}1329});133013311332