diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts index 16a000969fb..f980152fccd 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/languages.test.ts @@ -191,4 +191,47 @@ suite('vscode API - languages', () => { assert.ok(result!.items.some(i => i.label === 'foo'), 'Results do not include "foo"'); }); + test('folding command', async function () { + const content = `[ + /** + * This is a comment with indentation issues + */ + { + "name": "bag of items", + "items": [ + "foo", "bar" + ] + } + ]`; + const uri = await createRandomFile(content, undefined, '.jsonc'); + await vscode.workspace.openTextDocument(uri); + const jsonExtension = await vscode.extensions.getExtension('vscode.json-language-features'); + assert.ok(jsonExtension); + await jsonExtension.activate(); + const result1 = await vscode.commands.executeCommand('vscode.executeFoldingRangeProvider', uri); + assert.deepEqual(result1, [ + { start: 0, end: 9 }, + { start: 1, end: 3, kind: vscode.FoldingRangeKind.Comment }, + { start: 4, end: 8 }, + { start: 6, end: 7 }, + ]); + + await vscode.workspace.getConfiguration('editor').update('foldingStrategy', 'indentation'); + try { + const result2 = await vscode.commands.executeCommand('vscode.executeFoldingRangeProvider', uri); + assert.deepEqual(result2, [ + { start: 0, end: 10 }, + { start: 1, end: 2 }, + { start: 3, end: 9 }, + { start: 4, end: 8 }, + { start: 6, end: 7 }, + ]); + await vscode.workspace.getConfiguration('editor').update('folding', false); + const result3 = await vscode.commands.executeCommand('vscode.executeFoldingRangeProvider', uri); + assert.deepEqual(result3, []); + } finally { + await vscode.workspace.getConfiguration('editor').update('foldingStrategy', undefined); + await vscode.workspace.getConfiguration('editor').update('folding', undefined); + } + }); }); diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index e012f79787a..178a66a4727 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1394,6 +1394,20 @@ export class FoldingRangeKind { */ static readonly Region = new FoldingRangeKind('region'); + /** + * Returns a {@link FoldingRangeKind} for the given value. + * + * @param value of the kind. + */ + static fromValue(value: string) { + switch (value) { + case 'comment': return FoldingRangeKind.Comment; + case 'imports': return FoldingRangeKind.Imports; + case 'region': return FoldingRangeKind.Region; + } + return new FoldingRangeKind(value); + } + /** * Creates a new {@link FoldingRangeKind}. * diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index a4532d5f44f..2bd291740f0 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -5,7 +5,7 @@ import { CancelablePromise, createCancelablePromise, Delayer, RunOnceScheduler } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -21,7 +21,7 @@ import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; -import { FoldingRangeKind, FoldingRangeProvider } from 'vs/editor/common/languages'; +import { FoldingRange, FoldingRangeKind, FoldingRangeProvider } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { CollapseMemento, FoldingModel, getNextFoldLine, getParentFoldLine as getParentFoldLine, getPreviousFoldLine, setCollapseStateAtLevel, setCollapseStateForMatchingLines, setCollapseStateForRest, setCollapseStateForType, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateUp, toggleCollapseState } from 'vs/editor/contrib/folding/browser/foldingModel'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/browser/hiddenRangeModel'; @@ -37,6 +37,10 @@ import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs import { StopWatch } from 'vs/base/common/stopwatch'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { Emitter, Event } from 'vs/base/common/event'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { URI } from 'vs/base/common/uri'; +import { IModelService } from 'vs/editor/common/services/model'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const CONTEXT_FOLDING_ENABLED = new RawContextKey('foldingEnabled', false); @@ -75,6 +79,11 @@ export class FoldingController extends Disposable implements IEditorContribution private static _foldingRangeSelector: FoldingRangeProviderSelector | undefined; + public static getFoldingRangeProviders(languageFeaturesService: ILanguageFeaturesService, model: ITextModel): FoldingRangeProvider[] { + const foldingRangeProviders = languageFeaturesService.foldingRangeProvider.ordered(model); + return (FoldingController._foldingRangeSelector?.(foldingRangeProviders, model)) ?? foldingRangeProviders; + } + public static setFoldingRangeProviderSelector(foldingRangeSelector: FoldingRangeProviderSelector): IDisposable { FoldingController._foldingRangeSelector = foldingRangeSelector; return { dispose: () => { FoldingController._foldingRangeSelector = undefined; } }; @@ -289,8 +298,7 @@ export class FoldingController extends Disposable implements IEditorContribution const indentRangeProvider = new IndentRangeProvider(editorModel, this.languageConfigurationService, this._foldingLimitReporter); this.rangeProvider = indentRangeProvider; // fallback if (this._useFoldingProviders && this.foldingModel) { - const foldingProviders = this.languageFeaturesService.foldingRangeProvider.ordered(this.foldingModel.textModel); - const selectedProviders = (FoldingController._foldingRangeSelector?.(foldingProviders, editorModel)) ?? foldingProviders; + const selectedProviders = FoldingController.getFoldingRangeProviders(this.languageFeaturesService, editorModel); if (selectedProviders.length > 0) { this.rangeProvider = new SyntaxRangeProvider(editorModel, selectedProviders, () => this.triggerFoldingModelChanged(), this._foldingLimitReporter, indentRangeProvider); } @@ -1198,3 +1206,55 @@ for (let i = 1; i <= 7; i++) { }) ); } + +CommandsRegistry.registerCommand('_executeFoldingRangeProvider', async function (accessor, ...args) { + const [resource] = args; + if (!(resource instanceof URI)) { + throw illegalArgument(); + } + + const languageFeaturesService = accessor.get(ILanguageFeaturesService); + + const model = accessor.get(IModelService).getModel(resource); + if (!model) { + throw illegalArgument(); + } + + const configurationService = accessor.get(IConfigurationService); + if (!configurationService.getValue('editor.folding', { resource })) { + return []; + } + + const languageConfigurationService = accessor.get(ILanguageConfigurationService); + + const strategy = configurationService.getValue('editor.foldingStrategy', { resource }); + const foldingLimitReporter = { + get limit() { + return configurationService.getValue('editor.foldingMaximumRegions', { resource }); + }, + report: (info: FoldingLimitInfo) => { } + }; + + const indentRangeProvider = new IndentRangeProvider(model, languageConfigurationService, foldingLimitReporter); + let rangeProvider: RangeProvider = indentRangeProvider; + if (strategy !== 'indentation') { + const providers = FoldingController.getFoldingRangeProviders(languageFeaturesService, model); + if (providers.length) { + rangeProvider = new SyntaxRangeProvider(model, providers, () => { }, foldingLimitReporter, indentRangeProvider); + } + } + const ranges = await rangeProvider.compute(CancellationToken.None); + const result: FoldingRange[] = []; + try { + if (ranges) { + for (let i = 0; i < ranges.length; i++) { + const type = ranges.getType(i); + result.push({ start: ranges.getStartLineNumber(i), end: ranges.getEndLineNumber(i), kind: type ? FoldingRangeKind.fromValue(type) : undefined }); + } + } + return result; + } finally { + rangeProvider.dispose(); + } +}); + diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 0f55b7e3813..6ca39adb17e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -7313,6 +7313,12 @@ declare namespace monaco.languages { * The value of the kind is 'region'. */ static readonly Region: FoldingRangeKind; + /** + * Returns a {@link FoldingRangeKind} for the given value. + * + * @param value of the kind. + */ + static fromValue(value: string): FoldingRangeKind; /** * Creates a new {@link FoldingRangeKind}. * diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index e4326c4c6de..a6ff38438b2 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -338,6 +338,18 @@ const newCommands: ApiCommand[] = [ return result.map(typeConverters.InlayHint.to.bind(undefined, converter)); }) ), + // --- folding + new ApiCommand( + 'vscode.executeFoldingRangeProvider', '_executeFoldingRangeProvider', 'Execute folding range provider', + [ApiCommandArgument.Uri], + new ApiCommandResult('A promise that resolves to an array of FoldingRange objects', (result, args) => { + if (result) { + return result.map(typeConverters.FoldingRange.to); + } + return undefined; + }) + ), + // --- notebooks new ApiCommand( 'vscode.resolveNotebookContentProviders', '_resolveNotebookContentProvider', 'Resolve Notebook Content Providers', diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 64346991404..aa1fe665a4a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1382,6 +1382,13 @@ export namespace FoldingRange { } return range; } + export function to(r: languages.FoldingRange): vscode.FoldingRange { + const range: vscode.FoldingRange = { start: r.start - 1, end: r.end - 1 }; + if (r.kind) { + range.kind = FoldingRangeKind.to(r.kind); + } + return range; + } } export namespace FoldingRangeKind { @@ -1398,6 +1405,19 @@ export namespace FoldingRangeKind { } return undefined; } + export function to(kind: languages.FoldingRangeKind | undefined): vscode.FoldingRangeKind | undefined { + if (kind) { + switch (kind.value) { + case languages.FoldingRangeKind.Comment.value: + return types.FoldingRangeKind.Comment; + case languages.FoldingRangeKind.Imports.value: + return types.FoldingRangeKind.Imports; + case languages.FoldingRangeKind.Region.value: + return types.FoldingRangeKind.Region; + } + } + return undefined; + } } export interface TextEditorOpenOptions extends vscode.TextDocumentShowOptions {