The vscode page is divided into two parts, one part is provided by the plug-in, and the other part is the main body. Then vscode needs to consider combining these two parts for management in terms of multi-theme implementation, which is relatively more complicated than simply implementing multi-theme functions on web pages.
The main part is realized
Let's first look at how the style of the main part of vscode is drawn
registerThemingParticipant((theme, collector) => {
const activitybarTextForeground = theme.getColor(foreground);
if (activitybarTextForeground) {
/**
* color --> vscode原生
* background-color --> 插件
*/
collector.addRule(`
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item .action-label {
color: ${
activitybarTextForeground} !important;
}
`);
...
}
This is for the style code of the activitybar part, theme
to get the color, collector
collect the style, and finally insert it into the global style to complete the style setting.
Let's getColor
look inside the method
src/vs/workbench/services/themes/common/colorThemeData.ts
public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined {
let color: Color | undefined = this.customColorMap[colorId];
if (color) {
return color;
}
color = this.colorMap[colorId];
if (useDefault !== false && types.isUndefined(color)) {
color = this.getDefault(colorId);
}
return color;
}
Where does colorMap come from? Here comes the key point!
The multi-themes of vscode are actually managed through theme plug-ins.
All colors are taken from the json files in the themes directory.
{
"type": "dark",
"colors": {
"dropdown.background": "#525252",
"list.activeSelectionBackground": "#707070",
"quickInputList.focusBackground": "#707070",
"list.inactiveSelectionBackground": "#4e4e4e",
...
}
}
Here are the color variables to prevent.
Color registration is in most of this file
src/vs/platform/theme/common/colorRegistry.ts
...
// 基础颜色
export const secondForeground = registerColor('second.foreground', {
light: '#737373', dark: '#fff', hcDark: '#fff', hcLight: '#737373' }, nls.localize('secondForeground', "Color for text separators."));
...
vscode
The themes are roughly divided into four categories : dark theme, light theme, dark highlight theme, and light highlight theme .
Therefore, four default values are set here, and if the value cannot be obtained in the plug-in, the default value will be found.
Ok, here is the color setting of the main part, let's take a look at the color setting in the plug-in.
plugin section
.search-warapper .searchInput .search-btn {
...
background-color: var(--vscode-productMain-color);
}
The plugin part uses the var function of css.
CSS var函数
is a syntax for custom property values that is used to insert the value of a custom property, but not any part of another property's value. It allows you to change styles without changing the stylesheet, which improves maintainability and reusability. Variable values are inherited from parent classes.
All the styles obtained from the theme plugin are written into the html style.
Now I understand everything~
Theme style switching
wake up color picker
src/vs/workbench/contrib/themes/browser/themes.contribution.ts
override async run(accessor: ServicesAccessor) {
const themeService = accessor.get(IWorkbenchThemeService);
const installMessage = localize('installColorThemes', "Install Additional Color Themes...");
const browseMessage = '$(plus) ' + localize('browseColorThemes', "Browse Additional Color Themes...");
const placeholderMessage = localize('themes.selectTheme', "Select Color Theme (Up/Down Keys to Preview)");
const marketplaceTag = 'category:themes';
const setTheme = (theme: IWorkbenchTheme | undefined, settingsTarget: ThemeSettingTarget) => themeService.setColorTheme(theme as IWorkbenchColorTheme, settingsTarget);
const getMarketplaceColorThemes = (publisher: string, name: string, version: string) => themeService.getMarketplaceColorThemes(publisher, name, version);
const instantiationService = accessor.get(IInstantiationService);
const picker = instantiationService.createInstance(InstalledThemesPicker, installMessage, browseMessage, placeholderMessage, marketplaceTag, setTheme, getMarketplaceColorThemes);
const themes = await themeService.getColorThemes();
const currentTheme = themeService.getColorTheme();
const picks: QuickPickInput<ThemeItem>[] = [
...toEntries(themes.filter(t => t.type === ColorScheme.LIGHT), localize('themes.category.light', "light themes")),
...toEntries(themes.filter(t => t.type === ColorScheme.DARK), localize('themes.category.dark', "dark themes")),
...toEntries(themes.filter(t => isHighContrast(t.type)), localize('themes.category.hc', "high contrast themes")),
];
await picker.openQuickPick(picks, currentTheme);
}
This event is triggered when selecting up and down
quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false));
Continue to follow up in
src/vs/workbench/services/themes/browser/workbenchThemeService.ts
public setColorTheme(themeIdOrTheme: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchColorTheme | null> {
return this.colorThemeSequencer.queue(async () => {
return this.internalSetColorTheme(themeIdOrTheme, settingsTarget);
});
}
private async internalSetColorTheme(themeIdOrTheme: string | undefined | IWorkbenchColorTheme, settingsTarget: ThemeSettingTarget): Promise<IWorkbenchColorTheme | null> {
if (!themeIdOrTheme) {
return null;
}
const themeId = types.isString(themeIdOrTheme) ? validateThemeId(themeIdOrTheme) : themeIdOrTheme.id;
if (this.currentColorTheme.isLoaded && themeId === this.currentColorTheme.id) {
if (settingsTarget !== 'preview') {
this.currentColorTheme.toStorage(this.storageService);
}
return this.settings.setColorTheme(this.currentColorTheme, settingsTarget);
}
let themeData = this.colorThemeRegistry.findThemeById(themeId);
if (!themeData) {
if (themeIdOrTheme instanceof ColorThemeData) {
themeData = themeIdOrTheme;
} else {
return null;
}
}
try {
await themeData.ensureLoaded(this.extensionResourceLoaderService);
themeData.setCustomizations(this.settings);
return this.applyTheme(themeData, settingsTarget);
} catch (error) {
throw new Error(nls.localize('error.cannotloadtheme', "Unable to load {0}: {1}", themeData.location?.toString(), error.message));
}
}
Take a look at what is done in the themeData.ensureLoade method
src/vs/workbench/services/themes/common/colorThemeData.ts
public ensureLoaded(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
return !this.isLoaded ? this.load(extensionResourceLoaderService) : Promise.resolve(undefined);
}
public reload(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
return this.load(extensionResourceLoaderService);
}
private load(extensionResourceLoaderService: IExtensionResourceLoaderService): Promise<void> {
if (!this.location) {
return Promise.resolve(undefined);
}
this.themeTokenColors = [];
this.clearCaches();
const result = {
colors: {
},
textMateRules: [],
semanticTokenRules: [],
semanticHighlighting: false
};
return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => {
this.isLoaded = true;
this.semanticTokenRules = result.semanticTokenRules;
this.colorMap = result.colors; //生成colorMap
this.themeTokenColors = result.textMateRules;
this.themeSemanticHighlighting = result.semanticHighlighting;
});
}
The colorMap of the main part of vscode above is generated here
then go down
private updateDynamicCSSRules(themeData: IColorTheme) {
...
themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));
const colorVariables: string[] = [];
for (const item of getColorRegistry().getColors()) {
const color = themeData.getColor(item.id, true);
if (color) {
colorVariables.push(`${
asCssVariableName(item.id)}: ${
color.toString()};`);
}
}
ruleCollector.addRule(`.monaco-workbench { ${
colorVariables.join('\n')} }`);
_applyRules([...cssRules].join('\n'), colorThemeRulesClassName);
}
here
themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));
Very important, the code of the style collector is re-called here, and it is put back into the collector
registerThemingParticipant((theme, collector) => {
const activitybarTextForeground = theme.getColor(foreground);
if (activitybarTextForeground) {
/**
* color --> vscode原生
* background-color --> 插件
*/
collector.addRule(`
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item .action-label {
color: ${
activitybarTextForeground} !important;
}
`);
...
}
src/vs/workbench/services/themes/browser/workbenchThemeService.ts
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;
}
}
Here you can see that the style is rewritten in style.
Theme switching is complete!