From a5570d196115db6cedfd83ffd8fc807c2bc36ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 10 Aug 2020 10:55:07 +0200 Subject: [PATCH] introduce scm view service --- src/vs/workbench/contrib/scm/browser/menus.ts | 43 +++--- .../contrib/scm/browser/scm.contribution.ts | 4 +- .../contrib/scm/browser/scmViewPane.ts | 124 +++++++++--------- src/vs/workbench/contrib/scm/common/scm.ts | 14 ++ .../contrib/scm/common/scmViewService.ts | 82 ++++++++++++ 5 files changed, 178 insertions(+), 89 deletions(-) create mode 100644 src/vs/workbench/contrib/scm/common/scmViewService.ts diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index 500f7fa585a2..68f93b719383 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -10,9 +10,9 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { equals } from 'vs/base/common/arrays'; -import { ISplice, ISequence } from 'vs/base/common/sequence'; +import { ISplice } from 'vs/base/common/sequence'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -237,44 +237,37 @@ export class SCMMenus { readonly titleMenu: SCMTitleMenu; private readonly disposables = new DisposableStore(); - private readonly entries: { repository: ISCMRepository, dispose: () => void }[] = []; - private readonly menus = new Map(); + private readonly menus = new Map void }>(); constructor( - repositories: ISequence, + @ISCMService scmService: ISCMService, @IInstantiationService private instantiationService: IInstantiationService ) { this.titleMenu = instantiationService.createInstance(SCMTitleMenu); + scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + } - repositories.onDidSplice(this.onDidSplice, this, this.disposables); - this.onDidSplice({ start: 0, deleteCount: 0, toInsert: repositories.elements }); + private onDidRemoveRepository(repository: ISCMRepository): void { + const menus = this.menus.get(repository.provider); + menus?.dispose(); + this.menus.delete(repository.provider); } getRepositoryMenus(provider: ISCMProvider): SCMRepositoryMenus { - if (!this.menus.has(provider)) { - throw new Error('SCM Repository menu not found'); - } + let result = this.menus.get(provider); - return this.menus.get(provider)!; - } - - private onDidSplice({ start, deleteCount, toInsert }: ISplice): void { - const entriesToInsert = toInsert.map(repository => { - const menus = this.instantiationService.createInstance(SCMRepositoryMenus, repository.provider); + if (!result) { + const menus = this.instantiationService.createInstance(SCMRepositoryMenus, provider); const dispose = () => { menus.dispose(); - this.menus.delete(repository.provider); + this.menus.delete(provider); }; - this.menus.set(repository.provider, menus); - return { repository, dispose }; - }); - - const deletedEntries = this.entries.splice(start, deleteCount, ...entriesToInsert); - - for (const entry of deletedEntries) { - entry.dispose(); + result = { menus, dispose }; + this.menus.set(provider, result); } + + return result.menus; } dispose(): void { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index b0cda5d05adb..f522e31c5a21 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; -import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { SCMStatusController } from './activity'; @@ -24,6 +24,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane'; +import { SCMViewService } from 'vs/workbench/contrib/scm/common/scmViewService'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -225,3 +226,4 @@ MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { }); registerSingleton(ISCMService, SCMService); +registerSingleton(ISCMViewService, SCMViewService); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index faf74c036130..784ab9c1500d 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -6,11 +6,11 @@ import 'vs/css!./media/scm'; import { Event, Emitter } from 'vs/base/common/event'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; -import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { append, $, addClass, toggleClass, removeClass, Dimension } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMService, ISCMRepository, ISCMInput, IInputValidation } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMService, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -31,7 +31,7 @@ import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } import { disposableTimeout, ThrottledDelayer } from 'vs/base/common/async'; import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { ResourceTree, IResourceNode } from 'vs/base/common/resourceTree'; -import { ISequence, ISplice, SimpleSequence } from 'vs/base/common/sequence'; +import { ISplice } from 'vs/base/common/sequence'; import { ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; import { Iterable } from 'vs/base/common/iterator'; import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; @@ -803,13 +803,13 @@ interface IGroupItem { readonly element: ISCMResourceGroup; readonly resources: ISCMResource[]; readonly tree: ResourceTree; - readonly disposable: IDisposable; + dispose(): void; } interface IRepositoryItem { readonly element: ISCMRepository; readonly groupItems: IGroupItem[]; - readonly disposable: IDisposable; + dispose(): void; } function isRepositoryItem(item: IRepositoryItem | IGroupItem): item is IRepositoryItem { @@ -848,7 +848,7 @@ class ViewModel { set mode(mode: ViewModelMode) { this._mode = mode; - for (const item of this.items) { + for (const [, item] of this.items) { for (const groupItem of item.groupItems) { groupItem.tree.clear(); @@ -872,7 +872,7 @@ class ViewModel { } } - private items: IRepositoryItem[] = []; + private items = new Map(); private visibilityDisposables = new DisposableStore(); private scrollTop: number | undefined; private alwaysShowRepositories = false; @@ -882,7 +882,6 @@ class ViewModel { private disposables = new DisposableStore(); constructor( - readonly repositories: ISequence, private tree: WorkbenchCompressibleObjectTree, private menus: SCMMenus, private inputRenderer: InputRenderer, @@ -890,6 +889,7 @@ class ViewModel { private _sortKey: ViewModelSortKey, @IEditorService protected editorService: IEditorService, @IConfigurationService protected configurationService: IConfigurationService, + @ISCMViewService private scmViewService: ISCMViewService ) { this.onDidChangeRepositoryCollapseState = Event.any( this._onDidChangeRepositoryCollapseState.event, @@ -907,26 +907,27 @@ class ViewModel { } } - private _onDidSpliceRepositories({ start, deleteCount, toInsert }: ISplice): void { - const itemsToInsert = toInsert.map(repository => { + private _onDidChangeVisibleRepositories({ added, removed }: ISCMViewVisibleRepositoryChangeEvent): void { + for (const repository of added) { const disposable = combinedDisposable( repository.provider.groups.onDidSplice(splice => this._onDidSpliceGroups(item, splice)), repository.input.onDidChangeVisibility(() => this.refresh(item)) ); const groupItems = repository.provider.groups.elements.map(group => this.createGroupItem(group)); - const item: IRepositoryItem = { element: repository, groupItems, disposable }; + const item: IRepositoryItem = { + element: repository, groupItems, dispose() { + dispose(this.groupItems); + disposable.dispose(); + } + }; - return item; - }); + this.items.set(repository, item); + } - const itemsToDispose = this.items.splice(start, deleteCount, ...itemsToInsert); - - for (const item of itemsToDispose) { - for (const groupItem of item.groupItems) { - groupItem.disposable.dispose(); - } - - item.disposable.dispose(); + for (const repository of removed) { + const item = this.items.get(repository)!; + item.dispose(); + this.items.delete(repository); } this.refresh(); @@ -937,7 +938,7 @@ class ViewModel { const itemsToDispose = item.groupItems.splice(start, deleteCount, ...itemsToInsert); for (const item of itemsToDispose) { - item.disposable.dispose(); + item.dispose(); } this.refresh(); @@ -951,7 +952,7 @@ class ViewModel { group.onDidSplice(splice => this._onDidSpliceGroup(item, splice)) ); - const item: IGroupItem = { element: group, resources, tree, disposable }; + const item: IGroupItem = { element: group, resources, tree, dispose() { disposable.dispose(); } }; if (this._mode === ViewModelMode.Tree) { for (const resource of resources) { @@ -987,8 +988,8 @@ class ViewModel { setVisible(visible: boolean): void { if (visible) { this.visibilityDisposables = new DisposableStore(); - this.repositories.onDidSplice(this._onDidSpliceRepositories, this, this.visibilityDisposables); - this._onDidSpliceRepositories({ start: 0, deleteCount: 0, toInsert: this.repositories.elements }); + this.scmViewService.onDidChangeVisibleRepositories(this._onDidChangeVisibleRepositories, this, this.visibilityDisposables); + this._onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); this.repositoryCollapseStates = undefined; if (typeof this.scrollTop === 'number') { @@ -999,16 +1000,16 @@ class ViewModel { this.editorService.onDidActiveEditorChange(this.onDidActiveEditorChange, this, this.visibilityDisposables); this.onDidActiveEditorChange(); } else { - if (this.items.length > 1) { + if (this.items.size > 1) { this.repositoryCollapseStates = new Map(); - for (const item of this.items) { + for (const [, item] of this.items) { this.repositoryCollapseStates.set(item.element, this.tree.isCollapsed(item.element)); } } this.visibilityDisposables.dispose(); - this._onDidSpliceRepositories({ start: 0, deleteCount: this.items.length, toInsert: [] }); + this._onDidChangeVisibleRepositories({ added: Iterable.empty(), removed: [...this.items.keys()] }); this.scrollTop = this.tree.scrollTop; } @@ -1019,12 +1020,14 @@ class ViewModel { private refresh(item?: IRepositoryItem | IGroupItem): void { const focusedInput = this.inputRenderer.getFocusedInput(); - if (!this.alwaysShowRepositories && (this.items.length === 1 && (!item || isRepositoryItem(item)))) { - this.tree.setChildren(null, this.render(this.items[0]).children); + if (!this.alwaysShowRepositories && (this.items.size === 1 && (!item || isRepositoryItem(item)))) { + const item = Iterable.first(this.items.values())!; + this.tree.setChildren(null, this.render(item).children); } else if (item) { this.tree.setChildren(item.element, this.render(item).children); } else { - this.tree.setChildren(null, this.items.map(item => this.render(item))); + const items = this.scmViewService.visibleRepositories.map(r => this.items.get(r)!); + this.tree.setChildren(null, items.map(item => this.render(item))); } if (focusedInput) { @@ -1043,7 +1046,7 @@ class ViewModel { const children: ICompressedTreeElement[] = []; const hasSomeChanges = item.groupItems.some(item => item.element.elements.length > 0); - if (this.items.length === 1 || hasSomeChanges) { + if (this.items.size === 1 || hasSomeChanges) { if (item.element.input.visible) { children.push({ element: item.element.input, incompressible: true, collapsible: false }); } @@ -1085,8 +1088,9 @@ class ViewModel { return; } - for (let i = 0; i < this.items.length; i++) { - const item = this.items[i]; + for (const repository of this.scmViewService.visibleRepositories) { + const item = this.items.get(repository)!; + // go backwards from last group for (let j = item.groupItems.length - 1; j >= 0; j--) { const groupItem = item.groupItems[j]; @@ -1105,7 +1109,7 @@ class ViewModel { } focus() { - for (const repository of this.repositories.elements) { + for (const repository of this.scmViewService.visibleRepositories) { const widget = this.inputRenderer.getRenderedInputWidget(repository.input); if (widget) { @@ -1118,20 +1122,20 @@ class ViewModel { } getViewActions(): IAction[] { - if (this.repositories.elements.length === 0) { + if (this.scmViewService.visibleRepositories.length === 0) { return this.menus.titleMenu.actions; } - if (this.alwaysShowRepositories || this.repositories.elements.length !== 1) { + if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) { return []; } - const menus = this.menus.getRepositoryMenus(this.repositories.elements[0].provider); + const menus = this.menus.getRepositoryMenus(this.scmViewService.visibleRepositories[0].provider); return menus.titleMenu.actions; } getViewSecondaryActions(): IAction[] { - if (this.repositories.elements.length === 0) { + if (this.scmViewService.visibleRepositories.length === 0) { return this.menus.titleMenu.secondaryActions; } @@ -1140,11 +1144,11 @@ class ViewModel { this.disposables.add(this.viewSubMenuAction); } - if (this.alwaysShowRepositories || this.repositories.elements.length !== 1) { + if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) { return this.viewSubMenuAction.actions; } - const menus = this.menus.getRepositoryMenus(this.repositories.elements[0].provider); + const menus = this.menus.getRepositoryMenus(this.scmViewService.visibleRepositories[0].provider); const secondaryActions = menus.titleMenu.secondaryActions; if (secondaryActions.length === 0) { @@ -1155,19 +1159,19 @@ class ViewModel { } getViewActionsContext(): any { - if (this.repositories.elements.length === 0) { + if (this.scmViewService.visibleRepositories.length === 0) { return []; } - if (this.alwaysShowRepositories || this.repositories.elements.length !== 1) { + if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) { return undefined; } - return this.repositories.elements[0].provider; + return this.scmViewService.visibleRepositories[0].provider; } collapseAllProviders(): void { - for (const repository of this.repositories.elements) { + for (const repository of this.scmViewService.visibleRepositories) { if (this.tree.isCollapsible(repository)) { this.tree.collapse(repository); } @@ -1175,7 +1179,7 @@ class ViewModel { } expandAllProviders(): void { - for (const repository of this.repositories.elements) { + for (const repository of this.scmViewService.visibleRepositories) { if (this.tree.isCollapsible(repository)) { this.tree.expand(repository); } @@ -1183,30 +1187,26 @@ class ViewModel { } isAnyProviderCollapsible(): boolean { - if (!this.visible || this.repositories.elements.length === 1) { + if (!this.visible || this.scmViewService.visibleRepositories.length === 1) { return false; } - return this.repositories.elements.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r)); + return this.scmViewService.visibleRepositories.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r)); } areAllProvidersCollapsed(): boolean { - if (!this.visible || this.repositories.elements.length === 1) { + if (!this.visible || this.scmViewService.visibleRepositories.length === 1) { return false; } - return this.repositories.elements.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r))); + return this.scmViewService.visibleRepositories.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r))); } dispose(): void { this.visibilityDisposables.dispose(); this.disposables.dispose(); - - for (const item of this.items) { - item.disposable.dispose(); - } - - this.items = []; + dispose(this.items.values()); + this.items.clear(); } } @@ -1656,6 +1656,7 @@ export class SCMViewPane extends ViewPane { constructor( options: IViewPaneOptions, @ISCMService private scmService: ISCMService, + @ISCMViewService private scmViewService: ISCMViewService, @IKeybindingService protected keybindingService: IKeybindingService, @IThemeService protected themeService: IThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -1696,14 +1697,11 @@ export class SCMViewPane extends ViewPane { this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'))(updateProviderCountVisibility)); updateProviderCountVisibility(); - const repositories = new SimpleSequence(this.scmService.repositories, this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository); - this._register(repositories); - - this.menus = this.instantiationService.createInstance(SCMMenus, repositories); + this.menus = this.instantiationService.createInstance(SCMMenus); this._register(this.menus); this._register(this.menus.titleMenu.onDidChangeTitle(this.updateActions, this)); - this._register(repositories.onDidSplice(() => this.updateActions())); + this._register(this.scmViewService.onDidChangeVisibleRepositories(() => this.updateActions())); this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, (input, height) => this.tree.updateElementHeight(input, height)); const delegate = new ProviderListDelegate(this.inputRenderer); @@ -1762,7 +1760,7 @@ export class SCMViewPane extends ViewPane { viewMode = storageMode; } - this.viewModel = this.instantiationService.createInstance(ViewModel, repositories, this.tree, this.menus, this.inputRenderer, viewMode, ViewModelSortKey.Path); + this.viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.menus, this.inputRenderer, viewMode, ViewModelSortKey.Path); this._register(this.viewModel); addClass(this.listContainer, 'file-icon-themable-tree'); @@ -1829,7 +1827,7 @@ export class SCMViewPane extends ViewPane { return result; } - if (this.viewModel.repositories.elements.length < 2) { + if (this.scmViewService.visibleRepositories.length < 2) { return [...result, ...this.viewModel.getViewActions()]; } diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 7ee09f52d8a4..ccad186a67e1 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -113,3 +113,17 @@ export interface ISCMService { registerSCMProvider(provider: ISCMProvider): ISCMRepository; } + +export const ISCMViewService = createDecorator('scmView'); + +export interface ISCMViewVisibleRepositoryChangeEvent { + readonly added: Iterable; + readonly removed: Iterable; +} + +export interface ISCMViewService { + readonly _serviceBrand: undefined; + + visibleRepositories: ISCMRepository[]; + readonly onDidChangeVisibleRepositories: Event; +} diff --git a/src/vs/workbench/contrib/scm/common/scmViewService.ts b/src/vs/workbench/contrib/scm/common/scmViewService.ts new file mode 100644 index 000000000000..2ae341930d81 --- /dev/null +++ b/src/vs/workbench/contrib/scm/common/scmViewService.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Emitter } from 'vs/base/common/event'; +import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent } from './scm'; +import { Iterable } from 'vs/base/common/iterator'; + +export class SCMViewService implements ISCMViewService { + + declare readonly _serviceBrand: undefined; + + private disposables = new DisposableStore(); + + private _visibleRepositoriesSet = new Set(); + private _visibleRepositories: ISCMRepository[] = []; + + get visibleRepositories(): ISCMRepository[] { + return this._visibleRepositories; + } + + set visibleRepositories(visibleRepositories: ISCMRepository[]) { + const set = new Set(visibleRepositories); + const added = new Set(); + const removed = new Set(); + + for (const repository of visibleRepositories) { + if (!this._visibleRepositoriesSet.has(repository)) { + added.add(repository); + } + } + + for (const repository of this._visibleRepositories) { + if (!set.has(repository)) { + removed.add(repository); + } + } + + if (added.size === 0 && removed.size === 0) { + return; + } + + this._visibleRepositories = visibleRepositories; + this._visibleRepositoriesSet = set; + this._onDidChangeVisibleRepositories.fire({ added, removed }); + } + + private _onDidChangeVisibleRepositories = new Emitter(); + readonly onDidChangeVisibleRepositories = this._onDidChangeVisibleRepositories.event; + + constructor(@ISCMService scmService: ISCMService) { + scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); + scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + + for (const repository of scmService.repositories) { + this.onDidAddRepository(repository); + } + } + + private onDidAddRepository(repository: ISCMRepository): void { + this._visibleRepositories.push(repository); + this._visibleRepositoriesSet.add(repository); + this._onDidChangeVisibleRepositories.fire({ added: [repository], removed: Iterable.empty() }); + } + + private onDidRemoveRepository(repository: ISCMRepository): void { + const index = this._visibleRepositories.indexOf(repository); + + if (index > -1) { + this._visibleRepositories.splice(index, 1); + this._visibleRepositoriesSet.delete(repository); + this._onDidChangeVisibleRepositories.fire({ added: Iterable.empty(), removed: [repository] }); + } + } + + dispose(): void { + this.disposables.dispose(); + this._onDidChangeVisibleRepositories.dispose(); + } +}