Skip to content

Commit

Permalink
ask user to terminate or restart a task if it is active
Browse files Browse the repository at this point in the history
With changes in this pull request, Theia offers users the flexibility of terminating or restarting a task, if the user tries to run a task that is actively running.
This pull request resolves eclipse-theia#6618 (comment)

Signed-off-by: Liang Huang <[email protected]>
  • Loading branch information
Liang Huang authored and akosyakov committed Feb 24, 2020
1 parent d86defb commit 6638e8e
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 28 deletions.
19 changes: 6 additions & 13 deletions packages/task/src/browser/task-configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -414,7 +418,7 @@ export class TaskConfigurations implements Disposable {
*/
// tslint:disable-next-line:no-any
async updateTaskConfig(task: TaskConfiguration, update: { [name: string]: any }): Promise<void> {
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;
Expand Down Expand Up @@ -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;
}
}
2 changes: 2 additions & 0 deletions packages/task/src/browser/task-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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);
Expand Down
86 changes: 71 additions & 15 deletions packages/task/src/browser/task-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand All @@ -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
*/
Expand All @@ -161,12 +168,9 @@ export class TaskService implements TaskConfigurationClient {
return;
}
this.runningTasks.set(event.taskId, { exitCode: new Deferred<number | undefined>(), terminateSignal: new Deferred<string | undefined>() });
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) => {
Expand Down Expand Up @@ -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<TaskConfiguration[]> {
const configuredTasks = await this.getConfiguredTasks();
Expand Down Expand Up @@ -409,6 +415,52 @@ export class TaskService implements TaskConfigurationClient {
}

async runTask(task: TaskConfiguration, option?: RunTaskOption): Promise<TaskInfo | undefined> {
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<void> {
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<TaskInfo | undefined> {
await this.terminateTask(activeTaskInfo);
return this.doRunTask(activeTaskInfo.config, option);
}

protected async doRunTask(task: TaskConfiguration, option?: RunTaskOption): Promise<TaskInfo | undefined> {
if (option && option.customization) {
const taskDefinition = this.taskDefinitionRegistry.getDefinition(task);
if (taskDefinition) { // use the customization object to override the task config
Expand Down Expand Up @@ -648,7 +700,7 @@ export class TaskService implements TaskConfigurationClient {
TERMINAL_WIDGET_FACTORY_ID,
<TerminalWidgetFactoryOptions>{
created: new Date().toString(),
id: 'terminal-' + processId,
id: this.getTerminalWidgetId(processId),
title: taskInfo
? `Task: ${taskInfo.config.label}`
: `Task: #${taskId}`,
Expand All @@ -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<void> {
await this.taskConfigurations.configure(task);
}
Expand Down
43 changes: 43 additions & 0 deletions packages/task/src/browser/task-source-resolver.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 6638e8e

Please sign in to comment.