diff --git a/extensions/merge-conflict/src/codelensProvider.ts b/extensions/merge-conflict/src/codelensProvider.ts index fb31f4c382d..72135945054 100644 --- a/extensions/merge-conflict/src/codelensProvider.ts +++ b/extensions/merge-conflict/src/codelensProvider.ts @@ -9,11 +9,12 @@ import { loadMessageBundle } from 'vscode-nls'; const localize = loadMessageBundle(); export default class MergeConflictCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable { - private codeLensRegistrationHandle: vscode.Disposable | null; private config: interfaces.IExtensionConfiguration; + private tracker: interfaces.IDocumentMergeConflictTracker; - constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) { + constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) { + this.tracker = trackerService.createTracker('codelens'); } begin(config: interfaces.IExtensionConfiguration) { diff --git a/extensions/merge-conflict/src/commandHandler.ts b/extensions/merge-conflict/src/commandHandler.ts index 7a017e6ed3c..8ef9923dd63 100644 --- a/extensions/merge-conflict/src/commandHandler.ts +++ b/extensions/merge-conflict/src/commandHandler.ts @@ -29,8 +29,10 @@ enum NavigationDirection { export default class CommandHandler implements vscode.Disposable { private disposables: vscode.Disposable[] = []; + private tracker: interfaces.IDocumentMergeConflictTracker; - constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) { + constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) { + this.tracker = trackerService.createTracker('commands'); } begin() { diff --git a/extensions/merge-conflict/src/documentTracker.ts b/extensions/merge-conflict/src/documentTracker.ts index 06c526d5c01..ebde2956aab 100644 --- a/extensions/merge-conflict/src/documentTracker.ts +++ b/extensions/merge-conflict/src/documentTracker.ts @@ -8,29 +8,70 @@ import { MergeConflictParser } from './mergeConflictParser'; import * as interfaces from './interfaces'; import { Delayer } from './delayer'; -export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTracker { +class ScanTask { + public origins: Set = new Set(); + public delayTask: Delayer; - private cache: Map> = new Map(); - private delayExpireTime: number = 150; + constructor(delayTime: number, initialOrigin: string) { + this.origins.add(initialOrigin); + this.delayTask = new Delayer(delayTime); + } + + public addOrigin(name: string): boolean { + if (this.origins.has(name)) { + return false; + } + + return false; + } + + public hasOrigin(name: string): boolean { + return this.origins.has(name); + } +} + +class OriginDocumentMergeConflictTracker implements interfaces.IDocumentMergeConflictTracker { + constructor(private parent: DocumentMergeConflictTracker, private origin: string) { + } getConflicts(document: vscode.TextDocument): PromiseLike { + return this.parent.getConflicts(document, this.origin); + } + + isPending(document: vscode.TextDocument): boolean { + return this.parent.isPending(document, this.origin); + } + + forget(document: vscode.TextDocument) { + this.parent.forget(document); + } +} + +export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTrackerService { + private cache: Map = new Map(); + private delayExpireTime: number = 300; + + getConflicts(document: vscode.TextDocument, origin: string): PromiseLike { // Attempt from cache let key = this.getCacheKey(document); if (!key) { // Document doesnt have a uri, can't cache it, so return - return Promise.resolve(this.getConflictsOrEmpty(document)); + return Promise.resolve(this.getConflictsOrEmpty(document, [origin])); } let cacheItem = this.cache.get(key); if (!cacheItem) { - cacheItem = new Delayer(this.delayExpireTime); + cacheItem = new ScanTask(this.delayExpireTime, origin); this.cache.set(key, cacheItem); } + else { + cacheItem.addOrigin(origin); + } - return cacheItem.trigger(() => { - let conflicts = this.getConflictsOrEmpty(document); + return cacheItem.delayTask.trigger(() => { + let conflicts = this.getConflictsOrEmpty(document, Array.from(cacheItem!.origins)); if (this.cache) { this.cache.delete(key!); @@ -40,6 +81,29 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, }); } + isPending(document: vscode.TextDocument, origin: string): boolean { + if (!document) { + return false; + } + + let key = this.getCacheKey(document); + if (!key) { + return false; + } + + var task = this.cache.get(key); + + if (!task) { + return false; + } + + return task.hasOrigin(origin); + } + + createTracker(origin: string): interfaces.IDocumentMergeConflictTracker { + return new OriginDocumentMergeConflictTracker(this, origin); + } + forget(document: vscode.TextDocument) { let key = this.getCacheKey(document); @@ -52,12 +116,12 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, this.cache.clear(); } - private getConflictsOrEmpty(document: vscode.TextDocument): interfaces.IDocumentMergeConflict[] { + private getConflictsOrEmpty(document: vscode.TextDocument, origins: string[]): interfaces.IDocumentMergeConflict[] { let stepStart = process.hrtime(); const containsConflict = MergeConflictParser.containsConflict(document); let stepEnd = process.hrtime(stepStart); - console.info('%s -> Check document execution time: %dms', document.uri.toString(), stepEnd[1] / 1000000); + console.info('[%s] %s -> Check document execution time: %dms', origins.join(', '), document.uri.toString(), stepEnd[1] / 1000000); if (!containsConflict) { return []; @@ -67,7 +131,7 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, const conflicts = MergeConflictParser.scanDocument(document); stepEnd = process.hrtime(stepStart); - console.info('%s -> Find conflict regions execution time: %dms', document.uri.toString(), stepEnd[1] / 1000000); + console.info('[%s] %s -> Find conflict regions execution time: %dms', origins.join(', '), document.uri.toString(), stepEnd[1] / 1000000); return conflicts; } @@ -79,4 +143,5 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, return null; } -} \ No newline at end of file +} + diff --git a/extensions/merge-conflict/src/interfaces.ts b/extensions/merge-conflict/src/interfaces.ts index e13530a3ac7..9d411befaba 100644 --- a/extensions/merge-conflict/src/interfaces.ts +++ b/extensions/merge-conflict/src/interfaces.ts @@ -37,5 +37,11 @@ export interface IDocumentMergeConflictDescriptor { export interface IDocumentMergeConflictTracker { getConflicts(document: vscode.TextDocument): PromiseLike; + isPending(document: vscode.TextDocument): boolean; + forget(document: vscode.TextDocument); +} + +export interface IDocumentMergeConflictTrackerService { + createTracker(origin: string): IDocumentMergeConflictTracker; forget(document: vscode.TextDocument); } diff --git a/extensions/merge-conflict/src/mergeDecorator.ts b/extensions/merge-conflict/src/mergeDecorator.ts index 3124fd5d262..f6d0135aad9 100644 --- a/extensions/merge-conflict/src/mergeDecorator.ts +++ b/extensions/merge-conflict/src/mergeDecorator.ts @@ -17,8 +17,10 @@ export default class MergeDectorator implements vscode.Disposable { private currentColorRgb = `32,200,94`; private incomingColorRgb = `24,134,255`; private config: interfaces.IExtensionConfiguration; + private tracker: interfaces.IDocumentMergeConflictTracker; - constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) { + constructor(private context: vscode.ExtensionContext, trackerService: interfaces.IDocumentMergeConflictTrackerService) { + this.tracker = trackerService.createTracker('decorator'); } begin(config: interfaces.IExtensionConfiguration) { @@ -149,10 +151,14 @@ export default class MergeDectorator implements vscode.Disposable { return; } + // If we have a pending scan from the same origin, exit early. + if (this.tracker.isPending(editor.document)) { + return; + } + let conflicts = await this.tracker.getConflicts(editor.document); if (conflicts.length === 0) { - // TODO: Remove decorations this.removeDecorations(editor); return; }