mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01:00
don't attempt to convert API types inside the renderer, break up mainThread-api arguments (and plan future removal)
This commit is contained in:
@@ -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);
|
||||
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 ?? []);
|
||||
|
||||
} else if (command.command && isNonEmptyArray(command.arguments)) {
|
||||
const apiCommand = this._lookupApiCommand(command.command);
|
||||
if (apiCommand) {
|
||||
// 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 (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
|
||||
|
||||
Reference in New Issue
Block a user