From 8fe69fe73f4caa0659709f97277525cb984ea886 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Wed, 16 Jan 2019 21:59:26 -0800 Subject: [PATCH] Start to hook up the terminal and execution --- src/vs/vscode.d.ts | 4 +- .../api/electron-browser/mainThreadTask.ts | 9 +- src/vs/workbench/api/node/extHost.api.impl.ts | 2 +- src/vs/workbench/api/node/extHost.protocol.ts | 3 +- src/vs/workbench/api/node/extHostTask.ts | 126 ++++++++++++++++-- src/vs/workbench/parts/tasks/common/tasks.ts | 17 ++- .../electron-browser/terminalTaskSystem.ts | 4 +- 7 files changed, 142 insertions(+), 23 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 819041cf9a5..7156214fe6f 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5096,12 +5096,12 @@ declare module 'vscode' { /** * @param callback The callback that will be called when the extension callback task is executed. */ - constructor(callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken) => Thenable); + constructor(callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken, thisArg?: any) => Thenable); /** * The callback used to execute the task. */ - callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken) => Thenable; + callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken, thisArg?: any) => Thenable; } /** diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index ac233ceeb65..bb5b2f761fb 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -417,7 +417,7 @@ export class MainThreadTask implements MainThreadTaskShape { this._taskService.onDidStateChange((event: TaskEvent) => { let task = event.__task; if (event.kind === TaskEventKind.Start) { - this._proxy.$onDidStartTask(TaskExecutionDTO.from(task.getTaskExecution())); + this._proxy.$onDidStartTask(TaskExecutionDTO.from(task.getTaskExecution()), event.terminalId); } else if (event.kind === TaskEventKind.ProcessStarted) { this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId)); } else if (event.kind === TaskEventKind.ProcessEnded) { @@ -435,6 +435,13 @@ export class MainThreadTask implements MainThreadTaskShape { this._providers.clear(); } + $createTaskId(taskDTO: TaskDTO): Promise { + return new Promise((resolve) => { + let task = TaskDTO.to(taskDTO, this._workspaceContextServer, true); + resolve(task._id); + }); + } + public $registerTaskProvider(handle: number): Promise { let provider: ITaskProvider = { provideTasks: (validTypes: IStringDictionary) => { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 89700ce33fb..3eb2fe278a2 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -116,7 +116,7 @@ export function createApiFactory( const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer, extHostLogService, extHostConfiguration)); - const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index d6341733d16..4c15cc806d2 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -527,6 +527,7 @@ export interface MainThreadSearchShape extends IDisposable { } export interface MainThreadTaskShape extends IDisposable { + $createTaskId(task: TaskDTO): Promise; $registerTaskProvider(handle: number): Promise; $unregisterTaskProvider(handle: number): Promise; $fetchTasks(filter?: TaskFilterDTO): Promise; @@ -938,7 +939,7 @@ export interface ExtHostSCMShape { export interface ExtHostTaskShape { $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable; - $onDidStartTask(execution: TaskExecutionDTO): void; + $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): void; $onDidStartTaskProcess(value: TaskProcessStartedDTO): void; $onDidEndTaskProcess(value: TaskProcessEndedDTO): void; $OnDidEndTask(execution: TaskExecutionDTO): void; diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 1e536286bba..3783d359f67 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -28,8 +28,9 @@ import { import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; +import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; namespace TaskDefinitionDTO { export function from(value: vscode.TaskDefinition): TaskDefinitionDTO { @@ -331,15 +332,55 @@ interface HandlerData { extension: IExtensionDescription; } +class ExtensionCallbackExecutionData { + private _cancellationSource?: CancellationTokenSource; + constructor(private readonly callbackData: vscode.ExtensionCallbackExecution, private readonly terminalService: ExtHostTerminalService) { + } + + public terminateCallback(): void { + if (this._cancellationSource) { + return undefined; + } + + this._cancellationSource.cancel(); + this._cancellationSource.dispose(); + this._cancellationSource = undefined; + } + + public startCallback(terminalId: number): Thenable { + if (this._cancellationSource) { + return undefined; + } + + let foundTerminal: ExtHostTerminal | undefined; + for (let terminal of this.terminalService.terminals) { + if (terminal._id === terminalId) { + foundTerminal = terminal; + break; + } + } + + if (!foundTerminal) { + throw new Error('Should have a terminal by this point.'); + } + + this._cancellationSource = new CancellationTokenSource(); + return this.callbackData.callback(undefined, this._cancellationSource.token); + } +} + export class ExtHostTask implements ExtHostTaskShape { private _proxy: MainThreadTaskShape; private _workspaceService: ExtHostWorkspace; private _editorService: ExtHostDocumentsAndEditors; private _configurationService: ExtHostConfiguration; + private _terminalService: ExtHostTerminalService; private _handleCounter: number; private _handlers: Map; private _taskExecutions: Map; + private _providedExtensionCallbacks: Map; + private _activeExtensionCallbacks: Map; private readonly _onDidExecuteTask: Emitter = new Emitter(); private readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -347,14 +388,22 @@ export class ExtHostTask implements ExtHostTaskShape { private readonly _onDidTaskProcessStarted: Emitter = new Emitter(); private readonly _onDidTaskProcessEnded: Emitter = new Emitter(); - constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) { + constructor( + mainContext: IMainContext, + workspaceService: ExtHostWorkspace, + editorService: ExtHostDocumentsAndEditors, + configurationService: ExtHostConfiguration, + extHostTerminalService: ExtHostTerminalService) { this._proxy = mainContext.getProxy(MainContext.MainThreadTask); this._workspaceService = workspaceService; this._editorService = editorService; this._configurationService = configurationService; + this._terminalService = extHostTerminalService; this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); + this._providedExtensionCallbacks = new Map(); + this._activeExtensionCallbacks = new Map(); } public get extHostWorkspace(): ExtHostWorkspace { @@ -422,7 +471,21 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidExecuteTask.event; } - public $onDidStartTask(execution: TaskExecutionDTO): void { + public $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): void { + // Once a terminal is spun up for the extension callback task execution + // this event will be fired. + // At that point, we need to actually start the callback, but + // only if it hasn't already begun. + const extensionCallback: ExtensionCallbackExecutionData | undefined = this._providedExtensionCallbacks.get(execution.id); + if (extensionCallback) { + // TODO: Verify whether this can ever happen??? + if (this._activeExtensionCallbacks.get(execution.id) === undefined) { + this._activeExtensionCallbacks.set(execution.id, extensionCallback); + } + + extensionCallback.startCallback(terminalId); + } + this._onDidExecuteTask.fire({ execution: this.getTaskExecution(execution) }); @@ -435,6 +498,7 @@ export class ExtHostTask implements ExtHostTaskShape { public $OnDidEndTask(execution: TaskExecutionDTO): void { const _execution = this.getTaskExecution(execution); this._taskExecutions.delete(execution.id); + this.terminateExtensionCallbackExecution(execution); this._onDidTerminateTask.fire({ execution: _execution }); @@ -473,21 +537,55 @@ export class ExtHostTask implements ExtHostTaskShape { if (!handler) { return Promise.reject(new Error('no handler found')); } - return asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { - let sanitized: vscode.Task[] = []; + + // For extension callback tasks, we need to store the execution objects locally + // since we obviously cannot send callback functions through the proxy. + // So, clear out any existing ones. + this._providedExtensionCallbacks.clear(); + + // Set up a list of task ID promises that we can wait on + // before returning the provided tasks. The ensures that + // our task IDs are calculated for any extension callback tasks. + // Knowing this ID ahead of time is needed because when a task + // start event is fired this is when the extension callback is called. + // The task start event is also the first time we see the ID from the main + // thread, which is too late for us because we need to save an map + // from an ID to an extension callback function. (Kind of a cart before the horse problem). + let taskIdPromises: Promise[] = []; + let fetchPromise = asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { + const taskDTOs: TaskDTO[] = []; for (let task of value) { - if (task.definition && validTypes[task.definition.type] === true) { - sanitized.push(task); - } else { - sanitized.push(task); + if (!task.definition || !validTypes[task.definition.type]) { console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } + + const taskDTO: TaskDTO = TaskDTO.from(task, handler.extension); + taskDTOs.push(taskDTO); + + if (CallbackExecutionDTO.is(taskDTO.execution)) { + taskIdPromises.push(new Promise((resolve) => { + // The ID is calculated on the main thread task side, so, let's call into it here. + this._proxy.$createTaskId(taskDTO).then((taskId) => { + this._providedExtensionCallbacks.set(taskId, new ExtensionCallbackExecutionData(task.execution, this._terminalService)); + resolve(); + }); + })); + } } + return { - tasks: TaskDTO.fromMany(sanitized, handler.extension), + tasks: taskDTOs, extension: handler.extension }; }); + + return new Promise((resolve) => { + fetchPromise.then((result) => { + Promise.all(taskIdPromises).then(() => { + resolve(result); + }); + }); + }); } public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { @@ -544,4 +642,12 @@ export class ExtHostTask implements ExtHostTaskShape { this._taskExecutions.set(execution.id, result); return result; } + + private terminateExtensionCallbackExecution(execution: TaskExecutionDTO): void { + const extensionCallback: ExtensionCallbackExecutionData | undefined = this._activeExtensionCallbacks.get(execution.id); + if (extensionCallback) { + extensionCallback.terminateCallback(); + this._activeExtensionCallbacks.delete(execution.id); + } + } } diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/parts/tasks/common/tasks.ts index 8e4ad35743a..0343ca42e7c 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/parts/tasks/common/tasks.ts @@ -858,6 +858,7 @@ export interface TaskEvent { group?: string; processId?: number; exitCode?: number; + terminalId?: number; __task?: Task; } @@ -869,11 +870,12 @@ export const enum TaskRunSource { export namespace TaskEvent { export function create(kind: TaskEventKind.ProcessStarted | TaskEventKind.ProcessEnded, task: Task, processIdOrExitCode: number): TaskEvent; - export function create(kind: TaskEventKind.Start | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.Terminated | TaskEventKind.End, task: Task): TaskEvent; + export function create(kind: TaskEventKind.Start, task: Task, terminalId?: number): TaskEvent; + export function create(kind: TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.Terminated | TaskEventKind.End, task: Task): TaskEvent; export function create(kind: TaskEventKind.Changed): TaskEvent; - export function create(kind: TaskEventKind, task?: Task, processIdOrExitCode?: number): TaskEvent { + export function create(kind: TaskEventKind, task?: Task, processIdOrExitCodeOrTerminalId?: number): TaskEvent { if (task) { - let result = { + let result: TaskEvent = { kind: kind, taskId: task._id, taskName: task.configurationProperties.name, @@ -881,12 +883,15 @@ export namespace TaskEvent { group: task.configurationProperties.group, processId: undefined as number | undefined, exitCode: undefined as number | undefined, + terminalId: undefined as number | undefined, __task: task, }; - if (kind === TaskEventKind.ProcessStarted) { - result.processId = processIdOrExitCode; + if (kind === TaskEventKind.Start) { + result.terminalId = processIdOrExitCodeOrTerminalId; + } else if (kind === TaskEventKind.ProcessStarted) { + result.processId = processIdOrExitCodeOrTerminalId; } else if (kind === TaskEventKind.ProcessEnded) { - result.exitCode = processIdOrExitCode; + result.exitCode = processIdOrExitCodeOrTerminalId; } return Object.freeze(result); } else { diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index a46305d01ef..a71996a03dc 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -520,7 +520,7 @@ export class TerminalTaskSystem implements ITaskSystem { }, (_error) => { // The process never got ready. Need to think how to handle this. }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task)); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); const onData = terminal.onLineData((line) => { watchingProblemMatcher.processLine(line); @@ -587,7 +587,7 @@ export class TerminalTaskSystem implements ITaskSystem { }, (_error) => { // The process never got ready. Need to think how to handle this. }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task)); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService); -- GitLab