diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 25c1683e910d2bc5fa60320394f270d20c7a761e..85707031cf915ac0040ae80479223fd6f0b1cb9b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -13,7 +13,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceGroup, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol'; +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'; @@ -155,36 +155,43 @@ class MainThreadSCMProvider implements ISCMProvider { this._onDidChange.fire(); } - $updateGroupResourceStates(groups: SCMRawResourceGroup[]): void { - for (const [groupHandle, resources] of groups) { + $spliceGroupResourceStates(slices: SCMRawResourceSplices[]): void { + for (const [groupHandle, groupSlices] of slices) { const group = this._groupsByHandle[groupHandle]; if (!group) { return; } - group.resources = resources.map(rawResource => { - const [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] = rawResource; - const icon = icons[0]; - const iconDark = icons[1] || icon; - const decorations = { - icon: icon && URI.parse(icon), - iconDark: iconDark && URI.parse(iconDark), - tooltip, - strikeThrough, - faded - }; - - return new MainThreadSCMResource( - this.handle, - groupHandle, - handle, - URI.parse(sourceUri), - command, - group, - decorations - ); - }); + // reverse the splices sequence in order to apply them correctly + groupSlices.reverse(); + + for (const [start, deleteCount, rawResources] of groupSlices) { + const resources = rawResources.map(rawResource => { + const [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] = rawResource; + const icon = icons[0]; + const iconDark = icons[1] || icon; + const decorations = { + icon: icon && URI.parse(icon), + iconDark: iconDark && URI.parse(iconDark), + tooltip, + strikeThrough, + faded + }; + + return new MainThreadSCMResource( + this.handle, + groupHandle, + handle, + URI.parse(sourceUri), + command, + group, + decorations + ); + }); + + group.resources.splice(start, deleteCount, ...resources); + } } this._onDidChangeResources.fire(); @@ -317,7 +324,7 @@ export class MainThreadSCM implements MainThreadSCMShape { provider.$updateGroupLabel(groupHandle, label); } - $updateResourceStates(sourceControlHandle: number, resources: SCMRawResourceGroup[]): void { + $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void { const repository = this._repositories[sourceControlHandle]; if (!repository) { @@ -325,7 +332,7 @@ export class MainThreadSCM implements MainThreadSCMShape { } const provider = repository.provider as MainThreadSCMProvider; - provider.$updateGroupResourceStates(resources); + provider.$spliceGroupResourceStates(splices); } $unregisterGroup(sourceControlHandle: number, handle: number): void { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 160268c0b0c31dc6de6ddcf4f4f5411bccf3233d..b50ad9b9a2514a02752138105e9efa3bc1a04f05 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -337,11 +337,17 @@ export type SCMRawResource = [ boolean /*faded*/ ]; -export type SCMRawResourceGroup = [ - number, /*handle*/ +export type SCMRawResourceSplice = [ + number /* start */, + number /* delete count */, SCMRawResource[] ]; +export type SCMRawResourceSplices = [ + number, /*handle*/ + SCMRawResourceSplice[] +]; + export interface MainThreadSCMShape extends IDisposable { $registerSourceControl(handle: number, id: string, label: string): void; $updateSourceControl(handle: number, features: SCMProviderFeatures): void; @@ -352,7 +358,7 @@ export interface MainThreadSCMShape extends IDisposable { $updateGroupLabel(sourceControlHandle: number, handle: number, label: string): void; $unregisterGroup(sourceControlHandle: number, handle: number): void; - $updateResourceStates(sourceControlHandle: number, resources: SCMRawResourceGroup[]): void; + $spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void; $setInputBoxValue(sourceControlHandle: number, value: string): void; } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index a5e434044734c0c20cb9428497b50e84cbdad077..f9dc722312619bb24e7554f6f5a8a0b83673c145 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -12,8 +12,9 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { asWinJsPromise } from 'vs/base/common/async'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceGroup, IMainContext } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext } from './extHost.protocol'; import * as vscode from 'vscode'; +import { LcsDiff, ISequence } from 'vs/base/common/diff/diff'; function getIconPath(decorations: vscode.SourceControlResourceThemableDecorations) { if (!decorations) { @@ -59,6 +60,22 @@ export class ExtHostSCMInputBox { } } +class ResourceSequence implements ISequence { + + constructor(private resources: vscode.SourceControlResourceState[]) { } + + getLength() { + return this.resources.length; + } + + getElementHash(index) { + const resource = this.resources[index]; + + // TODO!!! + return resource.resourceUri.toString(); + } +} + class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceGroup { private static _handlePool: number = 0; @@ -73,6 +90,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _onDidDispose = new Emitter(); readonly onDidDispose = this._onDidDispose.event; + private _handlesSnapshot: number[] = []; + private _resourcesSnapshot: vscode.SourceControlResourceState[] = []; + get id(): string { return this._id; } get label(): string { return this._label; } @@ -94,43 +114,61 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG this._onDidUpdateResourceStates.fire(); } - get _rawResources(): SCMRawResource[] { - const handles: number[] = []; - const rawResources = this._resourceStates.map(r => { - const handle = this._resourceHandlePool++; - this._resourceStatesMap.set(handle, r); - handles.push(handle); + _snapshot(): SCMRawResourceSplice[] { + const original = new ResourceSequence(this._resourcesSnapshot); + const modified = new ResourceSequence(this._resourceStates); + const lcs = new LcsDiff(original, modified); + const diffs = lcs.ComputeDiff(false); + const handlesToDelete: number[] = []; + + const splices = diffs.map(diff => { + const start = diff.originalStart; + const deleteCount = diff.originalLength; + const handles: number[] = []; + + const rawResources = this._resourceStates + .slice(diff.modifiedStart, diff.modifiedStart + diff.modifiedLength) + .map(r => { + const handle = this._resourceHandlePool++; + this._resourceStatesMap.set(handle, r); + handles.push(handle); + + const sourceUri = r.resourceUri.toString(); + const command = this._commands.toInternal(r.command); + const iconPath = getIconPath(r.decorations); + const lightIconPath = r.decorations && getIconPath(r.decorations.light) || iconPath; + const darkIconPath = r.decorations && getIconPath(r.decorations.dark) || iconPath; + const icons: string[] = []; + + if (lightIconPath || darkIconPath) { + icons.push(lightIconPath); + } - const sourceUri = r.resourceUri.toString(); - const command = this._commands.toInternal(r.command); - const iconPath = getIconPath(r.decorations); - const lightIconPath = r.decorations && getIconPath(r.decorations.light) || iconPath; - const darkIconPath = r.decorations && getIconPath(r.decorations.dark) || iconPath; - const icons: string[] = []; + if (darkIconPath !== lightIconPath) { + icons.push(darkIconPath); + } - if (lightIconPath || darkIconPath) { - icons.push(lightIconPath); - } + const tooltip = (r.decorations && r.decorations.tooltip) || ''; + const strikeThrough = r.decorations && !!r.decorations.strikeThrough; + const faded = r.decorations && !!r.decorations.faded; - if (darkIconPath !== lightIconPath) { - icons.push(darkIconPath); - } + return [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] as SCMRawResource; + }); - const tooltip = (r.decorations && r.decorations.tooltip) || ''; - const strikeThrough = r.decorations && !!r.decorations.strikeThrough; - const faded = r.decorations && !!r.decorations.faded; + handlesToDelete.push(...this._handlesSnapshot.splice(start, deleteCount, ...handles)); - return [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] as SCMRawResource; + return [start, deleteCount, rawResources] as SCMRawResourceSplice; }); - const disposable = () => handles.forEach(handle => this._resourceStatesMap.delete(handle)); + const disposable = () => handlesToDelete.forEach(handle => this._resourceStatesMap.delete(handle)); this._resourceStatesRollingDisposables.push(disposable); while (this._resourceStatesRollingDisposables.length >= 10) { this._resourceStatesRollingDisposables.shift()(); } - return rawResources; + this._resourcesSnapshot = this._resourceStates; + return splices; } readonly handle = ExtHostSourceControlResourceGroup._handlePool++; @@ -266,12 +304,12 @@ class ExtHostSourceControl implements vscode.SourceControl { @debounce(100) eventuallyUpdateResourceStates(): void { - const resources: SCMRawResourceGroup[] = []; + const resources: SCMRawResourceSplices[] = []; this.updatedResourceGroups - .forEach(group => resources.push([group.handle, group._rawResources])); + .forEach(group => resources.push([group.handle, group._snapshot()])); - this._proxy.$updateResourceStates(this.handle, resources); + this._proxy.$spliceResourceStates(this.handle, resources); this.updatedResourceGroups.clear(); }