未验证 提交 e84fc702 编写于 作者: A Alex Ross 提交者: GitHub

Adopt TerminalVirtualProcess for Custom task execution (#76852)

* Adopt TerminalVirtualProcess for Custom task execution

* Update Custom task execution API to return TerminalVirtualProcess in callback

This also required the addtion of a start on the virtual terminal process

* Clarify start API

Fixes #76492
上级 5a834e71
......@@ -1394,6 +1394,11 @@ declare module 'vscode' {
* Implement to handle when the terminal shuts down by an act of the user.
*/
shutdown?(): void;
/**
* Implement to handle when the terminal is ready to start firing events.
*/
start?(): void;
}
//#endregion
......@@ -1494,6 +1499,23 @@ declare module 'vscode' {
callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable<number>;
}
/**
* Class used to execute an extension callback as a task.
*/
export class CustomExecution2 {
/**
* @param process The [TerminalVirtualProcess](#TerminalVirtualProcess) to be used by the task to display output.
* @param callback The callback that will be called when the task is started by a user.
*/
constructor(callback: (thisArg?: any) => Thenable<TerminalVirtualProcess>);
/**
* The callback used to execute the task. Cancellation should be handled using the shutdown method of [TerminalVirtualProcess](#TerminalVirtualProcess).
* When the task is complete, onDidExit should be fired on the TerminalVirtualProcess with the exit code with '0' for success and a non-zero value for failure.
*/
callback: (thisArg?: any) => Thenable<TerminalVirtualProcess>;
}
/**
* A task to execute
*/
......@@ -1510,12 +1532,12 @@ declare module 'vscode' {
* or '$eslint'. Problem matchers can be contributed by an extension using
* the `problemMatchers` extension point.
*/
constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]);
constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]);
/**
* The task's execution engine
*/
execution2?: ProcessExecution | ShellExecution | CustomExecution;
execution2?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2;
}
//#region Tasks
......
......@@ -29,7 +29,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import {
TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO,
ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO,
ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, CustomExecution2DTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO,
RunOptionsDTO
} from 'vs/workbench/api/common/shared/tasks';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
......@@ -131,7 +131,7 @@ namespace ProcessExecutionOptionsDTO {
}
namespace ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ProcessExecutionDTO {
const candidate = value as ProcessExecutionDTO;
return candidate && !!candidate.process;
}
......@@ -199,7 +199,7 @@ namespace ShellExecutionOptionsDTO {
}
namespace ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is ShellExecutionDTO {
const candidate = value as ShellExecutionDTO;
return candidate && (!!candidate.commandLine || !!candidate.command);
}
......@@ -231,7 +231,7 @@ namespace ShellExecutionDTO {
}
namespace CustomExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is CustomExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecutionDTO {
const candidate = value as CustomExecutionDTO;
return candidate && candidate.customExecution === 'customExecution';
}
......@@ -250,6 +250,26 @@ namespace CustomExecutionDTO {
}
}
namespace CustomExecution2DTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO): value is CustomExecution2DTO {
const candidate = value as CustomExecution2DTO;
return candidate && candidate.customExecution === 'customExecution2';
}
export function from(value: CommandConfiguration): CustomExecution2DTO {
return {
customExecution: 'customExecution2'
};
}
export function to(value: CustomExecution2DTO): CommandConfiguration {
return {
runtime: RuntimeType.CustomExecution2,
presentation: undefined
};
}
}
namespace TaskSourceDTO {
export function from(value: TaskSource): TaskSourceDTO {
const result: TaskSourceDTO = {
......@@ -353,6 +373,8 @@ namespace TaskDTO {
command = ProcessExecutionDTO.to(task.execution);
} else if (CustomExecutionDTO.is(task.execution)) {
command = CustomExecutionDTO.to(task.execution);
} else if (CustomExecution2DTO.is(task.execution)) {
command = CustomExecution2DTO.to(task.execution);
}
}
......
......@@ -1772,6 +1772,24 @@ export class CustomExecution implements vscode.CustomExecution {
}
}
export class CustomExecution2 implements vscode.CustomExecution2 {
private _callback: () => Thenable<vscode.TerminalVirtualProcess>;
constructor(callback: () => Thenable<vscode.TerminalVirtualProcess>) {
this._callback = callback;
}
public computeId(): string {
return 'customExecution' + generateUuid();
}
public set callback(value: () => Thenable<vscode.TerminalVirtualProcess>) {
this._callback = value;
}
public get callback(): (() => Thenable<vscode.TerminalVirtualProcess>) {
return this._callback;
}
}
@es5ClassCompat
export class Task implements vscode.Task2 {
......@@ -1785,7 +1803,7 @@ export class Task implements vscode.Task2 {
private _definition: vscode.TaskDefinition;
private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined;
private _name: string;
private _execution: ProcessExecution | ShellExecution | CustomExecution | undefined;
private _execution: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined;
private _problemMatchers: string[];
private _hasDefinedMatchers: boolean;
private _isBackground: boolean;
......@@ -1794,8 +1812,8 @@ export class Task implements vscode.Task2 {
private _presentationOptions: vscode.TaskPresentationOptions;
private _runOptions: vscode.RunOptions;
constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) {
this.definition = definition;
let problemMatchers: string | string[];
......@@ -1907,18 +1925,18 @@ export class Task implements vscode.Task2 {
}
get execution(): ProcessExecution | ShellExecution | undefined {
return (this._execution instanceof CustomExecution) ? undefined : this._execution;
return ((this._execution instanceof CustomExecution) || (this._execution instanceof CustomExecution2)) ? undefined : this._execution;
}
set execution(value: ProcessExecution | ShellExecution | undefined) {
this.execution2 = value;
}
get execution2(): ProcessExecution | ShellExecution | CustomExecution | undefined {
get execution2(): ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined {
return this._execution;
}
set execution2(value: ProcessExecution | ShellExecution | CustomExecution | undefined) {
set execution2(value: ProcessExecution | ShellExecution | CustomExecution | CustomExecution2 | undefined) {
if (value === null) {
value = undefined;
}
......
......@@ -70,6 +70,10 @@ export interface CustomExecutionDTO {
customExecution: 'customExecution';
}
export interface CustomExecution2DTO {
customExecution: 'customExecution2';
}
export interface TaskSourceDTO {
label: string;
extensionId?: string;
......@@ -84,7 +88,7 @@ export interface TaskHandleDTO {
export interface TaskDTO {
_id: string;
name?: string;
execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | undefined;
execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined;
definition: TaskDefinitionDTO;
isBackground?: boolean;
source: TaskSourceDTO;
......
......@@ -849,6 +849,7 @@ export function createApiFactory(
ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext,
ExtensionKind: extHostTypes.ExtensionKind,
CustomExecution: extHostTypes.CustomExecution,
CustomExecution2: extHostTypes.CustomExecution2,
FileChangeType: extHostTypes.FileChangeType,
FileSystemError: extHostTypes.FileSystemError,
FileType: files.FileType,
......
......@@ -22,6 +22,7 @@ import {
ProcessExecutionOptionsDTO, ProcessExecutionDTO,
ShellExecutionOptionsDTO, ShellExecutionDTO,
CustomExecutionDTO,
CustomExecution2DTO,
TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO
} from '../common/shared/tasks';
import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
......@@ -79,7 +80,7 @@ namespace ProcessExecutionOptionsDTO {
}
namespace ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO {
if (value) {
const candidate = value as ProcessExecutionDTO;
return candidate && !!candidate.process;
......@@ -124,7 +125,7 @@ namespace ShellExecutionOptionsDTO {
}
namespace ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO {
if (value) {
const candidate = value as ShellExecutionDTO;
return candidate && (!!candidate.commandLine || !!candidate.command);
......@@ -162,7 +163,7 @@ namespace ShellExecutionDTO {
}
namespace CustomExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is CustomExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecutionDTO {
if (value) {
let candidate = value as CustomExecutionDTO;
return candidate && candidate.customExecution === 'customExecution';
......@@ -178,6 +179,23 @@ namespace CustomExecutionDTO {
}
}
namespace CustomExecution2DTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO {
if (value) {
let candidate = value as CustomExecution2DTO;
return candidate && candidate.customExecution === 'customExecution2';
} else {
return false;
}
}
export function from(value: vscode.CustomExecution2): CustomExecution2DTO {
return {
customExecution: 'customExecution2'
};
}
}
namespace TaskHandleDTO {
export function from(value: types.Task): TaskHandleDTO {
let folder: UriComponents | undefined;
......@@ -211,14 +229,17 @@ namespace TaskDTO {
if (value === undefined || value === null) {
return undefined;
}
let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined;
let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | CustomExecution2DTO | undefined;
if (value.execution instanceof types.ProcessExecution) {
execution = ProcessExecutionDTO.from(value.execution);
} else if (value.execution instanceof types.ShellExecution) {
execution = ShellExecutionDTO.from(value.execution);
} else if ((<vscode.Task2>value).execution2 && (<vscode.Task2>value).execution2 instanceof types.CustomExecution) {
execution = CustomExecutionDTO.from(<types.CustomExecution>(<vscode.Task2>value).execution2);
} else if ((<vscode.Task2>value).execution2 && (<vscode.Task2>value).execution2 instanceof types.CustomExecution2) {
execution = CustomExecution2DTO.from(<types.CustomExecution2>(<vscode.Task2>value).execution2);
}
const definition: TaskDefinitionDTO | undefined = TaskDefinitionDTO.from(value.definition);
let scope: number | UriComponents;
if (value.scope) {
......@@ -468,6 +489,8 @@ export class ExtHostTask implements ExtHostTaskShape {
private _taskExecutions: Map<string, TaskExecutionImpl>;
private _providedCustomExecutions: Map<string, CustomExecutionData>;
private _activeCustomExecutions: Map<string, CustomExecutionData>;
private _providedCustomExecutions2: Map<string, vscode.CustomExecution2>;
private _activeCustomExecutions2: Map<string, vscode.CustomExecution2>;
private readonly _onDidExecuteTask: Emitter<vscode.TaskStartEvent> = new Emitter<vscode.TaskStartEvent>();
private readonly _onDidTerminateTask: Emitter<vscode.TaskEndEvent> = new Emitter<vscode.TaskEndEvent>();
......@@ -491,6 +514,8 @@ export class ExtHostTask implements ExtHostTaskShape {
this._taskExecutions = new Map<string, TaskExecutionImpl>();
this._providedCustomExecutions = new Map<string, CustomExecutionData>();
this._activeCustomExecutions = new Map<string, CustomExecutionData>();
this._providedCustomExecutions2 = new Map<string, vscode.CustomExecution2>();
this._activeCustomExecutions2 = new Map<string, vscode.CustomExecution2>();
}
public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable {
......@@ -555,6 +580,19 @@ export class ExtHostTask implements ExtHostTaskShape {
}
public async $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): Promise<void> {
const execution2: vscode.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id);
if (execution2) {
if (this._activeCustomExecutions2.get(execution.id) !== undefined) {
throw new Error('We should not be trying to start the same custom task executions twice.');
}
// Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task.
this._activeCustomExecutions2.set(execution.id, execution2);
this._terminalService.performTerminalIdAction(terminalId, async terminal => {
this._terminalService.attachVirtualProcessToTerminal(terminalId, await execution2.callback());
});
}
// Once a terminal is spun up for the custom execution task this event will be fired.
// At that point, we need to actually start the callback, but
// only if it hasn't already begun.
......@@ -630,6 +668,7 @@ export class ExtHostTask implements ExtHostTaskShape {
// since we obviously cannot send callback functions through the proxy.
// So, clear out any existing ones.
this._providedCustomExecutions.clear();
this._providedCustomExecutions2.clear();
// Set up a list of task ID promises that we can wait on
// before returning the provided tasks. The ensures that
......@@ -657,6 +696,9 @@ export class ExtHostTask implements ExtHostTaskShape {
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
// is invoked, we have to be able to map it back to our data.
taskIdPromises.push(this.addCustomExecution(taskDTO, <vscode.Task2>task));
} else if (CustomExecution2DTO.is(taskDTO.execution)) {
taskIdPromises.push(this.addCustomExecution2(taskDTO, <vscode.Task2>task));
}
}
}
......@@ -705,6 +747,10 @@ export class ExtHostTask implements ExtHostTaskShape {
await this.addCustomExecution(taskDTO, <vscode.Task2>task);
}
if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) {
await this.addCustomExecution2(taskDTO, <vscode.Task2>task);
}
return resolvedTaskDTO;
}
......@@ -758,6 +804,11 @@ export class ExtHostTask implements ExtHostTaskShape {
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
}
private async addCustomExecution2(taskDTO: TaskDTO, task: vscode.Task2): Promise<void> {
const taskId = await this._proxy.$createTaskId(taskDTO);
this._providedCustomExecutions2.set(taskId, <vscode.CustomExecution2>(<vscode.Task2>task).execution2);
}
private async getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): Promise<TaskExecutionImpl> {
if (typeof execution === 'string') {
const taskExecution = this._taskExecutions.get(execution);
......@@ -787,5 +838,9 @@ export class ExtHostTask implements ExtHostTaskShape {
this._proxy.$customExecutionComplete(execution.id, extensionCallback.result);
extensionCallback.dispose();
}
const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id);
if (extensionCallback2) {
this._activeCustomExecutions2.delete(execution.id);
}
}
}
......@@ -510,7 +510,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
if (terminal) {
callback(terminal);
}
}, EXT_HOST_CREATION_DELAY);
}, EXT_HOST_CREATION_DELAY * 2);
}
}
......@@ -793,6 +793,10 @@ class ExtHostVirtualProcess implements ITerminalChildProcess {
if (this._virtualProcess.onDidOverrideDimensions) {
this._virtualProcess.onDidOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e));
}
if (this._virtualProcess.start) {
this._virtualProcess.start();
}
}
}
......
......@@ -471,7 +471,7 @@ export class TerminalTaskSystem implements ITaskSystem {
const resolvedVariables = this.resolveVariablesFromSet(this.currentTask.systemInfo, this.currentTask.workspaceFolder!, task, variables);
return resolvedVariables.then((resolvedVariables) => {
const isCustomExecution = task.command.runtime === RuntimeType.CustomExecution;
const isCustomExecution = (task.command.runtime === RuntimeType.CustomExecution) || (task.command.runtime === RuntimeType.CustomExecution2);
if (resolvedVariables && task.command && task.command.runtime && (isCustomExecution || task.command.name)) {
this.currentTask.resolvedVariables = resolvedVariables;
return this.executeInTerminal(task, trigger, new VariableResolver(this.currentTask.workspaceFolder!, this.currentTask.systemInfo, resolvedVariables.variables, this.configurationResolverService));
......@@ -845,7 +845,7 @@ export class TerminalTaskSystem implements ITaskSystem {
}
}
} else {
let commandExecutable = task.command.runtime !== RuntimeType.CustomExecution ? CommandString.value(command) : undefined;
let commandExecutable = ((task.command.runtime !== RuntimeType.CustomExecution) && (task.command.runtime !== RuntimeType.CustomExecution2)) ? CommandString.value(command) : undefined;
let executable = !isShellCommand
? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}')
: commandExecutable;
......@@ -923,6 +923,13 @@ export class TerminalTaskSystem implements ITaskSystem {
name: this.createTerminalName(task),
initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
};
} else if (task.command.runtime === RuntimeType.CustomExecution2) {
this.currentTask.shellLaunchConfig = {
isVirtualProcess: true,
waitOnExit,
name: this.createTerminalName(task),
initialText: task.command.presentation && task.command.presentation.echo ? `\x1b[1m> Executing task: ${task._label} <\x1b[0m\n` : undefined
};
} else {
let resolvedResult: { command: CommandString, args: CommandString[] } = this.resolveCommandAndArgs(resolver, task.command);
command = resolvedResult.command;
......@@ -1138,7 +1145,7 @@ export class TerminalTaskSystem implements ITaskSystem {
private collectCommandVariables(variables: Set<string>, command: CommandConfiguration, task: CustomTask | ContributedTask): void {
// The custom execution should have everything it needs already as it provided
// the callback.
if (command.runtime === RuntimeType.CustomExecution) {
if ((command.runtime === RuntimeType.CustomExecution) || (command.runtime === RuntimeType.CustomExecution2)) {
return;
}
......
......@@ -273,7 +273,8 @@ export namespace PresentationOptions {
export enum RuntimeType {
Shell = 1,
Process = 2,
CustomExecution = 3
CustomExecution = 3,
CustomExecution2 = 4
}
export namespace RuntimeType {
......@@ -285,6 +286,8 @@ export namespace RuntimeType {
return RuntimeType.Process;
case 'customExecution':
return RuntimeType.CustomExecution;
case 'customExecution2':
return RuntimeType.CustomExecution2;
default:
return RuntimeType.Process;
}
......@@ -664,6 +667,10 @@ export class CustomTask extends CommonTask {
type = 'customExecution';
break;
case RuntimeType.CustomExecution2:
type = 'customExecution2';
break;
case undefined:
type = '$composite';
break;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册