status bar - adopt octicons for problems (#74285)

This commit is contained in:
Benjamin Pasero 2019-05-28 17:53:55 +02:00
parent f03f7c33a4
commit 8c35807271
10 changed files with 162 additions and 259 deletions

View file

@ -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 {

View file

@ -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) {

View file

@ -1 +0,0 @@
<svg width="13" height="13" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M6.5 0C2.91 0 0 2.91 0 6.5S2.91 13 6.5 13 13 10.09 13 6.5 10.09 0 6.5 0zm3.9 9.1l-1.3 1.3-2.6-2.6-2.6 2.6-1.3-1.3 2.6-2.635L2.6 3.9l1.3-1.3 2.6 2.6 2.6-2.6 1.3 1.3-2.6 2.565L10.4 9.1z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 335 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="-295.5 390.5 13 13"><path fill="#FFF" d="M-289 390.5c-3.6 0-6.5 2.9-6.5 6.5s2.9 6.5 6.5 6.5 6.5-2.9 6.5-6.5-2.9-6.5-6.5-6.5zm.9 11.1h-1.9v-6.5h1.9v6.5zm0-7.4h-1.9v-1.9h1.9v1.9z"/></svg>

Before

Width:  |  Height:  |  Size: 257 B

View file

@ -1 +0,0 @@
<svg width="13" height="13" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M7.15 0h-1.3L0 11.7 1.3 13h10.4l1.3-1.3L7.15 0zm0 11.7h-1.3v-1.3h1.3v1.3zm0-2.6h-1.3V3.9h1.3v5.2z" fill="#fff"/></g></svg>

Before

Width:  |  Height:  |  Size: 249 B

View file

@ -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');
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><g fill="#fff"><rect x="9" y="17" width="3" height="3"/><polygon points="22,10.488 22,7.334 18,10.488 18,4 14,4 14,10.488 10,7.334 10,10.488 16,15.218"/><rect x="14" y="17" width="4" height="3"/><rect x="20" y="17" width="3" height="3"/><path d="M25 14v7h-18v-7h-2v12.121c0 .887.641 1.879 1.503 1.879h18.774c.863 0 1.723-.992 1.723-1.879v-12.121h-2zm-11 12h-3v-3h3v3zm7 0h-3v-3h3v3z"/></g></svg>

Before

Width:  |  Height:  |  Size: 458 B

View file

@ -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<IWorkbenchActionRegistry>(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<void> {
private async updateRunningTasksStatus(): Promise<void> {
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<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor);
// Status bar
let statusbarRegistry = Registry.as<IStatusbarRegistry>(StatusbarExtensions.Statusbar);
statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */));
// tasks.json validation
let schemaId = 'vscode://schemas/tasks';
let schema: IJSONSchema = {

View file

@ -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();

View file

@ -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: