diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/platform/statusbar/common/statusbar.ts
index 92a2c0c99f5..c311a9180ff 100644
--- a/src/vs/platform/statusbar/common/statusbar.ts
+++ b/src/vs/platform/statusbar/common/statusbar.ts
@@ -60,6 +60,11 @@ export interface IStatusbarEntry {
* Wether to show a beak above the status bar entry.
*/
readonly showBeak?: boolean;
+
+ /**
+ * An identifier to associate with the status bar entry DOM element.
+ */
+ readonly elementId?: string;
}
export interface IStatusbarService {
diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
index 1070575c2be..3081e6e8c67 100644
--- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
+++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts
@@ -324,6 +324,13 @@ class StatusBarEntryItem extends Disposable {
private render(entry: IStatusbarEntry): void {
+ // Container
+ if (entry.elementId) {
+ this.container.id = entry.elementId;
+ } else if (this.container.id) {
+ delete this.container.id;
+ }
+
// Text Container
let textContainer: HTMLElement;
if (entry.command) {
diff --git a/src/vs/workbench/contrib/tasks/common/media/status-error.svg b/src/vs/workbench/contrib/tasks/common/media/status-error.svg
deleted file mode 100644
index 61b16362cb1..00000000000
--- a/src/vs/workbench/contrib/tasks/common/media/status-error.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/vs/workbench/contrib/tasks/common/media/status-info.svg b/src/vs/workbench/contrib/tasks/common/media/status-info.svg
deleted file mode 100644
index 16306356ba0..00000000000
--- a/src/vs/workbench/contrib/tasks/common/media/status-info.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/vs/workbench/contrib/tasks/common/media/status-warning.svg b/src/vs/workbench/contrib/tasks/common/media/status-warning.svg
deleted file mode 100644
index 5951d69539a..00000000000
--- a/src/vs/workbench/contrib/tasks/common/media/status-warning.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css
index 9c316230674..e9d227427eb 100644
--- a/src/vs/workbench/contrib/tasks/common/media/task.contribution.css
+++ b/src/vs/workbench/contrib/tasks/common/media/task.contribution.css
@@ -3,64 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-.task-statusbar-item {
- display: inline-block;
-}
-
-.task-statusbar-item-icon {
- background: url('task.svg') 50% 2px no-repeat;
- background-size: 18px;
- cursor: pointer;
- height: 22px;
- width: 24px;
- vertical-align: top;
-}
-
-.task-statusbar-item-building {
- height: 18px;
- padding: 0px 2px 0px 2px;
- display: inline-block;
- text-align: center;
- vertical-align: top;
-}
-
-.task-statusbar-item-label {
- display: inline-block;
- cursor: pointer;
- padding: 0 5px 0 5px;
-}
-
-.task-statusbar-item-label > .task-statusbar-item-label-counter {
- display: inline-block;
- vertical-align: top;
- padding-left: 2px;
- padding-right: 2px;
-}
-
-.task-statusbar-item-label > .task-statusbar-item-label-error,
-.task-statusbar-item-label > .task-statusbar-item-label-warning,
-.task-statusbar-item-label > .task-statusbar-item-label-info {
- display: inline-block;
- padding-right: 8px;
- width: 8px;
- height: 22px;
-}
-
-.task-statusbar-item-label > .task-statusbar-item-label-error {
- -webkit-mask: url('status-error.svg') no-repeat 50% 50%;
- -webkit-mask-size: 11px;
-}
-
-.task-statusbar-item-label > .task-statusbar-item-label-warning {
- -webkit-mask: url('status-warning.svg') no-repeat 50% 50%;
- -webkit-mask-size: 11px;
-}
-
-.task-statusbar-item-label > .task-statusbar-item-label-info {
- -webkit-mask: url('status-info.svg') no-repeat 50% 50%;
- -webkit-mask-size: 11px;
-}
-
.monaco-workbench .quick-open-task-configure {
background-image: url('configure.svg');
}
diff --git a/src/vs/workbench/contrib/tasks/common/media/task.svg b/src/vs/workbench/contrib/tasks/common/media/task.svg
deleted file mode 100644
index 7692f495b32..00000000000
--- a/src/vs/workbench/contrib/tasks/common/media/task.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts
index 0349151092b..56e9863d8d6 100644
--- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts
+++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts
@@ -14,8 +14,7 @@ import * as Objects from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { Action } from 'vs/base/common/actions';
-import * as Dom from 'vs/base/browser/dom';
-import { IDisposable, dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle';
+import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import * as Types from 'vs/base/common/types';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
@@ -51,7 +50,6 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
-import { IStatusbarItem, IStatusbarRegistry, Extensions as StatusbarExtensions, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
@@ -84,8 +82,6 @@ import { TerminalTaskSystem } from './terminalTaskSystem';
import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector';
import { QuickOpenActionContributor } from '../browser/quickOpen';
-import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
-import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry';
@@ -105,212 +101,143 @@ const actionRegistry = Registry.as(ActionExtensions.Wo
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowAutomaticTaskRunning, AllowAutomaticTaskRunning.ID, AllowAutomaticTaskRunning.LABEL), 'Tasks: Allow Automatic Tasks in Folder', tasksCategory);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowAutomaticTaskRunning, DisallowAutomaticTaskRunning.ID, DisallowAutomaticTaskRunning.LABEL), 'Tasks: Disallow Automatic Tasks in Folder', tasksCategory);
-
namespace ConfigureTaskAction {
export const ID = 'workbench.action.tasks.configureTaskRunner';
export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task");
}
-class BuildStatusBarItem extends Themable implements IStatusbarItem {
- private activeCount: number;
- private icons: HTMLElement[];
-
- constructor(
- @IPanelService private readonly panelService: IPanelService,
- @IMarkerService private readonly markerService: IMarkerService,
- @ITaskService private readonly taskService: ITaskService,
- @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
- @IThemeService themeService: IThemeService,
- @IWorkspaceContextService private readonly contextService: IWorkspaceContextService
- ) {
- super(themeService);
-
- this.activeCount = 0;
- this.icons = [];
-
- this.registerListeners();
- }
-
- private registerListeners(): void {
- this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
- }
-
- protected updateStyles(): void {
- super.updateStyles();
-
- this.icons.forEach(icon => {
- icon.style.backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND);
- });
- }
-
- public render(container: HTMLElement): IDisposable {
- let callOnDispose: IDisposable[] = [];
-
- const element = document.createElement('div');
- const label = document.createElement('a');
- const errorIcon = document.createElement('div');
- const warningIcon = document.createElement('div');
- const infoIcon = document.createElement('div');
- const error = document.createElement('div');
- const warning = document.createElement('div');
- const info = document.createElement('div');
- const building = document.createElement('div');
-
- const errorTitle = (n: number) => nls.localize('totalErrors', "{0} Errors", n);
- const warningTitle = (n: number) => nls.localize('totalWarnings', "{0} Warnings", n);
- const infoTitle = (n: number) => nls.localize('totalInfos', "{0} Infos", n);
-
- Dom.addClass(element, 'task-statusbar-item');
- element.title = nls.localize('problems', "Problems");
-
- Dom.addClass(label, 'task-statusbar-item-label');
- element.appendChild(label);
-
- Dom.addClass(errorIcon, 'task-statusbar-item-label-error');
- Dom.addClass(errorIcon, 'mask-icon');
- label.appendChild(errorIcon);
- this.icons.push(errorIcon);
-
- Dom.addClass(error, 'task-statusbar-item-label-counter');
- error.innerHTML = '0';
- error.title = errorIcon.title = errorTitle(0);
- label.appendChild(error);
-
- Dom.addClass(warningIcon, 'task-statusbar-item-label-warning');
- Dom.addClass(warningIcon, 'mask-icon');
- label.appendChild(warningIcon);
- this.icons.push(warningIcon);
-
- Dom.addClass(warning, 'task-statusbar-item-label-counter');
- warning.innerHTML = '0';
- warning.title = warningIcon.title = warningTitle(0);
- label.appendChild(warning);
-
- Dom.addClass(infoIcon, 'task-statusbar-item-label-info');
- Dom.addClass(infoIcon, 'mask-icon');
- label.appendChild(infoIcon);
- this.icons.push(infoIcon);
- Dom.hide(infoIcon);
-
- Dom.addClass(info, 'task-statusbar-item-label-counter');
- label.appendChild(info);
- Dom.hide(info);
-
- Dom.addClass(building, 'task-statusbar-item-building');
- element.appendChild(building);
- building.innerHTML = nls.localize('building', 'Building...');
- Dom.hide(building);
-
- callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => {
- const panel = this.panelService.getActivePanel();
- if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) {
- this.layoutService.setPanelHidden(true);
- } else {
- this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true);
- }
- }));
-
- const manyProblems = nls.localize('manyProblems', "10K+");
- const packNumber = (n: number) => n > 9999 ? manyProblems : n > 999 ? n.toString().charAt(0) + 'K' : n.toString();
- let updateLabel = (stats: MarkerStatistics) => {
- error.innerHTML = packNumber(stats.errors);
- error.title = errorIcon.title = errorTitle(stats.errors);
- warning.innerHTML = packNumber(stats.warnings);
- warning.title = warningIcon.title = warningTitle(stats.warnings);
- if (stats.infos > 0) {
- info.innerHTML = packNumber(stats.infos);
- info.title = infoIcon.title = infoTitle(stats.infos);
- Dom.show(info);
- Dom.show(infoIcon);
- } else {
- Dom.hide(info);
- Dom.hide(infoIcon);
- }
- };
-
- this.markerService.onMarkerChanged((changedResources) => {
- updateLabel(this.markerService.getStatistics());
- });
-
- callOnDispose.push(this.taskService.onDidStateChange((event) => {
- if (this.ignoreEvent(event)) {
- return;
- }
- switch (event.kind) {
- case TaskEventKind.Active:
- this.activeCount++;
- if (this.activeCount === 1) {
- Dom.show(building);
- }
- break;
- case TaskEventKind.Inactive:
- // Since the exiting of the sub process is communicated async we can't order inactive and terminate events.
- // So try to treat them accordingly.
- if (this.activeCount > 0) {
- this.activeCount--;
- if (this.activeCount === 0) {
- Dom.hide(building);
- }
- }
- break;
- case TaskEventKind.Terminated:
- if (this.activeCount !== 0) {
- Dom.hide(building);
- this.activeCount = 0;
- }
- break;
- }
- }));
-
- container.appendChild(element);
-
- this.updateStyles();
-
- return toDisposable(() => {
- callOnDispose = dispose(callOnDispose);
- });
- }
-
- private ignoreEvent(event: TaskEvent): boolean {
- if (!this.taskService.inTerminal()) {
- return false;
- }
- if (event.group !== TaskGroup.Build) {
- return true;
- }
- if (!event.__task) {
- return false;
- }
- return event.__task.configurationProperties.problemMatchers === undefined || event.__task.configurationProperties.problemMatchers.length === 0;
- }
-}
-
export class TaskStatusBarContributions extends Disposable implements IWorkbenchContribution {
- private item: IStatusbarEntryAccessor | undefined;
+ private runningTasksStatusItem: IStatusbarEntryAccessor | undefined;
+ private problemsStatusItem: IStatusbarEntryAccessor;
+
+ private activeTasksCount: number = 0;
constructor(
@ITaskService private readonly taskService: ITaskService,
+ @IMarkerService private readonly markerService: IMarkerService,
@IStatusbarService private readonly statusbarService: IStatusbarService
) {
super();
+ this.problemsStatusItem = this._register(this.statusbarService.addEntry(this.getProblemsItem(), StatusbarAlignment.LEFT, 50 /* Medium Priority */));
+
this.registerListeners();
}
+ private getProblemsItem(): IStatusbarEntry {
+ const problems = this.markerService.getStatistics();
+
+ return {
+ elementId: 'task-statusbar-item',
+ text: this.getProblemsText(problems),
+ tooltip: this.getProblemsTooltip(problems),
+ command: 'workbench.action.tasks.toggleProblems'
+ };
+ }
+
+ private getProblemsTooltip(stats: MarkerStatistics): string {
+ const errorTitle = (n: number) => nls.localize('totalErrors', "{0} Errors", n);
+ const warningTitle = (n: number) => nls.localize('totalWarnings', "{0} Warnings", n);
+ const infoTitle = (n: number) => nls.localize('totalInfos', "{0} Infos", n);
+
+ const titles: string[] = [];
+
+ if (stats.errors > 0) {
+ titles.push(errorTitle(stats.errors));
+ }
+
+ if (stats.warnings > 0) {
+ titles.push(warningTitle(stats.warnings));
+ }
+
+ if (stats.infos > 0) {
+ titles.push(infoTitle(stats.infos));
+ }
+
+ if (titles.length === 0) {
+ return nls.localize('noProblems', "No Problems");
+ }
+
+ return titles.join(', ');
+ }
+
+ private getProblemsText(stats: MarkerStatistics): string {
+ const problemsText: string[] = [];
+
+ // Errors
+ problemsText.push('$(error) ' + this.packNumber(stats.errors));
+
+ // Warnings
+ problemsText.push('$(warning) ' + this.packNumber(stats.warnings));
+
+ // Info (only if any)
+ if (stats.infos > 0) {
+ problemsText.push('$(info) ' + this.packNumber(stats.infos));
+ }
+
+ // Building (only if any running tasks)
+ if (this.activeTasksCount > 0) {
+ problemsText.push(nls.localize('building', 'Building...'));
+ }
+
+ return problemsText.join(' ');
+ }
+
+ private packNumber(n: number): string {
+ const manyProblems = nls.localize('manyProblems', "10K+");
+
+ return n > 9999 ? manyProblems : n > 999 ? n.toString().charAt(0) + 'K' : n.toString();
+ }
+
private registerListeners(): void {
+ this.markerService.onMarkerChanged(() => this.updateProblemsStatus());
+
this.taskService.onDidStateChange(event => {
if (event.kind === TaskEventKind.Changed) {
- this.update();
+ this.updateRunningTasksStatus();
+ }
+
+ if (!this.ignoreEventForUpdateRunningTasksCount(event)) {
+ let needsUpdate = false;
+
+ switch (event.kind) {
+ case TaskEventKind.Active:
+ this.activeTasksCount++;
+ if (this.activeTasksCount === 1) {
+ needsUpdate = true;
+ }
+ break;
+ case TaskEventKind.Inactive:
+ // Since the exiting of the sub process is communicated async we can't order inactive and terminate events.
+ // So try to treat them accordingly.
+ if (this.activeTasksCount > 0) {
+ this.activeTasksCount--;
+ if (this.activeTasksCount === 0) {
+ needsUpdate = true;
+ }
+ }
+ break;
+ case TaskEventKind.Terminated:
+ if (this.activeTasksCount !== 0) {
+ this.activeTasksCount = 0;
+ needsUpdate = true;
+ }
+ break;
+ }
+
+ if (needsUpdate) {
+ this.updateProblemsStatus();
+ }
}
});
}
- private async update(): Promise {
+ private async updateRunningTasksStatus(): Promise {
const tasks = await this.taskService.getActiveTasks();
if (tasks.length === 0) {
- if (this.item) {
- this.item.dispose();
- this.item = undefined;
+ if (this.runningTasksStatusItem) {
+ this.runningTasksStatusItem.dispose();
+ this.runningTasksStatusItem = undefined;
}
} else {
const itemProps: IStatusbarEntry = {
@@ -319,13 +246,33 @@ export class TaskStatusBarContributions extends Disposable implements IWorkbench
command: 'workbench.action.tasks.showTasks',
};
- if (!this.item) {
- this.item = this.statusbarService.addEntry(itemProps, StatusbarAlignment.LEFT, 50 /* Medium Priority */);
+ if (!this.runningTasksStatusItem) {
+ this.runningTasksStatusItem = this.statusbarService.addEntry(itemProps, StatusbarAlignment.LEFT, 50 /* Medium Priority */);
} else {
- this.item.update(itemProps);
+ this.runningTasksStatusItem.update(itemProps);
}
}
}
+
+ private updateProblemsStatus(): void {
+ this.problemsStatusItem.update(this.getProblemsItem());
+ }
+
+ private ignoreEventForUpdateRunningTasksCount(event: TaskEvent): boolean {
+ if (!this.taskService.inTerminal()) {
+ return false;
+ }
+
+ if (event.group !== TaskGroup.Build) {
+ return true;
+ }
+
+ if (!event.__task) {
+ return false;
+ }
+
+ return event.__task.configurationProperties.problemMatchers === undefined || event.__task.configurationProperties.problemMatchers.length === 0;
+ }
}
workbenchRegistry.registerWorkbenchContribution(TaskStatusBarContributions, LifecyclePhase.Restored);
@@ -470,7 +417,8 @@ class TaskService extends Disposable implements ITaskService {
@IDialogService private readonly dialogService: IDialogService,
@INotificationService private readonly notificationService: INotificationService,
@IContextKeyService contextKeyService: IContextKeyService,
- @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService
+ @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
+ @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
) {
super();
@@ -605,6 +553,15 @@ class TaskService extends Disposable implements ITaskService {
CommandsRegistry.registerCommand('workbench.action.tasks.showTasks', () => {
this.runShowTasks();
});
+
+ CommandsRegistry.registerCommand('workbench.action.tasks.toggleProblems', () => {
+ const panel = this.panelService.getActivePanel();
+ if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) {
+ this.layoutService.setPanelHidden(true);
+ } else {
+ this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true);
+ }
+ });
}
private get workspaceFolders(): IWorkspaceFolder[] {
@@ -1340,7 +1297,7 @@ class TaskService extends Disposable implements ITaskService {
this._taskSystem = new TerminalTaskSystem(
this.terminalService, this.outputService, this.panelService, this.markerService,
this.modelService, this.configurationResolverService, this.telemetryService,
- this.contextService, this._environmentService,
+ this.contextService, this.environmentService,
TaskService.OutputChannelId,
(workspaceFolder: IWorkspaceFolder) => {
if (!workspaceFolder) {
@@ -2687,10 +2644,6 @@ quickOpenRegistry.registerQuickOpenHandler(
const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor);
-// Status bar
-let statusbarRegistry = Registry.as(StatusbarExtensions.Statusbar);
-statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */));
-
// tasks.json validation
let schemaId = 'vscode://schemas/tasks';
let schema: IJSONSchema = {
diff --git a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts
index 036cedba11f..194cee71776 100644
--- a/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts
+++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts
@@ -214,7 +214,7 @@ class WelcomeOverlay extends Disposable {
}
private updateProblemsKey() {
- const problems = document.querySelector('.task-statusbar-item');
+ const problems = document.getElementById('task-statusbar-item');
const key = this._overlay.querySelector('.key.problems') as HTMLElement;
if (problems instanceof HTMLElement) {
const target = problems.getBoundingClientRect();
diff --git a/test/smoke/src/areas/statusbar/statusbar.ts b/test/smoke/src/areas/statusbar/statusbar.ts
index b36678ea7d2..25286b033c5 100644
--- a/test/smoke/src/areas/statusbar/statusbar.ts
+++ b/test/smoke/src/areas/statusbar/statusbar.ts
@@ -48,7 +48,7 @@ export class StatusBar {
case StatusBarElement.SYNC_STATUS:
return `${this.mainSelector} ${this.leftSelector} .octicon.octicon-sync`;
case StatusBarElement.PROBLEMS_STATUS:
- return `${this.mainSelector} ${this.leftSelector} .task-statusbar-item[title="Problems"]`;
+ return `${this.mainSelector} ${this.leftSelector} div[id="task-statusbar-item"]`;
case StatusBarElement.SELECTION_STATUS:
return `${this.mainSelector} ${this.rightSelector} .editor-status-selection`;
case StatusBarElement.INDENTATION_STATUS: