Change:
Quick pickers are itself disposables, as well as the events they provide. Noticed a few leaks around picker instances and/or events: //cc
There's already a that is called when the picker hides. Isn't that good enough?
see the various events (those beginning with ) and how they are not getting disposed.
But they are going to an emitter that is owned by the quickpick. Disposing the quickpick is also disposed all its emitters. There's no real leak here.
my understanding is that an that gets disposed (when you dispose the quick picker) does not dispose the -functions it had returned, i.e. this code is not running: But maybe or can also chime in.
Yes - an emitter doesn't dispose its listeners. you basically rely on GC'ing here and I am uncertain if that actually works (e.g some disposables are tracked centrally for later blaming). I understand your motives but we generally dispose all the things

Discussion:
public openQuickPick(value: string, currentTheme: IWorkbenchTheme | undefined, selectTheme: (theme: IWorkbenchTheme | undefined, applyTheme: boolean) => void): Promise<PickerResult> { let result: PickerResult | undefined = undefined; <ins> const disposables = new DisposableStore(); </ins> return new Promise<PickerResult>((s, _) => { <del> const quickpick = this.quickInputService.createQuickPick<ThemeItem>(); </del> <ins> const quickpick = disposables.add(this.quickInputService.createQuickPick<ThemeItem>()); </ins> quickpick.items = []; quickpick.sortByLabel = false; quickpick.matchOnDescription = true;

quickpick.title = 'Marketplace Themes'; quickpick.placeholder = localize('themes.selectMarketplaceTheme', "Type to Search More. Select to Install. Up/Down Keys to Preview"); quickpick.canSelectMany = false; <del> quickpick.onDidChangeValue(() => this.trigger(quickpick.value)); quickpick.onDidAccept(async _ => { </del> <ins> disposables.add(quickpick.onDidChangeValue(() => this.trigger(quickpick.value))); disposables.add(quickpick.onDidAccept(async _ => { </ins> const themeItem = quickpick.selectedItems[0]; if (themeItem?.galleryExtension) { result = 'selected';

selectTheme(currentTheme, true); } } <del> }); </del> <ins> })); </ins> <del> quickpick.onDidTriggerItemButton(e => { </del> <ins> disposables.add(quickpick.onDidTriggerItemButton(e => { </ins> if (isItem(e.item)) { const extensionId = e.item.theme?.extensionData?.extensionId; if (extensionId) {

openExtensionViewlet(this.paneCompositeService, `${this.marketplaceQuery} ${quickpick.value}`); } } <del> }); quickpick.onDidChangeActive(themes => { </del> <ins> })); disposables.add(quickpick.onDidChangeActive(themes => { </ins> if (result === undefined) { selectTheme(themes[0]?.theme, false); } <del> }); </del> <ins> })); </ins> <del> quickpick.onDidHide(() => { </del> <ins> disposables.add(quickpick.onDidHide(() => { </ins> if (result === undefined) { selectTheme(currentTheme, true); result = 'cancelled'; } <del> quickpick.dispose(); </del> s(result); <del> }); </del> <ins> })); </ins> <del> quickpick.onDidTriggerButton(e => { </del> <ins> disposables.add(quickpick.onDidTriggerButton(e => { </ins> if (e === this.quickInputService.backButton) { result = 'back'; quickpick.hide(); } <del> }); </del> <ins> })); </ins> <del> this.onDidChange(() => { </del> <ins> disposables.add(this.onDidChange(() => { </ins> let items = this.themes; if (this._searchOngoing) { items = items.concat({ label: '$(sync~spin) Searching for themes...', id: undefined, alwaysShow: true });

if (newActiveItem) { quickpick.activeItems = [newActiveItem as ThemeItem]; } <del> }); </del> <ins> })); </ins> this.trigger(value); quickpick.show(); <ins> }).finally(() => { disposables.dispose(); </ins> }); }

}; const pickInstalledThemes = (activeItemId: string | undefined) => { <ins> const disposables = new DisposableStore(); </ins> return new Promise<void>((s, _) => { let isCompleted = false; <del> const disposables = new DisposableStore(); </del> const autoFocusIndex = picks.findIndex(p => isItem(p) && p.id === activeItemId); <del> const quickpick = this.quickInputService.createQuickPick<ThemeItem>({ useSeparators: true }); </del> <ins> const quickpick = disposables.add(this.quickInputService.createQuickPick<ThemeItem>({ useSeparators: true })); </ins> quickpick.items = picks; quickpick.title = this.options.title; quickpick.description = this.options.description;

quickpick.canSelectMany = false; quickpick.toggles = this.options.toggles; quickpick.toggles?.forEach(toggle => { <del> toggle.onChange(() => this.options.onToggle?.(toggle, quickpick), undefined, disposables); </del> <ins> disposables.add(toggle.onChange(() => this.options.onToggle?.(toggle, quickpick))); </ins> }); quickpick.matchOnDescription = true; <del> quickpick.onDidAccept(async _ => { </del> <ins> disposables.add(quickpick.onDidAccept(async _ => { </ins> isCompleted = true; const theme = quickpick.selectedItems[0]; if (!theme || theme.configureItem) { // 'pick in marketplace' entry

quickpick.hide(); s(); <del> }); quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false)); quickpick.onDidHide(() => { </del> <ins> })); disposables.add(quickpick.onDidChangeActive(themes => selectTheme(themes[0]?.theme, false))); disposables.add(quickpick.onDidHide(() => { </ins> if (!isCompleted) { selectTheme(currentTheme, true); s(); } quickpick.dispose(); <del> disposables.dispose(); }); quickpick.onDidTriggerItemButton(e => { </del> <ins> })); disposables.add(quickpick.onDidTriggerItemButton(e => { </ins> if (isItem(e.item)) { const extensionId = e.item.theme?.extensionData?.extensionId; if (extensionId) {

openExtensionViewlet(this.paneCompositeService, `${this.options.marketplaceTag} ${quickpick.value}`); } } <del> }); </del> <ins> })); </ins> quickpick.show(); <ins> }).finally(() => { disposables.dispose(); </ins> }); }; await pickInstalledThemes(currentTheme.id);