From 05ae720020a15d6bf43d599e9806f779d4131cba Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Sun, 7 Aug 2022 22:28:06 +0200 Subject: [PATCH 1/2] fix: update Signature Help support to v3.15.0 LSP spec --- src/hover.ts | 48 ++++++++++++++++++++++++++++++++++++++---- src/lsp-server.spec.ts | 40 +++++++++++++++++++++++++++++++++++ src/lsp-server.ts | 17 +++++++++------ tsconfig.json | 1 + 4 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/hover.ts b/src/hover.ts index 78df885e..3f073f86 100644 --- a/src/hover.ts +++ b/src/hover.ts @@ -4,22 +4,42 @@ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ import * as lsp from 'vscode-languageserver'; import type tsp from 'typescript/lib/protocol.d.js'; import { asDocumentation, asPlainText } from './protocol-translation.js'; -export function asSignatureHelp(info: tsp.SignatureHelpItems): lsp.SignatureHelp { +export function asSignatureHelp(info: tsp.SignatureHelpItems, context?: lsp.SignatureHelpContext): lsp.SignatureHelp { + const signatures = info.items.map(asSignatureInformation); return { - activeSignature: info.selectedItemIndex, + activeSignature: getActiveSignature(info, signatures, context), activeParameter: getActiveParameter(info), - signatures: info.items.map(asSignatureInformation) + signatures }; } +function getActiveSignature(info: tsp.SignatureHelpItems, signatures: readonly lsp.SignatureInformation[], context?: lsp.SignatureHelpContext): number { + // Try matching the previous active signature's label to keep it selected + if (context?.activeSignatureHelp?.activeSignature !== undefined) { + const previouslyActiveSignature = context.activeSignatureHelp.signatures[context.activeSignatureHelp.activeSignature]; + if (previouslyActiveSignature && context.isRetrigger) { + const existingIndex = signatures.findIndex(other => other.label === previouslyActiveSignature.label); + if (existingIndex !== -1) { + return existingIndex; + } + } + } + + return info.selectedItemIndex; +} + function getActiveParameter(info: tsp.SignatureHelpItems): number { const activeSignature = info.items[info.selectedItemIndex]; - if (activeSignature && activeSignature.isVariadic) { + if (activeSignature?.isVariadic) { return Math.min(info.argumentIndex, activeSignature.parameters.length - 1); } return info.argumentIndex; @@ -46,3 +66,23 @@ function asParameterInformation(parameter: tsp.SignatureHelpParameter): lsp.Para documentation: asDocumentation(parameter) }; } + +export function toTsTriggerReason(context: lsp.SignatureHelpContext): tsp.SignatureHelpTriggerReason { + switch (context.triggerKind) { + case lsp.SignatureHelpTriggerKind.TriggerCharacter: + if (context.triggerCharacter) { + if (context.isRetrigger) { + return { kind: 'retrigger', triggerCharacter: context.triggerCharacter as any }; + } else { + return { kind: 'characterTyped', triggerCharacter: context.triggerCharacter as any }; + } + } else { + return { kind: 'invoked' }; + } + case lsp.SignatureHelpTriggerKind.ContentChange: + return context.isRetrigger ? { kind: 'retrigger' } : { kind: 'invoked' }; + case lsp.SignatureHelpTriggerKind.Invoked: + default: + return { kind: 'invoked' }; + } +} diff --git a/src/lsp-server.spec.ts b/src/lsp-server.spec.ts index c41ff38f..9dc459db 100644 --- a/src/lsp-server.spec.ts +++ b/src/lsp-server.spec.ts @@ -881,6 +881,7 @@ describe('signatureHelp', () => { version: 1, text: ` export function foo(bar: string, baz?:boolean): void {} + export function foo(n: number, baz?: boolean): void foo(param1, param2) ` }; @@ -892,6 +893,8 @@ describe('signatureHelp', () => { position: position(doc, 'param1') }))!; + assert.equal(result.signatures.length, 2); + assert.equal('bar: string', result.signatures[result.activeSignature!].parameters![result.activeParameter!].label); result = (await server.signatureHelp({ @@ -901,6 +904,43 @@ describe('signatureHelp', () => { assert.equal('baz?: boolean', result.signatures[result.activeSignature!].parameters![result.activeParameter!].label); }); + + it.only('retrigger with specific signature active', async () => { + const doc = { + uri: uri('bar.ts'), + languageId: 'typescript', + version: 1, + text: ` + export function foo(bar: string, baz?: boolean): void {} + export function foo(n: number, baz?: boolean): void + foo(param1, param2) + ` + }; + server.didOpenTextDocument({ textDocument: doc }); + let result = await server.signatureHelp({ + textDocument: doc, + position: position(doc, 'param1') + }); + assert.equal(result!.signatures.length, 2); + + result = (await server.signatureHelp({ + textDocument: doc, + position: position(doc, 'param1'), + context: { + isRetrigger: true, + triggerKind: lsp.SignatureHelpTriggerKind.Invoked, + activeSignatureHelp: { + signatures: result!.signatures, + activeSignature: 1 // select second signature + } + } + }))!; + const { activeSignature, signatures } = result!; + assert.equal(activeSignature, 1); + assert.deepInclude(signatures[activeSignature!], { + label: 'foo(n: number, baz?: boolean): void' + }); + }); }); describe('code actions', () => { diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 68f21417..21742984 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -26,7 +26,7 @@ import { } from './protocol-translation.js'; import { LspDocuments, LspDocument } from './document.js'; import { asCompletionItem, asResolvedCompletionItem, getCompletionTriggerCharacter } from './completion.js'; -import { asSignatureHelp } from './hover.js'; +import { asSignatureHelp, toTsTriggerReason } from './hover.js'; import { Commands } from './commands.js'; import { provideQuickFix } from './quickfix.js'; import { provideRefactors } from './refactor.js'; @@ -326,7 +326,8 @@ export class LspServer { renameProvider: true, referencesProvider: true, signatureHelpProvider: { - triggerCharacters: ['(', ',', '<'] + triggerCharacters: ['(', ',', '<'], + retriggerCharacters: [')'] }, workspaceSymbolProvider: true, implementationProvider: true, @@ -861,25 +862,27 @@ export class LspServer { return opts; } - async signatureHelp(params: lsp.TextDocumentPositionParams): Promise { + async signatureHelp(params: lsp.SignatureHelpParams): Promise { const file = uriToPath(params.textDocument.uri); this.logger.log('signatureHelp', params, file); if (!file) { return undefined; } - const response = await this.interuptDiagnostics(() => this.getSignatureHelp(file, params.position)); + const response = await this.interuptDiagnostics(() => this.getSignatureHelp(file, params)); if (!response || !response.body) { return undefined; } - return asSignatureHelp(response.body); + return asSignatureHelp(response.body, params.context); } - protected async getSignatureHelp(file: string, position: lsp.Position): Promise { + protected async getSignatureHelp(file: string, params: lsp.SignatureHelpParams): Promise { try { + const { position, context } = params; return await this.tspClient.request(CommandTypes.SignatureHelp, { file, line: position.line + 1, - offset: position.character + 1 + offset: position.character + 1, + triggerReason: context ? toTsTriggerReason(context) : undefined }); } catch (err) { return undefined; diff --git a/tsconfig.json b/tsconfig.json index 7f8126c3..be119af9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "es6" ], "alwaysStrict": true, + "strictBindCallApply": true, "strictNullChecks": true, "skipLibCheck": true, } From 7debec35588cef474b5bfc418054f65b90b5c94b Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Sun, 7 Aug 2022 23:06:06 +0200 Subject: [PATCH 2/2] remove "only" --- src/lsp-server.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lsp-server.spec.ts b/src/lsp-server.spec.ts index 9dc459db..a3ee4078 100644 --- a/src/lsp-server.spec.ts +++ b/src/lsp-server.spec.ts @@ -905,7 +905,7 @@ describe('signatureHelp', () => { assert.equal('baz?: boolean', result.signatures[result.activeSignature!].parameters![result.activeParameter!].label); }); - it.only('retrigger with specific signature active', async () => { + it('retrigger with specific signature active', async () => { const doc = { uri: uri('bar.ts'), languageId: 'typescript',