diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts new file mode 100644 index 00000000000..61450757073 --- /dev/null +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel } from 'vs/editor/common/model'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ICodeLensData } from 'vs/editor/contrib/codelens/codelens'; +import { LRUCache } from 'vs/base/common/map'; +import { ICodeLensSymbol, CodeLensProvider } from 'vs/editor/common/modes'; + +export const ICodeLensCache = createDecorator('ICodeLensCache'); + +export interface ICodeLensCache { + _serviceBrand: any; + put(model: ITextModel, data: ICodeLensData[]): void; + get(model: ITextModel): ICodeLensData[] | undefined; +} + + +registerSingleton(ICodeLensCache, class implements ICodeLensCache { + + _serviceBrand: any; + + private readonly _cache = new LRUCache(15, 0.75); + + private readonly _fakeProvider = new class implements CodeLensProvider { + provideCodeLenses(): ICodeLensSymbol[] { + throw new Error('not supported'); + } + }; + + put(model: ITextModel, data: ICodeLensData[]): void { + this._cache.set(this._makeKey(model), data.map(item => { + return { + symbol: item.symbol, + provider: this._fakeProvider + }; + })); + } + + get(model: ITextModel) { + return this._cache.get(this._makeKey(model)); + } + + private _makeKey(model: ITextModel): string { + return model.id + model.getVersionId; + } +}, true); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index f1681995636..b2249ef854b 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -17,6 +17,7 @@ import { ICodeLensData, getCodeLensData } from 'vs/editor/contrib/codelens/codel import { CodeLens, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ICodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; export class CodeLensContribution implements editorCommon.IEditorContribution { @@ -35,7 +36,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { constructor( private readonly _editor: editorBrowser.ICodeEditor, @ICommandService private readonly _commandService: ICommandService, - @INotificationService private readonly _notificationService: INotificationService + @INotificationService private readonly _notificationService: INotificationService, + @ICodeLensCache private readonly _codeLensCache: ICodeLensCache ) { this._isEnabled = this._editor.getConfiguration().contribInfo.codeLens; @@ -104,6 +106,11 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } } + const cachedLenses = this._codeLensCache.get(model); + if (cachedLenses) { + this._renderCodeLensSymbols(cachedLenses); + } + this._detectVisibleLenses = new RunOnceScheduler(() => { this._onViewportChanged(); }, 500); @@ -116,8 +123,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentFindCodeLensSymbolsPromise = createCancelablePromise(token => getCodeLensData(model, token)); - this._currentFindCodeLensSymbolsPromise.then((result) => { + this._currentFindCodeLensSymbolsPromise.then(result => { if (counterValue === this._modelChangeCounter) { // only the last one wins + this._codeLensCache.put(model, result); this._renderCodeLensSymbols(result); this._detectVisibleLenses.schedule(); } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 8ea8bf3f1e3..9a0a0bbe9c1 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -58,13 +58,14 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { private readonly _id: string; private readonly _domNode: HTMLElement; private readonly _editor: editorBrowser.ICodeEditor; + private readonly _commands = new Map(); private _widgetPosition: editorBrowser.IContentWidgetPosition; - private _commands: { [id: string]: Command } = Object.create(null); constructor( editor: editorBrowser.ICodeEditor, - symbolRange: Range + symbolRange: Range, + data: ICodeLensData[] ) { this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; @@ -74,9 +75,8 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._domNode = document.createElement('span'); this._domNode.innerHTML = ' '; dom.addClass(this._domNode, 'codelens-decoration'); - dom.addClass(this._domNode, 'invisible-cl'); this.updateHeight(); - this.updateVisibility(); + this.withCommands(data.map(data => data.symbol), false); } updateHeight(): void { @@ -88,15 +88,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._domNode.innerHTML = ' '; } - updateVisibility(): void { - if (this.isVisible()) { - dom.removeClass(this._domNode, 'invisible-cl'); - dom.addClass(this._domNode, 'fadein'); - } - } + withCommands(inSymbols: Array, animate: boolean): void { + this._commands.clear(); - withCommands(inSymbols: Array): void { - this._commands = Object.create(null); const symbols = coalesce(inSymbols); if (isFalsyOrEmpty(symbols)) { this._domNode.innerHTML = 'no commands'; @@ -111,7 +105,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { let part: string; if (command.id) { part = `${title}`; - this._commands[i] = command; + this._commands.set(String(i), command); } else { part = `${title}`; } @@ -119,13 +113,17 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { } } + const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' '; this._domNode.innerHTML = html.join(' | '); this._editor.layoutContentWidget(this); + if (wasEmpty && animate) { + dom.addClass(this._domNode, 'fadein'); + } } getCommand(link: HTMLLinkElement): Command | undefined { return link.parentElement === this._domNode - ? this._commands[link.id] + ? this._commands.get(link.id) : undefined; } @@ -228,7 +226,7 @@ export class CodeLens { }); if (range) { - this._contentWidget = new CodeLensContentWidget(editor, range); + this._contentWidget = new CodeLensContentWidget(editor, range, this._data); this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback); this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); @@ -273,7 +271,6 @@ export class CodeLens { } computeIfNecessary(model: ITextModel): ICodeLensData[] | null { - this._contentWidget.updateVisibility(); // trigger the fade in if (!this._contentWidget.isVisible()) { return null; } @@ -289,7 +286,7 @@ export class CodeLens { } updateCommands(symbols: Array): void { - this._contentWidget.withCommands(symbols); + this._contentWidget.withCommands(symbols, true); for (let i = 0; i < this._data.length; i++) { const resolved = symbols[i]; if (resolved) {