diff --git a/src/vs/base/common/sequence.ts b/src/vs/base/common/sequence.ts index 39565111a8186c96adddae00a487941220560bd5..8f67c41c67ef55c18cfa1ecf4fec246f07d1d997 100644 --- a/src/vs/base/common/sequence.ts +++ b/src/vs/base/common/sequence.ts @@ -5,7 +5,7 @@ 'use strict'; -import Event from 'vs/base/common/event'; +import Event, { Emitter } from 'vs/base/common/event'; export interface ISplice { readonly start: number; @@ -20,4 +20,17 @@ export interface ISpliceable { export interface ISequence { readonly elements: T[]; readonly onDidSplice: Event>; +} + +export class Sequence implements ISequence, ISpliceable { + + readonly elements: T[] = []; + + private _onDidSplice = new Emitter>(); + readonly onDidSplice: Event> = this._onDidSplice.event; + + splice(start: number, deleteCount: number, toInsert: T[] = []): void { + this.elements.splice(start, deleteCount, ...toInsert); + this._onDidSplice.fire({ start, deleteCount, toInsert }); + } } \ No newline at end of file diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 03484a9669a245aa5b8d3d580c1694494f5694d8..88c6377b3c46a08057eeb6eb0668a358bc3b11a5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -14,26 +14,20 @@ import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGr import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { Command } from 'vs/editor/common/modes'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { ISplice, ISequence } from 'vs/base/common/sequence'; +import { ISplice, Sequence } from 'vs/base/common/sequence'; -class MainThreadSCMResourceCollection implements ISequence { +class MainThreadSCMResourceGroup implements ISCMResourceGroup { readonly elements: ISCMResource[] = []; private _onDidSplice = new Emitter>(); readonly onDidSplice = this._onDidSplice.event; - splice(start: number, deleteCount: number, toInsert: ISCMResource[]) { - this.elements.splice(start, deleteCount, ...toInsert); - this._onDidSplice.fire({ start, deleteCount, toInsert }); - } -} - -class MainThreadSCMResourceGroup implements ISCMResourceGroup { - - readonly resources = new MainThreadSCMResourceCollection(); get hideWhenEmpty(): boolean { return this.features.hideWhenEmpty; } + private _onDidChange = new Emitter(); + get onDidChange(): Event { return this._onDidChange.event; } + constructor( private sourceControlHandle: number, private handle: number, @@ -50,6 +44,21 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { groupHandle: this.handle }; } + + splice(start: number, deleteCount: number, toInsert: ISCMResource[]) { + this.elements.splice(start, deleteCount, ...toInsert); + this._onDidSplice.fire({ start, deleteCount, toInsert }); + } + + $updateGroup(features: SCMGroupFeatures): void { + this.features = assign(this.features, features); + this._onDidChange.fire(); + } + + $updateGroupLabel(label: string): void { + this.label = label; + this._onDidChange.fire(); + } } class MainThreadSCMResource implements ISCMResource { @@ -84,13 +93,18 @@ class MainThreadSCMProvider implements ISCMProvider { private _id = `scm${MainThreadSCMProvider.ID_HANDLE++}`; get id(): string { return this._id; } - private _groups: MainThreadSCMResourceGroup[] = []; + readonly groups = new Sequence(); private _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null); - get resources(): ISCMResourceGroup[] { - return this._groups - .filter(g => g.resources.elements.length > 0 || !g.features.hideWhenEmpty); - } + // get groups(): ISequence { + // return { + // elements: this._groups, + // onDidSplice: this._onDidSplice.event + // }; + + // // return this._groups + // // .filter(g => g.resources.elements.length > 0 || !g.features.hideWhenEmpty); + // } private _onDidChangeResources = new Emitter(); get onDidChangeResources(): Event { return this._onDidChangeResources.event; } @@ -141,8 +155,8 @@ class MainThreadSCMProvider implements ISCMProvider { id ); - this._groups.push(group); this._groupsByHandle[handle] = group; + this.groups.splice(this.groups.elements.length, 0, [group]); } $updateGroup(handle: number, features: SCMGroupFeatures): void { @@ -152,8 +166,7 @@ class MainThreadSCMProvider implements ISCMProvider { return; } - group.features = assign(group.features, features); - this._onDidChange.fire(); + group.$updateGroup(features); } $updateGroupLabel(handle: number, label: string): void { @@ -163,8 +176,7 @@ class MainThreadSCMProvider implements ISCMProvider { return; } - group.label = label; - this._onDidChange.fire(); + group.$updateGroupLabel(label); } $spliceGroupResourceStates(splices: SCMRawResourceSplices[]): void { @@ -206,7 +218,7 @@ class MainThreadSCMProvider implements ISCMProvider { ); }); - group.resources.splice(start, deleteCount, resources); + group.splice(start, deleteCount, resources); } } @@ -221,7 +233,7 @@ class MainThreadSCMProvider implements ISCMProvider { } delete this._groupsByHandle[handle]; - this._groups.splice(this._groups.indexOf(group), 1); + this.groups.splice(this.groups.elements.indexOf(group), 1); } getOriginalResource(uri: URI): TPromise { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts index fa26eb0b5b44f1619edf174f03374db51faf8340..2e4409f95003db85041d4f9810a79e65913799a3 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts @@ -58,7 +58,7 @@ export class StatusUpdater implements IWorkbenchContribution { if (typeof repository.provider.count === 'number') { return r + repository.provider.count; } else { - return r + repository.provider.resources.reduce((r, g) => r + g.resources.elements.length, 0); + return r + repository.provider.groups.elements.reduce((r, g) => r + g.elements.length, 0); } }, 0); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 3829ab2d47df75b45c16487b8a9e56d041d6e7b9..f667cb46d8441479d447084bc630898d57075374 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -54,6 +54,8 @@ import { render as renderOcticons } from 'vs/base/browser/ui/octiconLabel/octico import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import * as platform from 'vs/base/common/platform'; import { format } from 'vs/base/common/strings'; +import { ISpliceable, ISequence, ISplice } from 'vs/base/common/sequence'; +import { firstIndex } from 'vs/base/common/arrays'; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect @@ -371,7 +373,7 @@ class ResourceGroupRenderer implements IRenderer 0 || !group.hideWhenEmpty; +} + +interface IGroupItem { + readonly group: ISCMResourceGroup; + visible: boolean; + readonly disposable: IDisposable; +} + +class ResourceGroupSplicer { + + private items: IGroupItem[] = []; + private disposables: IDisposable[] = []; + + constructor( + groupSequence: ISequence, + private spliceable: ISpliceable + ) { + groupSequence.onDidSplice(this.onDidSpliceGroups, this, this.disposables); + this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: groupSequence.elements }); + } + + private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { + let absoluteStart = 0; + + for (let i = 0; i < start; i++) { + const item = this.items[i]; + absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; + } + + let absoluteDeleteCount = 0; + + for (let i = 0; i < deleteCount; i++) { + const item = this.items[start + i]; + absoluteDeleteCount += (item.visible ? 1 : 0) + item.group.elements.length; + } + + const itemsToInsert: IGroupItem[] = []; + const absoluteToInsert: (ISCMResourceGroup | ISCMResource)[] = []; + + for (const group of toInsert) { + const visible = isGroupVisible(group); + + if (visible) { + absoluteToInsert.push(group); + } + + for (const element of group.elements) { + absoluteToInsert.push(element); + } + + const disposable = combinedDisposable([ + group.onDidChange(() => this.onDidChangeGroup(group)), + group.onDidSplice(splice => this.onDidSpliceGroup(group, splice)) + ]); + + itemsToInsert.push({ group, visible, disposable }); + } + + const itemsToDispose = this.items.splice(start, deleteCount, ...itemsToInsert); + + for (const item of itemsToDispose) { + item.disposable.dispose(); + } + + this.spliceable.splice(absoluteStart, absoluteDeleteCount, absoluteToInsert); + } + + private onDidChangeGroup(group: ISCMResourceGroup): void { + const itemIndex = firstIndex(this.items, item => item.group === group); + + if (itemIndex < 0) { + return; + } + + const item = this.items[itemIndex]; + const visible = isGroupVisible(group); + + if (item.visible === visible) { + return; + } + + let absoluteStart = 0; + + for (let i = 0; i < itemIndex; i++) { + const item = this.items[i]; + absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; + } + + if (visible) { + this.spliceable.splice(absoluteStart, 0, [group, ...group.elements]); + } else { + this.spliceable.splice(absoluteStart, 1 + group.elements.length, []); + } + + item.visible = visible; + } + + private onDidSpliceGroup(group: ISCMResourceGroup, { start, deleteCount, toInsert }: ISplice): void { + const itemIndex = firstIndex(this.items, item => item.group === group); + + if (itemIndex < 0) { + return; + } + + const item = this.items[itemIndex]; + const visible = isGroupVisible(group); + + if (!item.visible && !visible) { + return; + } + + let absoluteStart = start; + + for (let i = 0; i < itemIndex; i++) { + const item = this.items[i]; + absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; + } + + if (item.visible && !visible) { + this.spliceable.splice(absoluteStart, 1 + deleteCount, toInsert); + } else if (!item.visible && visible) { + this.spliceable.splice(absoluteStart, deleteCount, [group, ...toInsert]); + } else { + this.spliceable.splice(absoluteStart + 1, deleteCount, toInsert); + } + + item.visible = visible; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} + export class RepositoryPanel extends ViewletPanel { private cachedHeight: number | undefined = undefined; @@ -629,8 +767,8 @@ export class RepositoryPanel extends ViewletPanel { this.list.onContextMenu(this.onListContextMenu, this, this.disposables); this.disposables.push(this.list); - this.repository.provider.onDidChangeResources(this.updateList, this, this.disposables); - this.updateList(); + const listSplicer = new ResourceGroupSplicer(this.repository.provider.groups, this.list); + this.disposables.push(listSplicer); } layoutBody(height: number = this.cachedHeight): void { @@ -678,19 +816,6 @@ export class RepositoryPanel extends ViewletPanel { return this.repository.provider; } - private updateList(): void { - const elements = this.repository.provider.resources - .reduce<(ISCMResourceGroup | ISCMResource)[]>((r, g) => { - if (g.resources.elements.length === 0 && g.hideWhenEmpty) { - return r; - } - - return [...r, g, ...g.resources.elements]; - }, []); - - this.list.splice(0, this.list.length, elements); - } - private open(e: ISCMResource): void { e.open().done(undefined, onUnexpectedError); } diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 198a8be5625be3f257f992c04aa322d75556b56b..78c1252f599ec96ccb1e9c3fc4624cf085476a09 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -39,12 +39,12 @@ export interface ISCMResource { open(): TPromise; } -export interface ISCMResourceGroup { +export interface ISCMResourceGroup extends ISequence { readonly provider: ISCMProvider; readonly label: string; readonly id: string; - readonly resources: ISequence; readonly hideWhenEmpty: boolean; + readonly onDidChange: Event; } export interface ISCMProvider extends IDisposable { @@ -52,7 +52,9 @@ export interface ISCMProvider extends IDisposable { readonly id: string; readonly contextValue: string; - readonly resources: ISCMResourceGroup[]; + readonly groups: ISequence; + + // TODO@Joao: remove readonly onDidChangeResources: Event; readonly rootUri?: URI;