Skip to content

Commit

Permalink
Adopt TerminalVirtualProcess for Custom task execution (#76852)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alexr00 committed Jul 10, 2019
1 parent 5a834e7 commit e84fc70
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 22 deletions.
26 changes: 24 additions & 2 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand All @@ -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
Expand Down
30 changes: 26 additions & 4 deletions src/vs/workbench/api/browser/mainThreadTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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';
}
Expand All @@ -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 = {
Expand Down Expand Up @@ -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);
}
}

Expand Down
30 changes: 24 additions & 6 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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;
Expand All @@ -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[];
Expand Down Expand Up @@ -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;
}
Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/api/common/shared/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export interface CustomExecutionDTO {
customExecution: 'customExecution';
}

export interface CustomExecution2DTO {
customExecution: 'customExecution2';
}

export interface TaskSourceDTO {
label: string;
extensionId?: string;
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/node/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
63 changes: 59 additions & 4 deletions src/vs/workbench/api/node/extHostTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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>();
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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));

}
}
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
}

0 comments on commit e84fc70

Please sign in to comment.