提交 8fe69fe7 编写于 作者: G Gabriel DeBacker

Start to hook up the terminal and execution

上级 c73695ab
......@@ -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<void>);
constructor(callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken, thisArg?: any) => Thenable<void>);
/**
* The callback used to execute the task.
*/
callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken) => Thenable<void>;
callback: (args: ExtensionCallbackExecutionArgs, cancellationToken: CancellationToken, thisArg?: any) => Thenable<void>;
}
/**
......
......@@ -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<string> {
return new Promise((resolve) => {
let task = TaskDTO.to(taskDTO, this._workspaceContextServer, true);
resolve(task._id);
});
}
public $registerTaskProvider(handle: number): Promise<void> {
let provider: ITaskProvider = {
provideTasks: (validTypes: IStringDictionary<boolean>) => {
......
......@@ -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)));
......
......@@ -527,6 +527,7 @@ export interface MainThreadSearchShape extends IDisposable {
}
export interface MainThreadTaskShape extends IDisposable {
$createTaskId(task: TaskDTO): Promise<string>;
$registerTaskProvider(handle: number): Promise<void>;
$unregisterTaskProvider(handle: number): Promise<void>;
$fetchTasks(filter?: TaskFilterDTO): Promise<TaskDTO[]>;
......@@ -938,7 +939,7 @@ export interface ExtHostSCMShape {
export interface ExtHostTaskShape {
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<TaskSetDTO>;
$onDidStartTask(execution: TaskExecutionDTO): void;
$onDidStartTask(execution: TaskExecutionDTO, terminalId: number): void;
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
$OnDidEndTask(execution: TaskExecutionDTO): void;
......
......@@ -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<void> {
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<number, HandlerData>;
private _taskExecutions: Map<string, TaskExecutionImpl>;
private _providedExtensionCallbacks: Map<string, ExtensionCallbackExecutionData>;
private _activeExtensionCallbacks: Map<string, ExtensionCallbackExecutionData>;
private readonly _onDidExecuteTask: Emitter<vscode.TaskStartEvent> = new Emitter<vscode.TaskStartEvent>();
private readonly _onDidTerminateTask: Emitter<vscode.TaskEndEvent> = new Emitter<vscode.TaskEndEvent>();
......@@ -347,14 +388,22 @@ export class ExtHostTask implements ExtHostTaskShape {
private readonly _onDidTaskProcessStarted: Emitter<vscode.TaskProcessStartEvent> = new Emitter<vscode.TaskProcessStartEvent>();
private readonly _onDidTaskProcessEnded: Emitter<vscode.TaskProcessEndEvent> = new Emitter<vscode.TaskProcessEndEvent>();
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<number, HandlerData>();
this._taskExecutions = new Map<string, TaskExecutionImpl>();
this._providedExtensionCallbacks = new Map<string, ExtensionCallbackExecutionData>();
this._activeExtensionCallbacks = new Map<string, ExtensionCallbackExecutionData>();
}
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<void>[] = [];
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(<vscode.ExtensionCallbackExecution>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);
}
}
}
......@@ -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 {
......
......@@ -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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册