主体部分实现

我们先看下vscode主体部分样式是如何画出来了

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;
			}
		`);
		...
	}

这是出于activitybar部分的样式代码,theme获取颜色,collector收集样式,最后都会插入到全局的style里面完成样式设置。

我们到getColor方法里面看下

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;
}

colorMap来至哪里呢,重点来了!
vscode的多主题其实都是通过主题插件来管理的
vscode多主题色功能实现机制-LMLPHP
vscode多主题色功能实现机制-LMLPHP
所有颜色都是取于themes目录下的json文件。

{
	"type": "dark",
	"colors": {
		"dropdown.background": "#525252",
		"list.activeSelectionBackground": "#707070",
		"quickInputList.focusBackground": "#707070",
		"list.inactiveSelectionBackground": "#4e4e4e",
		...
		}
	}	

这里面都是防止的颜色变量。

颜色的注册在大部分这个文件里面
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的主题大概分为四类深色主题、浅色主题、深色高亮主题、浅色高亮主题

所以这里设置了四个默认值,如果插件里面取不到值的话就会找默认值。

好,这里是主体部分的颜色设置,下面我们看看插件里面的颜色设置。

插件部分

.search-warapper .searchInput .search-btn {
	...
	background-color: var(--vscode-productMain-color);
}

插件部分使用的是css的var函数。

CSS var函数是一种自定义属性值的语法,用于插入自定义属性的值,而不是另一个属性的值的任何部分。它可以使您在不更改样式表的情况下更改样式,从而提高了可维护性和可重用性。 变量值继承至父类。

vscode多主题色功能实现机制-LMLPHP
从主题插件里面获取到的样式全部写入了html的style里面。

这下就都懂了叭~

主题样式切换

vscode多主题色功能实现机制-LMLPHP
唤醒颜色选择器
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);
	}

上下选择的时候触发了这个事件

quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false));

继续跟进去
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));
		}

	}

看看在themeData.ensureLoade方法里面做了什么

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;
		});
	}

上文中vscode主体部分colorMap就是在这里生成的

接着往下走

	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);
	}

这里的

themingRegistry.getThemingParticipants().forEach(p => p(themeData, ruleCollector, this.environmentService));

很重要,这里重新调用了样式收集器代码,重新放进了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;
	}
}

这里可以看到样式重新写入了style里。
主题切换完成!

兄弟萌给个关注奥

07-06 14:17