mirror of
https://github.com/coder/code-server.git
synced 2026-05-09 13:57:26 +02:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user