mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-13 23:44:09 +01:00
Merge pull request #87405 from microsoft/aeschli/detectOSColorSchemeChanges
auto switch of color theme based on browser API `prefers-color-scheme`
This commit is contained in:
@@ -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);
|
||||
|
||||
let colorThemeSetting: string;
|
||||
if (this.environmentService.configuration.highContrast && detectHCThemeSetting) {
|
||||
colorThemeSetting = HC_THEME_ID;
|
||||
} else {
|
||||
colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
|
||||
}
|
||||
|
||||
let iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
|
||||
const colorThemeSetting = this.configurationService.getValue<string>(COLOR_THEME_SETTING);
|
||||
const iconThemeSetting = this.configurationService.getValue<string | null>(ICON_THEME_SETTING);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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 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 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);
|
||||
};
|
||||
|
||||
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';
|
||||
|
||||
Reference in New Issue
Block a user