diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 1b872a3c2..c15475304 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -63,7 +63,7 @@ "vscode-css-languageservice": "5.0.0", "vscode-emmet-helper": "2.1.2", "vscode-html-languageservice": "4.0.0", - "vscode-languageserver": "7.0.0", + "vscode-languageserver": "7.1.0-next.4", "vscode-languageserver-types": "3.16.0", "vscode-uri": "2.1.2" } diff --git a/packages/language-server/src/plugins/PluginHost.ts b/packages/language-server/src/plugins/PluginHost.ts index c32c47228..54fbb29fd 100644 --- a/packages/language-server/src/plugins/PluginHost.ts +++ b/packages/language-server/src/plugins/PluginHost.ts @@ -1,5 +1,6 @@ import { flatten } from 'lodash'; import { + CancellationToken, CodeAction, CodeActionContext, Color, @@ -86,7 +87,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { async getCompletions( textDocument: TextDocumentIdentifier, position: Position, - completionContext?: CompletionContext + completionContext?: CompletionContext, + cancellationToken?: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); if (!document) { @@ -96,7 +98,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { const completions = ( await this.execute( 'getCompletions', - [document, position, completionContext], + [document, position, completionContext, cancellationToken], ExecuteMode.Collect ) ).filter((completion) => completion != null); @@ -127,7 +129,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { async resolveCompletion( textDocument: TextDocumentIdentifier, - completionItem: AppCompletionItem + completionItem: AppCompletionItem, + cancellationToken: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); @@ -137,7 +140,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { const result = await this.execute( 'resolveCompletion', - [document, completionItem], + [document, completionItem, cancellationToken], ExecuteMode.FirstNonNull ); @@ -212,7 +215,10 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { ); } - async getDocumentSymbols(textDocument: TextDocumentIdentifier): Promise { + async getDocumentSymbols( + textDocument: TextDocumentIdentifier, + cancellationToken: CancellationToken + ): Promise { const document = this.getDocument(textDocument.uri); if (!document) { throw new Error('Cannot call methods on an unopened document'); @@ -221,7 +227,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { return flatten( await this.execute( 'getDocumentSymbols', - [document], + [document, cancellationToken], ExecuteMode.Collect ) ); @@ -256,7 +262,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { async getCodeActions( textDocument: TextDocumentIdentifier, range: Range, - context: CodeActionContext + context: CodeActionContext, + cancellationToken: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); if (!document) { @@ -266,7 +273,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { return flatten( await this.execute( 'getCodeActions', - [document, range, context], + [document, range, context, cancellationToken], ExecuteMode.Collect ) ); @@ -350,7 +357,8 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { async getSignatureHelp( textDocument: TextDocumentIdentifier, position: Position, - context: SignatureHelpContext | undefined + context: SignatureHelpContext | undefined, + cancellationToken: CancellationToken ): Promise { const document = this.getDocument(textDocument.uri); if (!document) { @@ -359,7 +367,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { return await this.execute( 'getSignatureHelp', - [document, position, context], + [document, position, context, cancellationToken], ExecuteMode.FirstNonNull ); } @@ -403,7 +411,11 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { } } - async getSemanticTokens(textDocument: TextDocumentIdentifier, range?: Range) { + async getSemanticTokens( + textDocument: TextDocumentIdentifier, + range?: Range, + cancellationToken?: CancellationToken + ) { const document = this.getDocument(textDocument.uri); if (!document) { throw new Error('Cannot call methods on an unopened document'); @@ -411,7 +423,7 @@ export class PluginHost implements LSProvider, OnWatchFileChanges { return await this.execute( 'getSemanticTokens', - [document, range], + [document, range, cancellationToken], ExecuteMode.FirstNonNull ); } diff --git a/packages/language-server/src/plugins/interfaces.ts b/packages/language-server/src/plugins/interfaces.ts index a4d1122c5..5f6f9ac09 100644 --- a/packages/language-server/src/plugins/interfaces.ts +++ b/packages/language-server/src/plugins/interfaces.ts @@ -1,4 +1,5 @@ import { + CancellationToken, CompletionContext, FileChangeType, LinkedEditingRanges, @@ -53,12 +54,14 @@ export interface CompletionsProvider { getCompletions( document: Document, position: Position, - completionContext?: CompletionContext + completionContext?: CompletionContext, + cancellationToken?: CancellationToken ): Resolvable | null>; resolveCompletion?( document: Document, - completionItem: AppCompletionItem + completionItem: AppCompletionItem, + cancellationToken?: CancellationToken ): Resolvable>; } @@ -83,7 +86,10 @@ export interface ColorPresentationsProvider { } export interface DocumentSymbolsProvider { - getDocumentSymbols(document: Document): Resolvable; + getDocumentSymbols( + document: Document, + cancellationToken?: CancellationToken + ): Resolvable; } export interface DefinitionsProvider { @@ -101,7 +107,8 @@ export interface CodeActionsProvider { getCodeActions( document: Document, range: Range, - context: CodeActionContext + context: CodeActionContext, + cancellationToken?: CancellationToken ): Resolvable; executeCommand?( document: Document, @@ -140,7 +147,8 @@ export interface SignatureHelpProvider { getSignatureHelp( document: Document, position: Position, - context: SignatureHelpContext | undefined + context: SignatureHelpContext | undefined, + cancellationToken?: CancellationToken ): Resolvable; } diff --git a/packages/language-server/src/plugins/svelte/SveltePlugin.ts b/packages/language-server/src/plugins/svelte/SveltePlugin.ts index 7901fb025..88ded0cc9 100644 --- a/packages/language-server/src/plugins/svelte/SveltePlugin.ts +++ b/packages/language-server/src/plugins/svelte/SveltePlugin.ts @@ -9,7 +9,9 @@ import { Range, TextEdit, WorkspaceEdit, - SelectionRange + SelectionRange, + CancellationToken, + CompletionContext } from 'vscode-languageserver'; import { Document } from '../../lib/documents'; import { LSConfigManager, LSSvelteConfig } from '../../ls-config'; @@ -138,12 +140,22 @@ export class SveltePlugin } } - async getCompletions(document: Document, position: Position): Promise { + async getCompletions( + document: Document, + position: Position, + _?: CompletionContext, + cancellationToken?: CancellationToken + ): Promise { if (!this.featureEnabled('completions')) { return null; } - return getCompletions(await this.getSvelteDoc(document), position); + const svelteDoc = await this.getSvelteDoc(document); + if (cancellationToken?.isCancellationRequested) { + return null; + } + + return getCompletions(svelteDoc, position); } async doHover(document: Document, position: Position): Promise { @@ -157,13 +169,19 @@ export class SveltePlugin async getCodeActions( document: Document, range: Range, - context: CodeActionContext + context: CodeActionContext, + cancellationToken?: CancellationToken ): Promise { if (!this.featureEnabled('codeActions')) { return []; } const svelteDoc = await this.getSvelteDoc(document); + + if (cancellationToken?.isCancellationRequested) { + return []; + } + try { return getCodeActions(svelteDoc, range, context); } catch (error) { diff --git a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts index c07a6553a..046628987 100644 --- a/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts +++ b/packages/language-server/src/plugins/typescript/TypeScriptPlugin.ts @@ -1,5 +1,6 @@ import ts, { NavigationTree } from 'typescript'; import { + CancellationToken, CodeAction, CodeActionContext, CompletionContext, @@ -112,12 +113,15 @@ export class TypeScriptPlugin this.semanticTokensProvider = new SemanticTokensProviderImpl(this.lsAndTsDocResolver); } - async getDiagnostics(document: Document): Promise { + async getDiagnostics( + document: Document, + cancellationToken?: CancellationToken + ): Promise { if (!this.featureEnabled('diagnostics')) { return []; } - return this.diagnosticsProvider.getDiagnostics(document); + return this.diagnosticsProvider.getDiagnostics(document, cancellationToken); } async doHover(document: Document, position: Position): Promise { @@ -128,13 +132,21 @@ export class TypeScriptPlugin return this.hoverProvider.doHover(document, position); } - async getDocumentSymbols(document: Document): Promise { + async getDocumentSymbols( + document: Document, + cancellationToken?: CancellationToken + ): Promise { if (!this.featureEnabled('documentSymbols')) { return []; } const { lang, tsDoc } = await this.getLSAndTSDoc(document); const fragment = await tsDoc.getFragment(); + + if (cancellationToken?.isCancellationRequested) { + return []; + } + const navTree = lang.getNavigationTree(tsDoc.filePath); const symbols: SymbolInformation[] = []; @@ -209,7 +221,8 @@ export class TypeScriptPlugin async getCompletions( document: Document, position: Position, - completionContext?: CompletionContext + completionContext?: CompletionContext, + cancellationToken?: CancellationToken ): Promise | null> { if (!this.featureEnabled('completions')) { return null; @@ -224,7 +237,8 @@ export class TypeScriptPlugin const completions = await this.completionProvider.getCompletions( document, position, - completionContext + completionContext, + cancellationToken ); if (completions && tsDirectiveCommentCompletions) { @@ -239,9 +253,14 @@ export class TypeScriptPlugin async resolveCompletion( document: Document, - completionItem: AppCompletionItem + completionItem: AppCompletionItem, + cancellationToken?: CancellationToken ): Promise> { - return this.completionProvider.resolveCompletion(document, completionItem); + return this.completionProvider.resolveCompletion( + document, + completionItem, + cancellationToken + ); } async getDefinitions(document: Document, position: Position): Promise { @@ -304,13 +323,14 @@ export class TypeScriptPlugin async getCodeActions( document: Document, range: Range, - context: CodeActionContext + context: CodeActionContext, + cancellationToken?: CancellationToken ): Promise { if (!this.featureEnabled('codeActions')) { return []; } - return this.codeActionsProvider.getCodeActions(document, range, context); + return this.codeActionsProvider.getCodeActions(document, range, context, cancellationToken); } async executeCommand( @@ -405,23 +425,37 @@ export class TypeScriptPlugin async getSignatureHelp( document: Document, position: Position, - context: SignatureHelpContext | undefined + context: SignatureHelpContext | undefined, + cancellationToken?: CancellationToken ): Promise { if (!this.featureEnabled('signatureHelp')) { return null; } - return this.signatureHelpProvider.getSignatureHelp(document, position, context); + return this.signatureHelpProvider.getSignatureHelp( + document, + position, + context, + cancellationToken + ); } - async getSemanticTokens(textDocument: Document, range?: Range): Promise { + async getSemanticTokens( + textDocument: Document, + range?: Range, + cancellationToken?: CancellationToken + ): Promise { if (!this.featureEnabled('semanticTokens')) { return { data: [] }; } - return this.semanticTokensProvider.getSemanticTokens(textDocument, range); + return this.semanticTokensProvider.getSemanticTokens( + textDocument, + range, + cancellationToken + ); } private async getLSAndTSDoc(document: Document) { diff --git a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts index 9eb90ab20..1261bfd82 100644 --- a/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CodeActionsProvider.ts @@ -1,5 +1,6 @@ import ts from 'typescript'; import { + CancellationToken, CodeAction, CodeActionContext, CodeActionKind, @@ -39,27 +40,31 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { async getCodeActions( document: Document, range: Range, - context: CodeActionContext + context: CodeActionContext, + cancellationToken?: CancellationToken ): Promise { if (context.only?.[0] === CodeActionKind.SourceOrganizeImports) { - return await this.organizeImports(document); + return await this.organizeImports(document, cancellationToken); } if ( context.diagnostics.length && (!context.only || context.only.includes(CodeActionKind.QuickFix)) ) { - return await this.applyQuickfix(document, range, context); + return await this.applyQuickfix(document, range, context, cancellationToken); } if (!context.only || context.only.includes(CodeActionKind.Refactor)) { - return await this.getApplicableRefactors(document, range); + return await this.getApplicableRefactors(document, range, cancellationToken); } return []; } - private async organizeImports(document: Document): Promise { + private async organizeImports( + document: Document, + cancellationToken: CancellationToken | undefined + ): Promise { if (!document.scriptInfo && !document.moduleScriptInfo) { return []; } @@ -67,6 +72,10 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document); const fragment = await tsDoc.getFragment(); + if (cancellationToken?.isCancellationRequested) { + return []; + } + const changes = lang.organizeImports( { fileName: tsDoc.filePath, @@ -149,10 +158,19 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { return range; } - private async applyQuickfix(document: Document, range: Range, context: CodeActionContext) { + private async applyQuickfix( + document: Document, + range: Range, + context: CodeActionContext, + cancellationToken: CancellationToken | undefined + ) { const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document); const fragment = await tsDoc.getFragment(); + if (cancellationToken?.isCancellationRequested) { + return []; + } + const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start)); const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end)); const errorCodes: number[] = context.diagnostics.map((diag) => Number(diag.code)); @@ -257,7 +275,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { ); } - private async getApplicableRefactors(document: Document, range: Range): Promise { + private async getApplicableRefactors( + document: Document, + range: Range, + cancellationToken: CancellationToken | undefined + ): Promise { if ( !isRangeInTag(range, document.scriptInfo) && !isRangeInTag(range, document.moduleScriptInfo) @@ -278,6 +300,11 @@ export class CodeActionsProviderImpl implements CodeActionsProvider { const { lang, tsDoc, userPreferences } = await this.getLSAndTSDoc(document); const fragment = await tsDoc.getFragment(); + + if (cancellationToken?.isCancellationRequested) { + return []; + } + const textRange = { pos: fragment.offsetAt(fragment.getGeneratedPosition(range.start)), end: fragment.offsetAt(fragment.getGeneratedPosition(range.end)) diff --git a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts index c20a9cbe9..1e1cca834 100644 --- a/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/CompletionProvider.ts @@ -1,5 +1,6 @@ import ts from 'typescript'; import { + CancellationToken, CompletionContext, CompletionList, CompletionTriggerKind, @@ -68,7 +69,8 @@ export class CompletionsProviderImpl implements CompletionsProvider | null> { if (isInTag(position, document.styleInfo)) { return null; @@ -130,6 +132,10 @@ export class CompletionsProviderImpl implements CompletionsProvider + completionItem: AppCompletionItem, + cancellationToken?: CancellationToken ): Promise> { const { data: comp } = completionItem; const { tsDoc, lang, userPreferences } = await this.lsAndTsDocResolver.getLSAndTSDoc( @@ -327,11 +338,12 @@ export class CompletionsProviderImpl implements CompletionsProvider { + async getDiagnostics( + document: Document, + cancellationToken?: CancellationToken + ): Promise { const { lang, tsDoc } = await this.getLSAndTSDoc(document); - if (['coffee', 'coffeescript'].includes(document.getLanguageAttribute('script'))) { + if ( + ['coffee', 'coffeescript'].includes(document.getLanguageAttribute('script')) || + cancellationToken?.isCancellationRequested + ) { return []; } diff --git a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts index d2e3e7061..34727d0a5 100644 --- a/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/SemanticTokensProvider.ts @@ -1,5 +1,10 @@ import ts from 'typescript'; -import { Range, SemanticTokens, SemanticTokensBuilder } from 'vscode-languageserver'; +import { + CancellationToken, + Range, + SemanticTokens, + SemanticTokensBuilder +} from 'vscode-languageserver'; import { Document, mapRangeToOriginal } from '../../../lib/documents'; import { SemanticTokensProvider } from '../../interfaces'; import { SnapshotFragment } from '../DocumentSnapshot'; @@ -11,12 +16,19 @@ const CONTENT_LENGTH_LIMIT = 50000; export class SemanticTokensProviderImpl implements SemanticTokensProvider { constructor(private readonly lsAndTsDocResolver: LSAndTSDocResolver) {} - async getSemanticTokens(textDocument: Document, range?: Range): Promise { + async getSemanticTokens( + textDocument: Document, + range?: Range, + cancellationToken?: CancellationToken + ): Promise { const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(textDocument); const fragment = await tsDoc.getFragment(); // for better performance, don't do full-file semantic tokens when the file is too big - if (!range && fragment.text.length > CONTENT_LENGTH_LIMIT) { + if ( + (!range && fragment.text.length > CONTENT_LENGTH_LIMIT) || + cancellationToken?.isCancellationRequested + ) { return null; } diff --git a/packages/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts b/packages/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts index 82d900c77..194997713 100644 --- a/packages/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts +++ b/packages/language-server/src/plugins/typescript/features/SignatureHelpProvider.ts @@ -6,7 +6,8 @@ import { SignatureHelpTriggerKind, SignatureInformation, ParameterInformation, - MarkupKind + MarkupKind, + CancellationToken } from 'vscode-languageserver'; import { SignatureHelpProvider } from '../..'; import { Document } from '../../../lib/documents'; @@ -22,11 +23,16 @@ export class SignatureHelpProviderImpl implements SignatureHelpProvider { async getSignatureHelp( document: Document, position: Position, - context: SignatureHelpContext | undefined + context: SignatureHelpContext | undefined, + cancellationToken?: CancellationToken ): Promise { const { lang, tsDoc } = await this.lsAndTsDocResolver.getLSAndTSDoc(document); const fragment = await tsDoc.getFragment(); + if (cancellationToken?.isCancellationRequested) { + return null; + } + const offset = fragment.offsetAt(fragment.getGeneratedPosition(position)); const triggerReason = this.toTsTriggerReason(context); const info = lang.getSignatureHelpItems( diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 3aa029aa9..6933dc9c8 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -255,8 +255,8 @@ export function startServer(options?: LSOptions) { docManager.updateDocument(evt.textDocument, evt.contentChanges) ); connection.onHover((evt) => pluginHost.doHover(evt.textDocument, evt.position)); - connection.onCompletion((evt) => - pluginHost.getCompletions(evt.textDocument, evt.position, evt.context) + connection.onCompletion((evt, cancellationToken) => + pluginHost.getCompletions(evt.textDocument, evt.position, evt.context, cancellationToken) ); connection.onDocumentFormatting((evt) => pluginHost.formatDocument(evt.textDocument, evt.options) @@ -268,14 +268,16 @@ export function startServer(options?: LSOptions) { connection.onColorPresentation((evt) => pluginHost.getColorPresentations(evt.textDocument, evt.range, evt.color) ); - connection.onDocumentSymbol((evt) => pluginHost.getDocumentSymbols(evt.textDocument)); + connection.onDocumentSymbol((evt, cancellationToken) => + pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken) + ); connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position)); connection.onReferences((evt) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context) ); - connection.onCodeAction((evt) => - pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context) + connection.onCodeAction((evt, cancellationToken) => + pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context, cancellationToken) ); connection.onExecuteCommand(async (evt) => { const result = await pluginHost.executeCommand( @@ -294,18 +296,18 @@ export function startServer(options?: LSOptions) { } }); - connection.onCompletionResolve((completionItem) => { + connection.onCompletionResolve((completionItem, cancellationToken) => { const data = (completionItem as AppCompletionItem).data as TextDocumentIdentifier; if (!data) { return completionItem; } - return pluginHost.resolveCompletion(data, completionItem); + return pluginHost.resolveCompletion(data, completionItem, cancellationToken); }); - connection.onSignatureHelp((evt) => - pluginHost.getSignatureHelp(evt.textDocument, evt.position, evt.context) + connection.onSignatureHelp((evt, cancellationToken) => + pluginHost.getSignatureHelp(evt.textDocument, evt.position, evt.context, cancellationToken) ); connection.onSelectionRanges((evt) => @@ -343,11 +345,11 @@ export function startServer(options?: LSOptions) { updateAllDiagnostics(); }); - connection.onRequest(SemanticTokensRequest.type, (evt) => - pluginHost.getSemanticTokens(evt.textDocument) + connection.onRequest(SemanticTokensRequest.type, (evt, cancellationToken) => + pluginHost.getSemanticTokens(evt.textDocument, undefined, cancellationToken) ); - connection.onRequest(SemanticTokensRangeRequest.type, (evt) => - pluginHost.getSemanticTokens(evt.textDocument, evt.range) + connection.onRequest(SemanticTokensRangeRequest.type, (evt, cancellationToken) => + pluginHost.getSemanticTokens(evt.textDocument, evt.range, cancellationToken) ); connection.onRequest( diff --git a/packages/language-server/test/plugins/PluginHost.test.ts b/packages/language-server/test/plugins/PluginHost.test.ts index bfe733a35..ee89abfc0 100644 --- a/packages/language-server/test/plugins/PluginHost.test.ts +++ b/packages/language-server/test/plugins/PluginHost.test.ts @@ -80,10 +80,16 @@ describe('PluginHost', () => { }); sinon.assert.calledOnce(plugin.getCompletions); - sinon.assert.calledWithExactly(plugin.getCompletions, document, pos, { - triggerKind: CompletionTriggerKind.TriggerCharacter, - triggerCharacter: '.' - }); + sinon.assert.calledWithExactly( + plugin.getCompletions, + document, + pos, + { + triggerKind: CompletionTriggerKind.TriggerCharacter, + triggerCharacter: '.' + }, + undefined + ); }); describe('getCompletions (incomplete)', () => { diff --git a/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts b/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts index 78646827c..188831cb3 100644 --- a/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts +++ b/packages/language-server/test/plugins/svelte/SveltePlugin.test.ts @@ -1,7 +1,12 @@ import * as assert from 'assert'; import { SveltePlugin } from '../../../src/plugins'; import { DocumentManager, Document } from '../../../src/lib/documents'; -import { Diagnostic, Range, DiagnosticSeverity } from 'vscode-languageserver'; +import { + Diagnostic, + Range, + DiagnosticSeverity, + CancellationTokenSource +} from 'vscode-languageserver'; import { LSConfigManager } from '../../../src/ls-config'; import * as importPackage from '../../../src/importPackage'; import sinon from 'sinon'; @@ -143,4 +148,50 @@ describe('Svelte Plugin', () => { }); }); }); + + it('can cancel completion before promise resolved', async () => { + const { plugin, document } = setup('{#'); + const cancellationTokenSource = new CancellationTokenSource(); + + const completionsPromise = plugin.getCompletions( + document, + { line: 0, character: 2 }, + undefined, + cancellationTokenSource.token + ); + + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await completionsPromise, null); + }); + + it('can cancel code action before promise resolved', async () => { + const { plugin, document } = setup(''); + const cancellationTokenSource = new CancellationTokenSource(); + const range = { + start: { line: 0, character: 0 }, + end: { line: 0, character: 7 } + }; + + const codeActionPromise = plugin.getCodeActions( + document, + range, + { + diagnostics: [ + { + message: 'A11y: element should have child content', + code: 'a11y-missing-content', + range, + severity: DiagnosticSeverity.Warning, + source: 'svelte' + } + ] + }, + cancellationTokenSource.token + ); + + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await codeActionPromise, []); + }); }); diff --git a/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts b/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts index 0fa3fe27b..3fc0ba311 100644 --- a/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts +++ b/packages/language-server/test/plugins/typescript/TypescriptPlugin.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import ts from 'typescript'; import fs from 'fs'; -import { FileChangeType, Position, Range } from 'vscode-languageserver'; +import { CancellationTokenSource, FileChangeType, Position, Range } from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../src/lib/documents'; import { LSConfigManager } from '../../../src/ls-config'; import { LSAndTSDocResolver, TypeScriptPlugin } from '../../../src/plugins'; @@ -99,6 +99,15 @@ describe('TypescriptPlugin', () => { ); }); + it('can cancel document symbols before promise resolved', async () => { + const { plugin, document } = setup('documentsymbols.svelte'); + const cancellationTokenSource = new CancellationTokenSource(); + const symbolsPromise = plugin.getDocumentSymbols(document, cancellationTokenSource.token); + + cancellationTokenSource.cancel(); + assert.deepStrictEqual(await symbolsPromise, []); + }); + it('provides definitions within svelte doc', async () => { const { plugin, document } = setup('definitions.svelte'); diff --git a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts index 874deb7a6..882a922b5 100644 --- a/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CodeActionsProvider.test.ts @@ -10,7 +10,8 @@ import { Position, CodeActionKind, TextDocumentEdit, - CodeAction + CodeAction, + CancellationTokenSource } from 'vscode-languageserver'; import { CompletionsProviderImpl } from '../../../../src/plugins/typescript/features/CompletionProvider'; import { LSConfigManager } from '../../../../src/ls-config'; @@ -795,4 +796,46 @@ describe('CodeActionsProvider', () => { ] }); }); + + it('can cancel quick fix before promise resolved', async () => { + const { provider, document } = setup('codeactions.svelte'); + const cancellationTokenSource = new CancellationTokenSource(); + + const codeActionsPromise = provider.getCodeActions( + document, + Range.create(Position.create(6, 4), Position.create(6, 5)), + { + diagnostics: [ + { + code: 6133, + message: "'a' is declared but its value is never read.", + range: Range.create(Position.create(6, 4), Position.create(6, 5)), + source: 'ts' + } + ], + only: [CodeActionKind.QuickFix] + }, + cancellationTokenSource.token + ); + + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await codeActionsPromise, []); + }); + + it('can cancel refactor before promise resolved', async () => { + const { provider, document } = setup('codeactions.svelte'); + const cancellationTokenSource = new CancellationTokenSource(); + + const codeActionsPromise = provider.getCodeActions( + document, + Range.create(Position.create(8, 8), Position.create(8, 42)), + { diagnostics: [], only: [CodeActionKind.Refactor] }, + cancellationTokenSource.token + ); + + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await codeActionsPromise, []); + }); }); diff --git a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts index 52f16378b..655da186f 100644 --- a/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/CompletionProvider.test.ts @@ -12,7 +12,8 @@ import { Range, CompletionTriggerKind, MarkupKind, - TextEdit + TextEdit, + CancellationTokenSource } from 'vscode-languageserver'; import { CompletionsProviderImpl, @@ -723,6 +724,43 @@ describe('CompletionProviderImpl', () => { ]); }); + it('can be canceled before promise resolved', async () => { + const { completionProvider, document } = setup('importcompletions1.svelte'); + const cancellationTokenSource = new CancellationTokenSource(); + + const completionsPromise = completionProvider.getCompletions( + document, + Position.create(1, 3), + undefined, + cancellationTokenSource.token + ); + + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await completionsPromise, null); + }); + + it('can cancel completion resolving before promise resolved', async () => { + const { completionProvider, document } = setup('importcompletions1.svelte'); + const cancellationTokenSource = new CancellationTokenSource(); + + const completions = await completionProvider.getCompletions( + document, + Position.create(1, 3) + ); + + const item = completions?.items.find((item) => item.label === 'blubb'); + + const completionResolvingPromise = completionProvider.resolveCompletion( + document, + item!, + cancellationTokenSource.token + ); + cancellationTokenSource.cancel(); + + assert.deepStrictEqual((await completionResolvingPromise).additionalTextEdits, undefined); + }); + const testForJsDocTemplateCompletion = async (position: Position, newText: string) => { const { completionProvider, document } = setup('jsdoc-completions.svelte'); diff --git a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts index b596bb558..605a03f6e 100644 --- a/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/SemanticTokensProvider.test.ts @@ -1,7 +1,12 @@ import path from 'path'; import ts from 'typescript'; import assert from 'assert'; -import { Position, Range, SemanticTokensBuilder } from 'vscode-languageserver'; +import { + CancellationTokenSource, + Position, + Range, + SemanticTokensBuilder +} from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../../src/lib/documents'; import { TokenModifier, TokenType } from '../../../../src/lib/semanticToken/semanticTokenLegend'; import { LSConfigManager } from '../../../../src/ls-config'; @@ -53,6 +58,19 @@ describe('SemanticTokensProvider', () => { assertResult(data, getExpected(/* isFull */ false)); }); + it('can cancel semantic token before promise resolved', async () => { + const { provider, document } = setup(); + const cancellationTokenSource = new CancellationTokenSource(); + const tokenPromise = provider.getSemanticTokens( + document, + undefined, + cancellationTokenSource.token + ); + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await tokenPromise, null); + }); + function getExpected(full: boolean) { const tokenDataScript: Array<{ line: number; diff --git a/packages/language-server/test/plugins/typescript/features/SignatureHelpProvider.test.ts b/packages/language-server/test/plugins/typescript/features/SignatureHelpProvider.test.ts index 1d796400f..6db80024c 100644 --- a/packages/language-server/test/plugins/typescript/features/SignatureHelpProvider.test.ts +++ b/packages/language-server/test/plugins/typescript/features/SignatureHelpProvider.test.ts @@ -1,7 +1,12 @@ import path from 'path'; import assert from 'assert'; import ts from 'typescript'; -import { MarkupKind, Position, SignatureHelp } from 'vscode-languageserver'; +import { + CancellationTokenSource, + MarkupKind, + Position, + SignatureHelp +} from 'vscode-languageserver'; import { Document, DocumentManager } from '../../../../src/lib/documents'; import { SignatureHelpProviderImpl } from '../../../../src/plugins/typescript/features/SignatureHelpProvider'; import { LSAndTSDocResolver } from '../../../../src/plugins/typescript/LSAndTSDocResolver'; @@ -96,4 +101,19 @@ describe('SignatureHelpProvider', () => { assert.equal(result, null); }); + + it('provide signature help with formatted documentation', async () => { + const { provider, document } = setup(); + const cancellationTokenSource = new CancellationTokenSource(); + + const signatureHelpPromise = provider.getSignatureHelp( + document, + Position.create(3, 8), + undefined, + cancellationTokenSource.token + ); + cancellationTokenSource.cancel(); + + assert.deepStrictEqual(await signatureHelpPromise, null); + }); }); diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 5ac335bd0..081087f50 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -49,7 +49,7 @@ "rollup-plugin-cleanup": "^3.0.0", "rollup-plugin-copy": "^3.0.0", "svelte-language-server": "*", - "vscode-languageserver": "7.0.0", + "vscode-languageserver": "7.1.0-next.4", "vscode-languageserver-protocol": "3.16.0", "vscode-languageserver-types": "3.16.0", "vscode-uri": "2.1.2" diff --git a/yarn.lock b/yarn.lock index d16f01af8..82b6810af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2614,6 +2614,11 @@ vscode-jsonrpc@6.0.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@6.1.0-next.2: + version "6.1.0-next.2" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.1.0-next.2.tgz#b5b67028d551d486c4ac1ec139ffc29382b36a2e" + integrity sha512-nkiNDGI+Ytp7uj1lxHXddXCoEunhcry1D+KmVHBfUUgWT9jMF8ZJyH5KQObdF+OGAh7bXZxD/SV4uGwSCeHHWA== + vscode-languageclient@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" @@ -2631,6 +2636,14 @@ vscode-languageserver-protocol@3.16.0: vscode-jsonrpc "6.0.0" vscode-languageserver-types "3.16.0" +vscode-languageserver-protocol@3.17.0-next.5: + version "3.17.0-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.5.tgz#295ebd7c416d7224b57e4de43c4bfefcc8095f06" + integrity sha512-LFZ6WMB3iPezQAU9OnGoERzcIVKhcs0OLfD/NHcqSj3g1wgxuLUL5kSlZbbjFySQCmhzm6b0yb3hjTSeBtq1+w== + dependencies: + vscode-jsonrpc "6.1.0-next.2" + vscode-languageserver-types "3.17.0-next.1" + vscode-languageserver-textdocument@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" @@ -2641,12 +2654,17 @@ vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.15.1, vscode- resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== -vscode-languageserver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver-types@3.17.0-next.1: + version "3.17.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.1.tgz#396348493ccf567f0c22538033ebf95da5c5e4e3" + integrity sha512-VGzh06oynbYa6JbTKUbxOEZN7CYEtWhN7DK5wfzUpeCJl8X8xZX39g2PVfpqXrIEduu7dcJgK007KgnX9tHNKA== + +vscode-languageserver@7.1.0-next.4: + version "7.1.0-next.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.1.0-next.4.tgz#e9503af9d028dfc3a89e210646c265a087f46b8c" + integrity sha512-/65lxR/CuLJoOdzTjOTYUPWS7k5qzaWese4PObnWc6jwLryUrSa7DslYfaRXigh5/xr1nlaUZCcJwkpgM0wFvw== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.0-next.5" vscode-nls@^5.0.0: version "5.0.0"