// ***************************************************************************** // 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-only WITH Classpath-exception-2.0 // ***************************************************************************** import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { Diagnostic, DiagnosticSeverity } from '@theia/core/shared/vscode-languageserver-protocol'; import { Event, Emitter } from '@theia/core/lib/common/event'; import { Title, Widget } from '@theia/core/shared/@lumino/widgets'; import { WidgetDecoration } from '@theia/core/lib/browser/widget-decoration'; import { TabBarDecorator } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { Marker } from '../../common/marker'; import { ProblemManager } from './problem-manager'; import { ProblemPreferences, ProblemConfiguration } from '../../common/problem-preferences'; import { Navigatable } from '@theia/core/lib/browser'; import { PreferenceChangeEvent } from '@theia/core'; @injectable() export class ProblemTabBarDecorator implements TabBarDecorator { readonly id = 'theia-problem-tabbar-decorator'; protected readonly emitter = new Emitter(); @inject(ProblemPreferences) protected readonly preferences: ProblemPreferences; @inject(ProblemManager) protected readonly problemManager: ProblemManager; @postConstruct() protected init(): void { this.problemManager.onDidChangeMarkers(() => this.fireDidChangeDecorations()); this.preferences.onPreferenceChanged(event => this.handlePreferenceChange(event)); } decorate(title: Title): WidgetDecoration.Data[] { if (!this.preferences['problems.decorations.tabbar.enabled']) { return []; } const widget = title.owner; if (Navigatable.is(widget)) { const resourceUri = widget.getResourceUri(); if (resourceUri) { // Get the list of problem markers for the given resource URI. const markers: Marker[] = this.problemManager.findMarkers({ uri: resourceUri }); // If no markers are available, return early. if (markers.length === 0) { return []; } // Store the marker with the highest severity. let maxSeverity: Marker | undefined; // Iterate over available markers to determine that which has the highest severity. // Only display a decoration if an error or warning marker is available. for (const marker of markers) { // Break early if an error marker is present, since it represents the highest severity. if (marker.data.severity === DiagnosticSeverity.Error) { maxSeverity = marker; break; } else if (marker.data.severity === DiagnosticSeverity.Warning) { maxSeverity = marker; } } // Decorate the tabbar with the highest marker severity if available. return maxSeverity ? [this.toDecorator(maxSeverity)] : []; } } return []; } get onDidChangeDecorations(): Event { return this.emitter.event; } protected fireDidChangeDecorations(): void { this.emitter.fire(undefined); } /** * Handle changes in preference. * @param {PreferenceChangeEvent} event The event of the changes in preference. */ protected async handlePreferenceChange(event: PreferenceChangeEvent): Promise { const { preferenceName } = event; if (preferenceName === 'problems.decorations.tabbar.enabled') { this.fireDidChangeDecorations(); } } /** * Convert a diagnostic marker to a decorator. * @param {Marker} marker A diagnostic marker. * @returns {WidgetDecoration.Data} The decoration data. */ protected toDecorator(marker: Marker): WidgetDecoration.Data { const position = WidgetDecoration.IconOverlayPosition.BOTTOM_RIGHT; const icon = this.getOverlayIcon(marker); const color = this.getOverlayIconColor(marker); return { iconOverlay: { position, icon, color, background: { shape: 'circle', color: 'transparent' } } }; } /** * Get the appropriate overlay icon for decoration. * @param {Marker} marker A diagnostic marker. * @returns {string} A string representing the overlay icon class. */ protected getOverlayIcon(marker: Marker): string { const { severity } = marker.data; switch (severity) { case 1: return 'times-circle'; case 2: return 'exclamation-circle'; case 3: return 'info-circle'; default: return 'hand-o-up'; } } /** * Get the appropriate overlay icon color for decoration. * @param {Marker} marker A diagnostic marker. * @returns {WidgetDecoration.Color} The decoration color. */ protected getOverlayIconColor(marker: Marker): WidgetDecoration.Color { const { severity } = marker.data; switch (severity) { case 1: return 'var(--theia-list-errorForeground)'; case 2: return 'var(--theia-list-warningForeground)'; default: return 'var(--theia-successBackground)'; } } }