Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt TerminalVirtualProcess for Custom task execution #76852

Merged
merged 3 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 24 additions & 2 deletions src/vs/vscode.proposed.d.ts
Expand Up @@ -1400,6 +1400,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 @@ -1500,6 +1505,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 @@ -1516,12 +1538,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
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
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
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
Expand Up @@ -850,6 +850,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
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);
}
}
}