From 883936de2e56ab743dc1463a8b4b20733768a31d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 13 Nov 2020 10:00:52 +0100 Subject: [PATCH] modernize vscode.executeCompletionItemProvider-command --- src/vs/editor/contrib/suggest/suggest.ts | 52 ++++++++++++------- .../api/common/extHostApiCommands.ts | 50 ++++++++---------- .../browser/api/extHostApiCommands.test.ts | 10 +++- 3 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 650701dea1f1e..d6c466f335e05 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -6,7 +6,6 @@ import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import * as modes from 'vs/editor/common/modes'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -18,6 +17,10 @@ import { isDisposable, DisposableStore, IDisposable } from 'vs/base/common/lifec import { MenuId } from 'vs/platform/actions/common/actions'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { assertType } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const Context = { Visible: new RawContextKey('suggestWidgetVisible', false), @@ -341,31 +344,42 @@ export function getSuggestionComparator(snippetConfig: SnippetSortOrder): (a: Co return _snippetComparators.get(snippetConfig)!; } -registerDefaultLanguageCommand('_executeCompletionItemProvider', async (model, position, args) => { +CommandsRegistry.registerCommand('_executeCompletionItemProvider', async (accessor, ...args: [URI, IPosition, string?, number?]) => { + const [uri, position, triggerCharacter, maxItemsToResolve] = args; + assertType(URI.isUri(uri)); + assertType(Position.isIPosition(position)); + assertType(typeof triggerCharacter === 'string' || !triggerCharacter); + assertType(typeof maxItemsToResolve === 'number' || !maxItemsToResolve); - const result: modes.CompletionList = { - incomplete: false, - suggestions: [] - }; + const ref = await accessor.get(ITextModelService).createModelReference(uri); + try { - const resolving: Promise[] = []; - const maxItemsToResolve = args['maxItemsToResolve'] || 0; + const result: modes.CompletionList = { + incomplete: false, + suggestions: [] + }; - const completions = await provideSuggestionItems(model, position); - for (const item of completions.items) { - if (resolving.length < maxItemsToResolve) { - resolving.push(item.resolve(CancellationToken.None)); + const resolving: Promise[] = []; + const completions = await provideSuggestionItems(ref.object.textEditorModel, Position.lift(position), undefined, { triggerCharacter, triggerKind: triggerCharacter ? modes.CompletionTriggerKind.TriggerCharacter : modes.CompletionTriggerKind.Invoke }); + for (const item of completions.items) { + if (resolving.length < (maxItemsToResolve ?? 0)) { + resolving.push(item.resolve(CancellationToken.None)); + } + result.incomplete = result.incomplete || item.container.incomplete; + result.suggestions.push(item.completion); + } + + try { + await Promise.all(resolving); + return result; + } finally { + setTimeout(() => completions.disposable.dispose(), 100); } - result.incomplete = result.incomplete || item.container.incomplete; - result.suggestions.push(item.completion); - } - try { - await Promise.all(resolving); - return result; } finally { - setTimeout(() => completions.disposable.dispose(), 100); + ref.dispose(); } + }); interface SuggestController extends IEditorContribution { diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 26300e195f5af..ae66e163b3ca3 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -12,7 +12,7 @@ import { IRawColorInfo, IWorkspaceEditDto, ICallHierarchyItemDto, IIncomingCallD import * as modes from 'vs/editor/common/modes'; import * as search from 'vs/workbench/contrib/search/common/search'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -43,7 +43,7 @@ export class ApiCommandResult { constructor( readonly description: string, - readonly convert: (v: V, apiArgs: any[]) => O + readonly convert: (v: V, apiArgs: any[], cmdConverter: CommandsConverter) => O ) { } } @@ -69,7 +69,7 @@ export class ApiCommand { }); const internalResult = await commands.executeCommand(this.internalId, ...internalArgs); - return this.result.convert(internalResult, apiArgs); + return this.result.convert(internalResult, apiArgs, commands.converter); }, undefined, this._getCommandHandlerDesc()); } @@ -235,6 +235,23 @@ const newCommands: ApiCommand[] = [ 'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.', [ApiCommandArgument.Uri, new ApiCommandArgument('linkResolveCount', '(optional) Number of links that should be resolved, only when links are unresolved.', v => typeof v === 'number' || typeof v === 'undefined', v => v)], new ApiCommandResult('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to)) + ), + // --- completions + new ApiCommand( + 'vscode.executeCompletionItemProvider', '_executeCompletionItemProvider', 'Execute completion item provider.', + [ + ApiCommandArgument.Uri, + ApiCommandArgument.Position, + new ApiCommandArgument('triggerCharacter', '(optional) Trigger completion when the user types the character, like `,` or `(`', v => typeof v === 'string' || typeof v === 'undefined', v => v), + new ApiCommandArgument('itemResolveCount', '(optional) Number of completions to resolve (too large numbers slow down completions)', v => typeof v === 'number' || typeof v === 'undefined', v => v) + ], + new ApiCommandResult('A promise that resolves to a CompletionList-instance.', (value, _args, converter) => { + if (!value) { + return new types.CompletionList([]); + } + const items = value.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion, converter)); + return new types.CompletionList(items, value.incomplete); + }) ) ]; @@ -267,16 +284,7 @@ export class ExtHostApiCommands { ], returns: 'A promise that resolves to SignatureHelp.' }); - this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider, { - description: 'Execute completion item provider.', - args: [ - { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position }, - { name: 'triggerCharacter', description: '(optional) Trigger completion when the user types the character, like `,` or `(`', constraint: (value: any) => value === undefined || typeof value === 'string' }, - { name: 'itemResolveCount', description: '(optional) Number of completions to resolve (too large numbers slow down completions)', constraint: (value: any) => value === undefined || typeof value === 'number' } - ], - returns: 'A promise that resolves to a CompletionList-instance.' - }); + this._register('vscode.executeCodeActionProvider', this._executeCodeActionProvider, { description: 'Execute code action provider.', args: [ @@ -402,22 +410,6 @@ export class ExtHostApiCommands { }); } - private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Promise { - const args = { - resource, - position: position && typeConverters.Position.from(position), - triggerCharacter, - maxItemsToResolve - }; - return this._commands.executeCommand('_executeCompletionItemProvider', args).then(result => { - if (result) { - const items = result.suggestions.map(suggestion => typeConverters.CompletionItem.to(suggestion, this._commands.converter)); - return new types.CompletionList(items, result.incomplete); - } - return undefined; - }); - } - private _executeDocumentColorProvider(resource: URI): Promise { const args = { resource diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index e13e1e2b73677..c33328c2ce6c2 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -28,7 +28,7 @@ import 'vs/workbench/contrib/search/browser/search.contribution'; import { NullLogService } from 'vs/platform/log/common/log'; import { ITextModel } from 'vs/editor/common/model'; import { nullExtensionDescription, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { dispose } from 'vs/base/common/lifecycle'; +import { dispose, ImmortalReference } from 'vs/base/common/lifecycle'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { mock } from 'vs/base/test/common/mock'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; @@ -48,6 +48,7 @@ import 'vs/editor/contrib/parameterHints/provideSignatureHelp'; import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/suggest/suggest'; import 'vs/editor/contrib/rename/rename'; +import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = createTextModel( @@ -108,6 +109,13 @@ suite('ExtHostLanguageFeatureCommands', function () { services.set(IModelService, new class extends mock() { getModel() { return model; } }); + services.set(ITextModelService, new class extends mock() { + async createModelReference() { + return new ImmortalReference(new class extends mock() { + textEditorModel = model; + }); + } + }); services.set(IEditorWorkerService, new class extends mock() { async computeMoreMinimalEdits(_uri: any, edits: any) { return edits || undefined;