未验证 提交 139e59f1 编写于 作者: M Martin Aeschlimann 提交者: GitHub

Merge pull request #87405 from microsoft/aeschli/detectOSColorSchemeChanges

auto switch of color theme based on browser API `prefers-color-scheme`
......@@ -6,7 +6,7 @@
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, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } 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';
......@@ -14,7 +14,7 @@ import * as errors from 'vs/base/common/errors';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData';
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService';
import { ITheme, Extensions as ThemingExtensions, IThemingRegistry, ThemeType, LIGHT, DARK, HIGH_CONTRAST } 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';
......@@ -35,13 +35,25 @@ 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';
// settings
const PREFERRED_DARK_THEME_SETTING = 'workbench.preferredDarkColorTheme';
const PREFERRED_LIGHT_THEME_SETTING = 'workbench.preferredLightColorTheme';
const PREFERRED_HC_THEME_SETTING = 'workbench.preferredHighContrastColorTheme';
const DETECT_COLOR_SCHEME_SETTING = 'workbench.autoDetectColorScheme';
const DETECT_HC_SETTING = 'window.autoDetectHighContrast';
// implementation
const DEFAULT_THEME_ID = 'vs-dark vscode-theme-defaults-themes-dark_plus-json';
const DEFAULT_THEME_SETTING_VALUE = 'Default Dark+';
const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark+';
const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light+';
const DEFAULT_THEME_HC_SETTING_VALUE = 'Default High Contrast';
const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData';
const PERSISTED_ICON_THEME_STORAGE_KEY = 'iconThemeData';
const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme';
const defaultThemeExtensionId = 'vscode-theme-defaults';
const oldDefaultThemeExtensionId = 'vscode-theme-colorful-defaults';
......@@ -148,6 +160,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
this.initialize().then(undefined, errors.onUnexpectedError).then(_ => {
this.installConfigurationListener();
this.installPreferredSchemeListener();
});
let prevColorId: string | undefined = undefined;
......@@ -155,8 +168,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
// update settings schema setting for theme specific settings
this.colorThemeStore.onDidChange(async event => {
// updates enum for the 'workbench.colorTheme` setting
colorThemeSettingSchema.enum = event.themes.map(t => t.settingsId);
colorThemeSettingSchema.enumDescriptions = event.themes.map(t => t.description || '');
colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...event.themes.map(t => t.settingsId));
colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...event.themes.map(t => t.description || ''));
const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} };
const themeSpecificTokenColors: IJSONSchema = { properties: {} };
......@@ -248,44 +261,40 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
}
private initialize(): Promise<[IColorTheme | null, IFileIconTheme | null]> {
let detectHCThemeSetting = this.configurationService.getValue<boolean>(DETECT_HC_SETTING);
const colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
const iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
let colorThemeSetting: string;
if (this.environmentService.configuration.highContrast && detectHCThemeSetting) {
colorThemeSetting = HC_THEME_ID;
} else {
colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
}
const extDevLocs = this.environmentService.extensionDevelopmentLocationURI;
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
const initializeColorTheme = async () => {
if (extDevLocs && extDevLocs.length > 0) { // in dev mode, switch to a theme provided by the extension under dev.
const devThemes = await this.colorThemeStore.findThemeDataByParentLocation(extDevLocs[0]);
if (devThemes.length) {
return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
}
}
let theme = await this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID);
const extDevLocs = this.environmentService.extensionDevelopmentLocationURI;
let uri: URI | undefined;
if (extDevLocs && extDevLocs.length > 0) {
// if there are more than one ext dev paths, use first
uri = extDevLocs[0];
}
const persistedColorScheme = this.storageService.get(PERSISTED_OS_COLOR_SCHEME, StorageScope.GLOBAL);
const preferredColorScheme = this.getPreferredColorScheme();
if (persistedColorScheme && preferredColorScheme && persistedColorScheme !== preferredColorScheme) {
return this.applyPreferredColorTheme(preferredColorScheme);
}
return this.setColorTheme(theme && theme.id, undefined);
};
return Promise.all([
this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => {
return this.colorThemeStore.findThemeDataByParentLocation(uri).then(devThemes => {
if (devThemes.length) {
return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
} else {
return this.setColorTheme(theme && theme.id, undefined);
}
});
}),
this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => {
return this.iconThemeStore.findThemeDataByParentLocation(uri).then(devThemes => {
if (devThemes.length) {
return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
} else {
return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined);
}
});
}),
]);
const initializeIconTheme = async () => {
if (extDevLocs && extDevLocs.length > 0) { // in dev mode, switch to a theme provided by the extension under dev.
const devThemes = await this.iconThemeStore.findThemeDataByParentLocation(extDevLocs[0]);
if (devThemes.length) {
return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
}
}
const theme = await this.iconThemeStore.findThemeBySettingsId(iconThemeSetting);
return this.setFileIconTheme(theme ? theme.id : DEFAULT_ICON_THEME_ID, undefined);
};
return Promise.all([initializeColorTheme(), initializeIconTheme()]);
}
private installConfigurationListener() {
......@@ -300,6 +309,18 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
});
}
}
if (e.affectsConfiguration(DETECT_COLOR_SCHEME_SETTING)) {
this.handlePreferredSchemeUpdated();
}
if (e.affectsConfiguration(PREFERRED_DARK_THEME_SETTING) && this.getPreferredColorScheme() === DARK) {
this.applyPreferredColorTheme(DARK);
}
if (e.affectsConfiguration(PREFERRED_LIGHT_THEME_SETTING) && this.getPreferredColorScheme() === LIGHT) {
this.applyPreferredColorTheme(LIGHT);
}
if (e.affectsConfiguration(PREFERRED_HC_THEME_SETTING) && this.getPreferredColorScheme() === HIGH_CONTRAST) {
this.applyPreferredColorTheme(HIGH_CONTRAST);
}
if (e.affectsConfiguration(ICON_THEME_SETTING)) {
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
if (iconThemeSetting !== this.currentIconTheme.settingsId) {
......@@ -330,6 +351,48 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
});
}
// preferred scheme handling
private installPreferredSchemeListener() {
window.matchMedia('(prefers-color-scheme: dark)').addListener(async () => this.handlePreferredSchemeUpdated());
}
private async handlePreferredSchemeUpdated() {
const scheme = this.getPreferredColorScheme();
this.storageService.store(PERSISTED_OS_COLOR_SCHEME, scheme, StorageScope.GLOBAL);
if (scheme) {
return this.applyPreferredColorTheme(scheme);
}
return undefined;
}
private getPreferredColorScheme(): ThemeType | undefined {
let detectHCThemeSetting = this.configurationService.getValue<boolean>(DETECT_HC_SETTING);
if (this.environmentService.configuration.highContrast && detectHCThemeSetting) {
return HIGH_CONTRAST;
}
if (this.configurationService.getValue<boolean>(DETECT_COLOR_SCHEME_SETTING)) {
if (window.matchMedia(`(prefers-color-scheme: light)`).matches) {
return LIGHT;
} else if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return DARK;
}
}
return undefined;
}
private async applyPreferredColorTheme(type: ThemeType): Promise<IColorTheme | null> {
const settingId = type === DARK ? PREFERRED_DARK_THEME_SETTING : type === LIGHT ? PREFERRED_LIGHT_THEME_SETTING : PREFERRED_HC_THEME_SETTING;
const themeSettingId = this.configurationService.getValue<string>(settingId);
if (themeSettingId) {
const theme = await this.colorThemeStore.findThemeDataBySettingsId(themeSettingId, undefined);
if (theme) {
return this.setColorTheme(theme.id, 'auto');
}
}
return null;
}
public getColorTheme(): IColorTheme {
return this.currentColorTheme;
}
......@@ -352,11 +415,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
themeId = validateThemeId(themeId); // migrate theme ids
return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(data => {
if (!data) {
return this.colorThemeStore.findThemeData(themeId, DEFAULT_THEME_ID).then(themeData => {
if (!themeData) {
return null;
}
const themeData = data;
return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => {
if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) {
this.currentColorTheme.clearCaches();
......@@ -641,14 +703,46 @@ registerFileIconThemeSchemas();
// Configuration: Themes
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const colorThemeSettingEnum: string[] = [];
const colorThemeSettingEnumDescriptions: string[] = [];
const colorThemeSettingSchema: IConfigurationPropertySchema = {
type: 'string',
description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."),
default: DEFAULT_THEME_SETTING_VALUE,
enum: [],
enumDescriptions: [],
enum: colorThemeSettingEnum,
enumDescriptions: colorThemeSettingEnumDescriptions,
errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = {
type: 'string',
description: nls.localize('preferredDarkColorTheme', 'Specifies the preferred color theme for dark OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING),
default: DEFAULT_THEME_DARK_SETTING_VALUE,
enum: colorThemeSettingEnum,
enumDescriptions: colorThemeSettingEnumDescriptions,
errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
const preferredLightThemeSettingSchema: IConfigurationPropertySchema = {
type: 'string',
description: nls.localize('preferredLightColorTheme', 'Specifies the preferred color theme for light OS appearance when \'{0}\' is enabled.', DETECT_COLOR_SCHEME_SETTING),
default: DEFAULT_THEME_LIGHT_SETTING_VALUE,
enum: colorThemeSettingEnum,
enumDescriptions: colorThemeSettingEnumDescriptions,
errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
const preferredHCThemeSettingSchema: IConfigurationPropertySchema = {
type: 'string',
description: nls.localize('preferredHCColorTheme', 'Specifies the preferred color theme used in high contrast mode when \'{0}\' is enabled.', DETECT_HC_SETTING),
default: DEFAULT_THEME_HC_SETTING_VALUE,
enum: colorThemeSettingEnum,
enumDescriptions: colorThemeSettingEnumDescriptions,
errorMessage: nls.localize('colorThemeError', "Theme is unknown or not installed."),
};
const detectColorSchemeSettingSchema: IConfigurationPropertySchema = {
type: 'boolean',
description: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance.'),
default: true
};
const iconThemeSettingSchema: IConfigurationPropertySchema = {
type: ['string', 'null'],
......@@ -675,6 +769,10 @@ const themeSettingsConfiguration: IConfigurationNode = {
type: 'object',
properties: {
[COLOR_THEME_SETTING]: colorThemeSettingSchema,
[PREFERRED_DARK_THEME_SETTING]: preferredDarkThemeSettingSchema,
[PREFERRED_LIGHT_THEME_SETTING]: preferredLightThemeSettingSchema,
[PREFERRED_HC_THEME_SETTING]: preferredHCThemeSettingSchema,
[DETECT_COLOR_SCHEME_SETTING]: detectColorSchemeSettingSchema,
[ICON_THEME_SETTING]: iconThemeSettingSchema,
[CUSTOM_WORKBENCH_COLORS_SETTING]: colorCustomizationsSchema
}
......
......@@ -18,7 +18,6 @@ export const VS_HC_THEME = 'hc-black';
export const HC_THEME_ID = 'Default High Contrast';
export const COLOR_THEME_SETTING = 'workbench.colorTheme';
export const DETECT_HC_SETTING = 'window.autoDetectHighContrast';
export const ICON_THEME_SETTING = 'workbench.iconTheme';
export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations';
export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册