diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index aea28ac21616a13e19f7a64cc7420964ddf1d7b9..ed6e0782e9f8f65d1ec5636688db774d7eee4609 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1461,15 +1461,21 @@ export interface IWebviewPanelOptions { readonly retainContextWhenHidden?: boolean; } -export interface ICodeLensSymbol { +export interface CodeLens { range: IRange; id?: string; command?: Command; } + +export interface CodeLensList { + lenses: CodeLens[]; + dispose(): void; +} + export interface CodeLensProvider { onDidChange?: Event; - provideCodeLenses(model: model.ITextModel, token: CancellationToken): ProviderResult; - resolveCodeLens?(model: model.ITextModel, codeLens: ICodeLensSymbol, token: CancellationToken): ProviderResult; + provideCodeLenses(model: model.ITextModel, token: CancellationToken): ProviderResult; + resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } // --- feature registries ------ diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index 6027dec365df515204f2915be1a9d4b83fa532ec..e642a92c45d22eeab06be9ee5bd571419a01c032 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -6,18 +6,19 @@ 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 { CodeLensModel } from 'vs/editor/contrib/codelens/codelens'; import { LRUCache, values } from 'vs/base/common/map'; -import { ICodeLensSymbol, CodeLensProvider } from 'vs/editor/common/modes'; +import { CodeLensProvider, CodeLensList, CodeLens } from 'vs/editor/common/modes'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Range } from 'vs/editor/common/core/range'; +import { runWhenIdle } from 'vs/base/common/async'; export const ICodeLensCache = createDecorator('ICodeLensCache'); export interface ICodeLensCache { _serviceBrand: any; - put(model: ITextModel, data: ICodeLensData[]): void; - get(model: ITextModel): ICodeLensData[] | undefined; + put(model: ITextModel, data: CodeLensModel): void; + get(model: ITextModel): CodeLensModel | undefined; delete(model: ITextModel): void; } @@ -30,7 +31,7 @@ class CacheItem { constructor( readonly lineCount: number, - readonly data: ICodeLensData[] + readonly data: CodeLensModel ) { } } @@ -39,7 +40,7 @@ export class CodeLensCache implements ICodeLensCache { _serviceBrand: any; private readonly _fakeProvider = new class implements CodeLensProvider { - provideCodeLenses(): ICodeLensSymbol[] { + provideCodeLenses(): CodeLensList { throw new Error('not supported'); } }; @@ -48,9 +49,12 @@ export class CodeLensCache implements ICodeLensCache { constructor(@IStorageService storageService: IStorageService) { - const key = 'codelens/cache'; + // remove old data + const oldkey = 'codelens/cache'; + runWhenIdle(() => storageService.remove(oldkey, StorageScope.WORKSPACE)); // restore lens data on start + const key = 'codelens/cache2'; const raw = storageService.get(key, StorageScope.WORKSPACE, '{}'); this._deserialize(raw); @@ -61,13 +65,12 @@ export class CodeLensCache implements ICodeLensCache { }); } - put(model: ITextModel, data: ICodeLensData[]): void { - const item = new CacheItem(model.getLineCount(), data.map(item => { - return { - symbol: item.symbol, - provider: this._fakeProvider - }; - })); + put(model: ITextModel, data: CodeLensModel): void { + + const lensModel = new CodeLensModel(); + lensModel.add({ lenses: data.lenses.map(v => v.symbol), dispose() { } }, this._fakeProvider, 0); + + const item = new CacheItem(model.getLineCount(), lensModel); this._cache.set(model.uri.toString(), item); } @@ -86,7 +89,7 @@ export class CodeLensCache implements ICodeLensCache { const data: Record = Object.create(null); this._cache.forEach((value, key) => { const lines = new Set(); - for (const d of value.data) { + for (const d of value.data.lenses) { lines.add(d.symbol.range.startLineNumber); } data[key] = { @@ -102,14 +105,14 @@ export class CodeLensCache implements ICodeLensCache { const data: Record = JSON.parse(raw); for (const key in data) { const element = data[key]; - const symbols: ICodeLensData[] = []; + const lenses: CodeLens[] = []; for (const line of element.lines) { - symbols.push({ - provider: this._fakeProvider, - symbol: { range: new Range(line, 1, line, 11) } - }); + lenses.push({ range: new Range(line, 1, line, 11) }); } - this._cache.set(key, new CacheItem(element.lineCount, symbols)); + + const model = new CodeLensModel(); + model.add({ lenses, dispose() { } }, this._fakeProvider, 0); + this._cache.set(key, new CacheItem(element.lineCount, model)); } } catch { // ignore... diff --git a/src/vs/editor/contrib/codelens/codelens.ts b/src/vs/editor/contrib/codelens/codelens.ts index 30468bf74a17746ae9a1ee9b947e759853df7b98..13c07c5ec46ec96a6400336da8d612dd03211605 100644 --- a/src/vs/editor/contrib/codelens/codelens.ts +++ b/src/vs/editor/contrib/codelens/codelens.ts @@ -9,38 +9,43 @@ import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/error import { URI } from 'vs/base/common/uri'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeLensProvider, CodeLensProviderRegistry, ICodeLensSymbol } from 'vs/editor/common/modes'; +import { CodeLensProvider, CodeLensProviderRegistry, CodeLens, CodeLensList } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { DisposableStore } from 'vs/base/common/lifecycle'; -export interface ICodeLensData { - symbol: ICodeLensSymbol; +export interface CodeLensItem { + symbol: CodeLens; provider: CodeLensProvider; + providerRank: number; } -export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { +export class CodeLensModel { - const symbols: ICodeLensData[] = []; - const provider = CodeLensProviderRegistry.ordered(model); + lenses: CodeLensItem[] = []; - const promises = provider.map(provider => Promise.resolve(provider.provideCodeLenses(model, token)).then(result => { - if (Array.isArray(result)) { - for (let symbol of result) { - symbols.push({ symbol, provider }); - } - } - }).catch(onUnexpectedExternalError)); + private readonly _dispoables = new DisposableStore(); - return Promise.all(promises).then(() => { + dispose(): void { + this._dispoables.dispose(); + } - return mergeSort(symbols, (a, b) => { + add(list: CodeLensList, provider: CodeLensProvider, providerIdx: number): void { + this._dispoables.add(list); + for (const symbol of list.lenses) { + this.lenses.push({ symbol, provider, providerRank: providerIdx }); + } + } + + seal(): void { + this.lenses = mergeSort(this.lenses, (a, b) => { // sort by lineNumber, provider-rank, and column if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { return -1; } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { return 1; - } else if (provider.indexOf(a.provider) < provider.indexOf(b.provider)) { + } else if (a.providerRank < b.providerRank) { return -1; - } else if (provider.indexOf(a.provider) > provider.indexOf(b.provider)) { + } else if (a.providerRank > b.providerRank) { return 1; } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { return -1; @@ -50,6 +55,21 @@ export function getCodeLensData(model: ITextModel, token: CancellationToken): Pr return 0; } }); + } +} + +export function getCodeLensData(model: ITextModel, token: CancellationToken): Promise { + + const provider = CodeLensProviderRegistry.ordered(model); + const result = new CodeLensModel(); + + const promises = provider.map((provider, i) => Promise.resolve(provider.provideCodeLenses(model, token)) + .then(list => list && result.add(list, provider, i)) + .catch(onUnexpectedExternalError)); + + return Promise.all(promises).then(() => { + result.seal(); + return result; }); } @@ -65,12 +85,12 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { throw illegalArgument(); } - const result: ICodeLensSymbol[] = []; + const result: CodeLens[] = []; return getCodeLensData(model, CancellationToken.None).then(value => { let resolve: Promise[] = []; - for (const item of value) { + for (const item of value.lenses) { if (typeof itemResolveCount === 'undefined' || Boolean(item.symbol.command)) { result.push(item.symbol); } else if (itemResolveCount-- > 0 && item.provider.resolveCodeLens) { @@ -78,7 +98,7 @@ registerLanguageCommand('_executeCodeLensProvider', function (accessor, args) { } } - return Promise.all(resolve); + return Promise.all(resolve).finally(() => value.dispose()); }).then(() => { return result; diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index c7d7ced6ec4cb4224dc4db378839e1182b78589c..ba60a74f4f296fbef30ef50ef972b33f454308fc 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -12,9 +12,9 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; -import { CodeLensProviderRegistry, ICodeLensSymbol } from 'vs/editor/common/modes'; -import { ICodeLensData, getCodeLensData } from 'vs/editor/contrib/codelens/codelens'; -import { CodeLens, CodeLensHelper } from 'vs/editor/contrib/codelens/codelensWidget'; +import { CodeLensProviderRegistry, CodeLens } from 'vs/editor/common/modes'; +import { CodeLensModel, getCodeLensData, CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensWidget, 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'; @@ -27,8 +27,9 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _globalToDispose: IDisposable[]; private _localToDispose: IDisposable[]; - private _lenses: CodeLens[]; - private _currentFindCodeLensSymbolsPromise: CancelablePromise | null; + private _lenses: CodeLensWidget[]; + private _currentFindCodeLensSymbolsPromise: CancelablePromise | null; + private _currentCodeLensModel: CodeLensModel | undefined; private _modelChangeCounter: number; private _currentResolveCodeLensSymbolsPromise: CancelablePromise | null; private _detectVisibleLenses: RunOnceScheduler; @@ -76,6 +77,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentResolveCodeLensSymbolsPromise = null; } this._localToDispose = dispose(this._localToDispose); + dispose(this._currentCodeLensModel); } getId(): string { @@ -136,7 +138,14 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._currentFindCodeLensSymbolsPromise.then(result => { if (counterValue === this._modelChangeCounter) { // only the last one wins + // lifecycle -> dispose old model + dispose(this._currentCodeLensModel); + this._currentCodeLensModel = result; + + // cache model to reduce flicker this._codeLensCache.put(model, result); + + // render lenses this._renderCodeLensSymbols(result); this._detectVisibleLenses.schedule(); } @@ -147,7 +156,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._localToDispose.push(this._editor.onDidChangeModelContent((e) => { this._editor.changeDecorations((changeAccessor) => { this._editor.changeViewZones((viewAccessor) => { - let toDispose: CodeLens[] = []; + let toDispose: CodeLensWidget[] = []; let lastLensLineNumber: number = -1; this._lenses.forEach((lens) => { @@ -228,16 +237,16 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._lenses = []; } - private _renderCodeLensSymbols(symbols: ICodeLensData[]): void { + private _renderCodeLensSymbols(symbols: CodeLensModel): void { if (!this._editor.hasModel()) { return; } let maxLineNumber = this._editor.getModel().getLineCount(); - let groups: ICodeLensData[][] = []; - let lastGroup: ICodeLensData[] | undefined; + let groups: CodeLensItem[][] = []; + let lastGroup: CodeLensItem[] | undefined; - for (let symbol of symbols) { + for (let symbol of symbols.lenses) { let line = symbol.symbol.range.startLineNumber; if (line < 1 || line > maxLineNumber) { // invalid code lens @@ -272,7 +281,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { groupsIndex++; codeLensIndex++; } else { - this._lenses.splice(codeLensIndex, 0, new CodeLens(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); + this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); codeLensIndex++; groupsIndex++; } @@ -286,7 +295,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { // Create extra symbols while (groupsIndex < groups.length) { - this._lenses.push(new CodeLens(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); + this._lenses.push(new CodeLensWidget(groups[groupsIndex], this._editor, helper, accessor, () => this._detectVisibleLenses.schedule())); groupsIndex++; } @@ -308,8 +317,8 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { return; } - const toResolve: ICodeLensData[][] = []; - const lenses: CodeLens[] = []; + const toResolve: CodeLensItem[][] = []; + const lenses: CodeLensWidget[] = []; this._lenses.forEach((lens) => { const request = lens.computeIfNecessary(model); if (request) { @@ -326,7 +335,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { const promises = toResolve.map((request, i) => { - const resolvedSymbols = new Array(request.length); + const resolvedSymbols = new Array(request.length); const promises = request.map((request, i) => { if (!request.symbol.command && typeof request.provider.resolveCodeLens === 'function') { return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => { diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 9a0a0bbe9c1b34f92865b09d5b912ab23257460b..24ed75a44056642023e9cd983a0372867d6d6715 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -11,9 +11,9 @@ import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { Command, ICodeLensSymbol } from 'vs/editor/common/modes'; +import { Command, CodeLens } from 'vs/editor/common/modes'; import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegistry'; -import { ICodeLensData } from 'vs/editor/contrib/codelens/codelens'; +import { CodeLensItem } from 'vs/editor/contrib/codelens/codelens'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -65,7 +65,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { constructor( editor: editorBrowser.ICodeEditor, symbolRange: Range, - data: ICodeLensData[] + data: CodeLensItem[] ) { this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; @@ -88,7 +88,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._domNode.innerHTML = ' '; } - withCommands(inSymbols: Array, animate: boolean): void { + withCommands(inSymbols: Array, animate: boolean): void { this._commands.clear(); const symbols = coalesce(inSymbols); @@ -189,17 +189,17 @@ export class CodeLensHelper { } } -export class CodeLens { +export class CodeLensWidget { private readonly _editor: editorBrowser.ICodeEditor; private readonly _viewZone: CodeLensViewZone; private readonly _viewZoneId: number; private readonly _contentWidget: CodeLensContentWidget; private _decorationIds: string[]; - private _data: ICodeLensData[]; + private _data: CodeLensItem[]; constructor( - data: ICodeLensData[], + data: CodeLensItem[], editor: editorBrowser.ICodeEditor, helper: CodeLensHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor, @@ -256,7 +256,7 @@ export class CodeLens { }); } - updateCodeLensSymbols(data: ICodeLensData[], helper: CodeLensHelper): void { + updateCodeLensSymbols(data: CodeLensItem[], helper: CodeLensHelper): void { while (this._decorationIds.length) { helper.removeDecoration(this._decorationIds.pop()!); } @@ -270,7 +270,7 @@ export class CodeLens { }); } - computeIfNecessary(model: ITextModel): ICodeLensData[] | null { + computeIfNecessary(model: ITextModel): CodeLensItem[] | null { if (!this._contentWidget.isVisible()) { return null; } @@ -285,7 +285,7 @@ export class CodeLens { return this._data; } - updateCommands(symbols: Array): void { + updateCommands(symbols: Array): void { this._contentWidget.withCommands(symbols, true); for (let i = 0; i < this._data.length; i++) { const resolved = symbols[i]; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2405c549d10991adfc2cc2344f6419cd065294ed..02d3ee384c97595d436f963fc6b948881e9e93c4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -5449,16 +5449,21 @@ declare namespace monaco.languages { arguments?: any[]; } - export interface ICodeLensSymbol { + export interface CodeLens { range: IRange; id?: string; command?: Command; } + export interface CodeLensList { + lenses: CodeLens[]; + dispose(): void; + } + export interface CodeLensProvider { onDidChange?: IEvent; - provideCodeLenses(model: editor.ITextModel, token: CancellationToken): ProviderResult; - resolveCodeLens?(model: editor.ITextModel, codeLens: ICodeLensSymbol, token: CancellationToken): ProviderResult; + provideCodeLenses(model: editor.ITextModel, token: CancellationToken): ProviderResult; + resolveCodeLens?(model: editor.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult; } export interface ILanguageExtensionPoint { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 474bde4c500d5c3331c3a3455cf5a15e649dd503..8e5dc539dc46b8e79a69b5faf16c25369819b3ac 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -139,25 +139,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void { const provider = { - provideCodeLenses: (model: ITextModel, token: CancellationToken): modes.ICodeLensSymbol[] | Promise => { - return this._proxy.$provideCodeLenses(handle, model.uri, token).then(dto => { - if (dto) { - dto.forEach(obj => { - this._heapService.trackObject(obj); - this._heapService.trackObject(obj.command); - }); + provideCodeLenses: (model: ITextModel, token: CancellationToken): Promise => { + return this._proxy.$provideCodeLenses(handle, model.uri, token).then(listDto => { + if (!listDto) { + return undefined; } - return dto; + return { + lenses: listDto.lenses, + dispose: () => listDto.cacheId && this._proxy.$releaseCodeLenses(handle, listDto.cacheId) + }; }); }, - resolveCodeLens: (_model: ITextModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): Promise => { - return this._proxy.$resolveCodeLens(handle, codeLens, token).then(obj => { - if (obj) { - this._heapService.trackObject(obj); - this._heapService.trackObject(obj.command); - } - return obj; - }); + resolveCodeLens: (_model: ITextModel, codeLens: modes.CodeLens, token: CancellationToken): Promise => { + return this._proxy.$resolveCodeLens(handle, codeLens, token); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9f88aa946ab15d401275f171069aa9c2853ca682..06160349ea4dfaa41a036972862473ceb9312853 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1008,9 +1008,14 @@ export interface LinkDto { tooltip?: string; } -export interface CodeLensDto extends ObjectIdentifier { +export interface CodeLensListDto { + cacheId?: number; + lenses: CodeLensDto[]; +} + +export interface CodeLensDto { + cacheId?: ChainedCacheId; range: IRange; - id?: string; command?: CommandDto; } @@ -1026,8 +1031,9 @@ export interface CallHierarchyDto { export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise; + $releaseCodeLenses(handle: number, id: number): void; $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index d4f1eb5ba6dd85d31790f8a8ce7f19c361e0f754..0c5874b2b06672e8c04bffebd634415ece8e69b0 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -504,7 +504,7 @@ export class ExtHostApiCommands { private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise { const args = { resource, itemResolveCount }; - return this._commands.executeCommand('_executeCodeLensProvider', args) + return this._commands.executeCommand('_executeCodeLensProvider', args) .then(tryMapWith(item => { return new types.CodeLens( typeConverters.Range.to(item.range), diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 59edc45abf376c170cf1e1b9e636c0dcc373a374..11df6eeef3cff3a4e7b9256ea7c7b840b7a2ba00 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -18,6 +18,7 @@ import { revive } from 'vs/base/common/marshalling'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { URI } from 'vs/base/common/uri'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; interface CommandHandler { callback: Function; @@ -211,6 +212,35 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } + toInternal2(command: vscode.Command | undefined, disposables: DisposableStore): CommandDto | undefined { + + if (!command) { + return undefined; + } + + const result: CommandDto = { + $ident: undefined, + id: command.command, + title: command.title, + tooltip: command.tooltip + }; + + if (command.command && isNonEmptyArray(command.arguments)) { + // we have a contributed command with arguments. that + // means we don't want to send the arguments around + + const id = this._heap.keep(command); + disposables.add(toDisposable(() => this._heap.delete(id))); + result.$ident = id; + + result.id = this._delegatingCommandId; + result.arguments = [id]; + + } + + return result; + } + toInternal(command: vscode.Command): CommandDto; toInternal(command: undefined): undefined; toInternal(command: vscode.Command | undefined): CommandDto | undefined; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 7a6103a6b3bb5a455a4aa99d99a853ee101c2030..dc0e77b0610d10f316294f76499f53f937734822 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; @@ -28,6 +28,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { LRUCache } from 'vs/base/common/map'; import { IURITransformer } from 'vs/base/common/uriIpc'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; // --- adapter @@ -102,34 +103,48 @@ class CodeLensAdapter { private static _badCmd: vscode.Command = { command: 'missing', title: '!!MISSING: command!!' }; + private readonly _cache = new Cache(); + private readonly _disposables = new Map(); + constructor( private readonly _documents: ExtHostDocuments, private readonly _commands: CommandsConverter, - private readonly _heapService: ExtHostHeapService, private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { + provideCodeLenses(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { - const result: CodeLensDto[] = []; - if (isNonEmptyArray(lenses)) { - for (const lens of lenses) { - const id = this._heapService.keep(lens); - result.push(ObjectIdentifier.mixin({ - range: typeConvert.Range.from(lens.range), - command: this._commands.toInternal(lens.command) - }, id)); - } + + if (!lenses || token.isCancellationRequested) { + return undefined; + } + + const cacheId = this._cache.add(lenses); + const disposables = new DisposableStore(); + this._disposables.set(cacheId, disposables); + + const result: CodeLensListDto = { + cacheId, + lenses: [], + }; + + for (let i = 0; i < lenses.length; i++) { + result.lenses.push({ + cacheId: [cacheId, i], + range: typeConvert.Range.from(lenses[i].range), + command: this._commands.toInternal2(lenses[i].command, disposables) + }); } + return result; }); } resolveCodeLens(symbol: CodeLensDto, token: CancellationToken): Promise { - const lens = this._heapService.get(ObjectIdentifier.of(symbol)); + const lens = symbol.cacheId && this._cache.get(...symbol.cacheId); if (!lens) { return Promise.resolve(undefined); } @@ -147,6 +162,12 @@ class CodeLensAdapter { return symbol; }); } + + releaseCodeLenses(cachedId: number): void { + dispose(this._disposables.get(cachedId)); + this._disposables.delete(cachedId); + this._cache.delete(cachedId); + } } function convertToLocationLinks(value: vscode.Definition): modes.LocationLink[] { @@ -1136,7 +1157,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { const handle = this._nextHandle(); const eventHandle = typeof provider.onDidChangeCodeLenses === 'function' ? this._nextHandle() : undefined; - this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider), extension)); + this._adapter.set(handle, new AdapterData(new CodeLensAdapter(this._documents, this._commands.converter, provider), extension)); this._proxy.$registerCodeLensSupport(handle, this._transformDocumentSelector(selector), eventHandle); let result = this._createDisposable(handle); @@ -1148,14 +1169,18 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return result; } - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), []); + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined); } - $resolveCodeLens(handle: number, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { + $resolveCodeLens(handle: number, symbol: CodeLensDto, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined); } + $releaseCodeLenses(handle: number, cacheId: number): void { + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined); + } + // --- declaration registerDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index d73ba1096ac925755dcd97493b2eb3f4a493544c..ff72eb8df42dcd66b77ed1709dfd048af9201769 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -194,7 +194,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); + assert.equal(value.lenses.length, 1); }); test('CodeLens, do not resolve a resolved lens', async () => { @@ -212,8 +212,8 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); - const data = value[0]; + assert.equal(value.lenses.length, 1); + const [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.equal(symbol!.command!.id, 'id'); assert.equal(symbol!.command!.title, 'Title'); @@ -229,8 +229,8 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); - assert.equal(value.length, 1); - let data = value[0]; + assert.equal(value.lenses.length, 1); + let [data] = value.lenses; const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); assert.equal(symbol!.command!.id, 'missing'); assert.equal(symbol!.command!.title, '!!MISSING: command!!');