diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index bae673a1dd7..4937473a077 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -427,29 +427,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- links - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void { - this._registrations[handle] = modes.LinkProviderRegistry.register(selector, { + $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void { + const provider: modes.LinkProvider = { provideLinks: (model, token) => { return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { - if (dto) { - dto.forEach(obj => { - MainThreadLanguageFeatures._reviveLinkDTO(obj); - this._heapService.trackObject(obj); - }); + if (!dto) { + return undefined; } - return { links: dto as modes.ILink[] }; - }); - }, - resolveLink: (link, token) => { - return this._proxy.$resolveDocumentLink(handle, link, token).then(obj => { - if (obj) { - MainThreadLanguageFeatures._reviveLinkDTO(obj); - this._heapService.trackObject(obj); - } - return obj as modes.ILink; + return { + links: dto.links.map(MainThreadLanguageFeatures._reviveLinkDTO), + dispose: () => { + if (typeof dto.id === 'number') { + this._proxy.$releaseDocumentLinks(handle, dto.id); + } + } + }; }); } - }); + }; + if (supportsResolve) { + provider.resolveLink = (link, token) => { + return this._proxy.$resolveDocumentLink(handle, link as LinkDto, token).then(obj => { + return obj && MainThreadLanguageFeatures._reviveLinkDTO(obj); + }); + }; + } + this._registrations[handle] = modes.LinkProviderRegistry.register(selector, provider); } // --- colors diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e8ad002e196..7fda02d342c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -336,7 +336,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportsResolveInitialValues: boolean): void; $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void; $registerSignatureHelpProvider(handle: number, selector: ISerializedDocumentFilter[], metadata: ISerializedSignatureHelpProviderMetadata): void; - $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void; + $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[], supportsResolve: boolean): void; $registerDocumentColorProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerFoldingRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void; @@ -953,7 +953,16 @@ export interface CodeActionDto { isPreferred?: boolean; } -export interface LinkDto extends ObjectIdentifier { +export type CacheId = number; +export type ChainedCacheId = [CacheId, CacheId]; + +export interface LinksListDto { + id?: CacheId; + links: LinkDto[]; +} + +export interface LinkDto { + cacheId?: ChainedCacheId; range: IRange; url?: string | UriComponents; } @@ -1002,8 +1011,9 @@ export interface ExtHostLanguageFeaturesShape { $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, id: number, pid: number, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; $resolveDocumentLink(handle: number, link: LinkDto, token: CancellationToken): Promise; + $releaseDocumentLinks(handle: number, id: number): void; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise; $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 8c98b018061..b9b7b890342 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/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, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto } from '../common/extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto, SuggestDataDto, LinksListDto } from '../common/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'; @@ -800,49 +800,78 @@ class SignatureHelpAdapter { } } +class Cache { + + private _data = new Map(); + private _idPool = 0; + + add(item: T[]): number { + const id = this._idPool++; + this._data.set(id, item); + return id; + } + + get(pid: number, id: number): T | undefined { + return this._data.has(pid) ? this._data.get(pid)![id] : undefined; + } + + delete(id: number) { + this._data.delete(id); + } +} + class LinkProviderAdapter { + private _cache = new Cache(); + constructor( private readonly _documents: ExtHostDocuments, - private readonly _heapService: ExtHostHeapService, private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { - if (!Array.isArray(links)) { + if (!Array.isArray(links) || links.length === 0) { + // bad result return undefined; } - const result: LinkDto[] = []; - for (const link of links) { - const data = typeConvert.DocumentLink.from(link); - const id = this._heapService.keep(link); - result.push(ObjectIdentifier.mixin(data, id)); + + if (typeof this._provider.resolveDocumentLink !== 'function') { + // no resolve -> no caching + return { links: links.map(typeConvert.DocumentLink.from) }; + + } else { + // cache links for future resolving + const pid = this._cache.add(links); + const result: LinksListDto = { links: [], id: pid }; + for (let i = 0; i < links.length; i++) { + const dto: LinkDto = typeConvert.DocumentLink.from(links[i]); + dto.cacheId = [pid, i]; + result.links.push(dto); + } + return result; } - return result; }); } resolveLink(link: LinkDto, token: CancellationToken): Promise { - if (typeof this._provider.resolveDocumentLink !== 'function') { + if (!link.cacheId || typeof this._provider.resolveDocumentLink !== 'function') { return Promise.resolve(undefined); } - - const id = ObjectIdentifier.of(link); - const item = this._heapService.get(id); + const item = this._cache.get(...link.cacheId); if (!item) { return Promise.resolve(undefined); } - return asPromise(() => this._provider.resolveDocumentLink!(item, token)).then(value => { - if (value) { - return typeConvert.DocumentLink.from(value); - } - return undefined; + return value && typeConvert.DocumentLink.from(value) || undefined; }); } + + releaseLinks(id: number): any { + this._cache.delete(id); + } } class ColorProviderAdapter { @@ -1400,19 +1429,23 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- links registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { - const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, this._heapService, provider), extension); - this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector)); + const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, provider), extension); + this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector), typeof provider.resolveDocumentLink === 'function'); return this._createDisposable(handle); } - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource), token), undefined); } - $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise { + $resolveDocumentLink(handle: number, link: LinkDto, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(link, token), undefined); } + $releaseDocumentLinks(handle: number, id: number): void { + this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.releaseLinks(id), undefined); + } + registerColorProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { const handle = this._addNewAdapter(new ColorProviderAdapter(this._documents, provider), extension); this._proxy.$registerDocumentColorProvider(handle, this._transformDocumentSelector(selector));