diff --git a/packages/task/src/browser/task-configurations.ts b/packages/task/src/browser/task-configurations.ts index cef3294c3c3f9..b0ff585d92253 100644 --- a/packages/task/src/browser/task-configurations.ts +++ b/packages/task/src/browser/task-configurations.ts @@ -21,6 +21,7 @@ import { TaskDefinitionRegistry } from './task-definition-registry'; import { ProvidedTaskConfigurations } from './provided-task-configurations'; import { TaskConfigurationManager } from './task-configuration-manager'; import { TaskSchemaUpdater } from './task-schema-updater'; +import { TaskSourceResolver } from './task-source-resolver'; import { Disposable, DisposableCollection, ResourceProvider } from '@theia/core/lib/common'; import URI from '@theia/core/lib/common/uri'; import { FileChange, FileChangeType } from '@theia/filesystem/lib/common/filesystem-watcher-protocol'; @@ -86,6 +87,9 @@ export class TaskConfigurations implements Disposable { @inject(TaskSchemaUpdater) protected readonly taskSchemaUpdater: TaskSchemaUpdater; + @inject(TaskSourceResolver) + protected readonly taskSourceResolver: TaskSourceResolver; + constructor() { this.toDispose.push(Disposable.create(() => { this.tasksMap.clear(); @@ -292,7 +296,7 @@ export class TaskConfigurations implements Disposable { return; } - const sourceFolderUri: string | undefined = this.getSourceFolderUriFromTask(task); + const sourceFolderUri: string | undefined = this.taskSourceResolver.resolve(task); if (!sourceFolderUri) { console.error('Global task cannot be customized'); return; @@ -414,7 +418,7 @@ export class TaskConfigurations implements Disposable { */ // tslint:disable-next-line:no-any async updateTaskConfig(task: TaskConfiguration, update: { [name: string]: any }): Promise { - const sourceFolderUri: string | undefined = this.getSourceFolderUriFromTask(task); + const sourceFolderUri: string | undefined = this.taskSourceResolver.resolve(task); if (!sourceFolderUri) { console.error('Global task cannot be customized'); return; @@ -464,15 +468,4 @@ export class TaskConfigurations implements Disposable { type: task.taskType || task.type }); } - - private getSourceFolderUriFromTask(task: TaskConfiguration): string | undefined { - const isDetectedTask = this.isDetectedTask(task); - let sourceFolderUri: string | undefined; - if (isDetectedTask) { - sourceFolderUri = task._scope; - } else { - sourceFolderUri = task._source; - } - return sourceFolderUri; - } } diff --git a/packages/task/src/browser/task-frontend-module.ts b/packages/task/src/browser/task-frontend-module.ts index 96f71519e1285..cc28be6be3965 100644 --- a/packages/task/src/browser/task-frontend-module.ts +++ b/packages/task/src/browser/task-frontend-module.ts @@ -38,6 +38,7 @@ import { bindTaskPreferences } from './task-preferences'; import '../../src/browser/style/index.css'; import './tasks-monaco-contribution'; import { TaskNameResolver } from './task-name-resolver'; +import { TaskSourceResolver } from './task-source-resolver'; import { TaskTemplateSelector } from './task-templates'; export default new ContainerModule(bind => { @@ -74,6 +75,7 @@ export default new ContainerModule(bind => { bindContributionProvider(bind, TaskContribution); bind(TaskSchemaUpdater).toSelf().inSingletonScope(); bind(TaskNameResolver).toSelf().inSingletonScope(); + bind(TaskSourceResolver).toSelf().inSingletonScope(); bind(TaskTemplateSelector).toSelf().inSingletonScope(); bindProcessTaskModule(bind); diff --git a/packages/task/src/browser/task-service.ts b/packages/task/src/browser/task-service.ts index f40189232de51..7b6a00b39a1d6 100644 --- a/packages/task/src/browser/task-service.ts +++ b/packages/task/src/browser/task-service.ts @@ -20,6 +20,7 @@ import { ILogger, CommandService } from '@theia/core/lib/common'; import { MessageService } from '@theia/core/lib/common/message-service'; import { Deferred } from '@theia/core/lib/common/promise-util'; import { QuickPickItem, QuickPickService } from '@theia/core/lib/common/quick-pick-service'; +import { LabelProvider } from '@theia/core/lib/browser/label-provider'; import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager'; @@ -48,6 +49,7 @@ import { TaskConfigurationClient, TaskConfigurations } from './task-configuratio import { TaskProviderRegistry, TaskResolverRegistry } from './task-contribution'; import { TaskDefinitionRegistry } from './task-definition-registry'; import { TaskNameResolver } from './task-name-resolver'; +import { TaskSourceResolver } from './task-source-resolver'; import { ProblemMatcherRegistry } from './task-problem-matcher-registry'; import { TaskSchemaUpdater } from './task-schema-updater'; import { TaskConfigurationManager } from './task-configuration-manager'; @@ -131,6 +133,9 @@ export class TaskService implements TaskConfigurationClient { @inject(TaskNameResolver) protected readonly taskNameResolver: TaskNameResolver; + @inject(TaskSourceResolver) + protected readonly taskSourceResolver: TaskSourceResolver; + @inject(TaskSchemaUpdater) protected readonly taskSchemaUpdater: TaskSchemaUpdater; @@ -140,6 +145,8 @@ export class TaskService implements TaskConfigurationClient { @inject(CommandService) protected readonly commands: CommandService; + @inject(LabelProvider) + protected readonly labelProvider: LabelProvider; /** * @deprecated To be removed in 0.5.0 */ @@ -161,12 +168,9 @@ export class TaskService implements TaskConfigurationClient { return; } this.runningTasks.set(event.taskId, { exitCode: new Deferred(), terminateSignal: new Deferred() }); - const task = event.config; - let taskIdentifier = event.taskId.toString(); - if (task) { - taskIdentifier = this.taskNameResolver.resolve(task); - } - this.messageService.info(`Task ${taskIdentifier} has been started`); + const taskConfig = event.config; + const taskIdentifier = taskConfig ? this.getTaskIdentifier(taskConfig) : event.taskId.toString(); + this.messageService.info(`Task '${taskIdentifier}' has been started.`); }); this.taskWatcher.onOutputProcessed((event: TaskOutputProcessedEvent) => { @@ -210,27 +214,29 @@ export class TaskService implements TaskConfigurationClient { this.runningTasks.get(event.taskId)!.terminateSignal.resolve(event.signal); setTimeout(() => this.runningTasks.delete(event.taskId), 60 * 1000); - const taskConfiguration = event.config; - let taskIdentifier = event.taskId.toString(); - if (taskConfiguration) { - taskIdentifier = this.taskNameResolver.resolve(taskConfiguration); - } - + const taskConfig = event.config; + const taskIdentifier = taskConfig ? this.getTaskIdentifier(taskConfig) : event.taskId.toString(); if (event.code !== undefined) { - const message = `Task ${taskIdentifier} has exited with code ${event.code}.`; + const message = `Task '${taskIdentifier}' has exited with code ${event.code}.`; if (event.code === 0) { this.messageService.info(message); } else { this.messageService.error(message); } } else if (event.signal !== undefined) { - this.messageService.info(`Task ${taskIdentifier} was terminated by signal ${event.signal}.`); + this.messageService.info(`Task '${taskIdentifier}' was terminated by signal ${event.signal}.`); } else { console.error('Invalid TaskExitedEvent received, neither code nor signal is set.'); } }); } + private getTaskIdentifier(taskConfig: TaskConfiguration): string { + const taskName = this.taskNameResolver.resolve(taskConfig); + const sourceStrUri = this.taskSourceResolver.resolve(taskConfig); + return `${taskName} (${this.labelProvider.getName(new URI(sourceStrUri))})`; + } + /** Returns an array of the task configurations configured in tasks.json and provided by the extensions. */ async getTasks(): Promise { const configuredTasks = await this.getConfiguredTasks(); @@ -409,6 +415,52 @@ export class TaskService implements TaskConfigurationClient { } async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise { + const runningTasksInfo: TaskInfo[] = await this.getRunningTasks(); + + // check if the task is active + const matchedRunningTaskInfo = runningTasksInfo.find(taskInfo => { + const taskConfig = taskInfo.config; + return this.taskDefinitionRegistry.compareTasks(taskConfig, task); + }); + if (matchedRunningTaskInfo) { // the task is active + const taskName = this.taskNameResolver.resolve(task); + const terminalId = matchedRunningTaskInfo.terminalId; + if (terminalId) { + const terminal = this.terminalService.getById(this.getTerminalWidgetId(terminalId)); + if (terminal) { + this.shell.activateWidget(terminal.id); // make the terminal visible and assign focus + } + } + const selectedAction = await this.messageService.info(`The task '${taskName}' is already active`, 'Terminate Task', 'Restart Task'); + if (selectedAction === 'Terminate Task') { + await this.terminateTask(matchedRunningTaskInfo); + } else if (selectedAction === 'Restart Task') { + return this.restartTask(matchedRunningTaskInfo, option); + } + } else { // run task as the task is not active + return this.doRunTask(task, option); + } + } + + /** + * Terminates a task that is actively running. + * @param activeTaskInfo the TaskInfo of the task that is actively running + */ + protected async terminateTask(activeTaskInfo: TaskInfo): Promise { + const taskId = activeTaskInfo.taskId; + return this.kill(taskId); + } + + /** + * Terminates a task that is actively running, and restarts it. + * @param activeTaskInfo the TaskInfo of the task that is actively running + */ + protected async restartTask(activeTaskInfo: TaskInfo, option?: RunTaskOption): Promise { + await this.terminateTask(activeTaskInfo); + return this.doRunTask(activeTaskInfo.config, option); + } + + protected async doRunTask(task: TaskConfiguration, option?: RunTaskOption): Promise { if (option && option.customization) { const taskDefinition = this.taskDefinitionRegistry.getDefinition(task); if (taskDefinition) { // use the customization object to override the task config @@ -648,7 +700,7 @@ export class TaskService implements TaskConfigurationClient { TERMINAL_WIDGET_FACTORY_ID, { created: new Date().toString(), - id: 'terminal-' + processId, + id: this.getTerminalWidgetId(processId), title: taskInfo ? `Task: ${taskInfo.config.label}` : `Task: #${taskId}`, @@ -660,6 +712,10 @@ export class TaskService implements TaskConfigurationClient { widget.start(processId); } + private getTerminalWidgetId(terminalId: number): string { + return `${TERMINAL_WIDGET_FACTORY_ID}-${terminalId}`; + } + async configure(task: TaskConfiguration): Promise { await this.taskConfigurations.configure(task); } diff --git a/packages/task/src/browser/task-source-resolver.ts b/packages/task/src/browser/task-source-resolver.ts new file mode 100644 index 0000000000000..90822ad6fd9ba --- /dev/null +++ b/packages/task/src/browser/task-source-resolver.ts @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (C) 2019 Ericsson 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 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { inject, injectable } from 'inversify'; +import { TaskConfiguration, ContributedTaskConfiguration } from '../common'; +import { TaskDefinitionRegistry } from './task-definition-registry'; + +@injectable() +export class TaskSourceResolver { + @inject(TaskDefinitionRegistry) + protected taskDefinitionRegistry: TaskDefinitionRegistry; + + /** + * Returns task source to display. + */ + resolve(task: TaskConfiguration): string | undefined { + const isDetectedTask = this.isDetectedTask(task); + let sourceFolderUri: string | undefined; + if (isDetectedTask) { + sourceFolderUri = task._scope; + } else { + sourceFolderUri = task._source; + } + return sourceFolderUri; + } + + private isDetectedTask(task: TaskConfiguration): task is ContributedTaskConfiguration { + return !!this.taskDefinitionRegistry.getDefinition(task); + } +}