// ***************************************************************************** // Copyright (C) 2018 Red Hat, Inc. and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { TasksMain, MAIN_RPC_CONTEXT, TaskExecutionDto, TasksExt, TaskDto, TaskPresentationOptionsDTO } from '../../common/plugin-api-rpc'; import { RPCProtocol } from '../../common/rpc-protocol'; import { Disposable, DisposableCollection } from '@theia/core/lib/common'; import { TaskProviderRegistry, TaskResolverRegistry, TaskProvider, TaskResolver } from '@theia/task/lib/browser/task-contribution'; import { interfaces } from '@theia/core/shared/inversify'; import { TaskInfo, TaskExitedEvent, TaskConfiguration, TaskOutputPresentation, RevealKind, PanelKind } from '@theia/task/lib/common/task-protocol'; import { TaskWatcher } from '@theia/task/lib/common/task-watcher'; import { TaskService } from '@theia/task/lib/browser/task-service'; import { TaskDefinitionRegistry } from '@theia/task/lib/browser'; const revealKindMap = new Map( [ [1, RevealKind.Always], [2, RevealKind.Silent], [3, RevealKind.Never], [RevealKind.Always, 1], [RevealKind.Silent, 2], [RevealKind.Never, 3] ] ); const panelKindMap = new Map( [ [1, PanelKind.Shared], [2, PanelKind.Dedicated], [3, PanelKind.New], [PanelKind.Shared, 1], [PanelKind.Dedicated, 2], [PanelKind.New, 3] ] ); export class TasksMainImpl implements TasksMain, Disposable { private readonly proxy: TasksExt; private readonly taskProviderRegistry: TaskProviderRegistry; private readonly taskResolverRegistry: TaskResolverRegistry; private readonly taskWatcher: TaskWatcher; private readonly taskService: TaskService; private readonly taskDefinitionRegistry: TaskDefinitionRegistry; private readonly taskProviders = new Map(); private readonly toDispose = new DisposableCollection(); constructor(rpc: RPCProtocol, container: interfaces.Container) { this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.TASKS_EXT); this.taskProviderRegistry = container.get(TaskProviderRegistry); this.taskResolverRegistry = container.get(TaskResolverRegistry); this.taskWatcher = container.get(TaskWatcher); this.taskService = container.get(TaskService); this.taskDefinitionRegistry = container.get(TaskDefinitionRegistry); this.toDispose.push(this.taskWatcher.onTaskCreated((event: TaskInfo) => { this.proxy.$onDidStartTask({ id: event.taskId, task: this.fromTaskConfiguration(event.config) }, event.terminalId!); })); this.toDispose.push(this.taskWatcher.onTaskExit((event: TaskExitedEvent) => { this.proxy.$onDidEndTask(event.taskId); })); this.toDispose.push(this.taskWatcher.onDidStartTaskProcess((event: TaskInfo) => { if (event.processId !== undefined) { this.proxy.$onDidStartTaskProcess(event.processId, { id: event.taskId, task: this.fromTaskConfiguration(event.config) }); } })); this.toDispose.push(this.taskWatcher.onDidEndTaskProcess((event: TaskExitedEvent) => { if (event.code !== undefined) { this.proxy.$onDidEndTaskProcess(event.code, event.taskId); } })); // Inform proxy about running tasks form previous session this.$taskExecutions().then(executions => { if (executions.length > 0) { this.proxy.$initLoadedTasks(executions); } }); } dispose(): void { this.toDispose.dispose(); } $registerTaskProvider(handle: number, type: string): void { const taskProvider = this.createTaskProvider(handle); const taskResolver = this.createTaskResolver(handle); const toDispose = new DisposableCollection( this.taskProviderRegistry.register(type, taskProvider, handle), this.taskResolverRegistry.registerTaskResolver(type, taskResolver), Disposable.create(() => this.taskProviders.delete(handle)) ); this.taskProviders.set(handle, toDispose); this.toDispose.push(toDispose); } $unregister(handle: number): void { const disposable = this.taskProviders.get(handle); if (disposable) { disposable.dispose(); } } async $fetchTasks(taskVersion: string | undefined, taskType: string | undefined): Promise { if (taskVersion && !taskVersion.startsWith('2.')) { // Theia does not support 1.x or earlier task versions return []; } const token: number = this.taskService.startUserAction(); const [configured, provided] = await Promise.all([ this.taskService.getConfiguredTasks(token), this.taskService.getProvidedTasks(token) ]); const result: TaskDto[] = []; for (const tasks of [configured, provided]) { for (const task of tasks) { if (!taskType || (!!this.taskDefinitionRegistry.getDefinition(task) ? task._source === taskType : task.type === taskType)) { result.push(this.fromTaskConfiguration(task)); } } } return result; } async $executeTask(taskDto: TaskDto): Promise { const taskConfig = this.toTaskConfiguration(taskDto); const taskInfo = await this.taskService.runTask(taskConfig); if (taskInfo) { return { id: taskInfo.taskId, task: this.fromTaskConfiguration(taskInfo.config) }; } } async $taskExecutions(): Promise<{ id: number; task: TaskDto; }[]> { const runningTasks = await this.taskService.getRunningTasks(); return runningTasks.map(taskInfo => ({ id: taskInfo.taskId, task: this.fromTaskConfiguration(taskInfo.config) })); } $terminateTask(id: number): void { this.taskService.kill(id); } async $customExecutionComplete(id: number, exitCode: number | undefined): Promise { this.taskService.customExecutionComplete(id, exitCode); } protected createTaskProvider(handle: number): TaskProvider { return { provideTasks: () => this.proxy.$provideTasks(handle).then(tasks => tasks.map(taskDto => this.toTaskConfiguration(taskDto) ) ) }; } protected createTaskResolver(handle: number): TaskResolver { return { resolveTask: taskConfig => this.proxy.$resolveTask(handle, this.fromTaskConfiguration(taskConfig)).then(task => this.toTaskConfiguration(task) ) }; } protected toTaskConfiguration(taskDto: TaskDto): TaskConfiguration { const { group, presentation, scope, source, runOptions, ...common } = taskDto ?? {}; const partialConfig: Partial = {}; if (presentation) { partialConfig.presentation = this.convertTaskPresentation(presentation); } if (group) { partialConfig.group = { kind: group.kind, isDefault: group.isDefault }; } return { ...common, ...partialConfig, runOptions, _scope: scope, _source: source, }; } protected fromTaskConfiguration(task: TaskConfiguration): TaskDto { const { group, presentation, _scope, _source, ...common } = task; const partialDto: Partial = {}; if (presentation) { partialDto.presentation = this.convertTaskPresentation(presentation); } if (group === 'build' || group === 'test') { partialDto.group = { kind: group, isDefault: false }; } else if (typeof group === 'object') { partialDto.group = group; } return { ...common, ...partialDto, scope: _scope, source: _source, }; } private convertTaskPresentation(presentationFrom: undefined): undefined; private convertTaskPresentation(presentationFrom: TaskOutputPresentation): TaskPresentationOptionsDTO; private convertTaskPresentation(presentationFrom: TaskPresentationOptionsDTO): TaskOutputPresentation; private convertTaskPresentation( presentationFrom: TaskOutputPresentation | TaskPresentationOptionsDTO | undefined ): TaskOutputPresentation | TaskPresentationOptionsDTO | undefined { if (presentationFrom) { const { reveal, panel, ...common } = presentationFrom; const presentationTo: Partial = {}; if (reveal) { presentationTo.reveal = revealKindMap.get(reveal); } if (panel) { presentationTo.panel = panelKindMap.get(panel)!; } return { ...common, ...presentationTo, }; } } }