Skip to content

Commit e7fb5e9

Browse files
authored
new theme notification (#306827)
* new theme notification * update * update * update * update description * add colon
1 parent d63819f commit e7fb5e9

File tree

6 files changed

+59
-201
lines changed

6 files changed

+59
-201
lines changed

src/vs/platform/theme/electron-main/themeMainService.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,5 @@ export interface IThemeMainService {
2424

2525
getColorScheme(): IColorScheme;
2626

27-
/**
28-
* Whether OS color-scheme auto-detection is active.
29-
* Returns `true` when the `window.autoDetectColorScheme` setting is enabled,
30-
* or for fresh installs where no theme has been stored yet and the user
31-
* has not explicitly configured the setting (e.g. via settings sync).
32-
*/
3327
isAutoDetectColorScheme(): boolean;
3428
}

src/vs/platform/theme/electron-main/themeMainServiceImpl.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,6 @@ export class ThemeMainService extends Disposable implements IThemeMainService {
185185
if (Setting.DETECT_COLOR_SCHEME.getValue(this.configurationService)) {
186186
return true;
187187
}
188-
// For new installs with no stored theme, auto-detect OS color scheme by default
189-
// unless the user has explicitly configured the setting (e.g. via settings sync)
190-
if (!this.stateService.getItem(THEME_STORAGE_KEY)) {
191-
const { userValue } = this.configurationService.inspect<boolean>(Setting.DETECT_COLOR_SCHEME.key);
192-
return userValue === undefined;
193-
}
194188
return false;
195189
}
196190

src/vs/platform/windows/electron-main/windowsMainService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
15611561
os: { release: release(), hostname: hostname(), arch: arch() },
15621562

15631563
autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true,
1564-
autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? this.themeMainService.isAutoDetectColorScheme(),
1564+
autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false,
15651565
accessibilitySupport: app.accessibilitySupportEnabled,
15661566
colorScheme: this.themeMainService.getColorScheme(),
15671567
policiesData: this.policyService.serialize(),

src/vs/workbench/services/themes/browser/workbenchThemeService.ts

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import { ILanguageService } from '../../../../editor/common/languages/language.j
4444
import { mainWindow } from '../../../../base/browser/window.js';
4545
import { generateColorThemeCSS } from './colorThemeCss.js';
4646
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
47-
import { ICommandService } from '../../../../platform/commands/common/commands.js';
47+
import { IHostService } from '../../host/browser/host.js';
48+
import { toAction } from '../../../../base/common/actions.js';
4849

4950
// implementation
5051

@@ -114,12 +115,11 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
114115
@IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService,
115116
@ILanguageService private readonly languageService: ILanguageService,
116117
@INotificationService private readonly notificationService: INotificationService,
117-
@ICommandService private readonly commandService: ICommandService
118+
@IHostService private readonly hostService: IHostService
118119
) {
119120
super();
120121
this.container = layoutService.mainContainer;
121-
const isNewUser = this.storageService.isNew(StorageScope.APPLICATION);
122-
this.settings = new ThemeConfiguration(configurationService, hostColorService, isNewUser);
122+
this.settings = new ThemeConfiguration(configurationService, hostColorService);
123123

124124
this.colorThemeRegistry = this._register(new ThemeRegistry(colorThemesExtPoint, ColorThemeData.fromExtensionTheme));
125125
this.colorThemeWatcher = this._register(new ThemeFileWatcher(fileService, environmentService, this.reloadCurrentColorTheme.bind(this)));
@@ -146,6 +146,7 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
146146
// themes are loaded asynchronously, we need to initialize
147147
// a color theme document with good defaults until the theme is loaded
148148
let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService);
149+
const previousColorThemeSetting = themeData?.settingsId;
149150
const colorThemeSetting = this.settings.colorTheme;
150151
if (themeData && colorThemeSetting !== themeData.settingsId) {
151152
themeData = undefined;
@@ -179,7 +180,7 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
179180
this.installConfigurationListener();
180181
this.installPreferredSchemeListener();
181182
this.installRegistryListeners();
182-
this.initialize().catch(errors.onUnexpectedError);
183+
this.initialize(previousColorThemeSetting).catch(errors.onUnexpectedError);
183184
});
184185

185186
const codiconStyleSheet = createStyleSheet();
@@ -195,7 +196,7 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
195196
delayer.schedule();
196197
}
197198

198-
private async initialize(): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> {
199+
private async initialize(themePreviousSettingsId: string | undefined): Promise<[IWorkbenchColorTheme | null, IWorkbenchFileIconTheme | null, IWorkbenchProductIconTheme | null]> {
199200
const extDevLocs = this.environmentService.extensionDevelopmentLocationURI;
200201
const extDevLoc = extDevLocs && extDevLocs.length === 1 ? extDevLocs[0] : undefined; // in dev mode, switch to a theme provided by the extension under dev.
201202

@@ -246,34 +247,64 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
246247

247248

248249
this.migrateColorThemeSettings();
249-
await this.migrateAutoDetectColorScheme();
250250
const result = await Promise.all([initializeColorTheme(), initializeFileIconTheme(), initializeProductIconTheme()]);
251-
this.showNewDefaultThemeNotification();
251+
await this.showNewDefaultThemeNotification(themePreviousSettingsId);
252252
return result;
253253
}
254254

255255
private static readonly NEW_THEME_NOTIFICATION_KEY = 'workbench.newDefaultThemeNotification';
256256

257-
private showNewDefaultThemeNotification(): void {
258-
const newDefaultThemes = new Set([ThemeSettingDefaults.COLOR_THEME_DARK, ThemeSettingDefaults.COLOR_THEME_LIGHT]);
259-
if (newDefaultThemes.has(this.currentColorTheme.settingsId)) {
260-
return; // already using a new default theme
261-
}
257+
private async showNewDefaultThemeNotification(previousSettingsId: string | undefined): Promise<void> {
262258
if (this.storageService.getBoolean(WorkbenchThemeService.NEW_THEME_NOTIFICATION_KEY, StorageScope.APPLICATION)) {
263259
return; // already shown
264260
}
265-
266-
const handle = this.notificationService.prompt(
267-
Severity.Info,
268-
nls.localize('newDefaultTheme', "New default themes are available for VS Code."),
269-
[{
270-
label: nls.localize('tryNewTheme', "Try Them Out"),
271-
run: () => this.commandService.executeCommand('workbench.action.tryNewDefaultThemes')
272-
}]
273-
);
274-
this._register(Event.once(handle.onDidClose)(() => {
261+
if (!(await this.hostService.hadLastFocus()) || this.environmentService.isSessionsWindow) {
262+
return;
263+
}
264+
try {
265+
if (!this.settings.isDefaultColorTheme() || !previousSettingsId) {
266+
return;
267+
}
268+
previousSettingsId = migrateThemeSettingsId(previousSettingsId);
269+
if (!['Dark Modern', 'Light Modern'].includes(previousSettingsId)) {
270+
return;
271+
}
272+
if (![ThemeSettingDefaults.COLOR_THEME_DARK, ThemeSettingDefaults.COLOR_THEME_LIGHT].includes(this.settings.colorTheme)) {
273+
return;
274+
}
275+
} finally {
276+
// remeber to not show the dialog again
275277
this.storageService.store(WorkbenchThemeService.NEW_THEME_NOTIFICATION_KEY, true, StorageScope.APPLICATION, StorageTarget.USER);
276-
}));
278+
}
279+
280+
const keepTheme = await new Promise(resolve => {
281+
this.notificationService.prompt(
282+
Severity.Info,
283+
nls.localize({ key: 'themeUpdatedNotification', comment: ['{0} is the name of the new default theme'] }, "VS Code has a new default theme: '{0}'.", this.getColorTheme().label),
284+
[
285+
toAction({
286+
id: 'themeUpdated.tryItOut',
287+
label: nls.localize('tryNewTheme', "Keep It"),
288+
run: () => resolve(true)
289+
}),
290+
toAction({
291+
id: 'themeUpdated.noThanks',
292+
label: nls.localize('noThanks', "No Thanks"),
293+
run: () => resolve(false)
294+
})
295+
],
296+
{
297+
onCancel: () => resolve(false)
298+
}
299+
);
300+
});
301+
302+
if (!keepTheme) {
303+
const previousTheme = this.colorThemeRegistry.findThemeBySettingsId(previousSettingsId);
304+
if (previousTheme) {
305+
this.setColorTheme(previousTheme.id, 'auto');
306+
}
307+
}
277308
}
278309

279310
/**
@@ -305,29 +336,6 @@ export class WorkbenchThemeService extends Disposable implements IWorkbenchTheme
305336
}
306337
}
307338

308-
/**
309-
* For new users who haven't explicitly configured `window.autoDetectColorScheme`,
310-
* persist `true` so that auto-detect becomes the default going forward.
311-
*/
312-
private async migrateAutoDetectColorScheme(): Promise<void> {
313-
if (!this.storageService.isNew(StorageScope.APPLICATION)) {
314-
return;
315-
}
316-
317-
// Ensure that user data (including synced settings) has finished initializing
318-
// so we do not overwrite values that arrive via settings sync.
319-
await this.userDataInitializationService.whenInitializationFinished();
320-
321-
const inspection = this.configurationService.inspect<boolean>(ThemeSettings.DETECT_COLOR_SCHEME);
322-
323-
// Treat any of userValue, userLocalValue, or userRemoteValue as an explicit configuration.
324-
if (inspection.userValue === undefined
325-
&& inspection.userLocalValue === undefined
326-
&& inspection.userRemoteValue === undefined) {
327-
await this.configurationService.updateValue(ThemeSettings.DETECT_COLOR_SCHEME, true, ConfigurationTarget.USER);
328-
}
329-
}
330-
331339
private installConfigurationListener() {
332340
this._register(this.configurationService.onDidChangeConfiguration(e => {
333341
if (e.affectsConfiguration(ThemeSettings.COLOR_THEME)

src/vs/workbench/services/themes/common/themeConfiguration.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,19 +282,7 @@ const colorSchemeToPreferred = {
282282
};
283283

284284
export class ThemeConfiguration {
285-
constructor(private configurationService: IConfigurationService, private hostColorService: IHostColorSchemeService, private readonly isNewUser: boolean = false) {
286-
}
287-
288-
private shouldAutoDetectColorScheme(): boolean {
289-
const { value, userValue, userLocalValue, userRemoteValue } = this.configurationService.inspect<boolean>(ThemeSettings.DETECT_COLOR_SCHEME);
290-
if (value) {
291-
return true;
292-
}
293-
if (this.isNewUser) {
294-
const hasUserScopedValue = userValue !== undefined || userLocalValue !== undefined || userRemoteValue !== undefined;
295-
return !hasUserScopedValue;
296-
}
297-
return false;
285+
constructor(private configurationService: IConfigurationService, private hostColorService: IHostColorSchemeService) {
298286
}
299287

300288
public get colorTheme(): string {
@@ -348,14 +336,14 @@ export class ThemeConfiguration {
348336
if (this.configurationService.getValue(ThemeSettings.DETECT_HC) && this.hostColorService.highContrast) {
349337
return this.hostColorService.dark ? ColorScheme.HIGH_CONTRAST_DARK : ColorScheme.HIGH_CONTRAST_LIGHT;
350338
}
351-
if (this.shouldAutoDetectColorScheme()) {
339+
if (this.isDetectingColorScheme()) {
352340
return this.hostColorService.dark ? ColorScheme.DARK : ColorScheme.LIGHT;
353341
}
354342
return undefined;
355343
}
356344

357345
public isDetectingColorScheme(): boolean {
358-
return this.shouldAutoDetectColorScheme();
346+
return this.configurationService.getValue(ThemeSettings.DETECT_COLOR_SCHEME);
359347
}
360348

361349
public getColorThemeSettingId(): ThemeSettings {

src/vs/workbench/services/themes/test/common/workbenchThemeService.test.ts

Lines changed: 0 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@
66
import assert from 'assert';
77
import { migrateThemeSettingsId, ThemeSettingDefaults } from '../../common/workbenchThemeService.js';
88
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
9-
import { ThemeConfiguration } from '../../common/themeConfiguration.js';
10-
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
11-
import { IHostColorSchemeService } from '../../common/hostColorSchemeService.js';
12-
import { ColorScheme } from '../../../../../platform/theme/common/theme.js';
13-
import { Event } from '../../../../../base/common/event.js';
14-
import { IConfigurationOverrides, IConfigurationValue } from '../../../../../platform/configuration/common/configuration.js';
159

1610
suite('WorkbenchThemeService', () => {
1711

@@ -40,124 +34,4 @@ suite('WorkbenchThemeService', () => {
4034
);
4135
});
4236
});
43-
44-
suite('ThemeConfiguration', () => {
45-
46-
function createHostColorSchemeService(dark: boolean, highContrast: boolean = false): IHostColorSchemeService {
47-
return {
48-
_serviceBrand: undefined,
49-
dark,
50-
highContrast,
51-
onDidChangeColorScheme: Event.None,
52-
};
53-
}
54-
55-
/**
56-
* Creates a config service that separates the resolved value from the user value,
57-
* matching production behaviour where getValue() returns the schema default
58-
* while inspect().userValue is undefined when no explicit user value is set.
59-
*/
60-
function createConfigServiceWithDefaults(
61-
userConfig: Record<string, unknown>,
62-
defaults: Record<string, unknown>
63-
): TestConfigurationService {
64-
const configService = new TestConfigurationService(userConfig);
65-
const originalInspect = configService.inspect.bind(configService);
66-
configService.inspect = <T>(key: string, overrides?: IConfigurationOverrides): IConfigurationValue<T> => {
67-
if (Object.prototype.hasOwnProperty.call(userConfig, key)) {
68-
return originalInspect(key, overrides);
69-
}
70-
// No explicit user value: return the default as the resolved value
71-
const defaultVal = defaults[key] as T;
72-
return {
73-
value: defaultVal,
74-
defaultValue: defaultVal,
75-
userValue: undefined,
76-
userLocalValue: undefined,
77-
};
78-
};
79-
const originalGetValue = configService.getValue.bind(configService);
80-
configService.getValue = <T>(arg1?: string | IConfigurationOverrides, arg2?: IConfigurationOverrides): T => {
81-
const result = originalGetValue(arg1, arg2);
82-
if (result === undefined && typeof arg1 === 'string' && Object.prototype.hasOwnProperty.call(defaults, arg1)) {
83-
return defaults[arg1] as T;
84-
}
85-
return result as T;
86-
};
87-
return configService;
88-
}
89-
90-
test('new user with no explicit setting gets auto-detect on light OS', () => {
91-
const configService = new TestConfigurationService();
92-
const hostColor = createHostColorSchemeService(false);
93-
const config = new ThemeConfiguration(configService, hostColor, true);
94-
95-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.LIGHT);
96-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
97-
});
98-
99-
test('new user with no explicit setting gets auto-detect on dark OS', () => {
100-
const configService = new TestConfigurationService();
101-
const hostColor = createHostColorSchemeService(true);
102-
const config = new ThemeConfiguration(configService, hostColor, true);
103-
104-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.DARK);
105-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
106-
});
107-
108-
test('new user with no explicit setting and schema default false still gets auto-detect', () => {
109-
// Simulates production: getValue() returns false (schema default) but userValue is undefined
110-
const configService = createConfigServiceWithDefaults({}, { 'window.autoDetectColorScheme': false });
111-
const hostColor = createHostColorSchemeService(false);
112-
const config = new ThemeConfiguration(configService, hostColor, true);
113-
114-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.LIGHT);
115-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
116-
});
117-
118-
test('new user with explicit false does not get auto-detect', () => {
119-
const configService = new TestConfigurationService({ 'window.autoDetectColorScheme': false });
120-
const hostColor = createHostColorSchemeService(false);
121-
const config = new ThemeConfiguration(configService, hostColor, true);
122-
123-
assert.deepStrictEqual(config.getPreferredColorScheme(), undefined);
124-
assert.deepStrictEqual(config.isDetectingColorScheme(), false);
125-
});
126-
127-
test('existing user without explicit setting does not get auto-detect', () => {
128-
const configService = new TestConfigurationService();
129-
const hostColor = createHostColorSchemeService(false);
130-
const config = new ThemeConfiguration(configService, hostColor, false);
131-
132-
assert.deepStrictEqual(config.getPreferredColorScheme(), undefined);
133-
assert.deepStrictEqual(config.isDetectingColorScheme(), false);
134-
});
135-
136-
test('existing user with explicit true gets auto-detect', () => {
137-
const configService = new TestConfigurationService({ 'window.autoDetectColorScheme': true });
138-
const hostColor = createHostColorSchemeService(false);
139-
const config = new ThemeConfiguration(configService, hostColor, false);
140-
141-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.LIGHT);
142-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
143-
});
144-
145-
test('high contrast OS takes priority over auto-detect for new user', () => {
146-
const configService = new TestConfigurationService({ 'window.autoDetectHighContrast': true });
147-
const hostColor = createHostColorSchemeService(true, true);
148-
const config = new ThemeConfiguration(configService, hostColor, true);
149-
150-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.HIGH_CONTRAST_DARK);
151-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
152-
});
153-
154-
test('high contrast light OS takes priority over auto-detect for new user', () => {
155-
const configService = new TestConfigurationService({ 'window.autoDetectHighContrast': true });
156-
const hostColor = createHostColorSchemeService(false, true);
157-
const config = new ThemeConfiguration(configService, hostColor, true);
158-
159-
assert.deepStrictEqual(config.getPreferredColorScheme(), ColorScheme.HIGH_CONTRAST_LIGHT);
160-
assert.deepStrictEqual(config.isDetectingColorScheme(), true);
161-
});
162-
});
16337
});

0 commit comments

Comments
 (0)