提交 f3efe70c 编写于 作者: J Johannes Rieken

don't attempt to convert API types inside the renderer, break up...

don't attempt to convert API types inside the renderer, break up mainThread-api arguments (and plan future removal)
上级 addaad3f
......@@ -96,45 +96,6 @@ CommandsRegistry.registerCommand({
}
});
export class DiffAPICommand {
public static readonly ID = 'vscode.diff';
public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: typeConverters.TextEditorOpenOptions): Promise<any> {
return executor.executeCommand('_workbench.diff', [
left, right,
label,
undefined,
typeConverters.TextEditorOpenOptions.from(options),
options ? typeConverters.ViewColumn.from(options.viewColumn) : undefined
]);
}
}
CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute));
export class OpenAPICommand {
public static readonly ID = 'vscode.open';
public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions, label?: string): Promise<any> {
let options: ITextEditorOptions | undefined;
let position: EditorGroupColumn | undefined;
if (columnOrOptions) {
if (typeof columnOrOptions === 'number') {
position = typeConverters.ViewColumn.from(columnOrOptions);
} else {
options = typeConverters.TextEditorOpenOptions.from(columnOrOptions);
position = typeConverters.ViewColumn.from(columnOrOptions.viewColumn);
}
}
return executor.executeCommand('_workbench.open', [
resource,
options,
position,
label
]);
}
}
CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute));
export class OpenWithAPICommand {
public static readonly ID = 'vscode.openWith';
public static execute(executor: ICommandsExecutor, resource: URI, viewType: string, columnOrOptions?: vscode.ViewColumn | typeConverters.TextEditorOpenOptions): Promise<any> {
......@@ -304,3 +265,31 @@ CommandsRegistry.registerCommand({
args: []
}
});
// -----------------------------------------------------------------
// The following commands are registered on the renderer but as API
// command. DO NOT USE this unless you have understood what this
// means
// -----------------------------------------------------------------
class OpenAPICommand {
public static readonly ID = 'vscode.open';
public static execute(executor: ICommandsExecutor, resource: URI): Promise<any> {
return executor.executeCommand('_workbench.open', resource);
}
}
CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute));
class DiffAPICommand {
public static readonly ID = 'vscode.diff';
public static execute(executor: ICommandsExecutor, left: URI, right: URI, label: string, options?: typeConverters.TextEditorOpenOptions): Promise<any> {
return executor.executeCommand('_workbench.diff', [
left, right,
label,
]);
}
}
CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute));
......@@ -14,12 +14,13 @@ import * as search from 'vs/workbench/contrib/search/common/search';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ApiCommand, ApiCommandArgument, ApiCommandResult, 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 { ICommandsExecutor, OpenFolderAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands';
import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IRange } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
import { TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
//#region --- NEW world
......@@ -129,7 +130,7 @@ const newCommands: ApiCommand[] = [
// -- symbol search
new ApiCommand(
'vscode.executeWorkspaceSymbolProvider', '_executeWorkspaceSymbolProvider', 'Execute all workspace symbol providers.',
[ApiCommandArgument.String('query', 'Search string')],
[ApiCommandArgument.String.with('query', 'Search string')],
new ApiCommandResult<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][], types.SymbolInformation[]>('A promise that resolves to an array of SymbolInformation-instances.', value => {
const result: types.SymbolInformation[] = [];
if (Array.isArray(value)) {
......@@ -159,7 +160,7 @@ const newCommands: ApiCommand[] = [
// --- rename
new ApiCommand(
'vscode.executeDocumentRenameProvider', '_executeDocumentRenameProvider', 'Execute rename provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String('newName', 'The new symbol name')],
[ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('newName', 'The new symbol name')],
new ApiCommandResult<IWorkspaceEditDto, types.WorkspaceEdit | undefined>('A promise that resolves to a WorkspaceEdit.', value => {
if (!value) {
return undefined;
......@@ -173,7 +174,7 @@ const newCommands: ApiCommand[] = [
// --- links
new ApiCommand(
'vscode.executeLinkProvider', '_executeLinkProvider', 'Execute document link provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Number('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()],
[ApiCommandArgument.Uri, ApiCommandArgument.Number.with('linkResolveCount', 'Number of links that should be resolved, only when links are unresolved.').optional()],
new ApiCommandResult<modes.ILink[], vscode.DocumentLink[]>('A promise that resolves to an array of DocumentLink-instances.', value => value.map(typeConverters.DocumentLink.to))
),
// --- completions
......@@ -182,8 +183,8 @@ const newCommands: ApiCommand[] = [
[
ApiCommandArgument.Uri,
ApiCommandArgument.Position,
ApiCommandArgument.String('triggerCharacter', 'Trigger completion when the user types the character, like `,` or `(`').optional(),
ApiCommandArgument.Number('itemResolveCount', 'Number of completions to resolve (too large numbers slow down completions)').optional()
ApiCommandArgument.String.with('triggerCharacter', 'Trigger completion when the user types the character, like `,` or `(`').optional(),
ApiCommandArgument.Number.with('itemResolveCount', 'Number of completions to resolve (too large numbers slow down completions)').optional()
],
new ApiCommandResult<modes.CompletionList, vscode.CompletionList>('A promise that resolves to a CompletionList-instance.', (value, _args, converter) => {
if (!value) {
......@@ -196,7 +197,7 @@ const newCommands: ApiCommand[] = [
// --- signature help
new ApiCommand(
'vscode.executeSignatureHelpProvider', '_executeSignatureHelpProvider', 'Execute signature help provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String('triggerCharacter', 'Trigger signature help when the user types the character, like `,` or `(`').optional()],
[ApiCommandArgument.Uri, ApiCommandArgument.Position, ApiCommandArgument.String.with('triggerCharacter', 'Trigger signature help when the user types the character, like `,` or `(`').optional()],
new ApiCommandResult<modes.SignatureHelp, vscode.SignatureHelp | undefined>('A promise that resolves to SignatureHelp.', value => {
if (value) {
return typeConverters.SignatureHelp.to(value);
......@@ -207,7 +208,7 @@ const newCommands: ApiCommand[] = [
// --- code lens
new ApiCommand(
'vscode.executeCodeLensProvider', '_executeCodeLensProvider', 'Execute code lens provider.',
[ApiCommandArgument.Uri, ApiCommandArgument.Number('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()],
[ApiCommandArgument.Uri, ApiCommandArgument.Number.with('itemResolveCount', 'Number of lenses that should be resolved and returned. Will only return resolved lenses, will impact performance)').optional()],
new ApiCommandResult<modes.CodeLens[], vscode.CodeLens[] | undefined>('A promise that resolves to an array of CodeLens-instances.', (value, _args, converter) => {
return tryMapWith<modes.CodeLens, vscode.CodeLens>(item => {
return new types.CodeLens(typeConverters.Range.to(item.range), item.command && converter.fromInternal(item.command));
......@@ -220,8 +221,8 @@ const newCommands: ApiCommand[] = [
[
ApiCommandArgument.Uri,
new ApiCommandArgument('rangeOrSelection', 'Range in a text document. Some refactoring provider requires Selection object.', v => types.Range.isRange(v), v => types.Selection.isSelection(v) ? typeConverters.Selection.from(v) : typeConverters.Range.from(v)),
ApiCommandArgument.String('kind', 'Code action kind to return code actions for').optional(),
ApiCommandArgument.Number('itemResolveCount', 'Number of code actions to resolve (too large numbers slow down code actions)').optional()
ApiCommandArgument.String.with('kind', 'Code action kind to return code actions for').optional(),
ApiCommandArgument.Number.with('itemResolveCount', 'Number of code actions to resolve (too large numbers slow down code actions)').optional()
],
new ApiCommandResult<CustomCodeAction[], (vscode.CodeAction | vscode.Command | undefined)[] | undefined>('A promise that resolves to an array of Command-instances.', (value, _args, converter) => {
return tryMapWith<CustomCodeAction, vscode.CodeAction | vscode.Command | undefined>((codeAction) => {
......@@ -297,7 +298,34 @@ const newCommands: ApiCommand[] = [
filenamePattern: item.filenamePattern.map(pattern => typeConverters.NotebookExclusiveDocumentPattern.to(pattern))
};
}))
)
),
// --- open'ish commands
new ApiCommand(
'vscode.open', '_workbench.open', 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.',
[
ApiCommandArgument.Uri,
new ApiCommandArgument<vscode.ViewColumn | typeConverters.TextEditorOpenOptions | undefined, [number?, ITextEditorOptions?] | undefined>('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions',
v => v === undefined || typeof v === 'number' || typeof v === 'object',
v => !v ? v : typeof v === 'number' ? [v, undefined] : [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)]
).optional(),
ApiCommandArgument.String.with('label', '').optional()
],
ApiCommandResult.Void
),
new ApiCommand(
'vscode.diff', '_workbench.diff', 'Opens the provided resources in the diff editor to compare their contents.',
[
ApiCommandArgument.Uri.with('left', 'Left-hand side resource of the diff editor'),
ApiCommandArgument.Uri.with('right', 'Rigth-hand side resource of the diff editor'),
ApiCommandArgument.String.with('title', 'Human readable title for the diff editor').optional(),
new ApiCommandArgument<typeConverters.TextEditorOpenOptions | undefined, [number?, ITextEditorOptions?] | undefined>('columnOrOptions', 'Either the column in which to open or editor options, see vscode.TextDocumentShowOptions',
v => v === undefined || typeof v === 'object',
v => v && [typeConverters.ViewColumn.from(v.viewColumn), typeConverters.TextEditorOpenOptions.from(v)]
).optional(),
],
ApiCommandResult.Void
),
];
//#endregion
......@@ -347,24 +375,6 @@ export class ExtHostApiCommands {
]
});
this._register(DiffAPICommand.ID, adjustHandler(DiffAPICommand.execute), {
description: 'Opens the provided resources in the diff editor to compare their contents.',
args: [
{ name: 'left', description: 'Left-hand side resource of the diff editor', constraint: URI },
{ name: 'right', description: 'Right-hand side resource of the diff editor', constraint: URI },
{ name: 'title', description: '(optional) Human readable title for the diff editor', constraint: (v: any) => v === undefined || typeof v === 'string' },
{ name: 'options', description: '(optional) Editor options, see vscode.TextDocumentShowOptions' }
]
});
this._register(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute), {
description: 'Opens the provided resource in the editor. Can be a text or binary file, or a http(s) url. If you need more control over the options for opening a text file, use vscode.window.showTextDocument instead.',
args: [
{ name: 'resource', description: 'Resource to open', constraint: URI },
{ name: 'columnOrOptions', description: '(optional) Either the column in which to open or editor options, see vscode.TextDocumentShowOptions', constraint: (v: any) => v === undefined || typeof v === 'number' || typeof v === 'object' }
]
});
this._register(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute), {
description: 'Removes an entry with the given path from the recently opened list.',
args: [
......
......@@ -20,7 +20,6 @@ import { URI } from 'vs/base/common/uri';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { DiffAPICommand, ICommandsExecutor, OpenAPICommand } from 'vs/workbench/api/common/apiCommands';
import { ISelection } from 'vs/editor/common/core/selection';
interface CommandHandler {
......@@ -38,6 +37,8 @@ export class ExtHostCommands implements ExtHostCommandsShape {
readonly _serviceBrand: undefined;
private readonly _commands = new Map<string, CommandHandler>();
private readonly _apiCommands = new Map<string, ApiCommand>();
private readonly _proxy: MainThreadCommandsShape;
private readonly _logService: ILogService;
private readonly _argumentProcessors: ArgumentProcessor[];
......@@ -50,7 +51,18 @@ export class ExtHostCommands implements ExtHostCommandsShape {
) {
this._proxy = extHostRpc.getProxy(MainContext.MainThreadCommands);
this._logService = logService;
this.converter = new CommandsConverter(this, logService);
this.converter = new CommandsConverter(
this,
id => {
// API commands that have no return type (void) can be
// converted to their internal command and don't need
// any indirection commands
const candidate = this._apiCommands.get(id);
return candidate?.result === ApiCommandResult.Void
? candidate : undefined;
},
logService
);
this._argumentProcessors = [
{
processArgument(a) {
......@@ -86,7 +98,8 @@ export class ExtHostCommands implements ExtHostCommandsShape {
registerApiCommand(apiCommand: ApiCommand): extHostTypes.Disposable {
return this.registerCommand(false, apiCommand.id, async (...apiArgs) => {
const registration = this.registerCommand(false, apiCommand.id, async (...apiArgs) => {
const internalArgs = apiCommand.args.map((arg, i) => {
if (!arg.validate(apiArgs[i])) {
......@@ -102,6 +115,13 @@ export class ExtHostCommands implements ExtHostCommandsShape {
args: apiCommand.args,
returns: apiCommand.result.description
});
this._apiCommands.set(apiCommand.id, apiCommand);
return new extHostTypes.Disposable(() => {
registration.dispose();
this._apiCommands.delete(apiCommand.id);
});
}
registerCommand(global: boolean, id: string, callback: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable {
......@@ -238,8 +258,6 @@ export const IExtHostCommands = createDecorator<IExtHostCommands>('IExtHostComma
export class CommandsConverter {
private readonly _commandConverter = new Map<string, (executor: ICommandsExecutor, ...more: any[]) => Promise<any>>();
private readonly _delegatingCommandId: string;
private readonly _cache = new Map<number, vscode.Command>();
private _cachIdPool = 0;
......@@ -247,20 +265,18 @@ export class CommandsConverter {
// --- conversion between internal and api commands
constructor(
private readonly _commands: ExtHostCommands,
private readonly _lookupApiCommand: (id: string) => ApiCommand | undefined,
private readonly _logService: ILogService
) {
this._delegatingCommandId = `_vscode_delegate_cmd_${Date.now().toString(36)}`;
this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this);
this._commandConverter.set(OpenAPICommand.ID, OpenAPICommand.execute);
this._commandConverter.set(DiffAPICommand.ID, DiffAPICommand.execute);
}
toInternal(command: vscode.Command, disposables: DisposableStore): ICommandDto;
toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined;
toInternal(command: vscode.Command | undefined, disposables: DisposableStore): ICommandDto | undefined {
if (!command) {
if (!command || !command.command) {
return undefined;
}
......@@ -271,19 +287,15 @@ export class CommandsConverter {
tooltip: command.tooltip
};
const apiCommand = this._commandConverter.get(command.command);
const apiCommand = this._lookupApiCommand(command.command);
if (apiCommand) {
// we have some API command registered for which we know how to convert
// them, e.g what internal ID they use and how to convert the arguments
apiCommand({
async executeCommand(id, ...internalArgs: []) {
result.id = id;
result.arguments = internalArgs;
return undefined;
}
}, ...command.arguments ?? []);
// API command with return-value can be converted inplace
result.id = apiCommand.internalId;
result.arguments = apiCommand.args.map((arg, i) => arg.convert(command.arguments && command.arguments[i]));
} else if (command.command && isNonEmptyArray(command.arguments)) {
} else if (isNonEmptyArray(command.arguments)) {
// we have a contributed command with arguments. that
// means we don't want to send the arguments around
......@@ -339,8 +351,8 @@ export class ApiCommandArgument<V, O = V> {
static readonly Range = new ApiCommandArgument<extHostTypes.Range, IRange>('range', 'A range in a text document', v => extHostTypes.Range.isRange(v), extHostTypeConverter.Range.from);
static readonly Selection = new ApiCommandArgument<extHostTypes.Selection, ISelection>('selection', 'A selection in a text document', v => extHostTypes.Selection.isSelection(v), extHostTypeConverter.Selection.from);
static Number(name: string, description: string) { return new ApiCommandArgument<number>(name, description, v => typeof v === 'number', v => v); }
static String(name: string, description: string) { return new ApiCommandArgument<string>(name, description, v => typeof v === 'string', v => v); }
static Number = new ApiCommandArgument<number>('number', '', v => typeof v === 'number', v => v);
static String = new ApiCommandArgument<string>('string', '', v => typeof v === 'string', v => v);
static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof extHostTypes.CallHierarchyItem, extHostTypeConverter.CallHierarchyItem.to);
......@@ -358,10 +370,16 @@ export class ApiCommandArgument<V, O = V> {
value => value === undefined ? undefined : value === null ? null : this.convert(value)
);
}
with(name: string | undefined, description: string | undefined): ApiCommandArgument<V, O> {
return new ApiCommandArgument(name ?? this.name, description ?? this.description, this.validate, this.convert);
}
}
export class ApiCommandResult<V, O = V> {
static readonly Void = new ApiCommandResult<void, void>('no result', v => v);
constructor(
readonly description: string,
readonly convert: (v: V, apiArgs: any[], cmdConverter: CommandsConverter) => O
......
......@@ -407,13 +407,13 @@ function registerOpenEditorAPICommands(): void {
];
}
CommandsRegistry.registerCommand(API_OPEN_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, payload: [UriComponents, ITextEditorOptions | undefined, EditorGroupColumn | undefined, string | undefined], context?: IOpenEvent<unknown>) {
CommandsRegistry.registerCommand(API_OPEN_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, resourceArg: UriComponents, columnAndOptions?: [ITextEditorOptions?, EditorGroupColumn?], label?: string, context?: IOpenEvent<unknown>) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const openerService = accessor.get(IOpenerService);
const [resourceArg, optionsArg, columnArg, label] = payload;
const resource = URI.revive(resourceArg);
const [optionsArg, columnArg] = columnAndOptions ?? [];
// use editor options or editor view column as a hint to use the editor service for opening
if (optionsArg || typeof columnArg === 'number') {
......@@ -433,12 +433,10 @@ function registerOpenEditorAPICommands(): void {
}
});
CommandsRegistry.registerCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, payload: [UriComponents, UriComponents, string, string, ITextEditorOptions | undefined, EditorGroupColumn | undefined], context?: IOpenEvent<unknown>) {
CommandsRegistry.registerCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, async function (accessor: ServicesAccessor, leftResource: UriComponents, rightResource: UriComponents, label?: string, optionsArg?: ITextEditorOptions, columnArg?: EditorGroupColumn, context?: IOpenEvent<unknown>) {
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
let [leftResource, rightResource, label, description, optionsArg, columnArg] = payload;
if (!optionsArg || typeof optionsArg !== 'object') {
optionsArg = {
preserveFocus: false
......@@ -451,7 +449,6 @@ function registerOpenEditorAPICommands(): void {
leftResource: URI.revive(leftResource),
rightResource: URI.revive(rightResource),
label,
description,
options
}, viewColumnToEditorGroup(editorGroupService, column));
});
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册