From 91b13bc560e1bb445a6f519bfc7d5a0727305207 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 23 Nov 2022 01:27:02 +0800 Subject: [PATCH] avoid cache cancelled response for CodeLens --- src/server/features/baseCodeLensProvider.ts | 50 +++------------------ src/server/languageProvider.ts | 5 +-- src/server/tsServer/cachedResponse.ts | 48 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 src/server/tsServer/cachedResponse.ts diff --git a/src/server/features/baseCodeLensProvider.ts b/src/server/features/baseCodeLensProvider.ts index ff47b04..8b6267b 100644 --- a/src/server/features/baseCodeLensProvider.ts +++ b/src/server/features/baseCodeLensProvider.ts @@ -2,58 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocument } from 'coc.nvim' -import { CodeLensProvider } from 'coc.nvim' -import { CancellationToken, CodeLens, Emitter, Event, Range } from 'vscode-languageserver-protocol' +import { CodeLensProvider, TextDocument } from 'coc.nvim' +import { CancellationToken, CodeLens, Range } from 'vscode-languageserver-protocol' import * as Proto from '../protocol' +import { CachedResponse } from '../tsServer/cachedResponse' import { ITypeScriptServiceClient } from '../typescriptService' import { escapeRegExp } from '../utils/regexp' import * as typeConverters from '../utils/typeConverters' -export class CachedNavTreeResponse { - private response?: Promise - private version = -1 - private document = '' - - public execute(document: TextDocument, f: () => Promise): Promise { - if (this.matches(document)) { - return this.response - } - - return this.update(document, f()) - } - - private matches(document: TextDocument): boolean { - return ( - this.version === document.version && - this.document === document.uri.toString() - ) - } - - private update( - document: TextDocument, - response: Promise - ): Promise { - this.response = response - this.version = document.version - this.document = document.uri.toString() - return response - } -} - export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider { - private onDidChangeCodeLensesEmitter = new Emitter() - public constructor( protected client: ITypeScriptServiceClient, - private cachedResponse: CachedNavTreeResponse, + private cachedResponse: CachedResponse, protected modeId: string ) {} - public get onDidChangeCodeLenses(): Event { - return this.onDidChangeCodeLensesEmitter.event - } - public async provideCodeLenses( document: TextDocument, token: CancellationToken @@ -65,12 +28,11 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider try { const response = await this.cachedResponse.execute(document, () => - this.client.execute('navtree', { file: filepath }, token) as any + this.client.execute('navtree', { file: filepath }, token) ) - if (!response) { + if (response.type !== 'response') { return [] } - const tree = response.body const referenceableSpans: Range[] = [] if (tree && tree.childItems) { diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 2590d82..f33c0f8 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -5,7 +5,6 @@ import { CodeActionKind, Diagnostic, DiagnosticSeverity, Disposable, disposeAll, DocumentFilter, languages, TextDocument, Uri, workspace } from 'coc.nvim' import path from 'path' import * as fileSchemes from '../utils/fileSchemes' -import { CachedNavTreeResponse } from './features/baseCodeLensProvider' import CallHierarchyProvider from './features/callHierarchy' import CompletionItemProvider from './features/completionItemProvider' import DefinitionProvider from './features/definitionProvider' @@ -32,8 +31,8 @@ import { TypeScriptDocumentSemanticTokensProvider } from './features/semanticTok import SignatureHelpProvider from './features/signatureHelp' import SmartSelection from './features/smartSelect' import TagClosing from './features/tagClosing' -import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import { OrganizeImportsCodeActionProvider } from './organizeImports' +import { CachedResponse } from './tsServer/cachedResponse' import { ClientCapability } from './typescriptService' import TypeScriptServiceClient from './typescriptServiceClient' import API from './utils/api' @@ -209,7 +208,7 @@ export default class LanguageProvider { 'tsserver', [CodeActionKind.QuickFix])) if (hasSemantic) { - let cachedResponse = new CachedNavTreeResponse() + let cachedResponse = new CachedResponse() if (this.client.apiVersion.gte(API.v206) && conf.get('referencesCodeLens.enabled')) { this._register(languages.registerCodeLensProvider(documentSelector.semantic, new ReferencesCodeLensProvider(client, cachedResponse, this.description.id))) } diff --git a/src/server/tsServer/cachedResponse.ts b/src/server/tsServer/cachedResponse.ts new file mode 100644 index 0000000..37913a1 --- /dev/null +++ b/src/server/tsServer/cachedResponse.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextDocument } from 'coc.nvim' +import type * as Proto from '../protocol' +import { ServerResponse } from '../typescriptService' + +type Resolve = () => Promise> + +/** + * Caches a class of TS Server request based on document. + */ +export class CachedResponse { + private response?: Promise> + private version: number = -1; + private document: string = ''; + + /** + * Execute a request. May return cached value or resolve the new value + * + * Caller must ensure that all input `resolve` functions return equivalent results (keyed only off of document). + */ + public execute( + document: TextDocument, + resolve: Resolve + ): Promise> { + if (this.response && this.matches(document)) { + // Chain so that on cancellation we fall back to the next resolve + return this.response = this.response.then(result => result.type === 'cancelled' ? resolve() : result) + } + return this.reset(document, resolve) + } + + private matches(document: TextDocument): boolean { + return this.version === document.version && this.document === document.uri.toString() + } + + private async reset( + document: TextDocument, + resolve: Resolve + ): Promise> { + this.version = document.version + this.document = document.uri.toString() + return this.response = resolve() + } +}