Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
export class BrowserHostColorSchemeService extends Disposable implements IHostColorSchemeService {
declare readonly _serviceBrand: undefined;
private readonly _onDidSchemeChangeEvent = this._register(new Emitter<void>());
constructor(
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
window.matchMedia('(prefers-color-scheme: dark)').addListener(() => {
this._onDidSchemeChangeEvent.fire();
});
window.matchMedia('(forced-colors: active)').addListener(() => {
this._onDidSchemeChangeEvent.fire();
});
}
get onDidChangeColorScheme(): Event<void> {
return this._onDidSchemeChangeEvent.event;
}
get dark(): boolean {
if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
return false;
} else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return true;
}
return this.environmentService.configuration.colorScheme.dark;
}
get highContrast(): boolean {
if (window.matchMedia(`(forced-colors: active)`).matches) {
return true;
}
return this.environmentService.configuration.colorScheme.highContrast;
}
}
registerSingleton(IHostColorSchemeService, BrowserHostColorSchemeService, true);

View File

@@ -0,0 +1,381 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as Paths from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import * as Json from 'vs/base/common/json';
import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IFileService } from 'vs/platform/files/common/files';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { asCSSUrl } from 'vs/base/browser/dom';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
export class FileIconThemeData implements IWorkbenchFileIconTheme {
static readonly STORAGE_KEY = 'iconThemeData';
id: string;
label: string;
settingsId: string | null;
description?: string;
hasFileIcons: boolean;
hasFolderIcons: boolean;
hidesExplorerArrows: boolean;
isLoaded: boolean;
location?: URI;
extensionData?: ExtensionData;
watch?: boolean;
styleSheetContent?: string;
private constructor(id: string, label: string, settingsId: string | null) {
this.id = id;
this.label = label;
this.settingsId = settingsId;
this.isLoaded = false;
this.hasFileIcons = false;
this.hasFolderIcons = false;
this.hidesExplorerArrows = false;
}
public ensureLoaded(fileService: IFileService): Promise<string | undefined> {
return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent);
}
public reload(fileService: IFileService): Promise<string | undefined> {
return this.load(fileService);
}
private load(fileService: IFileService): Promise<string | undefined> {
if (!this.location) {
return Promise.resolve(this.styleSheetContent);
}
return _loadIconThemeDocument(fileService, this.location).then(iconThemeDocument => {
const result = _processIconThemeDocument(this.id, this.location!, iconThemeDocument);
this.styleSheetContent = result.content;
this.hasFileIcons = result.hasFileIcons;
this.hasFolderIcons = result.hasFolderIcons;
this.hidesExplorerArrows = result.hidesExplorerArrows;
this.isLoaded = true;
return this.styleSheetContent;
});
}
static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): FileIconThemeData {
const id = extensionData.extensionId + '-' + iconTheme.id;
const label = iconTheme.label || Paths.basename(iconTheme.path);
const settingsId = iconTheme.id;
const themeData = new FileIconThemeData(id, label, settingsId);
themeData.description = iconTheme.description;
themeData.location = iconThemeLocation;
themeData.extensionData = extensionData;
themeData.watch = iconTheme._watch;
themeData.isLoaded = false;
return themeData;
}
private static _noIconTheme: FileIconThemeData | null = null;
static get noIconTheme(): FileIconThemeData {
let themeData = FileIconThemeData._noIconTheme;
if (!themeData) {
themeData = FileIconThemeData._noIconTheme = new FileIconThemeData('', '', null);
themeData.hasFileIcons = false;
themeData.hasFolderIcons = false;
themeData.hidesExplorerArrows = false;
themeData.isLoaded = true;
themeData.extensionData = undefined;
themeData.watch = false;
}
return themeData;
}
static createUnloadedTheme(id: string): FileIconThemeData {
const themeData = new FileIconThemeData(id, '', '__' + id);
themeData.isLoaded = false;
themeData.hasFileIcons = false;
themeData.hasFolderIcons = false;
themeData.hidesExplorerArrows = false;
themeData.extensionData = undefined;
themeData.watch = false;
return themeData;
}
static fromStorageData(storageService: IStorageService): FileIconThemeData | undefined {
const input = storageService.get(FileIconThemeData.STORAGE_KEY, StorageScope.GLOBAL);
if (!input) {
return undefined;
}
try {
let data = JSON.parse(input);
const theme = new FileIconThemeData('', '', null);
for (let key in data) {
switch (key) {
case 'id':
case 'label':
case 'description':
case 'settingsId':
case 'styleSheetContent':
case 'hasFileIcons':
case 'hidesExplorerArrows':
case 'hasFolderIcons':
case 'watch':
(theme as any)[key] = data[key];
break;
case 'location':
// ignore, no longer restore
break;
case 'extensionData':
theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
break;
}
}
return theme;
} catch (e) {
return undefined;
}
}
toStorage(storageService: IStorageService) {
const data = JSON.stringify({
id: this.id,
label: this.label,
description: this.description,
settingsId: this.settingsId,
styleSheetContent: this.styleSheetContent,
hasFileIcons: this.hasFileIcons,
hasFolderIcons: this.hasFolderIcons,
hidesExplorerArrows: this.hidesExplorerArrows,
extensionData: ExtensionData.toJSONObject(this.extensionData),
watch: this.watch
});
storageService.store(FileIconThemeData.STORAGE_KEY, data, StorageScope.GLOBAL);
}
}
interface IconDefinition {
iconPath: string;
fontColor: string;
fontCharacter: string;
fontSize: string;
fontId: string;
}
interface FontDefinition {
id: string;
weight: string;
style: string;
size: string;
src: { path: string; format: string; }[];
}
interface IconsAssociation {
folder?: string;
file?: string;
folderExpanded?: string;
rootFolder?: string;
rootFolderExpanded?: string;
folderNames?: { [folderName: string]: string; };
folderNamesExpanded?: { [folderName: string]: string; };
fileExtensions?: { [extension: string]: string; };
fileNames?: { [fileName: string]: string; };
languageIds?: { [languageId: string]: string; };
}
interface IconThemeDocument extends IconsAssociation {
iconDefinitions: { [key: string]: IconDefinition };
fonts: FontDefinition[];
light?: IconsAssociation;
highContrast?: IconsAssociation;
hidesExplorerArrows?: boolean;
}
function _loadIconThemeDocument(fileService: IFileService, location: URI): Promise<IconThemeDocument> {
return fileService.readFile(location).then((content) => {
let errors: Json.ParseError[] = [];
let contentValue = Json.parse(content.value.toString(), errors);
if (errors.length > 0) {
return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', '))));
} else if (Json.getNodeType(contentValue) !== 'object') {
return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for file icons theme file: Object expected.")));
}
return Promise.resolve(contentValue);
});
}
function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: IconThemeDocument): { content: string; hasFileIcons: boolean; hasFolderIcons: boolean; hidesExplorerArrows: boolean; } {
const result = { content: '', hasFileIcons: false, hasFolderIcons: false, hidesExplorerArrows: !!iconThemeDocument.hidesExplorerArrows };
if (!iconThemeDocument.iconDefinitions) {
return result;
}
let selectorByDefinitionId: { [def: string]: string[] } = {};
const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation);
function resolvePath(path: string) {
return resources.joinPath(iconThemeDocumentLocationDirname, path);
}
function collectSelectors(associations: IconsAssociation | undefined, baseThemeClassName?: string) {
function addSelector(selector: string, defId: string) {
if (defId) {
let list = selectorByDefinitionId[defId];
if (!list) {
list = selectorByDefinitionId[defId] = [];
}
list.push(selector);
}
}
if (associations) {
let qualifier = '.show-file-icons';
if (baseThemeClassName) {
qualifier = baseThemeClassName + ' ' + qualifier;
}
const expanded = '.monaco-tl-twistie.collapsible:not(.collapsed) + .monaco-tl-contents';
if (associations.folder) {
addSelector(`${qualifier} .folder-icon::before`, associations.folder);
result.hasFolderIcons = true;
}
if (associations.folderExpanded) {
addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded);
result.hasFolderIcons = true;
}
let rootFolder = associations.rootFolder || associations.folder;
let rootFolderExpanded = associations.rootFolderExpanded || associations.folderExpanded;
if (rootFolder) {
addSelector(`${qualifier} .rootfolder-icon::before`, rootFolder);
result.hasFolderIcons = true;
}
if (rootFolderExpanded) {
addSelector(`${qualifier} ${expanded} .rootfolder-icon::before`, rootFolderExpanded);
result.hasFolderIcons = true;
}
if (associations.file) {
addSelector(`${qualifier} .file-icon::before`, associations.file);
result.hasFileIcons = true;
}
let folderNames = associations.folderNames;
if (folderNames) {
for (let folderName in folderNames) {
addSelector(`${qualifier} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]);
result.hasFolderIcons = true;
}
}
let folderNamesExpanded = associations.folderNamesExpanded;
if (folderNamesExpanded) {
for (let folderName in folderNamesExpanded) {
addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]);
result.hasFolderIcons = true;
}
}
let languageIds = associations.languageIds;
if (languageIds) {
if (!languageIds.jsonc && languageIds.json) {
languageIds.jsonc = languageIds.json;
}
for (let languageId in languageIds) {
addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]);
result.hasFileIcons = true;
}
}
let fileExtensions = associations.fileExtensions;
if (fileExtensions) {
for (let fileExtension in fileExtensions) {
let selectors: string[] = [];
let segments = fileExtension.toLowerCase().split('.');
if (segments.length) {
for (let i = 0; i < segments.length; i++) {
selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`);
}
selectors.push('.ext-file-icon'); // extra segment to increase file-ext score
}
addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileExtensions[fileExtension]);
result.hasFileIcons = true;
}
}
let fileNames = associations.fileNames;
if (fileNames) {
for (let fileName in fileNames) {
let selectors: string[] = [];
fileName = fileName.toLowerCase();
selectors.push(`.${escapeCSS(fileName)}-name-file-icon`);
let segments = fileName.split('.');
if (segments.length) {
for (let i = 1; i < segments.length; i++) {
selectors.push(`.${escapeCSS(segments.slice(i).join('.'))}-ext-file-icon`);
}
selectors.push('.ext-file-icon'); // extra segment to increase file-ext score
}
addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]);
result.hasFileIcons = true;
}
}
}
}
collectSelectors(iconThemeDocument);
collectSelectors(iconThemeDocument.light, '.vs');
collectSelectors(iconThemeDocument.highContrast, '.hc-black');
if (!result.hasFileIcons && !result.hasFolderIcons) {
return result;
}
let cssRules: string[] = [];
let fonts = iconThemeDocument.fonts;
if (Array.isArray(fonts)) {
fonts.forEach(font => {
let src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', ');
cssRules.push(`@font-face { src: ${src}; font-family: '${font.id}'; font-weight: ${font.weight}; font-style: ${font.style}; }`);
});
cssRules.push(`.show-file-icons .file-icon::before, .show-file-icons .folder-icon::before, .show-file-icons .rootfolder-icon::before { font-family: '${fonts[0].id}'; font-size: ${fonts[0].size || '150%'}}`);
}
for (let defId in selectorByDefinitionId) {
let selectors = selectorByDefinitionId[defId];
let definition = iconThemeDocument.iconDefinitions[defId];
if (definition) {
if (definition.iconPath) {
cssRules.push(`${selectors.join(', ')} { content: ' '; background-image: ${asCSSUrl(resolvePath(definition.iconPath))}; }`);
}
if (definition.fontCharacter || definition.fontColor) {
let body = '';
if (definition.fontColor) {
body += ` color: ${definition.fontColor};`;
}
if (definition.fontCharacter) {
body += ` content: '${definition.fontCharacter}';`;
}
if (definition.fontSize) {
body += ` font-size: ${definition.fontSize};`;
}
if (definition.fontId) {
body += ` font-family: ${definition.fontId};`;
}
cssRules.push(`${selectors.join(', ')} { ${body} }`);
}
}
}
result.content = cssRules.join('\n');
return result;
}
function escapeCSS(str: string) {
return window.CSS.escape(str);
}

View File

@@ -0,0 +1,268 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as nls from 'vs/nls';
import * as Paths from 'vs/base/common/path';
import * as resources from 'vs/base/common/resources';
import * as Json from 'vs/base/common/json';
import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IFileService } from 'vs/platform/files/common/files';
import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { asCSSUrl } from 'vs/base/browser/dom';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration';
import { fontIdRegex, fontWeightRegex, fontStyleRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema';
import { isString } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const DEFAULT_PRODUCT_ICON_THEME_ID = ''; // TODO
export class ProductIconThemeData implements IWorkbenchProductIconTheme {
static readonly STORAGE_KEY = 'productIconThemeData';
id: string;
label: string;
settingsId: string;
description?: string;
isLoaded: boolean;
location?: URI;
extensionData?: ExtensionData;
watch?: boolean;
styleSheetContent?: string;
private constructor(id: string, label: string, settingsId: string) {
this.id = id;
this.label = label;
this.settingsId = settingsId;
this.isLoaded = false;
}
public ensureLoaded(fileService: IFileService, logService: ILogService): Promise<string | undefined> {
return !this.isLoaded ? this.load(fileService, logService) : Promise.resolve(this.styleSheetContent);
}
public reload(fileService: IFileService, logService: ILogService): Promise<string | undefined> {
return this.load(fileService, logService);
}
private load(fileService: IFileService, logService: ILogService): Promise<string | undefined> {
const location = this.location;
if (!location) {
return Promise.resolve(this.styleSheetContent);
}
return _loadProductIconThemeDocument(fileService, location).then(iconThemeDocument => {
const result = _processIconThemeDocument(this.id, location, iconThemeDocument);
this.styleSheetContent = result.content;
this.isLoaded = true;
if (result.warnings.length) {
logService.error(nls.localize('error.parseicondefs', "Problems processing product icons definitions in {0}:\n{1}", location.toString(), result.warnings.join('\n')));
}
return this.styleSheetContent;
});
}
static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): ProductIconThemeData {
const id = extensionData.extensionId + '-' + iconTheme.id;
const label = iconTheme.label || Paths.basename(iconTheme.path);
const settingsId = iconTheme.id;
const themeData = new ProductIconThemeData(id, label, settingsId);
themeData.description = iconTheme.description;
themeData.location = iconThemeLocation;
themeData.extensionData = extensionData;
themeData.watch = iconTheme._watch;
themeData.isLoaded = false;
return themeData;
}
static createUnloadedTheme(id: string): ProductIconThemeData {
const themeData = new ProductIconThemeData(id, '', '__' + id);
themeData.isLoaded = false;
themeData.extensionData = undefined;
themeData.watch = false;
return themeData;
}
private static _defaultProductIconTheme: ProductIconThemeData | null = null;
static get defaultTheme(): ProductIconThemeData {
let themeData = ProductIconThemeData._defaultProductIconTheme;
if (!themeData) {
themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE);
themeData.isLoaded = true;
themeData.extensionData = undefined;
themeData.watch = false;
}
return themeData;
}
static fromStorageData(storageService: IStorageService): ProductIconThemeData | undefined {
const input = storageService.get(ProductIconThemeData.STORAGE_KEY, StorageScope.GLOBAL);
if (!input) {
return undefined;
}
try {
let data = JSON.parse(input);
const theme = new ProductIconThemeData('', '', '');
for (let key in data) {
switch (key) {
case 'id':
case 'label':
case 'description':
case 'settingsId':
case 'styleSheetContent':
case 'watch':
(theme as any)[key] = data[key];
break;
case 'location':
// ignore, no longer restore
break;
case 'extensionData':
theme.extensionData = ExtensionData.fromJSONObject(data.extensionData);
break;
}
}
return theme;
} catch (e) {
return undefined;
}
}
toStorage(storageService: IStorageService) {
const data = JSON.stringify({
id: this.id,
label: this.label,
description: this.description,
settingsId: this.settingsId,
styleSheetContent: this.styleSheetContent,
watch: this.watch,
extensionData: ExtensionData.toJSONObject(this.extensionData),
});
storageService.store(ProductIconThemeData.STORAGE_KEY, data, StorageScope.GLOBAL);
}
}
interface IconDefinition {
fontCharacter: string;
fontId: string;
}
interface FontDefinition {
id: string;
weight: string;
style: string;
size: string;
src: { path: string; format: string; }[];
}
interface ProductIconThemeDocument {
iconDefinitions: { [key: string]: IconDefinition };
fonts: FontDefinition[];
}
function _loadProductIconThemeDocument(fileService: IFileService, location: URI): Promise<ProductIconThemeDocument> {
return fileService.readFile(location).then((content) => {
let errors: Json.ParseError[] = [];
let contentValue = Json.parse(content.value.toString(), errors);
if (errors.length > 0) {
return Promise.reject(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing product icons file: {0}", errors.map(e => getParseErrorMessage(e.error)).join(', '))));
} else if (Json.getNodeType(contentValue) !== 'object') {
return Promise.reject(new Error(nls.localize('error.invalidformat', "Invalid format for product icons theme file: Object expected.")));
} else if (!contentValue.iconDefinitions || !Array.isArray(contentValue.fonts) || !contentValue.fonts.length) {
return Promise.reject(new Error(nls.localize('error.missingProperties', "Invalid format for product icons theme file: Must contain iconDefinitions and fonts.")));
}
return Promise.resolve(contentValue);
});
}
function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, iconThemeDocument: ProductIconThemeDocument): { content: string; warnings: string[] } {
const warnings: string[] = [];
const result = { content: '', warnings };
if (!iconThemeDocument.iconDefinitions || !Array.isArray(iconThemeDocument.fonts) || !iconThemeDocument.fonts.length) {
return result;
}
const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation);
function resolvePath(path: string) {
return resources.joinPath(iconThemeDocumentLocationDirname, path);
}
const cssRules: string[] = [];
const fonts = iconThemeDocument.fonts;
const fontIdMapping: { [id: string]: string } = {};
for (const font of fonts) {
const src = font.src.map(l => `${asCSSUrl(resolvePath(l.path))} format('${l.format}')`).join(', ');
if (isString(font.id) && font.id.match(fontIdRegex)) {
const fontId = `pi-` + font.id;
fontIdMapping[font.id] = fontId;
let fontWeight = '';
if (isString(font.weight) && font.weight.match(fontWeightRegex)) {
fontWeight = `font-weight: ${font.weight};`;
} else {
warnings.push(nls.localize('error.fontWeight', 'Invalid font weight in font \'{0}\'. Ignoring setting.', font.id));
}
let fontStyle = '';
if (isString(font.style) && font.style.match(fontStyleRegex)) {
fontStyle = `font-style: ${font.style};`;
} else {
warnings.push(nls.localize('error.fontStyle', 'Invalid font style in font \'{0}\'. Ignoring setting.', font.id));
}
cssRules.push(`@font-face { src: ${src}; font-family: '${fontId}';${fontWeight}${fontStyle} }`);
} else {
warnings.push(nls.localize('error.fontId', 'Missing or invalid font id \'{0}\'. Skipping font definition.', font.id));
}
}
const primaryFontId = fonts.length > 0 ? fontIdMapping[fonts[0].id] : '';
const iconDefinitions = iconThemeDocument.iconDefinitions;
const iconRegistry = getIconRegistry();
for (let iconContribution of iconRegistry.getIcons()) {
const iconId = iconContribution.id;
let definition = iconDefinitions[iconId];
// look if an inherited icon has a definition
while (!definition && ThemeIcon.isThemeIcon(iconContribution.defaults)) {
const ic = iconRegistry.getIcon(iconContribution.defaults.id);
if (ic) {
definition = iconDefinitions[ic.id];
iconContribution = ic;
} else {
break;
}
}
if (definition) {
if (isString(definition.fontCharacter)) {
const fontId = definition.fontId !== undefined ? fontIdMapping[definition.fontId] : primaryFontId;
if (fontId) {
cssRules.push(`.codicon-${iconId}:before { content: '${definition.fontCharacter}' !important; font-family: ${fontId} !important; }`);
} else {
warnings.push(nls.localize('error.icon.fontId', 'Skipping icon definition \'{0}\'. Unknown font.', iconId));
}
} else {
warnings.push(nls.localize('error.icon.fontCharacter', 'Skipping icon definition \'{0}\'. Unknown fontCharacter.', iconId));
}
}
}
result.content = cssRules.join('\n');
return result;
}

View File

@@ -0,0 +1,716 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as types from 'vs/base/common/types';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, ThemeSettings, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Registry } from 'vs/platform/registry/common/platform';
import * as errors from 'vs/base/common/errors';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
import { IColorTheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
import { Event, Emitter } from 'vs/base/common/event';
import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { FileIconThemeData } from 'vs/workbench/services/themes/browser/fileIconThemeData';
import { createStyleSheet } from 'vs/base/browser/dom';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import * as resources from 'vs/base/common/resources';
import { registerColorThemeSchemas } from 'vs/workbench/services/themes/common/colorThemeSchema';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { ThemeRegistry, registerColorThemeExtensionPoint, registerFileIconThemeExtensionPoint, registerProductIconThemeExtensionPoint } from 'vs/workbench/services/themes/common/themeExtensionPoints';
import { updateColorThemeConfigurationSchemas, updateFileIconThemeConfigurationSchemas, ThemeConfiguration, updateProductIconThemeConfigurationSchemas } from 'vs/workbench/services/themes/common/themeConfiguration';
import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData';
import { registerProductIconThemeSchemas } from 'vs/workbench/services/themes/common/productIconThemeSchema';
import { ILogService } from 'vs/platform/log/common/log';
import { isWeb } from 'vs/base/common/platform';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
import { CodiconStyles } from 'vs/base/browser/ui/codicons/codiconStyles';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
// implementation
const DEFAULT_COLOR_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json';
const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme';
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';
const DEFAULT_FILE_ICON_THEME_ID = 'vscode.vscode-theme-seti-vs-seti';
const fileIconsEnabledClass = 'file-icons-enabled';
const colorThemeRulesClassName = 'contributedColorTheme';
const fileIconThemeRulesClassName = 'contributedFileIconTheme';
const productIconThemeRulesClassName = 'contributedProductIconTheme';
const themingRegistry = Registry.as<IThemingRegistry>(ThemingExtensions.ThemingContribution);
function validateThemeId(theme: string): string {
// migrations
switch (theme) {
case VS_LIGHT_THEME: return `vs ${defaultThemeExtensionId}-themes-light_vs-json`;
case VS_DARK_THEME: return `vs-dark ${defaultThemeExtensionId}-themes-dark_vs-json`;
case VS_HC_THEME: return `hc-black ${defaultThemeExtensionId}-themes-hc_black-json`;
case `vs ${oldDefaultThemeExtensionId}-themes-light_plus-tmTheme`: return `vs ${defaultThemeExtensionId}-themes-light_plus-json`;
case `vs-dark ${oldDefaultThemeExtensionId}-themes-dark_plus-tmTheme`: return `vs-dark ${defaultThemeExtensionId}-themes-dark_plus-json`;
}
return theme;
}
const colorThemesExtPoint = registerColorThemeExtensionPoint();
const fileIconThemesExtPoint = registerFileIconThemeExtensionPoint();
const productIconThemesExtPoint = registerProductIconThemeExtensionPoint();
export class WorkbenchThemeService implements IWorkbenchThemeService {
declare readonly _serviceBrand: undefined;
private readonly container: HTMLElement;
private settings: ThemeConfiguration;
private readonly colorThemeRegistry: ThemeRegistry<ColorThemeData>;
private currentColorTheme: ColorThemeData;
private readonly onColorThemeChange: Emitter<IWorkbenchColorTheme>;
private readonly colorThemeWatcher: ThemeFileWatcher;
private colorThemingParticipantChangeListener: IDisposable | undefined;
private readonly fileIconThemeRegistry: ThemeRegistry<FileIconThemeData>;
private currentFileIconTheme: FileIconThemeData;
private readonly onFileIconThemeChange: Emitter<IWorkbenchFileIconTheme>;
private readonly fileIconThemeWatcher: ThemeFileWatcher;
private readonly productIconThemeRegistry: ThemeRegistry<ProductIconThemeData>;
private currentProductIconTheme: ProductIconThemeData;
private readonly onProductIconThemeChange: Emitter<IWorkbenchProductIconTheme>;
private readonly productIconThemeWatcher: ThemeFileWatcher;
private themeSettingIdBeforeSchemeSwitch: string | undefined;
constructor(
@IExtensionService extensionService: IExtensionService,
@IStorageService private readonly storageService: IStorageService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService,
@IFileService private readonly fileService: IFileService,
@IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService,
@IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService,
@ILogService private readonly logService: ILogService,
@IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService,
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
) {
// roam persisted color theme colors. Don't enable for icons as they contain references to fonts and images.
storageKeysSyncRegistryService.registerStorageKey({ key: ColorThemeData.STORAGE_KEY, version: 1 });
this.container = layoutService.container;
this.settings = new ThemeConfiguration(configurationService);
this.colorThemeRegistry = new ThemeRegistry(extensionService, colorThemesExtPoint, ColorThemeData.fromExtensionTheme);
this.colorThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this));
this.onColorThemeChange = new Emitter<IWorkbenchColorTheme>({ leakWarningThreshold: 400 });
this.currentColorTheme = ColorThemeData.createUnloadedTheme('');
this.fileIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentFileIconTheme.bind(this));
this.fileIconThemeRegistry = new ThemeRegistry(extensionService, fileIconThemesExtPoint, FileIconThemeData.fromExtensionTheme, true, FileIconThemeData.noIconTheme);
this.onFileIconThemeChange = new Emitter<IWorkbenchFileIconTheme>();
this.currentFileIconTheme = FileIconThemeData.createUnloadedTheme('');
this.productIconThemeWatcher = new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentProductIconTheme.bind(this));
this.productIconThemeRegistry = new ThemeRegistry(extensionService, productIconThemesExtPoint, ProductIconThemeData.fromExtensionTheme, true, ProductIconThemeData.defaultTheme, true);
this.onProductIconThemeChange = new Emitter<IWorkbenchProductIconTheme>();
this.currentProductIconTheme = ProductIconThemeData.createUnloadedTheme('');
// In order to avoid paint flashing for tokens, because
// themes are loaded asynchronously, we need to initialize
// a color theme document with good defaults until the theme is loaded
let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService);
// the preferred color scheme (high contrast, light, dark) has changed since the last start
const preferredColorScheme = this.getPreferredColorScheme();
if (preferredColorScheme && themeData?.type !== preferredColorScheme && this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL) !== preferredColorScheme) {
themeData = ColorThemeData.createUnloadedThemeForThemeType(preferredColorScheme);
}
if (!themeData) {
const initialColorTheme = environmentService.options?.initialColorTheme;
if (initialColorTheme) {
themeData = ColorThemeData.createUnloadedThemeForThemeType(initialColorTheme.themeType, initialColorTheme.colors);
}
}
if (!themeData) {
themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? ColorScheme.LIGHT : ColorScheme.DARK);
}
themeData.setCustomizations(this.settings);
this.applyTheme(themeData, undefined, true);
const fileIconData = FileIconThemeData.fromStorageData(this.storageService);
if (fileIconData) {
this.applyAndSetFileIconTheme(fileIconData, true);
}
const productIconData = ProductIconThemeData.fromStorageData(this.storageService);
if (productIconData) {
this.applyAndSetProductIconTheme(productIconData, true);
}
this.initialize().then(undefined, errors.onUnexpectedError).then(_ => {
this.installConfigurationListener();
this.installPreferredSchemeListener();
this.installRegistryListeners();
});
const codiconStyleSheet = createStyleSheet();
codiconStyleSheet.id = 'codiconStyles';
function updateAll() {
codiconStyleSheet.textContent = CodiconStyles.getCSS();
}
const delayer = new RunOnceScheduler(updateAll, 0);
CodiconStyles.onDidChange(() => delayer.schedule());
delayer.schedule();
}
private initialize(): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> {
const extDevLocs = this.environmentService.extensionDevelopmentLocationURI;
const extDevLoc = extDevLocs && extDevLocs.length === 1 ? extDevLocs[0] : undefined; // in dev mode, switch to a theme provided by the extension under dev.
const initializeColorTheme = async () => {
const devThemes = await this.colorThemeRegistry.findThemeByExtensionLocation(extDevLoc);
if (devThemes.length) {
return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
}
const theme = await this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, DEFAULT_COLOR_THEME_ID);
const preferredColorScheme = this.getPreferredColorScheme();
const prevScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL);
if (preferredColorScheme !== prevScheme) {
this.storageService.store(PERSISTED_OS_COLOR_SCHEME, preferredColorScheme, StorageScope.GLOBAL);
if (preferredColorScheme && theme?.type !== preferredColorScheme) {
return this.applyPreferredColorTheme(preferredColorScheme);
}
}
return this.setColorTheme(theme && theme.id, undefined);
};
const initializeFileIconTheme = async () => {
const devThemes = await this.fileIconThemeRegistry.findThemeByExtensionLocation(extDevLoc);
if (devThemes.length) {
return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
}
const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(this.settings.fileIconTheme);
return this.setFileIconTheme(theme ? theme.id : DEFAULT_FILE_ICON_THEME_ID, undefined);
};
const initializeProductIconTheme = async () => {
const devThemes = await this.productIconThemeRegistry.findThemeByExtensionLocation(extDevLoc);
if (devThemes.length) {
return this.setProductIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
}
const theme = await this.productIconThemeRegistry.findThemeBySettingsId(this.settings.productIconTheme);
return this.setProductIconTheme(theme ? theme.id : DEFAULT_PRODUCT_ICON_THEME_ID, undefined);
};
return Promise.all([initializeColorTheme(), initializeFileIconTheme(), initializeProductIconTheme()]);
}
private installConfigurationListener() {
this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(ThemeSettings.COLOR_THEME)) {
this.restoreColorTheme();
}
if (e.affectsConfiguration(ThemeSettings.DETECT_COLOR_SCHEME) || e.affectsConfiguration(ThemeSettings.DETECT_HC)) {
this.handlePreferredSchemeUpdated();
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_DARK_THEME) && this.getPreferredColorScheme() === ColorScheme.DARK) {
this.applyPreferredColorTheme(ColorScheme.DARK);
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_LIGHT_THEME) && this.getPreferredColorScheme() === ColorScheme.LIGHT) {
this.applyPreferredColorTheme(ColorScheme.LIGHT);
}
if (e.affectsConfiguration(ThemeSettings.PREFERRED_HC_THEME) && this.getPreferredColorScheme() === ColorScheme.HIGH_CONTRAST) {
this.applyPreferredColorTheme(ColorScheme.HIGH_CONTRAST);
}
if (e.affectsConfiguration(ThemeSettings.FILE_ICON_THEME)) {
this.restoreFileIconTheme();
}
if (e.affectsConfiguration(ThemeSettings.PRODUCT_ICON_THEME)) {
this.restoreProductIconTheme();
}
if (this.currentColorTheme) {
let hasColorChanges = false;
if (e.affectsConfiguration(ThemeSettings.COLOR_CUSTOMIZATIONS)) {
this.currentColorTheme.setCustomColors(this.settings.colorCustomizations);
hasColorChanges = true;
}
if (e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS)) {
this.currentColorTheme.setCustomTokenColors(this.settings.tokenColorCustomizations);
hasColorChanges = true;
}
if (e.affectsConfiguration(ThemeSettings.SEMANTIC_TOKEN_COLOR_CUSTOMIZATIONS) || e.affectsConfiguration(ThemeSettings.TOKEN_COLOR_CUSTOMIZATIONS_EXPERIMENTAL)) {
this.currentColorTheme.setCustomSemanticTokenColors(this.settings.semanticTokenColorCustomizations, this.settings.experimentalSemanticTokenColorCustomizations);
hasColorChanges = true;
}
if (hasColorChanges) {
this.updateDynamicCSSRules(this.currentColorTheme);
this.onColorThemeChange.fire(this.currentColorTheme);
}
}
});
}
private installRegistryListeners(): Promise<any> {
let prevColorId: string | undefined = undefined;
// update settings schema setting for theme specific settings
this.colorThemeRegistry.onDidChange(async event => {
updateColorThemeConfigurationSchemas(event.themes);
if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) {
// restore theme
this.setColorTheme(prevColorId, 'auto');
prevColorId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
this.reloadCurrentColorTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
// current theme is no longer available
prevColorId = this.currentColorTheme.id;
this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto');
}
});
let prevFileIconId: string | undefined = undefined;
this.fileIconThemeRegistry.onDidChange(async event => {
updateFileIconThemeConfigurationSchemas(event.themes);
if (await this.restoreFileIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentFileIconTheme.id === DEFAULT_FILE_ICON_THEME_ID && !types.isUndefined(prevFileIconId) && await this.fileIconThemeRegistry.findThemeById(prevFileIconId)) {
this.setFileIconTheme(prevFileIconId, 'auto');
prevFileIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
this.reloadCurrentFileIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
// current theme is no longer available
prevFileIconId = this.currentFileIconTheme.id;
this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto');
}
});
let prevProductIconId: string | undefined = undefined;
this.productIconThemeRegistry.onDidChange(async event => {
updateProductIconThemeConfigurationSchemas(event.themes);
if (await this.restoreProductIconTheme()) { // checks if theme from settings exists and is set
// restore theme
if (this.currentProductIconTheme.id === DEFAULT_PRODUCT_ICON_THEME_ID && !types.isUndefined(prevProductIconId) && await this.productIconThemeRegistry.findThemeById(prevProductIconId)) {
this.setProductIconTheme(prevProductIconId, 'auto');
prevProductIconId = undefined;
} else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
this.reloadCurrentProductIconTheme();
}
} else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
// current theme is no longer available
prevProductIconId = this.currentProductIconTheme.id;
this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto');
}
});
return Promise.all([this.getColorThemes(), this.getFileIconThemes(), this.getProductIconThemes()]).then(([ct, fit, pit]) => {
updateColorThemeConfigurationSchemas(ct);
updateFileIconThemeConfigurationSchemas(fit);
updateProductIconThemeConfigurationSchemas(pit);
});
}
// preferred scheme handling
private installPreferredSchemeListener() {
this.hostColorService.onDidChangeColorScheme(() => this.handlePreferredSchemeUpdated());
}
private async handlePreferredSchemeUpdated() {
const scheme = this.getPreferredColorScheme();
const prevScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL);
if (scheme !== prevScheme) {
this.storageService.store(PERSISTED_OS_COLOR_SCHEME, scheme, StorageScope.GLOBAL);
if (scheme) {
if (!prevScheme) {
// remember the theme before scheme switching
this.themeSettingIdBeforeSchemeSwitch = this.settings.colorTheme;
}
return this.applyPreferredColorTheme(scheme);
} else if (prevScheme && this.themeSettingIdBeforeSchemeSwitch) {
// reapply the theme before scheme switching
const theme = await this.colorThemeRegistry.findThemeBySettingsId(this.themeSettingIdBeforeSchemeSwitch, undefined);
if (theme) {
this.setColorTheme(theme.id, 'auto');
}
}
}
return undefined;
}
private getPreferredColorScheme(): ColorScheme | undefined {
if (this.configurationService.getValue<boolean>(ThemeSettings.DETECT_HC) && this.hostColorService.highContrast) {
return ColorScheme.HIGH_CONTRAST;
}
if (this.configurationService.getValue<boolean>(ThemeSettings.DETECT_COLOR_SCHEME)) {
return this.hostColorService.dark ? ColorScheme.DARK : ColorScheme.LIGHT;
}
return undefined;
}
private async applyPreferredColorTheme(type: ColorScheme): Promise<IWorkbenchColorTheme | null> {
const settingId = type === ColorScheme.DARK ? ThemeSettings.PREFERRED_DARK_THEME : type === ColorScheme.LIGHT ? ThemeSettings.PREFERRED_LIGHT_THEME : ThemeSettings.PREFERRED_HC_THEME;
const themeSettingId = this.configurationService.getValue<string>(settingId);
if (themeSettingId) {
const theme = await this.colorThemeRegistry.findThemeBySettingsId(themeSettingId, undefined);
if (theme) {
const configurationTarget = this.settings.findAutoConfigurationTarget(settingId);
return this.setColorTheme(theme.id, configurationTarget);
}
}
return null;
}
public getColorTheme(): IWorkbenchColorTheme {
return this.currentColorTheme;
}
public getColorThemes(): Promise<IWorkbenchColorTheme[]> {
return this.colorThemeRegistry.getThemes();
}
public get onDidColorThemeChange(): Event<IWorkbenchColorTheme> {
return this.onColorThemeChange.event;
}
public setColorTheme(themeId: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IWorkbenchColorTheme | null> {
if (!themeId) {
return Promise.resolve(null);
}
if (themeId === this.currentColorTheme.id && this.currentColorTheme.isLoaded) {
return this.settings.setColorTheme(this.currentColorTheme, settingsTarget);
}
themeId = validateThemeId(themeId); // migrate theme ids
return this.colorThemeRegistry.findThemeById(themeId, DEFAULT_COLOR_THEME_ID).then(themeData => {
if (!themeData) {
return null;
}
return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => {
themeData.setCustomizations(this.settings);
return this.applyTheme(themeData, settingsTarget);
}, error => {
return Promise.reject(new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location!.toString(), error.message)));
});
});
}
private async reloadCurrentColorTheme() {
await this.currentColorTheme.reload(this.extensionResourceLoaderService);
this.currentColorTheme.setCustomizations(this.settings);
this.applyTheme(this.currentColorTheme, undefined, false);
}
public async restoreColorTheme(): Promise<boolean> {
const settingId = this.settings.colorTheme;
const theme = await this.colorThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentColorTheme.settingsId) {
await this.setColorTheme(theme.id, undefined);
}
return true;
}
return false;
}
private updateDynamicCSSRules(themeData: IColorTheme) {
const cssRules = new Set<string>();
const ruleCollector = {
addRule: (rule: string) => {
if (!cssRules.has(rule)) {
cssRules.add(rule);
}
}
};
ruleCollector.addRule(`.monaco-workbench { forced-color-adjust: none; }`);
themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));
_applyRules([...cssRules].join('\n'), colorThemeRulesClassName);
}
private applyTheme(newTheme: ColorThemeData, settingsTarget: ConfigurationTarget | undefined | 'auto', silent = false): Promise<IWorkbenchColorTheme | null> {
this.updateDynamicCSSRules(newTheme);
if (this.currentColorTheme.id) {
this.container.classList.remove(...this.currentColorTheme.classNames);
} else {
this.container.classList.remove(VS_DARK_THEME, VS_LIGHT_THEME, VS_HC_THEME);
}
this.container.classList.add(...newTheme.classNames);
this.currentColorTheme.clearCaches();
this.currentColorTheme = newTheme;
if (!this.colorThemingParticipantChangeListener) {
this.colorThemingParticipantChangeListener = themingRegistry.onThemingParticipantAdded(_ => this.updateDynamicCSSRules(this.currentColorTheme));
}
this.colorThemeWatcher.update(newTheme);
this.sendTelemetry(newTheme.id, newTheme.extensionData, 'color');
if (silent) {
return Promise.resolve(null);
}
this.onColorThemeChange.fire(this.currentColorTheme);
// remember theme data for a quick restore
if (newTheme.isLoaded) {
newTheme.toStorage(this.storageService);
}
return this.settings.setColorTheme(this.currentColorTheme, settingsTarget);
}
private themeExtensionsActivated = new Map<string, boolean>();
private sendTelemetry(themeId: string, themeData: ExtensionData | undefined, themeType: string) {
if (themeData) {
const key = themeType + themeData.extensionId;
if (!this.themeExtensionsActivated.get(key)) {
type ActivatePluginClassification = {
id: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
name: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
publisherDisplayName: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
themeId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
};
type ActivatePluginEvent = {
id: string;
name: string;
isBuiltin: boolean;
publisherDisplayName: string;
themeId: string;
};
this.telemetryService.publicLog2<ActivatePluginEvent, ActivatePluginClassification>('activatePlugin', {
id: themeData.extensionId,
name: themeData.extensionName,
isBuiltin: themeData.extensionIsBuiltin,
publisherDisplayName: themeData.extensionPublisher,
themeId: themeId
});
this.themeExtensionsActivated.set(key, true);
}
}
}
public getFileIconThemes(): Promise<IWorkbenchFileIconTheme[]> {
return this.fileIconThemeRegistry.getThemes();
}
public getFileIconTheme() {
return this.currentFileIconTheme;
}
public get onDidFileIconThemeChange(): Event<IWorkbenchFileIconTheme> {
return this.onFileIconThemeChange.event;
}
public async setFileIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IWorkbenchFileIconTheme> {
iconTheme = iconTheme || '';
if (iconTheme === this.currentFileIconTheme.id && this.currentFileIconTheme.isLoaded) {
await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget);
return this.currentFileIconTheme;
}
const newThemeData = (await this.fileIconThemeRegistry.findThemeById(iconTheme)) || FileIconThemeData.noIconTheme;
await newThemeData.ensureLoaded(this.fileService);
this.applyAndSetFileIconTheme(newThemeData);
// remember theme data for a quick restore
if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) {
newThemeData.toStorage(this.storageService);
}
await this.settings.setFileIconTheme(this.currentFileIconTheme, settingsTarget);
return newThemeData;
}
private async reloadCurrentFileIconTheme() {
await this.currentFileIconTheme.reload(this.fileService);
this.applyAndSetFileIconTheme(this.currentFileIconTheme);
}
public async restoreFileIconTheme(): Promise<boolean> {
const settingId = this.settings.fileIconTheme;
const theme = await this.fileIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentFileIconTheme.settingsId) {
await this.setFileIconTheme(theme.id, undefined);
}
return true;
}
return false;
}
private applyAndSetFileIconTheme(iconThemeData: FileIconThemeData, silent = false): void {
this.currentFileIconTheme = iconThemeData;
_applyRules(iconThemeData.styleSheetContent!, fileIconThemeRulesClassName);
if (iconThemeData.id) {
this.container.classList.add(fileIconsEnabledClass);
} else {
this.container.classList.remove(fileIconsEnabledClass);
}
this.fileIconThemeWatcher.update(iconThemeData);
if (iconThemeData.id) {
this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'fileIcon');
}
if (!silent) {
this.onFileIconThemeChange.fire(this.currentFileIconTheme);
}
}
public getProductIconThemes(): Promise<IWorkbenchProductIconTheme[]> {
return this.productIconThemeRegistry.getThemes();
}
public getProductIconTheme() {
return this.currentProductIconTheme;
}
public get onDidProductIconThemeChange(): Event<IWorkbenchProductIconTheme> {
return this.onProductIconThemeChange.event;
}
public async setProductIconTheme(iconTheme: string | undefined, settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise<IWorkbenchProductIconTheme> {
iconTheme = iconTheme || '';
if (iconTheme === this.currentProductIconTheme.id && this.currentProductIconTheme.isLoaded) {
await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget);
return this.currentProductIconTheme;
}
const newThemeData = await this.productIconThemeRegistry.findThemeById(iconTheme) || ProductIconThemeData.defaultTheme;
await newThemeData.ensureLoaded(this.fileService, this.logService);
this.applyAndSetProductIconTheme(newThemeData);
// remember theme data for a quick restore
if (newThemeData.isLoaded && (!newThemeData.location || !getRemoteAuthority(newThemeData.location))) {
newThemeData.toStorage(this.storageService);
}
await this.settings.setProductIconTheme(this.currentProductIconTheme, settingsTarget);
return newThemeData;
}
private async reloadCurrentProductIconTheme() {
await this.currentProductIconTheme.reload(this.fileService, this.logService);
this.applyAndSetProductIconTheme(this.currentProductIconTheme);
}
public async restoreProductIconTheme(): Promise<boolean> {
const settingId = this.settings.productIconTheme;
const theme = await this.productIconThemeRegistry.findThemeBySettingsId(settingId);
if (theme) {
if (settingId !== this.currentProductIconTheme.settingsId) {
await this.setProductIconTheme(theme.id, undefined);
}
return true;
}
return false;
}
private applyAndSetProductIconTheme(iconThemeData: ProductIconThemeData, silent = false): void {
this.currentProductIconTheme = iconThemeData;
_applyRules(iconThemeData.styleSheetContent!, productIconThemeRulesClassName);
this.productIconThemeWatcher.update(iconThemeData);
if (iconThemeData.id) {
this.sendTelemetry(iconThemeData.id, iconThemeData.extensionData, 'productIcon');
}
if (!silent) {
this.onProductIconThemeChange.fire(this.currentProductIconTheme);
}
}
}
class ThemeFileWatcher {
private inExtensionDevelopment: boolean;
private watchedLocation: URI | undefined;
private watcherDisposable: IDisposable | undefined;
private fileChangeListener: IDisposable | undefined;
constructor(private fileService: IFileService, environmentService: IWorkbenchEnvironmentService, private onUpdate: () => void) {
this.inExtensionDevelopment = !!environmentService.extensionDevelopmentLocationURI;
}
update(theme: { location?: URI, watch?: boolean; }) {
if (!resources.isEqual(theme.location, this.watchedLocation)) {
this.dispose();
if (theme.location && (theme.watch || this.inExtensionDevelopment)) {
this.watchedLocation = theme.location;
this.watcherDisposable = this.fileService.watch(theme.location);
this.fileService.onDidFilesChange(e => {
if (this.watchedLocation && e.contains(this.watchedLocation, FileChangeType.UPDATED)) {
this.onUpdate();
}
});
}
}
}
dispose() {
this.watcherDisposable = dispose(this.watcherDisposable);
this.fileChangeListener = dispose(this.fileChangeListener);
this.watchedLocation = undefined;
}
}
function _applyRules(styleSheetContent: string, rulesClassName: string) {
const themeStyles = document.head.getElementsByClassName(rulesClassName);
if (themeStyles.length === 0) {
const elStyle = document.createElement('style');
elStyle.type = 'text/css';
elStyle.className = rulesClassName;
elStyle.textContent = styleSheetContent;
document.head.appendChild(elStyle);
} else {
(<HTMLStyleElement>themeStyles[0]).textContent = styleSheetContent;
}
}
registerColorThemeSchemas();
registerFileIconThemeSchemas();
registerProductIconThemeSchemas();
registerSingleton(IWorkbenchThemeService, WorkbenchThemeService);