From 8eccd64352f2b99fd8bfe55f236efcf3ddf765d3 Mon Sep 17 00:00:00 2001 From: Dirk Baeumer Date: Mon, 10 Jul 2017 15:26:55 +0200 Subject: [PATCH] Fixes #29913: Action to show and navigate to running tasks --- src/vs/code/electron-main/menus.ts | 4 +- .../parts/tasks/common/taskService.ts | 1 + .../parts/tasks/common/taskSystem.ts | 2 + .../media/task.contribution.css | 10 ++ .../electron-browser/task.contribution.ts | 99 ++++++++++++++++++- .../electron-browser/terminalTaskSystem.ts | 14 +++ .../parts/tasks/node/processTaskSystem.ts | 5 + 7 files changed, 130 insertions(+), 5 deletions(-) diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 7c8e51044c4..15ec1c62f18 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -913,9 +913,10 @@ export class CodeMenu { private setTaskMenu(taskMenu: Electron.Menu): void { const runTask = this.createMenuItem(nls.localize({ key: 'miRunTask', comment: ['&& denotes a mnemonic'] }, "&&Run Task..."), 'workbench.action.tasks.runTask'); + const buildTask = this.createMenuItem(nls.localize({ key: 'miBuildTask', comment: ['&& denotes a mnemonic'] }, "Run &&Build Task..."), 'workbench.action.tasks.build'); + const showTasks = this.createMenuItem(nls.localize({ key: 'miRunningTask', comment: ['&& denotes a mnemonic'] }, "Show Runnin&&g Tasks..."), 'workbench.action.tasks.showTasks'); const restartTask = this.createMenuItem(nls.localize({ key: 'miRestartTask', comment: ['&& denotes a mnemonic'] }, "R&&estart Running Task..."), 'workbench.action.tasks.restartTask'); const terminateTask = this.createMenuItem(nls.localize({ key: 'miTerminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task..."), 'workbench.action.tasks.terminate'); - const buildTask = this.createMenuItem(nls.localize({ key: 'miBuildTask', comment: ['&& denotes a mnemonic'] }, "Run &&Build Task..."), 'workbench.action.tasks.build'); // const testTask = this.createMenuItem(nls.localize({ key: 'miTestTask', comment: ['&& denotes a mnemonic'] }, "Run Test T&&ask..."), 'workbench.action.tasks.test'); // const showTaskLog = this.createMenuItem(nls.localize({ key: 'miShowTaskLog', comment: ['&& denotes a mnemonic'] }, "&&Show Task Log"), 'workbench.action.tasks.showLog'); const configureTask = this.createMenuItem(nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks"), 'workbench.action.tasks.configureTaskRunner'); @@ -930,6 +931,7 @@ export class CodeMenu { __separator__(), terminateTask, restartTask, + showTasks, __separator__(), //showTaskLog, configureTask, diff --git a/src/vs/workbench/parts/tasks/common/taskService.ts b/src/vs/workbench/parts/tasks/common/taskService.ts index bd469539ad7..a2cdeb91456 100644 --- a/src/vs/workbench/parts/tasks/common/taskService.ts +++ b/src/vs/workbench/parts/tasks/common/taskService.ts @@ -21,6 +21,7 @@ export namespace TaskServiceEvents { export let Inactive: string = 'inactive'; export let ConfigChanged: string = 'configChanged'; export let Terminated: string = 'terminated'; + export let Changed: string = 'changed'; } export interface ITaskProvider { diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 0923378df89..48ce82ee764 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -82,6 +82,7 @@ export namespace TaskSystemEvents { export let Active: string = 'active'; export let Inactive: string = 'inactive'; export let Terminated: string = 'terminated'; + export let Changed: string = 'changed'; } export enum TaskType { @@ -113,4 +114,5 @@ export interface ITaskSystem extends IEventEmitter { canAutoTerminate(): boolean; terminate(id: string): TPromise; terminateAll(): TPromise; + revealTask(task: Task): boolean; } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css b/src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css index f8b7e9e67e5..c02899f2f02 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css +++ b/src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css @@ -3,6 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.task-statusbar-runningItem { + display: inline-block; +} + +.task-statusbar-runningItem-label { + display: inline-block; + cursor: pointer; + padding: 0 5px 0 5px; +} + .task-statusbar-item { display: inline-block; } 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 c339df79443..95d154b9836 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -263,7 +263,7 @@ class ViewTerminalAction extends Action { } } -class StatusBarItem extends Themable implements IStatusbarItem { +class BuildStatusBarItem extends Themable implements IStatusbarItem { private intervalToken: any; private activeCount: number; private static progressChars: string = '|/-\\'; @@ -309,7 +309,7 @@ class StatusBarItem extends Themable implements IStatusbarItem { Dom.addClass(progress, 'task-statusbar-item-progress'); element.appendChild(progress); - progress.innerHTML = StatusBarItem.progressChars[0]; + progress.innerHTML = BuildStatusBarItem.progressChars[0]; $(progress).hide(); Dom.addClass(label, 'task-statusbar-item-label'); @@ -384,7 +384,7 @@ class StatusBarItem extends Themable implements IStatusbarItem { this.activeCount++; if (this.activeCount === 1) { let index = 1; - let chars = StatusBarItem.progressChars; + let chars = BuildStatusBarItem.progressChars; progress.innerHTML = chars[0]; this.intervalToken = setInterval(() => { progress.innerHTML = chars[index]; @@ -454,6 +454,69 @@ class StatusBarItem extends Themable implements IStatusbarItem { } } +class TaskStatusBarItem extends Themable implements IStatusbarItem { + + constructor( + @IPanelService private panelService: IPanelService, + @IMarkerService private markerService: IMarkerService, + @IOutputService private outputService: IOutputService, + @ITaskService private taskService: ITaskService, + @IPartService private partService: IPartService, + @IThemeService themeService: IThemeService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + ) { + super(themeService); + } + + protected updateStyles(): void { + super.updateStyles(); + } + + public render(container: HTMLElement): IDisposable { + let callOnDispose: IDisposable[] = []; + + const element = document.createElement('div'); + const label = document.createElement('a'); + + Dom.addClass(element, 'task-statusbar-runningItem'); + + Dom.addClass(label, 'task-statusbar-runningItem-label'); + element.appendChild(label); + element.title = nls.localize('runningTasks', "Show Running Tasks"); + + callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => { + (this.taskService as TaskService).runShowTasks(); + })); + + let updateStatus = (): void => { + this.taskService.getActiveTasks().then(tasks => { + if (tasks.length === 0) { + label.innerHTML = nls.localize('nothingRunner', 'Running Tasks: 0'); + } else if (tasks.length === 1) { + label.innerHTML = nls.localize('oneTasksRunnering', 'Running Tasks: 1'); + } else { + label.innerHTML = nls.localize('nTasksRunnering', 'Running Tasks: {0}', tasks.length); + } + }); + }; + + callOnDispose.push(this.taskService.addListener(TaskServiceEvents.Changed, (event: TaskEvent) => { + updateStatus(); + })); + + container.appendChild(element); + + this.updateStyles(); + updateStatus(); + + return { + dispose: () => { + callOnDispose = dispose(callOnDispose); + } + }; + } +} + interface TaskServiceEventData { error?: any; } @@ -465,6 +528,9 @@ class NullTaskSystem extends EventEmitter implements ITaskSystem { promise: TPromise.as({}) }; } + public revealTask(task: Task): boolean { + return false; + } public isActive(): TPromise { return TPromise.as(false); } @@ -686,6 +752,10 @@ class TaskService extends EventEmitter implements ITaskService { CommandsRegistry.registerCommand('workbench.action.tasks.configureDefaultTestTask', () => { this.runConfigureDefaultTestTask(); }); + + CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', () => { + this.runShowTasks(); + }); } private showOutput(): void { @@ -1189,6 +1259,7 @@ class TaskService extends EventEmitter implements ITaskService { this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Active, (event) => this.emit(TaskServiceEvents.Active, event))); this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Inactive, (event) => this.emit(TaskServiceEvents.Inactive, event))); this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Terminated, (event) => this.emit(TaskServiceEvents.Terminated, event))); + this._taskSystemListeners.push(this._taskSystem.addListener(TaskSystemEvents.Changed, () => this.emit(TaskServiceEvents.Changed))); return this._taskSystem; } @@ -1874,6 +1945,24 @@ class TaskService extends EventEmitter implements ITaskService { this.configureAction().run(); } } + + public runShowTasks(): void { + if (!this.canRunCommand()) { + return; + } + if (!this._taskSystem) { + this.messageService.show(Severity.Info, nls.localize('TaskService.noTaskIsRunning', 'No task is running.')); + return; + } + this.getActiveTasks().then((tasks) => { + this.showQuickPick(tasks, nls.localize('TaskService.pickShowTask', 'Select the task to show its output'), false, true).then((task) => { + if (!task || !this._taskSystem) { + return; + } + this._taskSystem.revealTask(task); + }); + }); + } } @@ -1883,6 +1972,7 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Config MenuRegistry.addCommand({ id: 'workbench.action.tasks.showLog', title: { value: nls.localize('ShowLogAction.label', "Show Task Log"), original: 'Show Task Log' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.runTask', title: { value: nls.localize('RunTaskAction.label', "Run Task"), original: 'Run Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.restartTask', title: { value: nls.localize('RestartTaskAction.label', "Restart Running Task"), original: 'Restart Running Task' }, category: { value: tasksCategory, original: 'Tasks' } }); +MenuRegistry.addCommand({ id: 'workbench.action.tasks.showTasks', title: { value: nls.localize('ShowTasksAction.label', "Show Running Tasks"), original: 'Show Running Tasks' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.terminate', title: { value: nls.localize('TerminateAction.label', "Terminate Task"), original: 'Terminate Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.build', title: { value: nls.localize('BuildAction.label', "Run Build Task"), original: 'Run Build Task' }, category: { value: tasksCategory, original: 'Tasks' } }); MenuRegistry.addCommand({ id: 'workbench.action.tasks.test', title: { value: nls.localize('TestAction.label', "Run Test Task"), original: 'Run Test Task' }, category: { value: tasksCategory, original: 'Tasks' } }); @@ -1913,7 +2003,8 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionCont // Status bar let statusbarRegistry = Registry.as(StatusbarExtensions.Statusbar); -statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(StatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); +statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); +statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(TaskStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); // Output channel let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index b5611d8c7ae..3ad7ca39063 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -162,6 +162,17 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { } } + + public revealTask(task: Task): boolean { + let terminalData = this.activeTasks[task._id]; + if (!terminalData) { + return false; + } + this.terminalService.setActiveInstance(terminalData.terminal); + this.terminalService.showPanel(task.command.presentation.focus); + return true; + } + public isActive(): TPromise { return TPromise.as(this.isActiveSync()); } @@ -301,6 +312,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { onData.dispose(); onExit.dispose(); delete this.activeTasks[task._id]; + this.emit(TaskSystemEvents.Changed); switch (task.command.presentation.panel) { case PanelKind.Dedicated: this.sameTaskTerminals[task._id] = terminal.id.toString(); @@ -347,6 +359,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { onData.dispose(); onExit.dispose(); delete this.activeTasks[task._id]; + this.emit(TaskSystemEvents.Changed); switch (task.command.presentation.panel) { case PanelKind.Dedicated: this.sameTaskTerminals[task._id] = terminal.id.toString(); @@ -372,6 +385,7 @@ export class TerminalTaskSystem extends EventEmitter implements ITaskSystem { this.terminalService.showPanel(task.command.presentation.focus); } this.activeTasks[task._id] = { terminal, task, promise }; + this.emit(TaskSystemEvents.Changed); return promise.then((summary) => { try { let telemetryEvent: TelemetryEvent = { diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index da8779d160b..ed4a91d2d4e 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -90,6 +90,11 @@ export class ProcessTaskSystem extends EventEmitter implements ITaskSystem { return this.executeTask(task); } + public revealTask(task: Task): boolean { + this.showOutput(); + return true; + } + public hasErrors(value: boolean): void { this.errorsShown = !value; }