diff --git a/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts b/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts new file mode 100644 index 0000000000000000000000000000000000000000..af80138ff2b0dd86c7ec1006bd02adc93c6cdcb2 --- /dev/null +++ b/src/vs/workbench/parts/tasks/browser/terminateQuickOpen.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import nls = require('vs/nls'); +import Filters = require('vs/base/common/filters'); +import { TPromise } from 'vs/base/common/winjs.base'; +import Quickopen = require('vs/workbench/browser/quickopen'); +import QuickOpen = require('vs/base/parts/quickopen/common/quickOpen'); +import Model = require('vs/base/parts/quickopen/browser/quickOpenModel'); +import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; + +import { Task } from 'vs/workbench/parts/tasks/common/tasks'; +import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; + +class TaskEntry extends Model.QuickOpenEntry { + + private taskService: ITaskService; + private task: Task; + + constructor(taskService: ITaskService, task: Task, highlights: Model.IHighlight[] = []) { + super(highlights); + this.taskService = taskService; + this.task = task; + } + + public getLabel(): string { + return this.task.name; + } + + public getAriaLabel(): string { + return nls.localize('entryAriaLabel', "{0}, tasks", this.getLabel()); + } + + public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { + if (mode === QuickOpen.Mode.PREVIEW) { + return false; + } + this.taskService.terminate(this.task._id); + return true; + } +} + +export class QuickOpenHandler extends Quickopen.QuickOpenHandler { + + private quickOpenService: IQuickOpenService; + private taskService: ITaskService; + + constructor( + @IQuickOpenService quickOpenService: IQuickOpenService, + @ITaskService taskService: ITaskService + ) { + super(); + + this.quickOpenService = quickOpenService; + this.taskService = taskService; + } + + public getAriaLabel(): string { + return nls.localize('tasksAriaLabel', "Type the name of a task to terminate"); + } + + public getResults(input: string): TPromise { + return this.taskService.getActiveTasks().then(tasks => tasks + .sort((a, b) => a.name.localeCompare(b.name)) + .map(task => ({ task: task, highlights: Filters.matchesContiguousSubString(input, task.name) })) + .filter(({ highlights }) => !!highlights) + .map(({ task, highlights }) => new TaskEntry(this.taskService, task, highlights)) + , _ => []).then(e => new Model.QuickOpenModel(e)); + } + + public getClass(): string { + return null; + } + + public canRun(): boolean { + return true; + } + + public getAutoFocus(input: string): QuickOpen.IAutoFocus { + return { + autoFocusFirstEntry: !!input + }; + } + + public onClose(canceled: boolean): void { + return; + } + + public getGroupLabel(): string { + return null; + } + + public getEmptyLabel(searchString: string): string { + if (searchString.length > 0) { + return nls.localize('noTasksMatching', "No tasks matching"); + } + return nls.localize('noTasksFound', "No tasks to terminate found"); + } +} diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts index d22e0a3d5fb211c55d725f4c574d4fe9edeb7a86..2dd95e1c0c52f468465635e6fe8a21a8984e9ffd 100644 --- a/src/vs/workbench/parts/tasks/common/taskService.ts +++ b/src/vs/workbench/parts/tasks/common/taskService.ts @@ -37,7 +37,9 @@ export interface ITaskService extends IEventEmitter { run(task: string | Task): TPromise; inTerminal(): boolean; isActive(): TPromise; - terminate(): TPromise; + getActiveTasks(): TPromise; + terminate(id: string): TPromise; + terminateAll(): TPromise; tasks(): TPromise; registerTaskProvider(handle: number, taskProvider: ITaskProvider): void; diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 1c323f38bf67bf17a162e069f98b24d32938d90e..cca11d1e6a9d96c8c68cd82e44790da24390371e 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -100,6 +100,8 @@ export interface ITaskSystem extends IEventEmitter { run(task: Task, resolver: ITaskResolver): ITaskExecuteResult; isActive(): TPromise; isActiveSync(): boolean; + getActiveTasks(): Task[]; canAutoTerminate(): boolean; - terminate(): TPromise; + terminate(id: string): TPromise; + terminateAll(): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 0f213710ba666beceec53a691e793910ec24e94a..6ba302a2a4ca2528cd686cb6dfc0b3329bfbc9a5 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -7,6 +7,7 @@ import 'vs/css!./media/task.contribution'; import 'vs/workbench/parts/tasks/browser/taskQuickOpen'; +import 'vs/workbench/parts/tasks/browser/terminateQuickOpen'; import * as nls from 'vs/nls'; @@ -413,10 +414,16 @@ class NullTaskSystem extends EventEmitter implements ITaskSystem { public isActiveSync(): boolean { return false; } + public getActiveTasks(): Task[] { + return []; + } public canAutoTerminate(): boolean { return true; } - public terminate(): TPromise { + public terminate(task: string | Task): TPromise { + return TPromise.as({ success: true }); + } + public terminateAll(): TPromise { return TPromise.as({ success: true }); } } @@ -495,7 +502,6 @@ class TaskService extends EventEmitter implements ITaskService { private clearTaskSystemPromise: boolean; private outputChannel: IOutputChannel; - private fileChangesListener: IDisposable; private providers: Map; constructor( @IModeService modeService: IModeService, @IConfigurationService configurationService: IConfigurationService, @@ -597,13 +603,6 @@ class TaskService extends EventEmitter implements ITaskService { this.taskSystemListeners = dispose(this.taskSystemListeners); } - private disposeFileChangesListener(): void { - if (this.fileChangesListener) { - this.fileChangesListener.dispose(); - this.fileChangesListener = null; - } - } - public registerTaskProvider(handle: number, provider: ITaskProvider): void { if (!provider) { return; @@ -632,6 +631,14 @@ class TaskService extends EventEmitter implements ITaskService { return this._taskSystem.isActive(); } + public getActiveTasks(): TPromise { + if (!this._taskSystem) { + return TPromise.as([]); + } + return TPromise.as(this._taskSystem.getActiveTasks()); + } + + public build(): TPromise { return this.getTaskSets().then((values) => { let runnable = this.createRunnableTask(values, (set) => set.buildTasks); @@ -768,13 +775,23 @@ class TaskService extends EventEmitter implements ITaskService { }); } - public terminate(): TPromise { + public terminate(task: string | Task): TPromise { if (!this._taskSystem) { return TPromise.as({ success: true }); } - return this._taskSystem.terminate().then((response) => { + const id: string = Types.isString(task) ? task : task._id; + return this._taskSystem.terminate(id).then((response) => { + this.emit(TaskServiceEvents.Terminated, {}); + return response; + }); + } + + public terminateAll(): TPromise { + if (!this._taskSystem) { + return TPromise.as({ success: true }); + } + return this._taskSystem.terminateAll().then((response) => { this.emit(TaskServiceEvents.Terminated, {}); - this.disposeFileChangesListener(); return response; }); } @@ -985,11 +1002,10 @@ class TaskService extends EventEmitter implements ITaskService { message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'), primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task") })) { - return this._taskSystem.terminate().then((response) => { + return this._taskSystem.terminateAll().then((response) => { if (response.success) { this.emit(TaskServiceEvents.Terminated, {}); this._taskSystem = null; - this.disposeFileChangesListener(); this.disposeTaskSystemListeners(); return false; // no veto } else if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { @@ -1079,14 +1095,22 @@ class TaskService extends EventEmitter implements ITaskService { return; } if (this.inTerminal()) { - this.messageService.show(Severity.Info, { - message: nls.localize('TerminateAction.terminalSystem', 'The tasks are executed in the integrated terminal. Use the terminal to manage the tasks.'), - actions: [new ViewTerminalAction(this.terminalService), new CloseMessageAction()] - }); + if (!this._taskSystem) { + return; + } + let activeTasks = this._taskSystem.getActiveTasks(); + if (activeTasks.length === 0) { + return; + } + if (activeTasks.length === 1) { + this._taskSystem.terminate(activeTasks[0]._id); + } else { + this.quickOpenService.show('terminate task '); + } } else { this.isActive().then((active) => { if (active) { - this.terminate().then((response) => { + this.terminateAll().then((response) => { if (response.success) { return undefined; } else if (response.code && response.code === TerminateResponseCode.ProcessNotFound) { @@ -1118,12 +1142,23 @@ MenuRegistry.addCommand({ id: 'workbench.action.tasks.test', title: nls.localize registerSingleton(ITaskService, TaskService); // Register Quick Open -(Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( +const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); + +quickOpenRegistry.registerQuickOpenHandler( new QuickOpenHandlerDescriptor( 'vs/workbench/parts/tasks/browser/taskQuickOpen', 'QuickOpenHandler', 'task ', - nls.localize('taskCommands', "Run Task") + nls.localize('quickOpen.task', "Run Task") + ) +); + +quickOpenRegistry.registerQuickOpenHandler( + new QuickOpenHandlerDescriptor( + 'vs/workbench/parts/tasks/browser/terminateQuickOpen', + 'QuickOpenHandler', + 'terminate task ', + nls.localize('quickOpen.terminateTask', "Terminate Task") ) ); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 614f8bcd1ca3dad31b31d3dcea43d6c51cb775fd..cccf41d09810818eecc0f586d8d6ab52316c3f15 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -166,7 +166,20 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { return Object.keys(this.activeTasks).every(key => !this.activeTasks[key].task.promptOnClose); } - public terminate(): TPromise { + public getActiveTasks(): Task[] { + return Object.keys(this.activeTasks).map(key => this.activeTasks[key].task); + } + + public terminate(id: string): TPromise { + let terminalData = this.activeTasks[id]; + if (!terminalData) { + return TPromise.as({ success: false }); + }; + terminalData.terminal.dispose(); + return TPromise.as({ success: true }); + } + + public terminateAll(): TPromise { Object.keys(this.activeTasks).forEach((key) => { let data = this.activeTasks[key]; data.terminal.dispose(); diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index 8adda7fd2b43d8ca409b297acf7aa6dcb02152ed..36b676b158329244dc26dbef5f0901292163f73f 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -74,6 +74,14 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem { return !!this.childProcess; } + public getActiveTasks(): Task[] { + let result: Task[] = []; + if (this.activeTask) { + result.push(this.activeTask); + } + return result; + } + public run(task: Task): ITaskExecuteResult { if (this.activeTask) { return { kind: TaskExecuteKind.Active, active: { same: this.activeTask._id === task._id, background: this.activeTask.isBackground }, promise: this.activeTaskPromise }; @@ -95,7 +103,14 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem { return true; } - public terminate(): TPromise { + public terminate(_id: string): TPromise { + if (!this.activeTask || this.activeTask._id !== _id) { + return TPromise.as({ success: false }); + } + return this.terminateAll(); + } + + public terminateAll(): TPromise { if (this.childProcess) { return this.childProcess.terminate(); }