@@ -18,7 +18,7 @@ import { Color } from 'vs/base/common/color';
18
18
import { ColorScheme , isHighContrast } from 'vs/platform/theme/common/theme' ;
19
19
import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema' ;
20
20
import { isCancellationError , onUnexpectedError } from 'vs/base/common/errors' ;
21
- import { IQuickInputButton , IQuickInputService , IQuickPickItem , QuickPickInput } from 'vs/platform/quickinput/common/quickInput' ;
21
+ import { IQuickInputButton , IQuickInputService , IQuickInputToggle , IQuickPick , IQuickPickItem , QuickPickInput } from 'vs/platform/quickinput/common/quickInput' ;
22
22
import { DEFAULT_PRODUCT_ICON_THEME_ID , ProductIconThemeData } from 'vs/workbench/services/themes/browser/productIconThemeData' ;
23
23
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite' ;
24
24
import { ViewContainerLocation } from 'vs/workbench/common/views' ;
@@ -45,11 +45,22 @@ import { isWeb } from 'vs/base/common/platform';
45
45
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
46
46
import { IHostService } from 'vs/workbench/services/host/browser/host' ;
47
47
import { mainWindow } from 'vs/base/browser/window' ;
48
+ import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences' ;
49
+ import { Toggle } from 'vs/base/browser/ui/toggle/toggle' ;
50
+ import { defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles' ;
51
+ import { DisposableStore } from 'vs/base/common/lifecycle' ;
52
+ import { COLOR_THEME_CONFIGURATION_SETTINGS_TAG } from 'vs/workbench/services/themes/common/themeConfiguration' ;
48
53
49
54
export const manageExtensionIcon = registerIcon ( 'theme-selection-manage-extension' , Codicon . gear , localize ( 'manageExtensionIcon' , 'Icon for the \'Manage\' action in the theme selection quick pick.' ) ) ;
50
55
51
56
type PickerResult = 'back' | 'selected' | 'cancelled' ;
52
57
58
+ enum ConfigureItem {
59
+ BROWSE_GALLERY = 'marketplace' ,
60
+ EXTENSIONS_VIEW = 'extensions' ,
61
+ CUSTOM_TOP_ENTRY = 'customTopEntry'
62
+ }
63
+
53
64
class MarketplaceThemesPicker {
54
65
private readonly _installedExtensions : Promise < Set < string > > ;
55
66
private readonly _marketplaceExtensions : Set < string > = new Set ( ) ;
@@ -273,13 +284,20 @@ class MarketplaceThemesPicker {
273
284
}
274
285
}
275
286
287
+ interface InstalledThemesPickerOptions {
288
+ readonly installMessage : string ;
289
+ readonly browseMessage ?: string ;
290
+ readonly placeholderMessage : string ;
291
+ readonly marketplaceTag : string ;
292
+ readonly title ?: string ;
293
+ readonly description ?: string ;
294
+ readonly toggles ?: IQuickInputToggle [ ] ;
295
+ readonly onToggle ?: ( toggle : IQuickInputToggle , quickInput : IQuickPick < ThemeItem > ) => Promise < void > ;
296
+ }
276
297
277
298
class InstalledThemesPicker {
278
299
constructor (
279
- private readonly installMessage : string ,
280
- private readonly browseMessage : string | undefined ,
281
- private readonly placeholderMessage : string ,
282
- private readonly marketplaceTag : string ,
300
+ private readonly options : InstalledThemesPickerOptions ,
283
301
private readonly setTheme : ( theme : IWorkbenchTheme | undefined , settingsTarget : ThemeSettingTarget ) => Promise < any > ,
284
302
private readonly getMarketplaceColorThemes : ( publisher : string , name : string , version : string ) => Promise < IWorkbenchTheme [ ] > ,
285
303
@IQuickInputService private readonly quickInputService : IQuickInputService ,
@@ -291,13 +309,14 @@ class InstalledThemesPicker {
291
309
}
292
310
293
311
public async openQuickPick ( picks : QuickPickInput < ThemeItem > [ ] , currentTheme : IWorkbenchTheme ) {
312
+
294
313
let marketplaceThemePicker : MarketplaceThemesPicker | undefined ;
295
314
if ( this . extensionGalleryService . isEnabled ( ) ) {
296
- if ( this . extensionResourceLoaderService . supportsExtensionGalleryResources && this . browseMessage ) {
297
- marketplaceThemePicker = this . instantiationService . createInstance ( MarketplaceThemesPicker , this . getMarketplaceColorThemes . bind ( this ) , this . marketplaceTag ) ;
298
- picks = [ ... configurationEntries ( this . browseMessage ) , ...picks ] ;
315
+ if ( this . extensionResourceLoaderService . supportsExtensionGalleryResources && this . options . browseMessage ) {
316
+ marketplaceThemePicker = this . instantiationService . createInstance ( MarketplaceThemesPicker , this . getMarketplaceColorThemes . bind ( this ) , this . options . marketplaceTag ) ;
317
+ picks = [ configurationEntry ( this . options . browseMessage , ConfigureItem . BROWSE_GALLERY ) , ...picks ] ;
299
318
} else {
300
- picks = [ ...picks , ... configurationEntries ( this . installMessage ) ] ;
319
+ picks = [ ...picks , { type : 'separator' } , configurationEntry ( this . options . installMessage , ConfigureItem . EXTENSIONS_VIEW ) ] ;
301
320
}
302
321
}
303
322
@@ -322,25 +341,34 @@ class InstalledThemesPicker {
322
341
const pickInstalledThemes = ( activeItemId : string | undefined ) => {
323
342
return new Promise < void > ( ( s , _ ) => {
324
343
let isCompleted = false ;
344
+ const disposables = new DisposableStore ( ) ;
325
345
326
346
const autoFocusIndex = picks . findIndex ( p => isItem ( p ) && p . id === activeItemId ) ;
327
347
const quickpick = this . quickInputService . createQuickPick < ThemeItem > ( ) ;
328
348
quickpick . items = picks ;
329
- quickpick . placeholder = this . placeholderMessage ;
349
+ quickpick . title = this . options . title ;
350
+ quickpick . description = this . options . description ;
351
+ quickpick . placeholder = this . options . placeholderMessage ;
330
352
quickpick . activeItems = [ picks [ autoFocusIndex ] as ThemeItem ] ;
331
353
quickpick . canSelectMany = false ;
354
+ quickpick . toggles = this . options . toggles ;
355
+ quickpick . toggles ?. forEach ( toggle => {
356
+ toggle . onChange ( ( ) => this . options . onToggle ?.( toggle , quickpick ) , undefined , disposables ) ;
357
+ } ) ;
332
358
quickpick . matchOnDescription = true ;
333
359
quickpick . onDidAccept ( async _ => {
334
360
isCompleted = true ;
335
361
const theme = quickpick . selectedItems [ 0 ] ;
336
- if ( ! theme || typeof theme . id === 'undefined' ) { // 'pick in marketplace' entry
337
- if ( marketplaceThemePicker ) {
338
- const res = await marketplaceThemePicker . openQuickPick ( quickpick . value , currentTheme , selectTheme ) ;
339
- if ( res === 'back' ) {
340
- await pickInstalledThemes ( undefined ) ;
362
+ if ( ! theme || theme . configureItem ) { // 'pick in marketplace' entry
363
+ if ( ! theme || theme . configureItem === ConfigureItem . EXTENSIONS_VIEW ) {
364
+ openExtensionViewlet ( this . paneCompositeService , `${ this . options . marketplaceTag } ${ quickpick . value } ` ) ;
365
+ } else if ( theme . configureItem === ConfigureItem . BROWSE_GALLERY ) {
366
+ if ( marketplaceThemePicker ) {
367
+ const res = await marketplaceThemePicker . openQuickPick ( quickpick . value , currentTheme , selectTheme ) ;
368
+ if ( res === 'back' ) {
369
+ await pickInstalledThemes ( undefined ) ;
370
+ }
341
371
}
342
- } else {
343
- openExtensionViewlet ( this . paneCompositeService , `${ this . marketplaceTag } ${ quickpick . value } ` ) ;
344
372
}
345
373
} else {
346
374
selectTheme ( theme . theme , true ) ;
@@ -356,14 +384,15 @@ class InstalledThemesPicker {
356
384
s ( ) ;
357
385
}
358
386
quickpick . dispose ( ) ;
387
+ disposables . dispose ( ) ;
359
388
} ) ;
360
389
quickpick . onDidTriggerItemButton ( e => {
361
390
if ( isItem ( e . item ) ) {
362
391
const extensionId = e . item . theme ?. extensionData ?. extensionId ;
363
392
if ( extensionId ) {
364
393
openExtensionViewlet ( this . paneCompositeService , `@id:${ extensionId } ` ) ;
365
394
} else {
366
- openExtensionViewlet ( this . paneCompositeService , `${ this . marketplaceTag } ${ quickpick . value } ` ) ;
395
+ openExtensionViewlet ( this . paneCompositeService , `${ this . options . marketplaceTag } ${ quickpick . value } ` ) ;
367
396
}
368
397
}
369
398
} ) ;
@@ -394,28 +423,80 @@ registerAction2(class extends Action2 {
394
423
} ) ;
395
424
}
396
425
426
+ private getTitle ( colorScheme : ColorScheme | undefined ) : string | undefined {
427
+ switch ( colorScheme ) {
428
+ case ColorScheme . DARK : return localize ( 'themes.selectTheme.darkScheme' , "Select Color Theme for Dark Mode" ) ;
429
+ case ColorScheme . LIGHT : return localize ( 'themes.selectTheme.lightScheme' , "Select Color Theme for Light Mode" ) ;
430
+ case ColorScheme . HIGH_CONTRAST_DARK : return localize ( 'themes.selectTheme.darkHC' , "Select Color Theme for Dark High Contrast Mode" ) ;
431
+ case ColorScheme . HIGH_CONTRAST_LIGHT : return localize ( 'themes.selectTheme.lightHC' , "Select Color Theme for Light High Contrast Mode" ) ;
432
+ default :
433
+ return undefined ;
434
+ }
435
+ }
436
+
397
437
override async run ( accessor : ServicesAccessor ) {
398
438
const themeService = accessor . get ( IWorkbenchThemeService ) ;
439
+ const preferencesService = accessor . get ( IPreferencesService ) ;
399
440
400
- const installMessage = localize ( 'installColorThemes' , "Install Additional Color Themes..." ) ;
401
- const browseMessage = '$(plus) ' + localize ( 'browseColorThemes' , "Browse Additional Color Themes..." ) ;
402
- const placeholderMessage = localize ( 'themes.selectTheme' , "Select Color Theme (Up/Down Keys to Preview)" ) ;
403
- const marketplaceTag = 'category:themes' ;
441
+ const preferredColorScheme = themeService . getPreferredColorScheme ( ) ;
442
+
443
+ let modeConfigureToggle ;
444
+ if ( preferredColorScheme ) {
445
+ modeConfigureToggle = new Toggle ( {
446
+ title : 'Automatic Mode Switching is Enabled. Click to configure.' ,
447
+ icon : Codicon . colorMode ,
448
+ isChecked : false ,
449
+ ...defaultToggleStyles
450
+ } ) ;
451
+ } else {
452
+ modeConfigureToggle = new Toggle ( {
453
+ title : 'Click to configure automatic mode switching.' ,
454
+ icon : Codicon . gear ,
455
+ isChecked : false ,
456
+ ...defaultToggleStyles
457
+ } ) ;
458
+ }
459
+
460
+ const options = {
461
+ installMessage : localize ( 'installColorThemes' , "Install Additional Color Themes..." ) ,
462
+ browseMessage : '$(plus) ' + localize ( 'browseColorThemes' , "Browse Additional Color Themes..." ) ,
463
+ placeholderMessage : this . getTitle ( preferredColorScheme ) ?? 'Select Color Theme (Mode Switching Disabled)' ,
464
+ marketplaceTag : 'category:themes' ,
465
+ toggles : [ modeConfigureToggle ] ,
466
+ onToggle : async ( toggle , picker ) => {
467
+ picker . hide ( ) ;
468
+ await preferencesService . openSettings ( { query : `@tag:${ COLOR_THEME_CONFIGURATION_SETTINGS_TAG } ` } ) ;
469
+ }
470
+ } satisfies InstalledThemesPickerOptions ;
404
471
const setTheme = ( theme : IWorkbenchTheme | undefined , settingsTarget : ThemeSettingTarget ) => themeService . setColorTheme ( theme as IWorkbenchColorTheme , settingsTarget ) ;
405
472
const getMarketplaceColorThemes = ( publisher : string , name : string , version : string ) => themeService . getMarketplaceColorThemes ( publisher , name , version ) ;
406
473
407
474
const instantiationService = accessor . get ( IInstantiationService ) ;
408
- const picker = instantiationService . createInstance ( InstalledThemesPicker , installMessage , browseMessage , placeholderMessage , marketplaceTag , setTheme , getMarketplaceColorThemes ) ;
475
+ const picker = instantiationService . createInstance ( InstalledThemesPicker , options , setTheme , getMarketplaceColorThemes ) ;
409
476
410
477
const themes = await themeService . getColorThemes ( ) ;
411
478
const currentTheme = themeService . getColorTheme ( ) ;
412
479
413
- const picks : QuickPickInput < ThemeItem > [ ] = [
414
- ...toEntries ( themes . filter ( t => t . type === ColorScheme . LIGHT ) , localize ( 'themes.category.light' , "light themes" ) ) ,
415
- ...toEntries ( themes . filter ( t => t . type === ColorScheme . DARK ) , localize ( 'themes.category.dark' , "dark themes" ) ) ,
416
- ...toEntries ( themes . filter ( t => isHighContrast ( t . type ) ) , localize ( 'themes.category.hc' , "high contrast themes" ) ) ,
417
- ] ;
480
+ const lightEntries = toEntries ( themes . filter ( t => t . type === ColorScheme . LIGHT ) , localize ( 'themes.category.light' , "light themes" ) ) ;
481
+ const darkEntries = toEntries ( themes . filter ( t => t . type === ColorScheme . DARK ) , localize ( 'themes.category.dark' , "dark themes" ) ) ;
482
+ const hcEntries = toEntries ( themes . filter ( t => isHighContrast ( t . type ) ) , localize ( 'themes.category.hc' , "high contrast themes" ) ) ;
483
+
484
+ let picks ;
485
+ switch ( preferredColorScheme ) {
486
+ case ColorScheme . DARK :
487
+ picks = [ ...darkEntries , ...lightEntries , ...hcEntries ] ;
488
+ break ;
489
+ case ColorScheme . HIGH_CONTRAST_DARK :
490
+ case ColorScheme . HIGH_CONTRAST_LIGHT :
491
+ picks = [ ...hcEntries , ...lightEntries , ...darkEntries ] ;
492
+ break ;
493
+ case ColorScheme . LIGHT :
494
+ default :
495
+ picks = [ ...lightEntries , ...darkEntries , ...hcEntries ] ;
496
+ break ;
497
+ }
418
498
await picker . openQuickPick ( picks , currentTheme ) ;
499
+
419
500
}
420
501
} ) ;
421
502
@@ -435,14 +516,16 @@ registerAction2(class extends Action2 {
435
516
override async run ( accessor : ServicesAccessor ) {
436
517
const themeService = accessor . get ( IWorkbenchThemeService ) ;
437
518
438
- const installMessage = localize ( 'installIconThemes' , "Install Additional File Icon Themes..." ) ;
439
- const placeholderMessage = localize ( 'themes.selectIconTheme' , "Select File Icon Theme (Up/Down Keys to Preview)" ) ;
440
- const marketplaceTag = 'tag:icon-theme' ;
519
+ const options = {
520
+ installMessage : localize ( 'installIconThemes' , "Install Additional File Icon Themes..." ) ,
521
+ placeholderMessage : localize ( 'themes.selectIconTheme' , "Select File Icon Theme (Up/Down Keys to Preview)" ) ,
522
+ marketplaceTag : 'tag:icon-theme'
523
+ } ;
441
524
const setTheme = ( theme : IWorkbenchTheme | undefined , settingsTarget : ThemeSettingTarget ) => themeService . setFileIconTheme ( theme as IWorkbenchFileIconTheme , settingsTarget ) ;
442
525
const getMarketplaceColorThemes = ( publisher : string , name : string , version : string ) => themeService . getMarketplaceFileIconThemes ( publisher , name , version ) ;
443
526
444
527
const instantiationService = accessor . get ( IInstantiationService ) ;
445
- const picker = instantiationService . createInstance ( InstalledThemesPicker , installMessage , undefined , placeholderMessage , marketplaceTag , setTheme , getMarketplaceColorThemes ) ;
528
+ const picker = instantiationService . createInstance ( InstalledThemesPicker , options , setTheme , getMarketplaceColorThemes ) ;
446
529
447
530
const picks : QuickPickInput < ThemeItem > [ ] = [
448
531
{ type : 'separator' , label : localize ( 'fileIconThemeCategory' , 'file icon themes' ) } ,
@@ -470,15 +553,17 @@ registerAction2(class extends Action2 {
470
553
override async run ( accessor : ServicesAccessor ) {
471
554
const themeService = accessor . get ( IWorkbenchThemeService ) ;
472
555
473
- const installMessage = localize ( 'installProductIconThemes' , "Install Additional Product Icon Themes..." ) ;
474
- const browseMessage = '$(plus) ' + localize ( 'browseProductIconThemes' , "Browse Additional Product Icon Themes..." ) ;
475
- const placeholderMessage = localize ( 'themes.selectProductIconTheme' , "Select Product Icon Theme (Up/Down Keys to Preview)" ) ;
476
- const marketplaceTag = 'tag:product-icon-theme' ;
556
+ const options = {
557
+ installMessage : localize ( 'installProductIconThemes' , "Install Additional Product Icon Themes..." ) ,
558
+ browseMessage : '$(plus) ' + localize ( 'browseProductIconThemes' , "Browse Additional Product Icon Themes..." ) ,
559
+ placeholderMessage : localize ( 'themes.selectProductIconTheme' , "Select Product Icon Theme (Up/Down Keys to Preview)" ) ,
560
+ marketplaceTag : 'tag:product-icon-theme'
561
+ } ;
477
562
const setTheme = ( theme : IWorkbenchTheme | undefined , settingsTarget : ThemeSettingTarget ) => themeService . setProductIconTheme ( theme as IWorkbenchProductIconTheme , settingsTarget ) ;
478
563
const getMarketplaceColorThemes = ( publisher : string , name : string , version : string ) => themeService . getMarketplaceProductIconThemes ( publisher , name , version ) ;
479
564
480
565
const instantiationService = accessor . get ( IInstantiationService ) ;
481
- const picker = instantiationService . createInstance ( InstalledThemesPicker , installMessage , browseMessage , placeholderMessage , marketplaceTag , setTheme , getMarketplaceColorThemes ) ;
566
+ const picker = instantiationService . createInstance ( InstalledThemesPicker , options , setTheme , getMarketplaceColorThemes ) ;
482
567
483
568
const picks : QuickPickInput < ThemeItem > [ ] = [
484
569
{ type : 'separator' , label : localize ( 'productIconThemeCategory' , 'product icon themes' ) } ,
@@ -510,19 +595,14 @@ function findBuiltInThemes(themes: IWorkbenchColorTheme[], extension: { publishe
510
595
return themes . filter ( ( { extensionData } ) => extensionData && extensionData . extensionIsBuiltin && equalsIgnoreCase ( extensionData . extensionPublisher , extension . publisher ) && equalsIgnoreCase ( extensionData . extensionName , extension . name ) ) ;
511
596
}
512
597
513
- function configurationEntries ( label : string ) : QuickPickInput < ThemeItem > [ ] {
514
- return [
515
- {
516
- type : 'separator'
517
- } ,
518
- {
519
- id : undefined ,
520
- label : label ,
521
- alwaysShow : true ,
522
- buttons : [ configureButton ]
523
- }
524
- ] ;
525
-
598
+ function configurationEntry ( label : string , configureItem : ConfigureItem ) : QuickPickInput < ThemeItem > {
599
+ return {
600
+ id : undefined ,
601
+ label : label ,
602
+ alwaysShow : true ,
603
+ buttons : [ configureButton ] ,
604
+ configureItem : configureItem
605
+ } ;
526
606
}
527
607
528
608
function openExtensionViewlet ( paneCompositeService : IPaneCompositePartService , query : string ) {
@@ -540,6 +620,7 @@ interface ThemeItem extends IQuickPickItem {
540
620
readonly label : string ;
541
621
readonly description ?: string ;
542
622
readonly alwaysShow ?: boolean ;
623
+ readonly configureItem ?: ConfigureItem ;
543
624
}
544
625
545
626
function isItem ( i : QuickPickInput < ThemeItem > ) : i is ThemeItem {
0 commit comments