mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
deco - split between decorations (reading) and decorations data (providing)
This commit is contained in:
@@ -188,7 +188,7 @@ export class ResourceLabel extends IconLabel {
|
||||
this.options.fileKind !== FileKind.FILE
|
||||
);
|
||||
if (deco && this.options.fileDecorations.colors) {
|
||||
iconLabelOptions.extraClasses.push(deco.labelClasses);
|
||||
iconLabelOptions.extraClasses.push(deco.labelClassName);
|
||||
}
|
||||
if (deco && deco.letter && this.options.fileDecorations.badges) {
|
||||
iconLabelOptions.badge = {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IResourceDecorationsService, IDecorationsProvider, IResourceDecoration } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IResourceDecorationsService, IDecorationsProvider, IResourceDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event from 'vs/base/common/event';
|
||||
@@ -30,7 +30,7 @@ class MarkersDecorationsProvider implements IDecorationsProvider {
|
||||
this.onDidChange = _markerService.onMarkerChanged;
|
||||
}
|
||||
|
||||
provideDecorations(resource: URI): IResourceDecoration {
|
||||
provideDecorations(resource: URI): IResourceDecorationData {
|
||||
|
||||
const markers = this._markerService.read({ resource })
|
||||
.sort((a, b) => Severity.compare(a.severity, b.severity));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IResourceDecorationsService, IDecorationsProvider, IResourceDecoration } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IResourceDecorationsService, IDecorationsProvider, IResourceDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource } from 'vs/workbench/services/scm/common/scm';
|
||||
import URI from 'vs/base/common/uri';
|
||||
@@ -61,17 +61,16 @@ class SCMDecorationsProvider implements IDecorationsProvider {
|
||||
this._onDidChange.fire(uris);
|
||||
}
|
||||
|
||||
provideDecorations(uri: URI): IResourceDecoration {
|
||||
provideDecorations(uri: URI): IResourceDecorationData {
|
||||
const resource = this._data.get(uri.toString());
|
||||
if (!resource) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
severity: Severity.Info,
|
||||
tooltip: localize('tooltip', "{0} - {1}", resource.decorations.tooltip, this._provider.label),
|
||||
tooltip: localize('tooltip', "{0}, {1}", resource.decorations.tooltip, this._provider.label),
|
||||
color: resource.decorations.color,
|
||||
letter: resource.decorations.tooltip.charAt(0),
|
||||
icon: { light: resource.decorations.icon, dark: resource.decorations.iconDark },
|
||||
letter: resource.decorations.tooltip.charAt(0)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,22 +13,26 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const IResourceDecorationsService = createDecorator<IResourceDecorationsService>('IFileDecorationsService');
|
||||
|
||||
export interface IResourceDecoration {
|
||||
export interface IResourceDecorationData {
|
||||
readonly severity: Severity;
|
||||
readonly color?: ColorIdentifier;
|
||||
readonly letter?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly icon?: { light: URI, dark: URI };
|
||||
readonly leafOnly?: boolean;
|
||||
}
|
||||
|
||||
labelClasses?: string;
|
||||
badgeClassName?: string;
|
||||
export interface IResourceDecoration {
|
||||
readonly _decoBrand: undefined;
|
||||
readonly severity: Severity;
|
||||
readonly letter?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly labelClassName?: string;
|
||||
readonly badgeClassName?: string;
|
||||
}
|
||||
|
||||
export interface IDecorationsProvider {
|
||||
readonly label: string;
|
||||
readonly onDidChange: Event<URI[]>;
|
||||
provideDecorations(uri: URI): IResourceDecoration | Thenable<IResourceDecoration>;
|
||||
provideDecorations(uri: URI): IResourceDecorationData | Thenable<IResourceDecorationData>;
|
||||
}
|
||||
|
||||
export interface IResourceDecorationChangeEvent {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import Event, { Emitter, debounceEvent, any } from 'vs/base/common/event';
|
||||
import { IResourceDecorationsService, IResourceDecoration, IResourceDecorationChangeEvent, IDecorationsProvider } from './decorations';
|
||||
import { IResourceDecorationsService, IResourceDecoration, IResourceDecorationChangeEvent, IDecorationsProvider, IResourceDecorationData } from './decorations';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { isThenable } from 'vs/base/common/async';
|
||||
@@ -17,6 +17,79 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { listActiveSelectionForeground, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
|
||||
class DecorationColors {
|
||||
|
||||
private readonly _disposables: IDisposable[];
|
||||
private readonly _styleElement = createStyleSheet();
|
||||
private readonly _classNames = new IdGenerator('monaco-decoration-styles-');
|
||||
private readonly _classNames2ColorIds = new Map<string, [string, string]>();
|
||||
|
||||
constructor(
|
||||
private _themeService: IThemeService,
|
||||
) {
|
||||
this._disposables = [
|
||||
this._themeService.onThemeChange(this._onThemeChange, this),
|
||||
];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
this._styleElement.innerHTML = '';
|
||||
}
|
||||
|
||||
makeResourceDecoration(decoration: IResourceDecorationData): IResourceDecoration {
|
||||
if (!decoration) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let { severity, letter, tooltip } = decoration;
|
||||
let labelClassName, badgeClassName;
|
||||
|
||||
let tuple = this._classNames2ColorIds.get(decoration.color);
|
||||
|
||||
if (tuple) {
|
||||
// from cache
|
||||
labelClassName = tuple[0];
|
||||
badgeClassName = tuple[1];
|
||||
} else {
|
||||
// new css rules
|
||||
labelClassName = this._classNames.nextId();
|
||||
badgeClassName = this._classNames.nextId();
|
||||
this._classNames2ColorIds.set(decoration.color, [labelClassName, badgeClassName]);
|
||||
this._createCssRules(labelClassName, badgeClassName, decoration.color);
|
||||
}
|
||||
|
||||
return {
|
||||
_decoBrand: undefined,
|
||||
severity,
|
||||
letter,
|
||||
tooltip,
|
||||
labelClassName,
|
||||
badgeClassName
|
||||
};
|
||||
}
|
||||
|
||||
private _onThemeChange(): void {
|
||||
this._classNames2ColorIds.forEach((tuple, color) => {
|
||||
const [labelClassName, badgeClassName] = tuple;
|
||||
removeCSSRulesContainingSelector(labelClassName, this._styleElement);
|
||||
removeCSSRulesContainingSelector(badgeClassName, this._styleElement);
|
||||
this._createCssRules(labelClassName, badgeClassName, color);
|
||||
});
|
||||
}
|
||||
|
||||
private _createCssRules(labelClassName: string, badgeClassName: string, color: ColorIdentifier): void {
|
||||
const theme = this._themeService.getTheme();
|
||||
// label
|
||||
createCSSRule(`.${labelClassName}`, `color: ${theme.getColor(color)}`, this._styleElement);
|
||||
createCSSRule(`.selected .${labelClassName}`, `color: ${theme.getColor(listActiveSelectionForeground)}`, this._styleElement);
|
||||
|
||||
// badge
|
||||
createCSSRule(`.${badgeClassName}`, `background-color: ${theme.getColor(color)}; color: ${theme.getColor(listActiveSelectionForeground)};`, this._styleElement);
|
||||
}
|
||||
}
|
||||
|
||||
class FileDecorationChangeEvent implements IResourceDecorationChangeEvent {
|
||||
|
||||
private readonly _data = TernarySearchTree.forPaths<boolean>();
|
||||
@@ -49,6 +122,7 @@ class DecorationProviderWrapper {
|
||||
private readonly _dispoable: IDisposable;
|
||||
|
||||
constructor(
|
||||
private readonly _decorationStyles: DecorationColors,
|
||||
private readonly _provider: IDecorationsProvider,
|
||||
private readonly _emitter: Emitter<URI | URI[]>
|
||||
) {
|
||||
@@ -92,7 +166,7 @@ class DecorationProviderWrapper {
|
||||
const childTree = this._data.findSuperstr(key);
|
||||
if (childTree) {
|
||||
childTree.forEach(([, value]) => {
|
||||
if (value && !isThenable<void>(value) && !value.leafOnly) {
|
||||
if (value && !isThenable<void>(value)) {
|
||||
callback(value, true);
|
||||
}
|
||||
});
|
||||
@@ -102,88 +176,27 @@ class DecorationProviderWrapper {
|
||||
|
||||
private _fetchData(uri: URI): IResourceDecoration {
|
||||
|
||||
const decoOrThenable = this._provider.provideDecorations(uri);
|
||||
if (!isThenable(decoOrThenable)) {
|
||||
const dataOrThenable = this._provider.provideDecorations(uri);
|
||||
if (!isThenable(dataOrThenable)) {
|
||||
// sync -> we have a result now
|
||||
this._data.set(uri.toString(), decoOrThenable || null);
|
||||
this._emitter.fire(uri);
|
||||
return decoOrThenable;
|
||||
return this._keepItem(uri, dataOrThenable);
|
||||
|
||||
} else {
|
||||
// async -> we have a result soon
|
||||
const request = Promise.resolve(decoOrThenable)
|
||||
.then(data => {
|
||||
this._data.set(uri.toString(), data || null);
|
||||
this._emitter.fire(uri);
|
||||
})
|
||||
const request = Promise.resolve(dataOrThenable)
|
||||
.then(data => this._keepItem(uri, data))
|
||||
.catch(_ => this._data.delete(uri.toString()));
|
||||
|
||||
this._data.set(uri.toString(), request);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DecorationColors {
|
||||
|
||||
private readonly _disposables: IDisposable[];
|
||||
private readonly _styleElement = createStyleSheet();
|
||||
private readonly _classNames = new IdGenerator('monaco-decoration-styles-');
|
||||
private readonly _classNames2ColorIds = new Map<string, [string, string]>();
|
||||
|
||||
constructor(
|
||||
private _themeService: IThemeService,
|
||||
) {
|
||||
this._disposables = [
|
||||
this._themeService.onThemeChange(this._onThemeChange, this),
|
||||
];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
this._styleElement.innerHTML = '';
|
||||
}
|
||||
|
||||
ensureCssStyles(decoration: IResourceDecoration): void {
|
||||
if (!decoration || !decoration.color) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tuple = this._classNames2ColorIds.get(decoration.color);
|
||||
if (tuple) {
|
||||
// from cache
|
||||
decoration.labelClasses = tuple[0];
|
||||
decoration.badgeClassName = tuple[1];
|
||||
return;
|
||||
}
|
||||
|
||||
let labelClassName = this._classNames.nextId();
|
||||
let badgeClassName = this._classNames.nextId();
|
||||
|
||||
this._classNames2ColorIds.set(decoration.color, [labelClassName, badgeClassName]);
|
||||
decoration.labelClasses = labelClassName;
|
||||
decoration.badgeClassName = badgeClassName;
|
||||
|
||||
this._createCssRules(labelClassName, badgeClassName, decoration.color);
|
||||
}
|
||||
|
||||
private _onThemeChange(): void {
|
||||
this._classNames2ColorIds.forEach((tuple, color) => {
|
||||
const [labelClassName, badgeClassName] = tuple;
|
||||
removeCSSRulesContainingSelector(labelClassName, this._styleElement);
|
||||
removeCSSRulesContainingSelector(badgeClassName, this._styleElement);
|
||||
this._createCssRules(labelClassName, badgeClassName, color);
|
||||
});
|
||||
}
|
||||
|
||||
private _createCssRules(labelClassName: string, badgeClassName: string, color: ColorIdentifier): void {
|
||||
const theme = this._themeService.getTheme();
|
||||
// label
|
||||
createCSSRule(`.${labelClassName}`, `color: ${theme.getColor(color)}`, this._styleElement);
|
||||
createCSSRule(`.selected .${labelClassName}`, `color: ${theme.getColor(listActiveSelectionForeground)}`, this._styleElement);
|
||||
|
||||
// badge
|
||||
createCSSRule(`.${badgeClassName}`, `background-color: ${theme.getColor(color)}; color: ${theme.getColor(listActiveSelectionForeground)};`, this._styleElement);
|
||||
private _keepItem(uri: URI, data: IResourceDecorationData): IResourceDecoration {
|
||||
let deco = data ? this._decorationStyles.makeResourceDecoration(data) : null;
|
||||
this._data.set(uri.toString(), deco);
|
||||
this._emitter.fire(uri);
|
||||
return deco;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +229,11 @@ export class FileDecorationsService implements IResourceDecorationsService {
|
||||
|
||||
registerDecortionsProvider(provider: IDecorationsProvider): IDisposable {
|
||||
|
||||
const wrapper = new DecorationProviderWrapper(provider, this._onDidChangeDecorationsDelayed);
|
||||
const wrapper = new DecorationProviderWrapper(
|
||||
this._decorationStyles,
|
||||
provider,
|
||||
this._onDidChangeDecorationsDelayed
|
||||
);
|
||||
const remove = this._data.push(wrapper);
|
||||
return {
|
||||
dispose: () => {
|
||||
@@ -237,13 +254,13 @@ export class FileDecorationsService implements IResourceDecorationsService {
|
||||
if (isChild && top === candidate) {
|
||||
// only bubble up color
|
||||
top = {
|
||||
_decoBrand: undefined,
|
||||
severity: top.severity,
|
||||
color: top.color
|
||||
labelClassName: top.labelClassName
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
this._decorationStyles.ensureCssStyles(top);
|
||||
return top;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService';
|
||||
import { IDecorationsProvider, IResourceDecoration } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import { IDecorationsProvider, IResourceDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { toPromise } from 'vs/base/common/event';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
@@ -34,10 +34,11 @@ suite('DecorationsService', function () {
|
||||
readonly onDidChange: Event<URI[]> = Event.None;
|
||||
provideDecorations(uri: URI) {
|
||||
callCounter += 1;
|
||||
return new Promise<IResourceDecoration>(resolve => {
|
||||
return new Promise<IResourceDecorationData>(resolve => {
|
||||
setTimeout(() => resolve({
|
||||
severity: Severity.Info,
|
||||
color: 'someBlue'
|
||||
color: 'someBlue',
|
||||
letter: 'T'
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -52,7 +53,7 @@ suite('DecorationsService', function () {
|
||||
assert.equal(e.affectsResource(uri), true);
|
||||
|
||||
// sync result
|
||||
assert.deepEqual(service.getTopDecoration(uri, false), { severity: Severity.Info, color: 'someBlue' });
|
||||
assert.deepEqual(service.getTopDecoration(uri, false).letter, 'T');
|
||||
assert.equal(callCounter, 1);
|
||||
});
|
||||
});
|
||||
@@ -67,12 +68,12 @@ suite('DecorationsService', function () {
|
||||
readonly onDidChange: Event<URI[]> = Event.None;
|
||||
provideDecorations(uri: URI) {
|
||||
callCounter += 1;
|
||||
return { severity: Severity.Info, color: 'someBlue' };
|
||||
return { severity: Severity.Info, color: 'someBlue', letter: 'Z' };
|
||||
}
|
||||
});
|
||||
|
||||
// trigger -> sync
|
||||
assert.deepEqual(service.getTopDecoration(uri, false), { severity: Severity.Info, color: 'someBlue' });
|
||||
assert.deepEqual(service.getTopDecoration(uri, false).letter, 'Z');
|
||||
assert.equal(callCounter, 1);
|
||||
});
|
||||
|
||||
@@ -85,12 +86,12 @@ suite('DecorationsService', function () {
|
||||
readonly onDidChange: Event<URI[]> = Event.None;
|
||||
provideDecorations(uri: URI) {
|
||||
callCounter += 1;
|
||||
return { severity: Severity.Info, color: 'someBlue' };
|
||||
return { severity: Severity.Info, color: 'someBlue', letter: 'J' };
|
||||
}
|
||||
});
|
||||
|
||||
// trigger -> sync
|
||||
assert.deepEqual(service.getTopDecoration(uri, false), { severity: Severity.Info, color: 'someBlue' });
|
||||
assert.deepEqual(service.getTopDecoration(uri, false).letter, 'J');
|
||||
assert.equal(callCounter, 1);
|
||||
|
||||
// un-register -> ensure good event
|
||||
|
||||
Reference in New Issue
Block a user