From 8f9275001cd049957c84614ad7d8a3dec9321f1a Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 17 Aug 2022 00:48:46 +0200 Subject: [PATCH 1/2] Implement "Go To Source Definition" command --- README.md | 144 ++++++++++++++++++++++----- src/commands.ts | 5 +- src/features/source-definition.ts | 69 +++++++++++++ src/lsp-client.ts | 73 ++++++-------- src/lsp-server.spec.ts | 36 ++++++- src/lsp-server.ts | 33 +++--- src/test-utils.ts | 114 ++++++++++++++++----- src/tsp-client.spec.ts | 2 + src/tsp-client.ts | 5 + src/tsp-command-types.ts | 1 + test-data/source-definition/a.d.ts | 1 + test-data/source-definition/a.js | 1 + test-data/source-definition/index.ts | 2 + 13 files changed, 374 insertions(+), 112 deletions(-) create mode 100644 src/features/source-definition.ts create mode 100644 test-data/source-definition/a.d.ts create mode 100644 test-data/source-definition/a.js create mode 100644 test-data/source-definition/index.ts diff --git a/README.md b/README.md index 33dadb66..34b9f728 100644 --- a/README.md +++ b/README.md @@ -247,35 +247,127 @@ The user can enable it with a setting similar to (can vary per-editor): ## Workspace commands (`workspace/executeCommand`) -See [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspace_executeCommand). +See [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand). Most of the time, you'll execute commands with arguments retrieved from another request like `textDocument/codeAction`. There are some use cases for calling them manually. -Supported commands: - -`lsp` refers to the language server protocol, `tsp` refers to the typescript server protocol. - -* `_typescript.applyWorkspaceEdit` - ```ts - type Arguments = [lsp.WorkspaceEdit] - ``` -* `_typescript.applyCodeAction` - ```ts - type Arguments = [tsp.CodeAction] - ``` -* `_typescript.applyRefactoring` - ```ts - type Arguments = [tsp.GetEditsForRefactorRequestArgs] - ``` -* `_typescript.organizeImports` - ```ts - // The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ - type Arguments = [string] | [string, { skipDestructiveCodeActions?: boolean }] - ``` -* `_typescript.applyRenameFile` - ```ts - type Arguments = [{ sourceUri: string; targetUri: string; }] - ``` +`lsp` refers to the language server protocol types, `tsp` refers to the typescript server protocol types. + +### Go to Source Definition + +Request: + +```ts +{ + command: `_typescript.goToSourceDefinition` + arguments: [ + lsp.DocumentUri, // String URI of the document + lsp.Position, // Line and character position (zero-based) + ] +} +``` + +Response: + +```ts +lsp.Location[] | null +``` + +(This command is supported from Typescript 4.7.) + +### Apply Workspace Edits + +Request: + +```ts +{ + command: `_typescript.applyWorkspaceEdit` + arguments: [lsp.WorkspaceEdit] +} +``` + +Response: + +```ts +lsp.ApplyWorkspaceEditResult +``` + +### Apply Code Action + +Request: + +```ts +{ + command: `_typescript.applyCodeAction` + arguments: [ + tsp.CodeAction, // TypeScript Code Action object + ] +} +``` + +Response: + +```ts +void +``` + +### Apply Refactoring + +Request: + +```ts +{ + command: `_typescript.applyRefactoring` + arguments: [ + tsp.GetEditsForRefactorRequestArgs, + ] +} +``` + +Response: + +```ts +void +``` + +### Organize Imports + +Request: + +```ts +{ + command: `_typescript.organizeImports` + arguments: [ + // The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ + [string] | [string, { skipDestructiveCodeActions?: boolean }], + ] +} +``` + +Response: + +```ts +void +``` + +### Rename File + +Request: + +```ts +{ + command: `_typescript.applyRenameFile` + arguments: [ + { sourceUri: string; targetUri: string; }, + ] +} +``` + +Response: + +```ts +void +``` ## Inlay hints (`typescript/inlayHints`) (experimental) diff --git a/src/commands.ts b/src/commands.ts index 44ebc350..ef04c370 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -5,6 +5,8 @@ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 */ +import { SourceDefinitionCommand } from './features/source-definition.js'; + export const Commands = { APPLY_WORKSPACE_EDIT: '_typescript.applyWorkspaceEdit', APPLY_CODE_ACTION: '_typescript.applyCodeAction', @@ -13,5 +15,6 @@ export const Commands = { APPLY_RENAME_FILE: '_typescript.applyRenameFile', APPLY_COMPLETION_CODE_ACTION: '_typescript.applyCompletionCodeAction', /** Commands below should be implemented by the client */ - SELECT_REFACTORING: '_typescript.selectRefactoring' + SELECT_REFACTORING: '_typescript.selectRefactoring', + SOURCE_DEFINITION: SourceDefinitionCommand.id }; diff --git a/src/features/source-definition.ts b/src/features/source-definition.ts new file mode 100644 index 00000000..a92652b1 --- /dev/null +++ b/src/features/source-definition.ts @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 TypeFox and others. + * + * 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 API from '../utils/api.js'; +import * as typeConverters from '../utils/typeConverters.js'; +import { toLocation, uriToPath } from '../protocol-translation.js'; +import type { LspDocuments } from '../document.js'; +import type { TspClient } from '../tsp-client.js'; +import type { LspClient } from '../lsp-client.js'; +import { CommandTypes } from '../tsp-command-types.js'; + +export class SourceDefinitionCommand { + public static readonly id = '_typescript.goToSourceDefinition'; + public static readonly minVersion = API.v470; + + public static async execute( + uri: lsp.DocumentUri | undefined, + position: lsp.Position | undefined, + documents: LspDocuments, + tspClient: TspClient, + lspClient: LspClient, + reporter: lsp.WorkDoneProgressReporter + ): Promise { + if (tspClient.apiVersion.lt(SourceDefinitionCommand.minVersion)) { + lspClient.showErrorMessage('Go to Source Definition failed. Requires TypeScript 4.7+.'); + return; + } + + if (!position || typeof position.character !== 'number' || typeof position.line !== 'number') { + lspClient.showErrorMessage('Go to Source Definition failed. Invalid position.'); + return; + } + + let file: string | undefined; + + if (!uri || typeof uri !== 'string' || !(file = uriToPath(uri))) { + lspClient.showErrorMessage('Go to Source Definition failed. No resource provided.'); + return; + } + + const document = documents.get(file); + + if (!document) { + lspClient.showErrorMessage('Go to Source Definition failed. File not opened in the editor.'); + return; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(file, position); + return await lspClient.withProgress({ + message: 'Finding source definitions…', + reporter + }, async () => { + const response = await tspClient.request(CommandTypes.FindSourceDefinition, args); + if (response.type === 'response' && response.body?.length) { + return response.body.map(reference => toLocation(reference, documents)); + } + lspClient.showErrorMessage('No source definitions found.'); + }); + } +} diff --git a/src/lsp-client.ts b/src/lsp-client.ts index 4e65e8e1..c2cc2188 100644 --- a/src/lsp-client.ts +++ b/src/lsp-client.ts @@ -6,67 +6,50 @@ */ import * as lsp from 'vscode-languageserver'; +import { MessageType } from 'vscode-languageserver'; +import { attachWorkDone } from 'vscode-languageserver/lib/common/progress.js'; import { TypeScriptRenameRequest } from './ts-protocol.js'; -export interface ProgressReporter { - begin(message?: string): void; - report(message: string): void; - end(): void; +export interface WithProgressOptions { + message: string; + reporter: lsp.WorkDoneProgressReporter; } export interface LspClient { - setClientCapabilites(capabilites: lsp.ClientCapabilities): void; - createProgressReporter(): ProgressReporter; + createProgressReporter(token?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise; + withProgress(options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise): Promise; publishDiagnostics(args: lsp.PublishDiagnosticsParams): void; showMessage(args: lsp.ShowMessageParams): void; + showErrorMessage(message: string): void; logMessage(args: lsp.LogMessageParams): void; applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise; telemetry(args: any): void; rename(args: lsp.TextDocumentPositionParams): Promise; } -export class LspClientImpl implements LspClient { - private clientCapabilities?: lsp.ClientCapabilities; +// Hack around the LSP library that makes it otherwise impossible to differentiate between Null and Client-initiated reporter. +const nullProgressReporter = attachWorkDone(undefined as any, /* params */ undefined); +export class LspClientImpl implements LspClient { constructor(protected connection: lsp.Connection) {} - setClientCapabilites(capabilites: lsp.ClientCapabilities): void { - this.clientCapabilities = capabilites; + async createProgressReporter(_?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise { + let reporter: lsp.WorkDoneProgressReporter; + if (workDoneProgress && workDoneProgress.constructor !== nullProgressReporter.constructor) { + reporter = workDoneProgress; + } else { + reporter = workDoneProgress || await this.connection.window.createWorkDoneProgress(); + } + return reporter; } - createProgressReporter(): ProgressReporter { - let workDoneProgress: Promise | undefined; - return { - begin: (message = '') => { - if (this.clientCapabilities?.window?.workDoneProgress) { - workDoneProgress = this.connection.window.createWorkDoneProgress(); - workDoneProgress - .then((progress) => { - progress.begin(message); - }) - .catch(() => {}); - } - }, - report: (message: string) => { - if (workDoneProgress) { - workDoneProgress - .then((progress) => { - progress.report(message); - }) - .catch(() => {}); - } - }, - end: () => { - if (workDoneProgress) { - workDoneProgress - .then((progress) => { - progress.done(); - }) - .catch(() => {}); - workDoneProgress = undefined; - } - } - }; + async withProgress(options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise): Promise { + const { message, reporter } = options; + reporter.begin(message); + return task(reporter).then(result => { + reporter.done(); + return result; + }); } publishDiagnostics(args: lsp.PublishDiagnosticsParams): void { @@ -77,6 +60,10 @@ export class LspClientImpl implements LspClient { this.connection.sendNotification(lsp.ShowMessageNotification.type, args); } + showErrorMessage(message: string): void { + this.connection.sendNotification(lsp.ShowMessageNotification.type, { type: MessageType.Error, message }); + } + logMessage(args: lsp.LogMessageParams): void { this.connection.sendNotification(lsp.LogMessageNotification.type, args); } diff --git a/src/lsp-server.spec.ts b/src/lsp-server.spec.ts index 05db0482..d387d317 100644 --- a/src/lsp-server.spec.ts +++ b/src/lsp-server.spec.ts @@ -23,7 +23,7 @@ let server: TestLspServer; before(async () => { server = await createServer({ - rootUri: null, + rootUri: uri(), publishDiagnostics: args => diagnostics.set(args.uri, args) }); server.didChangeConfiguration({ @@ -1504,6 +1504,40 @@ describe('executeCommand', () => { ] ); }); + + it('go to source definition', async () => { + // NOTE: This test needs to reference files that physically exist for the feature to work. + const indexUri = uri('source-definition', 'index.ts'); + const indexDoc = { + uri: indexUri, + languageId: 'typescript', + version: 1, + text: readContents(filePath('source-definition', 'index.ts')) + }; + server.didOpenTextDocument({ textDocument: indexDoc }); + const result: lsp.Location[] | null = await server.executeCommand({ + command: Commands.SOURCE_DEFINITION, + arguments: [ + indexUri, + position(indexDoc, '/*identifier*/') + ] + }); + assert.isNotNull(result); + assert.equal(result!.length, 1); + assert.deepEqual(result![0], { + uri: uri('source-definition', 'a.js'), + range: { + start: { + line: 0, + character: 13 + }, + end: { + line: 0, + character: 14 + } + } + }); + }); }); describe('documentHighlight', () => { diff --git a/src/lsp-server.ts b/src/lsp-server.ts index 7a054b0b..3de08ad8 100644 --- a/src/lsp-server.ts +++ b/src/lsp-server.ts @@ -37,7 +37,8 @@ import { computeCallers, computeCallees } from './calls.js'; import { IServerOptions } from './utils/configuration.js'; import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider.js'; import { TypeScriptAutoFixProvider } from './features/fix-all.js'; -import { LspClient, ProgressReporter } from './lsp-client.js'; +import { SourceDefinitionCommand } from './features/source-definition.js'; +import { LspClient } from './lsp-client.js'; import { CodeActionKind } from './utils/types.js'; const DEFAULT_TSSERVER_PREFERENCES: Required = { @@ -74,7 +75,7 @@ const DEFAULT_TSSERVER_PREFERENCES: Required = { class ServerInitializingIndicator { private _loadingProjectName?: string; - private _progressReporter?: ProgressReporter; + private _progressReporter?: lsp.WorkDoneProgressReporter; constructor(private lspClient: LspClient) {} @@ -82,19 +83,19 @@ class ServerInitializingIndicator { if (this._loadingProjectName) { this._loadingProjectName = undefined; if (this._progressReporter) { - this._progressReporter.end(); + this._progressReporter.done(); this._progressReporter = undefined; } } } - public startedLoadingProject(projectName: string): void { + public async startedLoadingProject(projectName: string): Promise { // TS projects are loaded sequentially. Cancel existing task because it should always be resolved before // the incoming project loading task is. this.reset(); this._loadingProjectName = projectName; - this._progressReporter = this.lspClient.createProgressReporter(); + this._progressReporter = await this.lspClient.createProgressReporter(); this._progressReporter.begin('Initializing JS/TS language features…'); } @@ -102,7 +103,7 @@ class ServerInitializingIndicator { if (this._loadingProjectName === projectName) { this._loadingProjectName = undefined; if (this._progressReporter) { - this._progressReporter.end(); + this._progressReporter.done(); this._progressReporter = undefined; } } @@ -191,7 +192,6 @@ export class LspServer { } this.initializeParams = params; const clientCapabilities = this.initializeParams.capabilities; - this.options.lspClient.setClientCapabilites(clientCapabilities); this._loadingIndicator = new ServerInitializingIndicator(this.options.lspClient); this.workspaceRoot = this.initializeParams.rootUri ? uriToPath(this.initializeParams.rootUri) : this.initializeParams.rootPath || undefined; @@ -249,6 +249,7 @@ export class LspServer { this.logger ); this._tspClient = new TspClient({ + apiVersion: typescriptVersion.version || API.defaultVersion, tsserverPath: typescriptVersion.tsServerPath, logFile, logVerbosity, @@ -328,7 +329,8 @@ export class LspServer { Commands.APPLY_CODE_ACTION, Commands.APPLY_REFACTORING, Commands.ORGANIZE_IMPORTS, - Commands.APPLY_RENAME_FILE + Commands.APPLY_RENAME_FILE, + Commands.SOURCE_DEFINITION ] }, hoverProvider: true, @@ -978,13 +980,11 @@ export class LspServer { } } - async executeCommand(arg: lsp.ExecuteCommandParams): Promise { + async executeCommand(arg: lsp.ExecuteCommandParams, token?: lsp.CancellationToken, workDoneProgress?: lsp.WorkDoneProgressReporter): Promise { this.logger.log('executeCommand', arg); if (arg.command === Commands.APPLY_WORKSPACE_EDIT && arg.arguments) { const edit = arg.arguments[0] as lsp.WorkspaceEdit; - await this.options.lspClient.applyWorkspaceEdit({ - edit - }); + await this.options.lspClient.applyWorkspaceEdit({ edit }); } else if (arg.command === Commands.APPLY_CODE_ACTION && arg.arguments) { const codeAction = arg.arguments[0] as tsp.CodeAction; if (!await this.applyFileCodeEdits(codeAction.changes)) { @@ -1048,10 +1048,15 @@ export class LspServer { // Execute only the first code action. break; } + } else if (arg.command === Commands.SOURCE_DEFINITION) { + const [uri, position] = (arg.arguments || []) as [lsp.DocumentUri?, lsp.Position?]; + const reporter = await this.options.lspClient.createProgressReporter(token, workDoneProgress); + return SourceDefinitionCommand.execute(uri, position, this.documents, this.tspClient, this.options.lspClient, reporter); } else { this.logger.error(`Unknown command ${arg.command}.`); } } + protected async applyFileCodeEdits(edits: ReadonlyArray): Promise { if (!edits.length) { return false; @@ -1209,13 +1214,13 @@ export class LspServer { } } - protected onTsEvent(event: protocol.Event): void { + protected async onTsEvent(event: protocol.Event): Promise { if (event.event === EventTypes.SementicDiag || event.event === EventTypes.SyntaxDiag || event.event === EventTypes.SuggestionDiag) { this.diagnosticQueue?.updateDiagnostics(event.event, event as tsp.DiagnosticEvent); } else if (event.event === EventTypes.ProjectLoadingStart) { - this.loadingIndicator.startedLoadingProject((event as tsp.ProjectLoadingStartEvent).body.projectName); + await this.loadingIndicator.startedLoadingProject((event as tsp.ProjectLoadingStartEvent).body.projectName); } else if (event.event === EventTypes.ProjectLoadingFinish) { this.loadingIndicator.finishedLoadingProject((event as tsp.ProjectLoadingFinishEvent).body.projectName); } else { diff --git a/src/test-utils.ts b/src/test-utils.ts index 328a3f1c..e097b494 100644 --- a/src/test-utils.ts +++ b/src/test-utils.ts @@ -12,6 +12,8 @@ import { fileURLToPath } from 'node:url'; import deepmerge from 'deepmerge'; import * as lsp from 'vscode-languageserver'; import { normalizePath, pathToUri } from './protocol-translation.js'; +import { TypeScriptInitializationOptions } from './ts-protocol.js'; +import { LspClient, WithProgressOptions } from './lsp-client.js'; import { LspServer } from './lsp-server.js'; import { ConsoleLogger } from './logger.js'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -43,6 +45,25 @@ const DEFAULT_TEST_CLIENT_CAPABILITIES: lsp.ClientCapabilities = { } }; +const DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS: TypeScriptInitializationOptions = { + plugins: [], + preferences: { + allowIncompleteCompletions: true, + allowRenameOfImportPath: true, + allowTextChangesInNewFiles: true, + displayPartsForJSDoc: true, + generateReturnInDocTemplate: true, + includeAutomaticOptionalChainCompletions: true, + includeCompletionsForImportStatements: true, + includeCompletionsForModuleExports: true, + includeCompletionsWithClassMemberSnippets: true, + includeCompletionsWithInsertText: true, + includeCompletionsWithSnippetText: true, + jsxAttributeCompletionStyle: 'auto', + providePrefixAndSuffixTextForRename: true + } +}; + export function uri(...components: string[]): string { const resolved = filePath(...components); return pathToUri(resolved, undefined); @@ -84,49 +105,87 @@ export function toPlatformEOL(text: string): string { return text; } +class TestLspClient implements LspClient { + private workspaceEditsListener: (args: lsp.ApplyWorkspaceEditParams) => void | undefined; + + constructor(protected options: TestLspServerOptions, protected logger: ConsoleLogger) {} + + async createProgressReporter(_token?: lsp.CancellationToken, _workDoneProgress?: lsp.WorkDoneProgressReporter): Promise { + const reporter = new class implements lsp.WorkDoneProgressReporter { + begin(_title: string, _percentage?: number, _message?: string, _cancellable?: boolean): void {} + report(_message: any): void {} + done(): void {} + }; + return reporter; + } + + async withProgress(_options: WithProgressOptions, task: (progress: lsp.WorkDoneProgressReporter) => Promise): Promise { + const progress = await this.createProgressReporter(); + return await task(progress); + } + + publishDiagnostics(args: lsp.PublishDiagnosticsParams) { + return this.options.publishDiagnostics(args); + } + + showMessage(args: lsp.ShowMessageParams): void { + throw args; // should not be called. + } + + showErrorMessage(message: string) { + this.logger.error(`[showErrorMessage] ${message}`); + } + + logMessage(args: lsp.LogMessageParams): void { + this.logger.log('logMessage', JSON.stringify(args)); + } + + telemetry(args: any): void { + this.logger.log('telemetry', JSON.stringify(args)); + } + + addApplyWorkspaceEditListener(listener: (args: lsp.ApplyWorkspaceEditParams) => void): void { + this.workspaceEditsListener = listener; + } + + async applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise { + if (this.workspaceEditsListener) { + this.workspaceEditsListener(args); + } + return { applied: true }; + } + + async rename() { + throw new Error('unsupported'); + } +} + export class TestLspServer extends LspServer { workspaceEdits: lsp.ApplyWorkspaceEditParams[] = []; } -export async function createServer(options: { +interface TestLspServerOptions { rootUri: string | null; tsserverLogVerbosity?: string; publishDiagnostics: (args: lsp.PublishDiagnosticsParams) => void; clientCapabilitiesOverride?: lsp.ClientCapabilities; -}): Promise { +} + +export async function createServer(options: TestLspServerOptions): Promise { const typescriptVersionProvider = new TypeScriptVersionProvider(); const bundled = typescriptVersionProvider.bundledVersion(); const logger = new ConsoleLogger(CONSOLE_LOG_LEVEL); + const lspClient = new TestLspClient(options, logger); const server = new TestLspServer({ logger, tsserverPath: bundled!.tsServerPath, tsserverLogVerbosity: options.tsserverLogVerbosity, tsserverLogFile: path.resolve(PACKAGE_ROOT, 'tsserver.log'), - lspClient: { - setClientCapabilites() {}, - createProgressReporter() { - return { - begin() {}, - report() {}, - end() {} - }; - }, - publishDiagnostics: options.publishDiagnostics, - showMessage(args: lsp.ShowMessageParams): void { - throw args; // should not be called. - }, - logMessage(args: lsp.LogMessageParams): void { - logger.log('logMessage', JSON.stringify(args)); - }, - telemetry(args): void { - logger.log('telemetry', JSON.stringify(args)); - }, - async applyWorkspaceEdit(args: lsp.ApplyWorkspaceEditParams): Promise { - server.workspaceEdits.push(args); - return { applied: true }; - }, - rename: () => Promise.reject(new Error('unsupported')) - } + lspClient + }); + + lspClient.addApplyWorkspaceEditListener(args => { + server.workspaceEdits.push(args); }); await server.initialize({ @@ -134,6 +193,7 @@ export async function createServer(options: { rootUri: options.rootUri, processId: 42, capabilities: deepmerge(DEFAULT_TEST_CLIENT_CAPABILITIES, options.clientCapabilitiesOverride || {}), + initializationOptions: DEFAULT_TEST_CLIENT_INITIALIZATION_OPTIONS, workspaceFolders: null }); return server; diff --git a/src/tsp-client.spec.ts b/src/tsp-client.spec.ts index ecb2aaaa..aaf59163 100644 --- a/src/tsp-client.spec.ts +++ b/src/tsp-client.spec.ts @@ -10,6 +10,7 @@ import { TspClient } from './tsp-client.js'; import { ConsoleLogger } from './logger.js'; import { filePath, readContents } from './test-utils.js'; import { CommandTypes } from './tsp-command-types.js'; +import API from './utils/api.js'; import { TypeScriptVersionProvider } from './utils/versionProvider.js'; const assert = chai.assert; @@ -19,6 +20,7 @@ let server: TspClient; before(() => { server = new TspClient({ + apiVersion: API.defaultVersion, logger: new ConsoleLogger(), tsserverPath: bundled!.tsServerPath }); diff --git a/src/tsp-client.ts b/src/tsp-client.ts index c4f7cab0..5dcb2c23 100644 --- a/src/tsp-client.ts +++ b/src/tsp-client.ts @@ -15,8 +15,10 @@ import { temporaryFile } from 'tempy'; import { CommandTypes } from './tsp-command-types.js'; import { Logger, PrefixingLogger } from './logger.js'; import { Deferred } from './utils.js'; +import API from './utils/api.js'; export interface TspClientOptions { + apiVersion: API; logger: Logger; tsserverPath: string; logFile?: string; @@ -42,6 +44,7 @@ interface TypeScriptRequestTypes { 'definition': [tsp.FileLocationRequestArgs, tsp.DefinitionResponse]; 'definitionAndBoundSpan': [tsp.FileLocationRequestArgs, tsp.DefinitionInfoAndBoundSpanResponse]; 'docCommentTemplate': [tsp.FileLocationRequestArgs, tsp.DocCommandTemplateResponse]; + 'findSourceDefinition': [tsp.FileLocationRequestArgs, tsp.DefinitionResponse]; 'format': [tsp.FormatRequestArgs, tsp.FormatResponse]; 'formatonkey': [tsp.FormatOnKeyRequestArgs, tsp.FormatResponse]; 'getApplicableRefactors': [tsp.GetApplicableRefactorsRequestArgs, tsp.GetApplicableRefactorsResponse]; @@ -67,6 +70,7 @@ interface TypeScriptRequestTypes { } export class TspClient { + public apiVersion: API; private tsserverProc: cp.ChildProcess | null; private readlineInterface: readline.ReadLine; private seq = 0; @@ -76,6 +80,7 @@ export class TspClient { private cancellationPipeName: string | undefined; constructor(private options: TspClientOptions) { + this.apiVersion = options.apiVersion; this.logger = new PrefixingLogger(options.logger, '[tsclient]'); this.tsserverLogger = new PrefixingLogger(options.logger, '[tsserver]'); } diff --git a/src/tsp-command-types.ts b/src/tsp-command-types.ts index e76e5ecd..2756edec 100644 --- a/src/tsp-command-types.ts +++ b/src/tsp-command-types.ts @@ -34,6 +34,7 @@ export const enum CommandTypes { Implementation = 'implementation', Exit = 'exit', FileReferences = 'fileReferences', + FindSourceDefinition = 'findSourceDefinition', Format = 'format', Formatonkey = 'formatonkey', Geterr = 'geterr', diff --git a/test-data/source-definition/a.d.ts b/test-data/source-definition/a.d.ts new file mode 100644 index 00000000..187efa86 --- /dev/null +++ b/test-data/source-definition/a.d.ts @@ -0,0 +1 @@ +export declare const a: string diff --git a/test-data/source-definition/a.js b/test-data/source-definition/a.js new file mode 100644 index 00000000..c4a2d30f --- /dev/null +++ b/test-data/source-definition/a.js @@ -0,0 +1 @@ +export const a = 'a'; diff --git a/test-data/source-definition/index.ts b/test-data/source-definition/index.ts new file mode 100644 index 00000000..659561bc --- /dev/null +++ b/test-data/source-definition/index.ts @@ -0,0 +1,2 @@ +import { a } from "./a" +a/*identifier*/ From 0d2fadc3ae1574ea9fdc327f4651a1d31d7af7b1 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Wed, 17 Aug 2022 08:27:36 +0200 Subject: [PATCH 2/2] readme formatting --- README.md | 200 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 104 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 34b9f728..2fe29754 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,32 @@ Based on concepts and ideas from https://github.com/prabirshrestha/typescript-la Maintained by a [community of contributors](https://github.com/typescript-language-server/typescript-language-server/graphs/contributors) like you + + +- [Installing](#installing) +- [Running the language server](#running-the-language-server) +- [CLI Options](#cli-options) +- [initializationOptions](#initializationoptions) +- [workspace/didChangeConfiguration](#workspacedidchangeconfiguration) +- [Code actions on save](#code-actions-on-save) +- [Workspace commands \(`workspace/executeCommand`\)](#workspace-commands-workspaceexecutecommand) + - [Go to Source Definition](#go-to-source-definition) + - [Apply Workspace Edits](#apply-workspace-edits) + - [Apply Code Action](#apply-code-action) + - [Apply Refactoring](#apply-refactoring) + - [Organize Imports](#organize-imports) + - [Rename File](#rename-file) +- [Inlay hints \(`typescript/inlayHints`\) \(experimental\)](#inlay-hints-typescriptinlayhints-experimental) +- [Callers and callees \(`textDocument/calls`\) \(experimental\)](#callers-and-callees-textdocumentcalls-experimental) +- [Supported Protocol features](#supported-protocol-features) +- [Development](#development) + - [Build](#build) + - [Test](#test) + - [Watch](#watch) + - [Publishing](#publishing) + + + ## Installing ```sh @@ -255,119 +281,101 @@ Most of the time, you'll execute commands with arguments retrieved from another ### Go to Source Definition -Request: - -```ts -{ - command: `_typescript.goToSourceDefinition` - arguments: [ - lsp.DocumentUri, // String URI of the document - lsp.Position, // Line and character position (zero-based) - ] -} -``` - -Response: - -```ts -lsp.Location[] | null -``` +- Request: + ```ts + { + command: `_typescript.goToSourceDefinition` + arguments: [ + lsp.DocumentUri, // String URI of the document + lsp.Position, // Line and character position (zero-based) + ] + } + ``` +- Response: + ```ts + lsp.Location[] | null + ``` (This command is supported from Typescript 4.7.) ### Apply Workspace Edits -Request: - -```ts -{ - command: `_typescript.applyWorkspaceEdit` - arguments: [lsp.WorkspaceEdit] -} -``` - -Response: - -```ts -lsp.ApplyWorkspaceEditResult -``` +- Request: + ```ts + { + command: `_typescript.applyWorkspaceEdit` + arguments: [lsp.WorkspaceEdit] + } + ``` +- Response: + ```ts + lsp.ApplyWorkspaceEditResult + ``` ### Apply Code Action -Request: - -```ts -{ - command: `_typescript.applyCodeAction` - arguments: [ - tsp.CodeAction, // TypeScript Code Action object - ] -} -``` - -Response: - -```ts -void -``` +- Request: + ```ts + { + command: `_typescript.applyCodeAction` + arguments: [ + tsp.CodeAction, // TypeScript Code Action object + ] + } + ``` +- Response: + ```ts + void + ``` ### Apply Refactoring -Request: - -```ts -{ - command: `_typescript.applyRefactoring` - arguments: [ - tsp.GetEditsForRefactorRequestArgs, - ] -} -``` - -Response: - -```ts -void -``` +- Request: + ```ts + { + command: `_typescript.applyRefactoring` + arguments: [ + tsp.GetEditsForRefactorRequestArgs, + ] + } + ``` +- Response: + ```ts + void + ``` ### Organize Imports -Request: - -```ts -{ - command: `_typescript.organizeImports` - arguments: [ - // The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ - [string] | [string, { skipDestructiveCodeActions?: boolean }], - ] -} -``` - -Response: - -```ts -void -``` +- Request: + ```ts + { + command: `_typescript.organizeImports` + arguments: [ + // The "skipDestructiveCodeActions" argument is supported from Typescript 4.4+ + [string] | [string, { skipDestructiveCodeActions?: boolean }], + ] + } + ``` +- Response: + ```ts + void + ``` ### Rename File -Request: - -```ts -{ - command: `_typescript.applyRenameFile` - arguments: [ - { sourceUri: string; targetUri: string; }, - ] -} -``` - -Response: - -```ts -void -``` +- Request: + ```ts + { + command: `_typescript.applyRenameFile` + arguments: [ + { sourceUri: string; targetUri: string; }, + ] + } + ``` +- Response: + ```ts + void + ``` ## Inlay hints (`typescript/inlayHints`) (experimental)